Yet another generics question

Hi there,

Does anyone have an explanation for the List, Map, etc interfaces to define certain methods like :

public V get(Object o)

rather than

public V get(K k)

It seems to me there's no reason for this that i can think of, and i'd much prefer a compile time error when something other than the parametrized type is passed to such a method, but perhaps i'm just missing something.

[487 byte] By [remonvva] at [2007-10-2 19:40:47]
# 1

Hi!

I was puzzled, too, after reading your post, but I thik I have found a reason.

First of all, you will agree that it is mandatory to not break old (<= 1.4) code, so there _must_ be one method with signature

V get(Object)'.

in the Map interface. There is of course no compatibility problem with replacing the old return type 'Object' with the more specific type 'V'.

Now, when you try to add another method

V get(K)

you get a problem. See the following code snippet:

class C {

String f(Map<Object,String> m, Object key) {

return m.get(key);

}

}

This won't compile because the compiler does not know, which

of the two getters it should use. The concrete error is:

"The method get(Object) is ambiguous for the type Map<Object,String>"

Greetings,

Michael

m_schneia at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 2

Hm, i'm not sure if i agree with you here. If V get(K) would break 1.4 code, then why doesnt put(K, V) break it as well? In other words, why does put(K, V) qualify for put(Object, Object) as a 1.4 signature if get(K) doesnt qualify for get(Object)?

I understand having both get(Object) and get(K) in the same interface is ambiguous, but is that really the problem here? Like i said, there's no two "old style" put() method for that reason.

remonvva at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 3

>

> It seems to me there's no reason for this that i can

> think of, and i'd much prefer a compile time error

> when something other than the parametrized type is

> passed to such a method, but perhaps i'm just missing

> something.

Generics is about type safety, and there is nothing type unsafe about asking your Map if it contains an Object. If the Object is of the wrong type the get method returns null, which IMO is just what you would expect.

The same goes for the method containsKey().

From a practial point of view it makes perfect sense. Say you have a class that maintains a Map<K, V>. Now that class is passed an Object, and is asked to check if the map contains a value mapped to that Object. If the containsKey() method was declared as containsKey(K key) you would first have to do an instanceof on the Object, followed by a cast if the Object is of the correct type, before you could check the Map. By keeping the get/containsKey argument non-generic you gain a lot of flexibility.

Torgila at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 4

I dont quite get how that's more flexible. You'd be asking a question you already know the answer of. You are already guaranteed that anything you can "get" from the map has been "put" there previously, obeying the generics there, and all methods/constructors that modify/add to the collection use generics.

The instanceof check you speak if would be just as pointless as something like

if(MyObject instanceof Object) {...}

There simply isnt any other option. It's not possible to provide a type not obeying the K generic to get and get anything but null. It's quite hard to imagine a situation where this is intended behaviour, it's pretty much a glorified variation on if(true).

So if you have code that could pass anything other than a K type to such methods you could argue that that's just bad code. I'll be the first to agree it provides flexibility though, but that's like saying C-style pointers add flexibility...it does, but it comes at a cost. I'd go as far as to consider code that asks for the existence of keys that are guaranteed not to be in there at compile time bugged really...perhaps i'm being overly purist here though ;)

remonvva at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 5

I'm not sure if this is really an issue, it's just something that popped to mind.

You can compare any two Maps for equality, right? So I could compare a Map<Foo, Bar> to a Map<Baz, Qux>. To do that, at some point I'm going to have to test whether the Map<Baz, Qux> contains various Foos as keys. Would having the generic signature suggested screw that up, or make it ugly? Might this be a legitmate reason why you have to be able to test for the existence of an entry that the type parameters would disallow?

jverda at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 6

> I'm not sure if this is really an issue, it's just

> something that popped to mind.

>

> You can compare any two Maps for equality, right? So

> I could compare a Map<Foo, Bar> to a Map<Baz, Qux>.

> To do that, at some point I'm going to have to test

> whether the Map<Baz, Qux> contains various Foos as

> keys.

Well no, see below ;)

>Would having the generic signature suggested

> screw that up, or make it ugly? Might this be a

> legitmate reason why you have to be able to

> test for the existence of an entry that the type

> parameters would disallow?

This wouldnt be an issue because you're guaranteed those two maps are either both empty, or unequal, since they dont accept eachother's types. There can never be a Foo in the Baz keyed map and vice versa. It becomes slightly less obvious if Baz is a child class of Foo, but the same still applies.

remonvva at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 7

> > I'm not sure if this is really an issue, it's just

> > something that popped to mind.

> >

