Is this a bug?

Hi,

We are starting to adopt Java 5.0. The language improvements are interesting but not so trivial to understand. Definitely this forum has been a great source of information.

This is the piece of code that is making us scratch our heads:

class Container<E>

{

protected E[] data = (E[])new Object[0];

protectedvoid sysout()

{

System.out.println("data from Container<E>: " + data.hashCode());

}

}

class ExtendedContainerextends Container<Integer>

{

publicvoid sysoutMore()

{

sysout();

System.out.println("(Object)data from ExtendedContainer: " + ((Object)data).hashCode());

System.out.println("data from ExtendedContainer: " + data.hashCode());

}

}

publicclass Test

{

publicstaticvoid main(String[] args)

{

ExtendedContainer x =new ExtendedContainer();

x.sysoutMore();

}

}

In both Sun's javac and Eclipse, the 2nd sysout inExtendedContainer.sysoutMore() fails with aClassCastException. Looking at the byte code for the method, we can understand why: there is CHECKCAST that verifies if data is anInteger. It is *not*: in runtime data is anObject[].

Here's the bytecode:

// sysoutMore(): void

L0 (17)

ALOAD 0: this

INVOKEVIRTUAL ExtendedContainer.sysout(): void

L1 (18)

GETSTATIC System.out: PrintStream

NEW StringBuilder

DUP

LDC "(Object)data from ExtendedContainer: "

INVOKESPECIAL StringBuilder.<init>(String): void

ALOAD 0: this

GETFIELD ExtendedContainer.data: []Object

INVOKEVIRTUAL Object.hashCode(): int

INVOKEVIRTUAL StringBuilder.append(int): StringBuilder

INVOKEVIRTUAL StringBuilder.toString(): String

INVOKEVIRTUAL PrintStream.println(String): void

L2 (19)

GETSTATIC System.out: PrintStream

NEW StringBuilder

DUP

LDC "data from ExtendedContainer: "

INVOKESPECIAL StringBuilder.<init>(String): void

ALOAD 0: this

GETFIELD ExtendedContainer.data: []Object

CHECKCAST Integer

INVOKEVIRTUAL Object.hashCode(): int

INVOKEVIRTUAL StringBuilder.append(int): StringBuilder

INVOKEVIRTUAL StringBuilder.toString(): String

INVOKEVIRTUAL PrintStream.println(String): void

L3 (20)

RETURN

L4

Note that everything would be fine ifdata had a "non-array" generic type.

The questions are:

0. Is this a bug?

(the following questions are assuming this is not a bug)

1. As application/framework developers, it is really concerning to get aClassCastException trying to invoke ajava.lang.Objectmethod in a "not null" object. Regardless of the "deep down JVM reasons" that I hope someone here can explain ;-), it is disturbing just to think about how many lines of code can be broken because of this. Would there be any other "scary" thing developers should be aware of? For example, is there any other scenario in which assuming that an object is ajava.lang.Object would cause a runtime exception?

2. Shouldn't the CHECKCAST be checking whether the variable is aInteger[]?

Thanks in advance.

[4205 byte] By [MarceloPa] at [2007-10-3 6:39:44]
# 1

> The questions are:

>

> 0. Is this a bug?

No.

>

> (the following questions are assuming this is not a

> bug)

>

> 1. As application/framework developers, it is really

> concerning to get a ClassCastException trying

> to invoke a java.lang.Object method in a "not

> null" object. Regardless of the "deep down JVM

> reasons" that I hope someone here can explain ;-), it

> is disturbing just to think about how many lines of

> code can be broken because of this. Would there be

> any other "scary" thing developers should be aware

> of?

You provoked the ClassCastException by doing an unchecked cast.

protected E[] data = (E[])new Object[0];

The compiler gave you a warning, didn't it?

Unchecked casts can not always be avoided, but the special case you used can and should be avoided.

The expression "new Object[0]" will definitely not be assignable to any other type "E[]" except for the case "E = Object".

Thus, this cast is not "unchecked", but rather "unsafe".

>

> 2. Shouldn't the CHECKCAST be checking whether the

> variable is a Integer[]?

>

Yes, and if you disassemble the class using javap, it will yield exactly that instruction. What tool did use use?

McNeppa at 2007-7-15 1:28:28 > top of Java-index,Core,Core APIs...
# 2
> You provoked the ClassCastException by doing an> unchecked cast. > protected E[] data = (E[])new Object[0];> The compiler gave you a warning, didn't it?I'm curious as to why it didn't occur earlier. Specifically in the new Whatever()
vanilla_loraxa at 2007-7-15 1:28:28 > top of Java-index,Core,Core APIs...
# 3

> > The questions are:

> >

> > 0. Is this a bug?

>

> No.

Yeah... Given that javac and Eclipse are behaving the same way, I was expecting to see this answer ;--)

>

> >

> > (the following questions are assuming this is not

> a

> > bug)

> >

> > 1. As application/framework developers, it is

> really

> > concerning to get a ClassCastException

> trying

> > to invoke a java.lang.Object method in a

> "not

> > null" object. Regardless of the "deep down JVM

> > reasons" that I hope someone here can explain ;-),

> it

> > is disturbing just to think about how many lines

> of

> > code can be broken because of this. Would there

> be

> > any other "scary" thing developers should be aware

> > of?

>

> You provoked the ClassCastException by doing an

> unchecked cast.

> protected E[] data = (E[])new Object[0];

> The compiler gave you a warning, didn't it?

>

> Unchecked casts can not always be avoided, but the

