I can understand that the subtype covariance relation should not hold for parameterized types, but why should it hold for arrays ? One of the books (Khalid Moghul) said

This relation holds for arrays because the element type is available at runtime

How does the element type being available at runtime have anything to do with that ?

Recommended Answers

All 10 Replies

How does the element type being available at runtime have anything to do with that ?

Because it enables fail fast behaviour. Array operations are checked at runtime since the type information is available to the JVM.

class Test {
    public static void main(final String[] args) {
        Object[] arr = new Integer[10];
        arr[0] = "WTH";
    }
}

/*output
Exception in thread "main" java.lang.ArrayStoreException: java.lang.String
	at Test.main(Main.java:4)
*/

BTW, there is a lot of controversy surrounding this decision of Java since some claim that if arrays were not covariant, no runtime checks would be required which would speed up code *but* at the cost of reduced flexibility. Another suggestion is to make arrays immutable which would always guarantee safe use of covariance feature. Rather than complicated things any further, simply put, when the author says that arrays are allowed to be covariant, it's simply because array stores are checked at runtime to prevent major screwups.

Thanks. But I don't get one thing. Type information is present for arrays at run time, therefore, its able to throw an Exception if a wrong value is passed. Type information about a variable means the type of the object that has been assigned to that variable (Right ?) That's why in the example u gave, the run time environment knew that "arr"'s type is Integer, and hence a String can't be passed to it. But why doesn't the compiler detect that ? Memory for the object is assigned at runtime, but the compiler KNOWS that the object's type will be Integer.

Also, technically, what is the relationship between a variable and its declared type called ? i.e. if Integer is called "type" of arr, then what is Object called of arr ?

Type information about a variable means the type of the object that has been assigned to that variable

Kind of; we are dealing with two concepts here: the real type of the object and the type of the variable to which the object is assigned.

List<Integer> lst = new ArrayList<Integer>();

Here, the entire code can play around `lst' blissfully without anyone knowing that the "real" type of object reference by the "List" variable is of type "ArrayList". Here the type of variable is List but the type of the actual object pointed is ArrayList.

That's why in the example u gave, the run time environment knew that "arr"'s type is Integer, and hence a String can't be passed to it. But why doesn't the compiler detect that ? Memory for the object is assigned at runtime, but the compiler KNOWS that the object's type will be Integer.

Consider this code:

public class Test {
    
    public static void main(final String[] args) {
        final Object[] arr1 = { "HI" };
        final Object[] arr2 = { 12.3 };
        if(positionOfStarsIsGood()) {
            doIt(arr1);
        } else {
            doIt(arr2);
        }
    }

    public static void doIt(final Object[] arr) {
        if(positionOfStarsIsGood()) {
            arr[0] = "HI THERE";
        } else {
            arr[0] = 666;
        }
    }
}

What do you propose the compiler should do in this case?

Also, technically, what is the relationship between a variable and its declared type called ? i.e. if Integer is called "type" of arr, then what is Object called of arr ?

Nope, `arr' still is an array of Objects or has the type Object[].class; it's just that due to covariance, the assignment is compatible and hence are allowed to have an array of Objects refer to an array of Integers.

IMO, try to think of the type of actual object and the type of the variable which holds it as two different things and you should be good to go.

Thanks. So there are 2 types of array declaration and initialization in Java, one like this :

Object[] ob=new Integer[10]

using the "new" operator that hardcodes the fact that any object passed into the array must be Integer or a subtype of Integer. The other is : Object [] ob={10} which doesn't hardcode that fact. Here, we can assign even a String to ob[0].

The example u gave used the second type to which the compiler is inherently lenient. But if we used the 1st type, the compiler knows for sure that ob has been passed an array of Integers. So, shouldn't it flag any attempt to add a String to it ? Whywait for runtime ?

if a single method having a formal parameter Object [] ob is compiled, then the compiler cannot be sure as to which array type will be passed to ob by the calling function, and so IN THIS CASE, it can allow anything to be added to ob.

using the "new" operator that hardcodes the fact that any object passed into the array must be Integer or a subtype of Integer. The other is : Object [] ob={10} which doesn't hardcode that fact. Here, we can assign even a String to ob[0].

No and yes. No for first because Object[] obj = { 10 } is just an alternate way of declaring an integer array since the compiler "infers" the type here. And yes for second, because irrespective of the "expression" on the RHS, the compiler "has" to allow the statement obj[0] = "HI" .