> > You can compare any two Maps for equality, right?

> So

> > I could compare a Map<Foo, Bar> to a Map<Baz,

> Qux>.

> > To do that, at some point I'm going to have to

> test

> > whether the Map<Baz, Qux> contains various Foos as

> > keys. Would having the generic signature suggested

> > screw that up, or make it ugly? Might this be a

> > legitmate reason why you have to be able to

> > test for the existence of an entry that the type

> > parameters would disallow?

>

> This wouldnt be an issue because you're guaranteed

> those two maps are either both empty, or unequal,

> since they dont accept eachother's types. There can

> never be a Foo in the Baz keyed map and vice versa.

> It becomes slightly less obvious if Baz is a child

> class of Foo, but the same still applies.

Yes, you and I know the two maps won't be equal, but equals(Object) lets it test against any other map. There's no way for equals to test anything like if map1's <K> !typeComaptibleWith map2's<K> then return false--it has to compare each key or entry.

jverda at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 8

> Yes, you and I know the two maps won't be equal, but

> equals(Object) lets it test against any other map.

> There's no way for equals to test anything like if

> map1's <K> !typeComaptibleWith map2's<K> then return

> false--it has to compare each key or entry.

Ah yes, you are right, i completely missed that point in your previous post, thanks! ;)

Although, i suppose if you're really into safety, i suppose you could make it so that this generates a compile-time "condition is always false" error/warning :

List<Object A> l1;

List<Object B> l2;

if(l1.equals(l2) {...} // <- compile time warning, always false.

But yeah, implementing an equals method is impossible without the get(Object o) method signature. I stand corrected ;)

remonvva at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 9

> I dont quite get how that's more flexible. You'd be

> asking a question you already know the answer of.

Maybe I'm misunderstanding you here, but you wouldn't already know the answer.

If you have a Map<K, V>, and you are passed an Object, it would be impossible for you to safely check if that Object is a key in your Map. The Object could be of type K, but you would have to cast it blindly before you check (because you can't do an instanceof with a generic type, so what I wrote above wouldn't work). That will of course result in a ClassCastException if Object isn't of type K.

Now, you are arguing that it would be "bad" code to ask if the Map contains an Object, that "good" code would only ask if the Map contains a K. I'm arguing that enforcing such a restriction would lead to very inflexible code. In practice you might not have any control over where the Object comes from, it might for instance be extracted from a List<?>, or deserialized from a stream

Torgila at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 10

I couldn't possibly answer what motivated Bloch and Gaffer because I'm not them, but I believe jverd is more or less on the right track. If you change the parameter type in the declaration then programmers are free to assume that it is that type and not any Object. This would be very bad when the equals implementation starts passing in something other than K because the equals implementation itself has absolutely no way of checking what K is for the passed in Map. You might say "Change the equals implementation!" but that becomes rather ugly when you realize that doing so would require changing the method signature and as soon as they do that it no longer overrides equals(Object).

So in my never to be humble opinion:

1. They must override equals(Object).

2. Overriding equals means they cannot change the signature.

3. It is impossible to check that the parameter is a Map<K, V> without changing the signature.

4. The implementation is forced to pass what could be any Object to another Map.

5. The methods it uses must accept an Object and not a K because it cannot guarantee it is actually passing a K.

6. If the methods took a K instead programmers would (rightly so) assume that the parameter must be a K, however, equals could easily break that.

Ex:

Map<String, String> stringMap = new HashMap<String, String>();

// Add key-value pairs.

Map<Integer, Integer> integerMap = new HashMap<Integer, Integer>();

// Add key-value pairs.

// How do you propose this next method call work without ever calling

// methods with an argument that could logically be any Object?

stringMap.equals(integerMap);

kablaira at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 11
I posted too late. Must type faster.
kablaira at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 12

> If you have a Map<K, V>, and you are passed an

> Object, it would be impossible for you to

> safely check if that Object is a key in your

> Map.

That sounds more less like what I was struggling toward, only more concise. :-)

I think the real issue here though is that the kind of thing the OP suggests is beyond what generics are intened for. While it might be "nice" if the compiler flagged these kinds of things that are logically impossible, that's not really what generics are for. They're for compile-time typesafety--for turning potential rutime CCEs into compile-time errors. Map<Foo, Bar>.get(Baz) cannot cause a CCE.

jverda at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 13

> > If you have a Map<K, V>, and you are passed an

> > Object, it would be impossible for you to

> > safely check if that Object is a key in

> your

> > Map.

>

> That sounds more less like what I was struggling

> toward, only more concise. :-)

>