> special case you used can and should be avoided.

> The expression "new Object[0]" will definitely not be

> assignable to any other type "E[]" except for the

> case "E = Object".

> Thus, this cast is not "unchecked", but rather

> "unsafe".

>

Indeed, the Eclipse compiler is marking this is a "Type safety" warning. One important comment is that I tried to reduce this to the simplest example I could. In our application, Container<E> is an implementation of List and from everything I read, E[] data would be the way to define the underlying array, wouldn't it? Btw. we run into that problem because letting the subclasses access directly the data array is very important due to performance reasons.

Two points I can't understand are:

1. Why there is no such CHECKCAST on the class that defines the array? In other words, why introduce then on subclasses?

2. Why introduce a CHECKCAST in a line that I am using the field as a java.lang.Object?

Not trying to diminish the importance of the "why", I have to confess that I am also interested in the "wow" ;-) My poor brain is hurting because

((Object)data).hashCode()

yields a different result than

data.hashCode()

which actually throws an Exception. I have written deeply in my "Java coding intuition" that "if an object is not null, I can invoke any method defined in java.lang.Object on it". Now it iseems that this is not true and I am really curious/worried that there may be something else that would contradict another fundamental assumption I have. In a not so unlikely scenario, I could not have access to the Container code and hence not be aware that data has a generic type. In this case I would probably have a huge surprise when my application fails.

> >

> > 2. Shouldn't the CHECKCAST be checking whether the

> > variable is a Integer[]?

> >

>

> Yes, and if you disassemble the class using javap, it

> will yield exactly that instruction. What tool did

> use use?

I am using a bytecode visualization plugin for Eclipse (de.loskutov.BytecodeOutline) that, at least up to this date, has showed me exactly the same results as javap. If that is not what you see, the plugin is either not producing the correct bytecode now or maybe there is something wrong with the Eclipse compiler.

Thanks for the inputs.

MarceloPa at 2007-7-15 1:28:28 > top of Java-index,Core,Core APIs...
# 4

> "Type safety" warning. One important comment is that

> I tried to reduce this to the simplest example I

> could. In our application, Container<E> is an

> implementation of List and from everything I

> read, E[] data would be the way to define the

> underlying array, wouldn't it?

Well, given the fact that java.util.ArrayList has been implemented that way, one is tempted to think likewise.

But I remember that one of the Sun engineers who retrofitted the Collection classes with Generics (it might even have been Neil Gafter) admitted in this forum that that design decision was a bad idea.

Note that the following, innocuous-looking code will always throw a ClassCastException when the default constructor is used:

public class Container<E> {

public Container() {

data = (E[])new Object[0];

}

public Container(E[] argv) {

data = argv.clone();

}

private E[] data;

public E[] data() {

return data.clone();

}

public static void main(String[] args) {

String[] aStringArray;

aStringArray = new Container<String>(new String[]{"hello"}).data();

aStringArray = new Container<String>().data();

}

}

IMO, a slightly better way of implementing such a container would be use a member of type Object[]. You will notice that this will warrant a few unchecked casts to (E) upon read-access of individual array elements, although probably fewer than you'd expect.

>

> 1. Why there is no such CHECKCAST on the class that

> defines the array? In other words, why introduce

> then on subclasses?

There's no way the compiler can put checked casts int the parameterized class' byte code, because the type "E" is not known at compile time.

However, in the subclass that has been explicitly derived from Container<Integer>, the compiler has all the information necessary to insert a "real" cast operation, and it does so on every occasion whery you might access the member as an Integer[].

If you explicitly cast it to Object, the compiler can prove that the inserted cast to Integer[] is not necessary and optimizes it away.

Whether this is according to the Spec, against it or undefined behaviour, I don't know!

McNeppa at 2007-7-15 1:28:28 > top of Java-index,Core,Core APIs...
# 5

Has a FRE or a bug id been submitted on this issue?

public class Container<E> {

public Container() {

data = (E[])new Object[0];

}

public Container(E[] argv) {

data = argv.clone();

}

private E[] data;

public E[] data() {

return data.clone();

}

public static void main(String[] args) {

String[] aStringArray;

aStringArray = new Container<String>(new String[]{"hello"}).data

();

aStringArray = new Container<String>().data();

}

}

:1

public class Container<E> {

public Container() {

data = (E[])new Object[0];

}

public Container(E[] argv) {

data = argv.clone();

}

private E[] data;

public E[] data() {

return data.clone();

}

public static void main(String[] args) {

String[] aStringArray;

aStringArray = new Container<String>(new String[]{"hello"}).data

();

aStringArray = new Container<String>().data();

}

}

:q

#

# javac Container.java -Xlint

Container.java:4: warning: [unchecked] unchecked cast

found: java.lang.Object[]

required: E[]

data = (E[])new Object[0];

^

1 warning

#

# java -version

java version "1.5.0_07"

Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b03)

Java HotSpot(TM) Client VM (build 1.5.0_07-b03, mixed mode, sharing)

#

jalzabrewera at 2007-7-15 1:28:28 > top of Java-index,Core,Core APIs...
# 6

> Has a FRE or a bug id been submitted on this issue?

What issue? If you mean the one the OP brought up, it's not a bug.

If you're talking about a different issue, please explain it.

When you post code, please use[code] and [/code] tags as described in [url=http://forum.java.sun.com/help.jspa?sec=formatting]Formatting tips[/url] on the message entry page. It makes it much easier to read.

jverda at 2007-7-15 1:28:28 > top of Java-index,Core,Core APIs...