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