Maybe, but your example was better :-)

> I think the real issue here though is that the kind

> of thing the OP suggests is beyond what generics are

> intened for. While it might be "nice" if the compiler

> flagged these kinds of things that are logically

> impossible, that's not really what generics are for.

> They're for compile-time typesafety--for turning

> potential rutime CCEs into compile-time errors.

> Map<Foo, Bar>.get(Baz) cannot cause a CCE.

I agree, and I tried to say something similar in my first reply.

Torgila at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 14
Okay guys, mystery solved ;) Thanks!
remonvva at 2007-7-13 22:18:47 > top of Java-index,Core,Core APIs...
# 15
> I agree, and I tried to say something similar in my> first reply.Ah, so you did.Note to self: Work on improving attention sp... HEY! A squirrel!
jverda at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 16
And maybe they should allowif(myMap instanceof Map<String, String>)*runs*
remonvva at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 17

> And maybe they should allow

>

> if(myMap instanceof Map<String,

> String>)

>

> *runs*

Actually, that's one thing I kind of wonder about.

I don't know anything about what's involved in actually specifying or implementing generics at the language or VM level, but I wonder if the type-erasure stuff was done as a first-cut, and there's some watercooler discussions about when and how retainin generic type details up through runtime will happen, or if that's a door that's well and truly closed.

Is it unfeasible, or ridiculously messy, or just very hard?

jverda at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 18

Well from what i understood the point of generics is that they are supposed to be 100% compile time things, so it was a bit of a joke more than anything else.

On the other hand, being able to do those kinda things might be handy at times, although i cant actually think of any practical problem it would solve.

remonvva at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 19

>

> I don't know anything about what's involved in

> actually specifying or implementing generics at the

> language or VM level, but I wonder if the

> type-erasure stuff was done as a first-cut, and

> there's some watercooler discussions about when and

> how retainin generic type details up through runtime

> will happen, or if that's a door that's well and

> truly closed.

>

> Is it unfeasible, or ridiculously messy, or just very

> hard?

I read something somewhere about the reason of using type-erasure had to do with backward compability. Maybe couple of new Java versions into the future, when backward compability with pre-Tiger isn't deemed as being of great importance, they will flip the magic switch and make everyone (except the ones still stuck with 1.4) happy.

Torgila at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 20

Sorry, I should have been explicit in telling

what I mean by "old code".

I was thinking on something like