The example u gave used the second type to which the compiler is inherently lenient.

The compiler is not lenient here; read above.

But if we used the 1st type, the compiler knows for sure that ob has been passed an array of Integers. So, shouldn't it flag any attempt to add a String to it ? Whywait for runtime ?

How does the compiler know for sure? The method invoked and the type of array passed depends on the "positionOfTheStars()" output which is decided at runtime. I'm not sure why you think the compiler can catch this at compile time.

if a single method having a formal parameter Object [] ob is compiled, then the compiler cannot be sure as to which array type will be passed to ob by the calling function, and so IN THIS CASE, it can allow anything to be added to ob.

That would be a wrong way of putting it. The "compiler" actually knows that it will be getting an Object array. It's just that since arrays in Java are covariant, I can have a String array referred by a Object array variable. This is how OO works; I don't care what is passed to the method as long as it's an Object array. This is how abstraction works by writing code which works on type hierarchies and contracts without bothering what the actual type of the passed in object is.

Code 1:

Object [] ob=new Integer[10];
ob[0]="tr";

Code 2:

Object [] ob={23};
ob[0]="tr";

Sorry, I got confused when I mentioned that Code 1 would not compile. What I don't understand is that Code 1 will throw an exception when run while Code 2 won't. The two methods of array initialization behave differently at runtime. Why ?

The other thing I was trying to ask (and messed it up) was that in code 1, since the compiler knows that an Integer type array has been assigned to ob, why not flag the assignment of a String to it (in line 2 of Code 1) as an error ? Why wait for runtime to throw an exception ?

When a function accepting an argument of type Object[] is compiled, the compiler can be sure that whatever will be passed to it will be an Object[], but it'll not know at the time of compilation whether the actual array passed was an Object[] only or a subtype of Object[]. So here, it'll not be able to decide whether to allow adding an Integer type to it and will correctly not issue a compile time error, waiting for runtime to know the actual type of array passed and then decide whether to throw an exception or not. But, as I said, in Code 1, it knows that the actual type (Integer []), so, WHY NOT throw a compile time error ? Why wait for runtime here too ?

Is it that variables are assigned values at runtime only ?

What I don't understand is that Code 1 will throw an exception when run while Code 2 won't. The two methods of array initialization behave differently at runtime. Why ?

The code you posted is not two different methods of array initialization but two completely different arrays. Try printing out the class of both those arrays to confirm the same. (i.e. ob.getClass() )

The other thing I was trying to ask (and messed it up) was that in code 1, since the compiler knows that an Integer type array has been assigned to ob, why not flag the assignment of a String to it (in line 2 of Code 1) as an error ? Why wait for runtime to throw an exception ?

Let me counter that question with another question from my side; why does this piece of code not compile?

public boolean doIt() {
       final int i = 0;
       if(i == 0) {
           return false;
       }
   }

Isn't it dead obvious that "i" will be 0 when the IF check is made? So why throw a compile time error?

There is a limit to how "smart" the compiler can be or how smart the compiler writers want it to be. If the compiler could, it would have checked that the user has left off a return statement and then try to see what value "i" has. But this would involve kind of "running" the code or simply put establishing a runtime context for the code in consideration.

Object[] arr = new Integer[10];
arr[0] = "HI";
arr = new String[10];
arr[0] = 1;
// and many more

Don't you think it would too much of a chore for the compiler to actually keep track of the "runtime" type of the "arr" reference, check all subsequent assignments, update their "runtime" information on reassignment, rinse and repeat? It might seem a very simple task for your toy programs but what if the code is *really* complicated? Can the compiler even compile the entire code in an acceptable time frame given the kind of tracking it would have to do, which is kind of a waste since the runtime anyways makes sure that nothing *bad* happens?

So to answer your question in simple term: a pragmatic decision made by the Java compiler writer/designer.

Thanks. When I printed the classes of Code 1, it returned Integer, whereas in the second code, it returned Object. Does that mean that the first line of Code 2 is equivalent to

Object ob[]=new Object[]{23};

That would explain why ob[0] can be assigned a String too.

Does that mean that the first line of Code 2 is equivalent to

Yes

That would explain why ob[0] can be assigned a String too.

Just remember that it really doesn't matter whether you have a new Object[] { 23 } or new Integer[] { 23 } since as long as ob is of type Object[] , you can always do ob[0] = "HI" .

Thanks

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.