Type erasure bug?

S

Safalra

Consider the following code:

Vector<String> s=new Vector<String>(10);
Vector o=s;
o.add(new Object());
Object p=s.get(0);

Obviously this gives an unchecked warning about the second line.
Because of type erasure the third line doesn't cause a run-time
exception either. What surprises me is that the fourth line doesn't
cause a class cast exception, despite accessing the Vector through s. I
presume internally type erasure turns it into:

Object p=(Object)(String)s.get(0);

Which the compiler simplifies to:

Object p=s.get(0);

Is my understanding of this correct, and do people here regard this a
bug in the compiler, or just another consequence of allowing unchecked
exceptions in your code?

(I discovered this while writing an article for a friend of mine who
isn't a fan of Java - you can read it at
http://www.safalra.com/programming/java/wrongerasure/ but remember that
I *do* actually like Java in general, I just dislike certain features.)
 
T

Thomas Hawtin

Safalra said:
Consider the following code:

Vector<String> s=new Vector<String>(10);
Vector o=s;
o.add(new Object());
Object p=s.get(0);

Obviously this gives an unchecked warning about the second line.
Because of type erasure the third line doesn't cause a run-time
exception either. What surprises me is that the fourth line doesn't
cause a class cast exception, despite accessing the Vector through s. I
presume internally type erasure turns it into:

Object p=(Object)(String)s.get(0);

Which the compiler simplifies to:

Object p=s.get(0);

Is my understanding of this correct, and do people here regard this a
bug in the compiler, or just another consequence of allowing unchecked
exceptions in your code?

If you try to subvert generic types on your own head be it. It seems
reasonable to avoid unnecessary casts. It would be even more expensive
to check super bounds.

Beta versions of 5.0 had a bug where the removal of unnecessary casts
went a little too far. If you had List<char[]> and then passed the
elements to System.out.println, it would use System.out.println(Object)
instead of System.out.println(char[]).

Tom Hawtin
 
R

Raymond DeCampo

Safalra said:
Consider the following code:

Vector<String> s=new Vector<String>(10);
Vector o=s;
o.add(new Object());
Object p=s.get(0);

Obviously this gives an unchecked warning about the second line.
Because of type erasure the third line doesn't cause a run-time
exception either. What surprises me is that the fourth line doesn't
cause a class cast exception, despite accessing the Vector through s. I
presume internally type erasure turns it into:

Why would assigning anything (except a primitive type, which would cause
a compiler error) to Object ever cause a class cast exception?

Ray
 
T

Thomas G. Marshall

Raymond DeCampo coughed up:
Why would assigning anything (except a primitive type, which would
cause a compiler error) to Object ever cause a class cast exception?


You're missing the OP's point here.

Generics allows you to omit the cast when retrieving values. Given the OP's
vector of strings "s":

String str = s.get(0);

is allowed without requiring the

String str = (String)s.get(0);

cast that you would have to do pre-generics. That cast is "added in". So
the question is what happens in the 4th line of his example:

Object p = s.get(0);

One would think that this might internally become:

Object p = (String)s.get(0);

because of "s" being a generic (Vector of String). That is where the CCE
might have come in. What he has at slot 0 in the vector is a plain old
Object, not a String. So his question is why doesn't this fail.
 
M

Mike Schilling

Thomas G. Marshall said:
Raymond DeCampo coughed up:


You're missing the OP's point here.

Generics allows you to omit the cast when retrieving values. Given the
OP's vector of strings "s":

String str = s.get(0);

is allowed without requiring the

String str = (String)s.get(0);

cast that you would have to do pre-generics. That cast is "added in". So
the question is what happens in the 4th line of his example:

Object p = s.get(0);

One would think that this might internally become:

Object p = (String)s.get(0);

because of "s" being a generic (Vector of String). That is where the CCE
might have come in. What he has at slot 0 in the vector is a plain old
Object, not a String. So his question is why doesn't this fail.


Mine too. Surely if I wrote

methodX(s.get(0))

and methodX had overloads methodX(String) and methodX(Object), this should
generate

methodX((String)s.get(0))

On the other hand, if methodX only has the overload methodX(Object), the
cast in

methodX((String)s.get(0))

is unnecessary and, it can be argued, should be omitted. Is it the case,
then, that the return type implied by the use of the generic method (i.e.
"get()") determines whether the cast is applied? That seems evil to me,
being the moral equivalent of overloading methods by return type. (And it
would cause an odd bug if the overload methodX(String) were added after the
caller was compiled) But I can't in all honesty say that I've thought this
through.
 
C

Chris Uppal

Mike said:
Is it the case,
then, that the return type implied by the use of the generic method (i.e.
"get()") determines whether the cast is applied? That seems evil to me,
being the moral equivalent of overloading methods by return type. (And
it would cause an odd bug if the overload methodX(String) were added
after the caller was compiled)

I'm not sure if I'm understanding what you consider "evil", but -- although
surprising -- it doesn't seem all that damaging to me. After all the situation
only arises when you mix generic and non-generic code in the same application
/and/ expect the compiler to produce static type safety. Runtime type safety
is still (I think) assured, since the compiler only omits "unnecessary"
casts -- so runtime stuff that /should/ work /does/ work. The only thing
that's omitted is that runtime code that (according to static tests) cannot be
validated is allowed to run (or fail) dynamically, rather than duplicating the
"fail even if it is not actually doing anything illegal" behaviour that the
static type checking enforces.

BTW, don't forget that the signature of the called method is part of the
invokexxx bytecode(s), so if any other methodX(String) were added later, then
that would be "invisible" to the code that was compiled before it existed.