List lookupAllMatches(Map database, List keys) {

List result = new LinkedList();

for (Iterator i = keys.iterator(); i.hasNext(); ) {

Object key = i.next();

if (database.containsKey(key)) {

result.add(database.get(key);

}

return result;

}

In the above code, a Map<K,V> and a List<? extends K>

is received *unchecked*, but the code should of course

work as before.

But my idea was too focussed: In the meantime I have

learned from postings of other people, that this is

not just an issue of compatibility. The 'containsKey()'

in my example method should work on _any_ input,

without regarding its type (returning "no" in the

worst case of a wrong type). This will be a demand

in actual code, too, of course.

So through the postings of Torgil, jverd , e.a.,

my former posting gets @deprecated. :)

Michael

m_schneia at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 21

> Sorry, I should have been explicit in telling

> what I mean by "old code".

Sorry again, the above was an answer to the third posting of this thread:

remonw wrote:

> Hm, i'm not sure if i agree with you here. If V get(K) would

> break 1.4 code, then why doesnt put(K, V) break it as well?

> In other words, why does put(K, V) qualify for put(Object, Object) as a 1.4

> signature if get(K) doesnt qualify for get(Object)?

Michael

m_schneia at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 22

I think the actual code of AbstractMap.equals may just blow all above theories out of the water :

public boolean equals(Object o) {

if (o == this)

return true;

if (!(o instanceof Map))

return false;

Map<K,V> t = (Map<K,V>) o;

if (t.size() != size())

return false;

try {

Iterator<Entry><K,V>> i = entrySet().iterator();

while (i.hasNext()) {

Entry<K,V> e = i.next();

K key = e.getKey();

V value = e.getValue();

if (value == null) {

if (!(t.get(key)==null && t.containsKey(key)))

return false;

} else {

if (!value.equals(t.get(key)))

return false;

}

}

} catch(ClassCastException unused) {

return false;

} catch(NullPointerException unused) {

return false;

}

return true;

}

Unless i'm misreading this code the containsKey method is called by passing a K rather than an Object at compile time (and thus a containsKey(K key) wouldnt break this method).

Take note of this code snippet, an unchecked cast :

Map<K,V> t = (Map<K,V>) o;

This code runs just fine even if "o" is Map<String, String> and "this" is Map<Integer, Integer>. Ofcourse this is no huge surprise since generics are compile-time so at run-time this would just execute as Map t = (Map)o.

So i copied Map and AbstractMap and changed the method signatures to what i suggested and all compiles and runs fine, including the examples above that should break it. What am i missing?

remonvva at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 23
Hi! friends,i need the code fro connecting jsp with mysql.plz send the reply as soon as possibleregards,shiva
shivaaaaaaaa at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 24
hi! friends,Plz,give the connectivity code for jsp with mysql.
shivaaaaaaaa at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 25

> Generics is about type safety, and there is nothing

> type unsafe about asking your Map if it contains an

> Object. If the Object is of the wrong type the get

> method returns null, which IMO is just what you would

> expect.

>

> The same goes for the method containsKey().

>

I think that is pretty convincing: Why use a type parameter in a method signature if it doesn't increase type safety?

Another aspect of this reasoning becomes apparent if you look at code that utilizes wildcards:

if the methods had been declared as get(K key) or containsKey(K key), you couldn't use them on variables that contained wildcards, such as boolean hasZero(Map<? extends Number, String> map)

{

return map.containsKey(Integer.valueOf(0));

}

That would mean sacrificing flexiblity without gaining anything.

McNeppa at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 26
> hi! friends,> > Plz,give the connectivity code for jsp with mysql.Start your own thread, in the appropriate forum, and spell out words like "please."
jverda at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 27

> Another aspect of this reasoning becomes apparent if

> you look at code that utilizes wildcards:

> if the methods had been declared as get(K key)

> or containsKey(K key), you couldn't use them

> on variables that contained wildcards, such as

> > boolean hasZero(Map<? extends Number, String> map)

> {

>return map.containsKey(Integer.valueOf(0));

>

> That would mean sacrificing flexiblity without

> gaining anything.

Yup, you're absolutely right. Good point.

remonvva at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 28
> hi! friends,> > Plz,give the connectivity code for jsp with mysql....
remonvva at 2007-7-21 0:09:04 > top of Java-index,Core,Core APIs...
# 29

The first thing that popped to my mind while reading this thread is that there's no reason that a method declared as

get(K key)

couldn't accept an Object. After erasure, wouldn't K become Object anyway?

It occurs to me that this may not be the case if the map were declared

Map<K extends SomeClass, V>

but it's not. Does anyone see where I'm going wrong here?

tvynra at 2007-7-21 0:09:05 > top of Java-index,Core,Core APIs...
# 30

> So i copied Map and AbstractMap and changed the

> method signatures to what i suggested and all

> compiles and runs fine, including the examples above

> that should break it. What am i missing?

No, it's not really passing a K because it has no idea whether or not what it's passing actually is a K, it could be any Object. So what happens when you have a Map that assumes the argument is a K? What happens when I have an IntegerMap that tries to call intValue() but ends up being passed a String? You have to remember that Map is an interface, it can be implemented in any number of ways, the fact that the current implementations don't do anything that would break it in your example isn't relevent. Changing the declaration would open Pandora's box and it would be trivially easy for us to write a Map implementation that completely conforms to the Map interface and is horribly broken and not typesafe.

kablaira at 2007-7-21 0:09:09 > top of Java-index,Core,Core APIs...
# 31

> The first thing that popped to my mind while reading

> this thread is that there's no reason that a method

> declared as

> > get(K key)

>

> couldn't accept an Object. After erasure, wouldn't K

> become Object anyway?

Only if Object is the upper bounds and it's not necessarily.

> It occurs to me that this may not be the case if the

> map were declared

> > Map<K extends SomeClass, V>

>

> but it's not. Does anyone see where I'm going wrong

> here?

Map isn't, but the implementation is free to do exactly that.

public class IntegerMap implements Map<Integer, Integer> {

public boolean containsKey(Integer i) {

// blah

}

}

What happens if I use i.intValue() (for whatever reason, it's irrelevent) in containsKey? What happens when another Map ends up passing it a String? Boom? This would be a perfectly valid implementation were containsKey() declared using K instead of Object.

kablaira at 2007-7-21 0:09:09 > top of Java-index,Core,Core APIs...
# 32

Perfectly sensible, thank you. :)

Although it is rather unfortunate. It makes it rather difficult to operate on the key in question if it's a specialized implementation of a Map. I guess that's a side effect of not knowing the parameterized type at runtime?

We do seem to keep coming back to that.

tvynra at 2007-7-21 0:09:09 > top of Java-index,Core,Core APIs...