About Java's Collection

H

Hatter Jiang

the Collection interface declared like this:

public interface Collection<E> extends Iterable<E> {

and we can add an object extends(or implements) from E, declared like
this:
boolean add(E o);

but when I want to know if the collection contains an object
"oneObject", it declared like:
boolean contains(Object o);

then if I write:
List<Integer> list = new ArrayList<Integer>();

list.add(new Object()); // it will cause compile error
list.add(new Integer(0));

System.out.println(list.contains(new Integer(1));
System.out.println(list.contains(new Object()); // will
NOT cause compile error

WHY does Java was designed like this, ANY ONE KNOWS?
 
R

Roedy Green

public interface Collection<E> extends Iterable<E> {

and we can add an object extends(or implements) from E, declared like
this:
boolean add(E o);

but when I want to know if the collection contains an object
"oneObject", it declared like:
boolean contains(Object o);

then if I write:
List<Integer> list = new ArrayList<Integer>();

list.add(new Object()); // it will cause compile error
list.add(new Integer(0));

System.out.println(list.contains(new Integer(1));
System.out.println(list.contains(new Object()); // will
NOT cause compile error

WHY does Java was designed like this, ANY ONE KNOWS?

The parameter declarations are different:

boolean contains(Object o)

boolean add(E e)

Contains accepts any old object, not just one of the Collection type.
It makes no sense to try add an object not of type E, but it is
reasonable to ask if an object not proven to be of type E is a member.
It may or may not actually be of type E and it may or may not be a
member.

Is that what you were asking? How do you think it should work?

--
Roedy Green Canadian Mind Products
http://mindprod.com
PM Steven Harper is fixated on the costs of implementing Kyoto, estimated as high as 1% of GDP.
However, he refuses to consider the costs of not implementing Kyoto which the
famous economist Nicholas Stern estimated at 5 to 20% of GDP
 
H

Hatter Jiang

The parameter declarations are different:

boolean contains(Object o)

boolean add(E e)

Contains accepts any old object, not just one of the Collection type.
It makes no sense to try add an object not of type E, but it is
reasonable to ask if an object not proven to be of type E is a member.
It may or may not actually be of type E and it may or may not be a
member.

Is that what you were asking?  How do you think it should work?

--
Roedy Green Canadian Mind Productshttp://mindprod.com
PM Steven Harper is fixated on the costs of implementing Kyoto, estimated as high as 1% of GDP.
However, he refuses to consider the costs of not implementing Kyoto which the
famous economist Nicholas Stern estimated at 5 to 20% of GDP

I DO think contains SHOULD declared like:
boolean contains(E o)

if i user new ArrayList<Integer>(), then other type than Integer
SHOULD NOT contains in this collection, because then "contains
(Object)" does not check the type, sometimes this will cause program
logic error for passed a different type object.
I asked some other engineers they also made mistake like this.

i had written codes like this:
enum Type{
TypeA("a", TypeB("b");

private String value;

private Type(String value){
this.value = value;
}

public String getValue(){
return this.value;
}
}

List<String> typeList = new ArrayList<String>();
typeList.add(Type.TypeA.getValue());

typeList.contains(Type.TypeA); // this will cause logic error, but
will NOT cause compile error


P.S. sorry my english is not so good
 
L

Lew

Hatter said:
I DO think contains SHOULD declared like:
boolean contains(E o)

Apparently Java's designers disagree, and the use case that Roedy described
makes a good argument for why the argument should be 'Object'.
if i user new ArrayList<Integer>(), then other type than Integer
SHOULD NOT contains in this collection, because then "contains

And won't be.
(Object)" does not check the type, sometimes this will cause program
logic error for passed a different type object.

No, because if the object is not actually an 'Integer' then 'contains()' will
correctly return 'false' - no error whatsoever.
I asked some other engineers they also made mistake like this.

It's not a mistake.
 
E

Eric Sosman

Hatter said:
[...]
WHY does Java was designed like this, ANY ONE KNOWS?
The parameter declarations are different:

boolean contains(Object o)

boolean add(E e)

Contains accepts any old object, not just one of the Collection type.
It makes no sense to try add an object not of type E, but it is
reasonable to ask if an object not proven to be of type E is a member.
It may or may not actually be of type E and it may or may not be a
member.

Is that what you were asking? How do you think it should work?

I DO think contains SHOULD declared like:
boolean contains(E o)

if i user new ArrayList<Integer>(), then other type than Integer
SHOULD NOT contains in this collection, because then "contains
(Object)" does not check the type, sometimes this will cause program
logic error for passed a different type object.

I'm not sure what kind of "logic error" would occur. For
a List that contains only Integer objects, contains("foo") will
return false, correctly saying that the String "foo" is not a
member of the list.

The deeper answer to your "WHY" is that generics were not
part of original Java, but were added to it later in life.
Originally you could not say Vector<Integer> to describe a
Vector that contained only Integers; you just wrote Vector
and had to be careful about the things you added to it.

New style:

Vector<Integer> v = new Vector<Integer>();
v.add(new Integer(42));
Integer i = v.get(0);
v.add("forty-two"); // compile-time error

Old style:

Vector v = new Vector();
v.add(new Integer(42));
Integer i = v.get(0); // compile-time error
Integer j = (Integer)v.get(0); // cast required
v.add("forty-two"); // perfectly legal
Integer k = (Integer)v.get(1); // run-time error

An important point to remember is that the new style is
entirely due to the compiler; the run-time behavior is the
same in both styles. That is, the Vector<Integer> is really
just a plain Vector, or Vector<Object>, when the code finally
runs. The cast on the result of v.get() is still present, but
the compiler wrote it for you instead of making you do it
yourself. It is even possible to insert a String into a
Vector<Integer>, if you work hard enough:

((Vector<Object>)v).add("forty-two");

If generics had been part of Java from the beginning, it
is possible that Collections would have been designed in a
different way. But by the time generics were invented, the
Collections framework already existed and it worked with
plain Object references.
 
T

Tom Anderson

Hatter said:
[...]
WHY does Java was designed like this, ANY ONE KNOWS?
The parameter declarations are different:

boolean contains(Object o)

boolean add(E e)

Contains accepts any old object, not just one of the Collection type.
It makes no sense to try add an object not of type E, but it is
reasonable to ask if an object not proven to be of type E is a member.
It may or may not actually be of type E and it may or may not be a
member.

Is that what you were asking? How do you think it should work?

I DO think contains SHOULD declared like:
boolean contains(E o)

if i user new ArrayList<Integer>(), then other type than Integer
SHOULD NOT contains in this collection, because then "contains
(Object)" does not check the type, sometimes this will cause program
logic error for passed a different type object.

I'm not sure what kind of "logic error" would occur.

I'm actually with Hatter on this. Here's a motivating example inspired by
one he posted, and based on some work on internationalised e-commerce i've
been doing:

public class Product {
/**
@return the locales in which this product is on sale
*/
public Collection<Locale> getSaleLocales() ;
}

// elsewhere in the code ...

public void restrictCartToUK(ShoppingCart cart) {
Iterator<CartItem> items = cart.getItems() ;
while (items.hasNext()) {
if (!items.next().getSaleLocales().contains("en_GB"))
items.remove() ;
}
}

That should compile and run without complaint. However, it will always
remove all the items from your shopping cart. Why? Because in the test in
the loop, locale is being expressed as a String, not a Locale.

Our code uses a number of different ways of representing locales and
countries in different places in the code - Locale objects, strings
(sometimes a code, sometimes a name), objects from an ORM layer, and in
one place, even an integer index into an array. It would be nice to be
absolutely consistent across the whole app, but in each case, there are
reasons to be using a particular representation (perhaps not adequate
reasons, but reasons nonetheless, and this is the way our code is now).
Strong typing is one thing that helps us keep on top of this.

In short, the code above is 'correct' in some sense, but it's also very
much wrong. Making contains take a T instead of an Object would have
caught it.
The deeper answer to your "WHY" is that generics were not
part of original Java, but were added to it later in life.

Collection.add was retrofitted. There's absolutely no technical reason
Collection.contains *couldn't* have been.

However, there might well be reasons why it *wasn't*. What might they be?
When does requiring the argument to be of a plausible type matter?

You can imagine something a bit like this:

public class ChristmasWish {
private Product productWanted ;

public boolean canBeFulfilledBy(Collection<Product> productSelection) {
return productSelection.contains(productWanted) ;
}
}

But then:

List<ElectricalProduct> stereos = AudioStore.getStereos() ;
ChristmasWish wish = getUserProfile.getChristmasWish() ;
if (wish.canBeFulfilledBy(stereos)) {
redirectToAudioStore(AudioStore.Sections.STEREOS) ;
}

That won't work, because you can't pass a Collection<ElectricalProduct> to
a method which takes a Collection<Product>. But because
Collection.contains takes an object, you can declare canBeFulfilledBy as
taking a Collection<Object>, and you're fine. You can even declare it
Collection<? extends Product> for a little more safety. If contains took a
T, you'd be stuffed - there's just no way to pass a Collection<S> as a
Collection<T>, no matter what the relationship between S and T.

There is a workaround: Collections.unmodifiableCollection returns a
Collection<T>, but takes a Collection<? extends T> as the parameter, so
you can use to to safely upcast the collection:

if (wish.canBeFulfilledBy(Collections.<Product>unmodifiableCollection(stereos))) {

However, i'm not at all sure that unmodifiableCollection could or would
work like that of contains took a T rather than an Object.

tom

--
The Gospel is enlightened in interesting ways by reading Beowulf and The
Hobbit while listening to Radiohead's Hail to the Thief. To kill a dragon
(i.e. Serpent, Smaug, Wolf at the Door) you need 12 (disciples/dwarves)
plus one thief (burglar, Hail to the Thief/King/thief in the night),
making Christ/Bilbo the 13th Thief. -- Remy Wilkins
 
R

Roedy Green

I DO think contains SHOULD declared like:
boolean contains(E o)

Here is a second argument for Sun's way of doing things.

When generics were introduced, the same Collection byte code (both
created with and without generics) had to keep working as a commitment
to upward compatibility. If Sun had done things your way, you would
sometimes need to add a cast like this:

boolean exists = collection.contains((E) candidate);

This would break the upward compatibility rule.
--
Roedy Green Canadian Mind Products
http://mindprod.com
PM Steven Harper is fixated on the costs of implementing Kyoto, estimated as high as 1% of GDP.
However, he refuses to consider the costs of not implementing Kyoto which the
famous economist Nicholas Stern estimated at 5 to 20% of GDP
 
L

Lew

I'm actually with Hatter on this. Here's a motivating example inspired by
one he posted, and based on some work on internationalised e-commerce i've
been doing:

public class Product {
        /**
        @return the locales in which this product is on sale
        */
        public Collection<Locale> getSaleLocales() ;

}

// elsewhere in the code ...

public void restrictCartToUK(ShoppingCart cart) {
   Iterator<CartItem> items = cart.getItems() ;
   while (items.hasNext()) {
                if (!items.next().getSaleLocales().contains("en_GB"))
                        items.remove() ;
        }
}

That should compile and run without complaint. However, it will always
remove all the items from your shopping cart. Why? Because in the test in
the loop, locale is being expressed as a String, not a Locale.

That is no worse than the situation with raw types. of course. Since
the 'contains()' method has not changed, the onus remains on the
programmer not to make this kind of mistake. While it would be nice
if Java prevented all our bugs for us, at least they haven't made it
worse. In this case we just have to remain skilled practitioners
instead of robots.
 
D

Daniel Pitts

Tom said:
Hatter said:
On Dec 17, 1:44 pm, Roedy Green <[email protected]>
wrote:
[...]
WHY does Java was designed like this, ANY ONE KNOWS?
The parameter declarations are different:

boolean contains(Object o)

boolean add(E e)

Contains accepts any old object, not just one of the Collection type.
It makes no sense to try add an object not of type E, but it is
reasonable to ask if an object not proven to be of type E is a member.
It may or may not actually be of type E and it may or may not be a
member.

Is that what you were asking? How do you think it should work?

I DO think contains SHOULD declared like:
boolean contains(E o)

if i user new ArrayList<Integer>(), then other type than Integer
SHOULD NOT contains in this collection, because then "contains
(Object)" does not check the type, sometimes this will cause program
logic error for passed a different type object.

I'm not sure what kind of "logic error" would occur.

I'm actually with Hatter on this. Here's a motivating example inspired
by one he posted, and based on some work on internationalised e-commerce
i've been doing:

public class Product {
/**
@return the locales in which this product is on sale
*/
public Collection<Locale> getSaleLocales() ;
}

// elsewhere in the code ...

public void restrictCartToUK(ShoppingCart cart) {
Iterator<CartItem> items = cart.getItems() ;
while (items.hasNext()) {
if (!items.next().getSaleLocales().contains("en_GB"))
items.remove() ;
}
}

That should compile and run without complaint. However, it will always
remove all the items from your shopping cart. Why? Because in the test
in the loop, locale is being expressed as a String, not a Locale.

Our code uses a number of different ways of representing locales and
countries in different places in the code - Locale objects, strings
(sometimes a code, sometimes a name), objects from an ORM layer, and in
one place, even an integer index into an array. It would be nice to be
absolutely consistent across the whole app, but in each case, there are
reasons to be using a particular representation (perhaps not adequate
reasons, but reasons nonetheless, and this is the way our code is now).
Strong typing is one thing that helps us keep on top of this.

In short, the code above is 'correct' in some sense, but it's also very
much wrong. Making contains take a T instead of an Object would have
caught it.
The deeper answer to your "WHY" is that generics were not
part of original Java, but were added to it later in life.

Collection.add was retrofitted. There's absolutely no technical reason
Collection.contains *couldn't* have been.

However, there might well be reasons why it *wasn't*. What might they
be? When does requiring the argument to be of a plausible type matter?

You can imagine something a bit like this:

public class ChristmasWish {
private Product productWanted ;

public boolean canBeFulfilledBy(Collection<Product> productSelection) {
return productSelection.contains(productWanted) ;
}
}

But then:

List<ElectricalProduct> stereos = AudioStore.getStereos() ;
ChristmasWish wish = getUserProfile.getChristmasWish() ;
if (wish.canBeFulfilledBy(stereos)) {
redirectToAudioStore(AudioStore.Sections.STEREOS) ;
}

That won't work, because you can't pass a Collection<ElectricalProduct>
to a method which takes a Collection<Product>. But because
Collection.contains takes an object, you can declare canBeFulfilledBy as
taking a Collection<Object>, and you're fine. You can even declare it
Collection<? extends Product> for a little more safety. If contains took
a T, you'd be stuffed - there's just no way to pass a Collection<S> as a
Collection<T>, no matter what the relationship between S and T.

There is a workaround: Collections.unmodifiableCollection returns a
Collection<T>, but takes a Collection<? extends T> as the parameter, so
you can use to to safely upcast the collection:

if
(wish.canBeFulfilledBy(Collections.<Product>unmodifiableCollection(stereos)))
{

However, i'm not at all sure that unmodifiableCollection could or would
work like that of contains took a T rather than an Object.

tom
My IDE tells me that I have a "suspicious use of" contains. This is
good enough for me :)
 
E

Eric Sosman

Tom said:
[...]
The deeper answer to your "WHY" is that generics were not
part of original Java, but were added to it later in life.

Collection.add was retrofitted. There's absolutely no technical reason
Collection.contains *couldn't* have been.

Beg to differ: It would have changed the behavior of
existing code. Prior to generics,

Vector v = new Vector();
v.add(new Integer(1));
v.add(new Integer(2));
boolean b = v.contains("slithy toves");

.... was perfectly well-defined, and yielded b==false. If in
the new, genericized scheme of things the contains() method
on a Vector<T> accepted only arguments of type T, the formerly
valid code above would break:

Vector<Integer> v = new Vector<Integer>();
v.add(new Integer(1));
v.add(new Integer(2));
boolean b = v.contains("slithy toves"); // compile error?

Another "interesting" issue is illustrated by

boolean b2 = v.contains(null); // what's the type?
 
S

Stefan Ram

Eric Sosman said:
Vector v = new Vector(); (...)
the formerly valid code above would break: (...)
Vector<Integer> v = new Vector<Integer>();

That's not the code from above.
boolean b2 = v.contains(null); // what's the type?

There is also a special null type, the type of the
expression null

JLS3, 4.1

http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.1

A null literal is always of the null type.

JLS3, 3.10.7

http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.7

The name »b2« has the type »boolean«.
The type of »v« is unknown to me.
 
T

Tom Anderson

Tom said:
[...]
The deeper answer to your "WHY" is that generics were not
part of original Java, but were added to it later in life.

Collection.add was retrofitted. There's absolutely no technical reason
Collection.contains *couldn't* have been.

Beg to differ: It would have changed the behavior of
existing code. Prior to generics,

Vector v = new Vector();
v.add(new Integer(1));
v.add(new Integer(2));
boolean b = v.contains("slithy toves");

... was perfectly well-defined, and yielded b==false. If in
the new, genericized scheme of things the contains() method
on a Vector<T> accepted only arguments of type T, the formerly
valid code above would break:

Vector<Integer> v = new Vector<Integer>();
v.add(new Integer(1));
v.add(new Integer(2));
boolean b = v.contains("slithy toves"); // compile error?

May i bring to your attention the fact that this is in fact not existing
code at all? It's new code, with v being a different type, one including a
generic binding. You example is no different to me offering up this:

Vector v = new Vector() ;
v.add(new Integer(1)) ;
v.add("two") ;

Which is perfectly well-defined, and:

Vector<Integer> v = new Vector<Integer>() ;
v.add(new Integer(1)) ;
v.add("two") ;

Which is broken, and arguing that add should thus take an Object.
Another "interesting" issue is illustrated by

boolean b2 = v.contains(null); // what's the type?

The magical null type. This is much the same as:

Integer i = null ;

tom

--
It not infrequently happens that something about the earth, about the sky,
about other elements of this world, about the motion and rotation or even
the magnitude and distances of the stars, about definite eclipses of the
sun and moon, about the passage of years and seasons, about the nature
of animals, of fruits, of stones, and of other such things, may be known
with the greatest certainty by reasoning or by experience. -- St Augustine
 
T

Tom Anderson

That is no worse than the situation with raw types. of course.

Absolutely true.
Since the 'contains()' method has not changed, the onus remains on the
programmer not to make this kind of mistake. While it would be nice if
Java prevented all our bugs for us, at least they haven't made it worse.
In this case we just have to remain skilled practitioners instead of
robots.

I don't buy that line in the slightest. The whole point of introducing
generics was to make type safety stronger, and catch more type-related
bugs. If that was the goal, why was contains not tightened up along with
add?

tom

--
It not infrequently happens that something about the earth, about the sky,
about other elements of this world, about the motion and rotation or even
the magnitude and distances of the stars, about definite eclipses of the
sun and moon, about the passage of years and seasons, about the nature
of animals, of fruits, of stones, and of other such things, may be known
with the greatest certainty by reasoning or by experience. -- St Augustine
 
T

Tom Anderson

Here is a second argument for Sun's way of doing things.

When generics were introduced, the same Collection byte code (both
created with and without generics) had to keep working as a commitment
to upward compatibility. If Sun had done things your way, you would
sometimes need to add a cast like this:

boolean exists = collection.contains((E) candidate);

This would break the upward compatibility rule.

No, not at the bytecode level. Remember, types are erased in the bytecode
- if contains takes an E, then at the bytecode level, it takes an Object.
Old compiled code would keep working.

tom

--
It not infrequently happens that something about the earth, about the sky,
about other elements of this world, about the motion and rotation or even
the magnitude and distances of the stars, about definite eclipses of the
sun and moon, about the passage of years and seasons, about the nature
of animals, of fruits, of stones, and of other such things, may be known
with the greatest certainty by reasoning or by experience. -- St Augustine
 
T

Tom Anderson

My IDE tells me that I have a "suspicious use of" contains. This is good
enough for me :)

Ah, excellent. I wonder if that's based on a rule that contains should
take an E - in effect, that the IDE's suspicion rules recapitulate my
suggested change.

tom

--
It not infrequently happens that something about the earth, about the sky,
about other elements of this world, about the motion and rotation or even
the magnitude and distances of the stars, about definite eclipses of the
sun and moon, about the passage of years and seasons, about the nature
of animals, of fruits, of stones, and of other such things, may be known
with the greatest certainty by reasoning or by experience. -- St Augustine
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,816
Latest member
SapanaCarpetStudio

Latest Threads

Top