-- chris
 
R

Raymond DeCampo

Thomas said:
Raymond DeCampo coughed up:




You're missing the OP's point here.

You are correct.
Generics allows you to omit the cast when retrieving values. Given the OP's
vector of strings "s":

String str = s.get(0);

is allowed without requiring the

String str = (String)s.get(0);

cast that you would have to do pre-generics. That cast is "added in". So
the question is what happens in the 4th line of his example:

Object p = s.get(0);

One would think that this might internally become:

Object p = (String)s.get(0);

because of "s" being a generic (Vector of String). That is where the CCE
might have come in. What he has at slot 0 in the vector is a plain old
Object, not a String. So his question is why doesn't this fail.

Thanks for the exposition.

Ray
 
S

Safalra

Mike said:
Thomas G. Marshall said:
[snip]
Object p = s.get(0);
One would think that this might internally become:
Object p = (String)s.get(0);
because of "s" being a generic (Vector of String). That is where the CCE
might have come in. What he has at slot 0 in the vector is a plain old
Object, not a String. So his question is why doesn't this fail.

Mine too. Surely if I wrote
methodX(s.get(0))
and methodX had overloads methodX(String) and methodX(Object), this should
generate
methodX((String)s.get(0))
On the other hand, if methodX only has the overload methodX(Object), the
cast in
methodX((String)s.get(0))
is unnecessary and, it can be argued, should be omitted. Is it the case,
then, that the return type implied by the use of the generic method (i.e.
"get()") determines whether the cast is applied? That seems evil to me,
being the moral equivalent of overloading methods by return type.

I don't understand in what way you consider these equivalent.
 
T

Thomas G. Marshall

Mike Schilling coughed up:
"Thomas G. Marshall"
message
....[rip]...
One would think that this might internally become:

Object p = (String)s.get(0);

because of "s" being a generic (Vector of String). That is where
the CCE might have come in. What he has at slot 0 in the vector is
a plain old Object, not a String. So his question is why doesn't
this fail.


Mine too. Surely if I wrote

methodX(s.get(0))

and methodX had overloads methodX(String) and methodX(Object), this
should generate

methodX((String)s.get(0))

On the other hand, if methodX only has the overload methodX(Object),
the cast in

methodX((String)s.get(0))

is unnecessary and, it can be argued, should be omitted. Is it the
case, then, that the return type implied by the use of the generic
method (i.e. "get()") determines whether the cast is applied? That
seems evil to me, being the moral equivalent of overloading methods
by return type.

I'm not thrilled with hardly any of this generics business. As I've said
before, it really seems the wrong solution, and generics should have been a
full-blown runtime thing, backward compatibility be damned.

However, this particular blemish doesn't bother me as much as it seems to
you: it seems as if it is a compiler simply making a later pass over the
code and removing broken casts. (maybe).

That's the problem with half-baked solutions. They tend to necessitate
plaster and spackle in various places.
 
R

Roedy Green

Vector<String> s=new Vector<String>(10);
Vector o=s;
o.add(new Object());
Object p=s.get(0);

I would read it this way:

You have made a deal with Javac. You have said the Vector will
contain only Strings. If there is any unchecked code, you accept the
compiler's warning and agree to keep the contract manually to let only
Strings into the Vector.

So the compiler then looks at the line

Object p=s.get(0);

It says to itself, s contains only Strings. Therefore get must return
a String. A String is a species of Object, so no cast is needed.

Further, from a type erasure point of view, s at run time contains
only Objects (or subclasses) absolutely guaranteed, so no cast is
necessary.
 
M

Mike Schilling

Safalra said:
Mike said:
"Thomas G. Marshall"
[snip]
Object p = s.get(0);
One would think that this might internally become:
Object p = (String)s.get(0);
because of "s" being a generic (Vector of String). That is where the
CCE
might have come in. What he has at slot 0 in the vector is a plain old
Object, not a String. So his question is why doesn't this fail.

Mine too. Surely if I wrote
methodX(s.get(0))
and methodX had overloads methodX(String) and methodX(Object), this
should
generate
methodX((String)s.get(0))
On the other hand, if methodX only has the overload methodX(Object), the
cast in
methodX((String)s.get(0))
is unnecessary and, it can be argued, should be omitted. Is it the case,
then, that the return type implied by the use of the generic method (i.e.
"get()") determines whether the cast is applied? That seems evil to me,
being the moral equivalent of overloading methods by return type.

I don't understand in what way you consider these equivalent.

In both cases an expression's meaning is being (partially, at least)
determined by how it's used. That is, from the top down, rather than from
the bottom up. If s is a Vector<String>, a good, simple rule is that
s.get(0) is of type String. If it's sometimes of type object, things have
gotten far more complex. Likewise if foo(x)'s type depends on what's done
with foo's return value.
 
M

Mike Schilling

Roedy Green said:
I would read it this way:

You have made a deal with Javac. You have said the Vector will
contain only Strings. If there is any unchecked code, you accept the
compiler's warning and agree to keep the contract manually to let only
Strings into the Vector.

So the compiler then looks at the line

Object p=s.get(0);

It says to itself, s contains only Strings. Therefore get must return
a String. A String is a species of Object, so no cast is needed.

Top-down rather than bottom-up, expression parsing. Ugh.
 
M

Mike Schilling

Chris Uppal said:
BTW, don't forget that the signature of the called method is part of the
invokexxx bytecode(s), so if any other methodX(String) were added later,
then
that would be "invisible" to the code that was compiled before it existed.


Yeah, I realized that somewhat after I posted. I warned you I hadn't
thought it through.
 

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,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top