2017-09-17

2: Why Any Array Shouldn't Be Cast (nor Even Be Left to Be Auto Cast) to Another Array Type

<The previous article in this series | The table of contents of this series | The next article in this series>

Main Body START

Let's Convert, Not Cast, Arrays

About: Java Programming Language

We Can't Cast Any Objects Array Instance to the Strings Array Type, Even If the Objects Array Instance Has Only String Elements

-Hypothesizer

Ah, we can't cast an Object [] instance that contains only String elements to the String [] type. That is so . . .

-Rebutter

Of course, we can't.

-Hypothesizer

Of course? Is that such self evident? Well, we are talking about a program like this.

@Java Source Code
 void aMethod (String [] p_stringsArray) {
  for (String l_string: p_stringsArray) {
   System.out.println (l_string);
  }
 }
 
 void anotherMethod () {
  Object [] l_objectsArray = new Object [2];
  l_objectsArray [0] = "A String";
  l_objectsArray [1] = "Another String";
  aMethod ( (String []) l_objectsArray);
 }

That program can be successfully compiled, but throws an exception, "java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String", at run time.

-Rebutter

Can it be successfully compiled? I'm rather surprised at that, not at the run time exception.

-Hypothesizer

Are you? Why?

-Rebutter

Because that is a violation of a principle of object oriented languages, and the compiler should know that at compile time.

-Hypothesizer

I rather thought, "What's the harm of the casting?"

-Rebutter

You may think that that won't cause any harm, but it is only for your program. Generally speaking, such a violation of a principle of object oriented languages can cause some harms.

-Hypothesizer

I don't understand how that casting is a violation of a principle of object oriented languages.

-Rebutter

Well, do you understand that casting of objects is intrinsically different from casting of primitives?

-Hypothesizer

I think I do.

-Rebutter

Let's review it.

Let's Review the Difference Between Casting of Primitives and Casting of Objects

-Hypothesizer

When a primitive is cast, the value itself is converted. For example, let's think of this casting.

@Java Source Code
  int l_int = 1;
  short l_short = (short) l_int;

The value contained in 'l_int' is a four bytes value while the value contained in 'l_short' is a two bytes value: the value is converted by the casting.

On the other hand, when an object is cast, the object itself isn't converted at all. For example, let's think of this casting.

@Java Source Code
  Object l_object = "A String";
  String l_string = (String) l_object;

The String object, "A String", isn't converted by the casting. Casting for an object isn't a conversion of the object, but just a reassurance by the programmer to the compiler: "Mr. compiler, you may not know that 'l_object' is a kind of String, but I know and assure you that it is a kind of String. So, please let it compiled. If something goes wrong, I will take the responsibility."

-Rebutter

Yes, you seem to understand. The compiler suppresses the compile error if 'l_object' could be a kind of String, but if 'l_object' isn't really a kind of String at run time, the runtime will throw an exception of 'java.lang.ClassCastException'.

Any Objects Array Instance Is Never a Kind of Strings Array

-Hypothesizer

So what?

-Rebutter

Well, you seem to not understand that any Objects array instance is never a kind of Strings array.

-Hypothesizer

Is it?

-Rebutter

Think of the definition of Strings array.

-Hypothesizer

The definition of String [] is an array into which any String instance can be put as an element.

-Rebutter

That isn't so. The property that only String instances can be put into it is crucial. Otherwise, we couldn't do like this.

@Java Source Code
 void aMethod (String [] p_stringsArray) {
  for (String l_string: p_stringsArray) {
   System.out.println (l_string);
  }
 }
-Hypothesizer

Ah, if it isn't guaranteed that all the elements are Strings, we can't do that. So, a more accurate definition of String [] is an array into which any String instance and only String instances can be put as an element.

-Rebutter

If something is able to be said 'a kind of String []', that something must fulfill the definition. That is the meaning of definition. Does any Object [] fulfill that definition?

-Hypothesizer

As non-String element can be put into any Object [] instance, certainly, any Object [] instance doesn't fulfill the definition. Even if an Object [] instance contained only String elements at a time, that doesn't mean that only String elements can be put into the Object [] instance. As for my first program above, even if the Object [] instance had only String elements at the time of casting, any non-String element can be put into it afterward, for example from another thread. As the Object [] instance isn't an immutable, we can't say it's a kind of String [] because it contained only String elements at a time.

So, any Object [] instance isn't a kind of String [], and such casting should never be allowed, for otherwise, we couldn't trust something cast to String [] to be a kind of String []. What a weird world would that be? A value of a String [] variable might not be a kind of String [] . . .. Then what is the type declaration for?

In Fact, Any Strings Array Instance Is Never a Kind of Objects Array Either

-Hypothesizer

On the other hand, we can do like this.

@Java Source Code
 void aMethod (Object [] p_objectsArray) {
  for (Object l_object: p_objectsArray) {
   System.out.println (l_object.toString ());
  }
 }
 
 void anotherMethod () {
  String [] l_stringsArray = new String [2];
  l_stringsArray [0] = "A String";
  l_stringsArray [1] = "Another String";
  aMethod (l_stringsArray);
 }
-Rebutter

Huh? Can we do that?

-Hypothesizer

Actually, we can.

-Rebutter

Doesn't the String [] even have to be cast to Object []?

-Hypothesizer

It seems to be automatically cast to Object [], and the program doesn't throw any run time exception.

-Rebutter

That's a problem. You know, any String [] isn't a kind of Object [].

-Hypothesizer

That's more obvious than before. The definition of Object [] is an array into which any Object instance and only Object instances can be put as an element. As any String [] doesn't fulfill the definition (into which only String instances can be put), it isn't a kind of Object [].

-Rebutter

By analogy of any String's being a kind of Object, any String [] might intuitively feel to be a kind of Object [], but that isn't so.

-Hypothesizer

Any array of type 'XXX' isn't a kind of array of type 'YYY' when 'XXX' isn't 'YYY'. Whether 'XXX' is an ascendant or a descendant of 'YYY', it doesn't matter. So, casting between array types is fundamentally illegitimate.

-Rebutter

And the compiler should be able to detect such illegitimate casting at compile time. In fact, your first program above shouldn't be successfully compiled.

-Hypothesizer

The compiler lets it compiled because we can do like this.

@Java Source Code
 void anotherMethod () {
  Object [] l_objectsArray = new String [2];
  l_objectsArray [0] = "A String";
  l_objectsArray [1] = "Another String";
  aMethod ( (String []) l_objectsArray);
 }
-Rebutter

Plainly speaking, "Object [] l_objectsArray = new String [2];" shouldn't be allowed because the String [] instance isn't a kind of Object []. As the compiler allows that, "(String []) l_objectsArray" becomes possible. If the compiler did legitimate type checks from the beginning, all casting between array types could be detected as errors at compile time.

Harms of Allowing Such Illegitimate Casting

-Hypothesizer

To understand harms of allowing such illegitimate casting, let's think of a program like this.

@Java Source Code
 public static void aMethod (Object [] p_objectsArray) {
  if (p_objectsArray != null && p_objectsArray.length > 0) {
   p_objectsArray [0] = new Integer (1);
   for (Object l_object: p_objectsArray) {
    System.out.println (l_object.toString ());
   }
  }
 }
-Rebutter

That seems a quite normal and infallible program: the author of the method declared the argument as Object [], and therefore treated it as such. If the method fails, it will be absurd.

-Hypothesizer

However, we can easily make it fail like this.

@Java Source Code
 public static void anotherMethod () {
  String [] l_stringsArray = new String [2];
  l_stringsArray [0] = "A String";
  l_stringsArray [1] = "Another String";
  aMethod (l_stringsArray);
 }
-Rebutter

It is absurd that while the argument is declared as Object [], it can't be trusted to be a kind of Object []. . . . And we didn't even do any casting.

-Hypothesizer

As we do casting on our responsibility, something might go wrong if our casting is wrong. But we didn't do any casting, and such a type exception can happen . . .

Then, What Should We Do?

-Hypothesizer

After all, when we have to put an array to a variable of another array type (including passing the array to a method argument), we should forget casting, and do converting without reserve.

-Rebutter

Casting, including auto casting, may be allowed by the compiler, and we may even get away with it, but it is never legitimate.

-Hypothesizer

We can convert arrays using 'java.util.Arrays.copyOf' like this.

@Java Source Code
import java.util.Arrays;

  String [] l_stringsArray = new String [2];
  l_stringsArray [0] = "A String";
  l_stringsArray [1] = "Another String";
  Object [] l_objectsArray = Arrays. <Object, String>copyOf (l_stringsArray, l_stringsArray.length, Object [].class);
-Rebutter

It might look lengthy compared with casting, but it is a necessary thing to do.

-Hypothesizer

If calling that function feels cumbersome, I think, a syntax sugar for copying arrays should be implemented into the program language. Definitely, allowing illegitimate, weird casting shouldn't be the solution.

And if a base class of various objects array types (array types whose elements types are not primitives) is desired, I think, there should be a base abstract class of all the objects array types.

-Rebutter

I guess, that base class allows us to get elements in the Object type, but not put anything into it, for what can be put into it can't be judged from the type.

-Hypothesizer

Yes, but we can use the base type as an argument of a method and cast an instance referred to by the argument to a concrete array type, for example String [], if the instance is really a String [] instance. Then, we can put elements into it. That casting isn't illegitimate or weird, and conforms to all the principles of object oriented languages.

Main Body END

<The previous article in this series | The table of contents of this series | The next article in this series>