Java 11 Developer Certification - Polymorphism - Casting Special Concepts

March 10, 2021

In this section, we will look at casting for arrays and generics. First we will have a look at the arrays part. Lets directly jump into the code to understand more.

We have a class CastExtras, two nested static classes BaseClass and NextClass. NextClass extends the BaseClass. Then we have the main method. We have created a generic Object array, filling that array with objects that are of type NextClass, adding a StringBuilder in the array, and then just printing them out.

package maxCode.online.polymorphism;

import java.util.Arrays;

public class CastExtras {
	// Just want a few classes to play with
	static class BaseClass {
		String name = "Base Case";

		public String toString() {
			return getClass().getName();
		}
	}

	static class NextClass extends BaseClass {
		String name = "Next Best Case";
	}

	// main method, we'll test out some more casting examples
	public static void main(String[] args) {
        // Part 1 starts here
		// Let's create the most generic of arrays
		Object[] myObjectArray = new Object[10];

		// This fills an Object array with Objects that are NextClass
		// It does not change the type of array to NextClass[]
		Arrays.fill(myObjectArray, new NextClass());

		// You can put any type of Object in there
		myObjectArray[5] = new StringBuilder("test");

		System.out.println(myObjectArray.getClass().getTypeName());
		System.out.println(Arrays.toString(myObjectArray));
        // Part 1 ends here

		// We are going to loop through our array and cast each
		// object first to a NextClass to print the name attribute on
		// NextClass, and then we cast to BaseClass to print the
		// name attribute on the BaseClass
        // Part 2 starts here
		try {
			for (Object o : myObjectArray) {
				// We can cast to most specific class
				NextClass n = (NextClass) o;
				System.out.println(n.name);

				// We can cast to less specific class if we prefer more
				// generic name
				BaseClass b = (BaseClass) o;
				System.out.println(b.name);
			}
		} catch (Exception e) {
			// Not to mention any object might be in your array...
			e.printStackTrace(System.out);
		}
        // Part 2 ends here

		// Next we'll create an array of mixed types, using the common
		// denominator BaseClass
        // Part 3 starts here
		BaseClass mixedArray[] = new BaseClass[6];
		// Fill half with NextClass
		Arrays.fill(mixedArray, 0, 3, new NextClass());
		// Fill half with BaseClass
		Arrays.fill(mixedArray, 3, 6, new BaseClass());

		System.out.println(Arrays.toString(mixedArray));
		for (BaseClass n : mixedArray) {
			// We cast if we want NextClass's more specific name...
			System.out.println(n + ":" +
			// ternary conditional operator uses instanceof
					((n instanceof NextClass) ? ((NextClass) n).name : n.name));
		}
        // Part 3 ends here
		// Compiler let's you get away with it, it's feasible that
		// that mixedArray could be populated with objects of its subtype only
		// but JVM won't allow it
		// NextClass[] nextArray = (NextClass[]) mixedArray;

		// Let's actually fill it with just NextClass objects .
		// Arrays.fill(mixedArray, new NextClass());
		// // JVM still doesn't allow it
		// NextClass[] nextArray2 = (NextClass[]) mixedArray;
	}
}

Part 1 Output

java.lang.Object[]
[maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass, test, maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass, maxCode.online.polymorphism.CastExtras$NextClass]

The first part of the output prints the object array elements as NextClass objects except for one, which is the test StringBuilder, at index 5. As we can imagine, accessing the generic array elements would require a cast each time we need the element. That would eventually affect the performance of the code.

In the Part 2, we are going to loop through our Object array, cast each object to NextClass and print the name attribute. Then we cast each object to BaseClass and print the name attribute of the BaseClass. This is all enclosed in a try block so that we can catch the exception in case one arise. We do expect an exception since the element at index 5 is a StringBuilder and we are trying to cast it to a NextClass object.

Part 2 Output

Next Best Case
Base Case
Next Best Case
Base Case
Next Best Case
Base Case
Next Best Case
Base Case
Next Best Case
Base Case
java.lang.ClassCastException: java.lang.StringBuilder cannot be cast to maxCode.online.polymorphism.CastExtras$NextClass
	at maxCode.online.polymorphism.CastExtras.main(CastExtras.java:41)

So as expected, we get the exception on the element at index 5 while casting it to NextClass as it is a StringBuilder object. This is the reason putting everything in an Object array is quite dangerous, and we also have to cast every element back to its original type while fetching from the array.

Now lets look at the next part. We create a BaseClass of 6 elements, fill them with 3 elements from NextClass instances and 3 elements from BaseClass instances. We then print the instances and use a ternary operator with instanceof operator to determine the type of object.

Part 3 Output

maxCode.online.polymorphism.CastExtras$NextClass:Next Best Case
maxCode.online.polymorphism.CastExtras$NextClass:Next Best Case
maxCode.online.polymorphism.CastExtras$NextClass:Next Best Case
maxCode.online.polymorphism.CastExtras$BaseClass:Base Case
maxCode.online.polymorphism.CastExtras$BaseClass:Base Case
maxCode.online.polymorphism.CastExtras$BaseClass:Base Case

The output clearly shows that the first 3 elements are of type NextClass and next 3 are of type BaseClass.

We can also cast the entire array at one go, by doing something like NextClass[] nextArray = (NextClass[]) mixedArray;, but JVM doesnt allow this in our case and throws a ClassCast Exception.

Even if fill the mixedArray with NextClass elements and then cast it to NextClass[], the JVM wont even allow this. It will throw the same ClassCastException, as we cannot downcast an array to find with parent type to its child type. There wont be any compiler error, but it will throw Exception. Thats why the last sections of code have been commented out.

Now lets have a look at some of the special concepts of casting in generics. The term generic was introduced to implement more generic programming, extending Java’s type system to allow “a type or method to operate on objects of various types while providing compile time type safety”.

Considering the last example, generics would have helped us to not allow to add the StringBuilder object in the collection and have only NextClass objects. The compiler would recognize such issues and highlight it as an error with generics.

The Java collections framework supports generics to specify the type of objects stored in a collection instance. A generic type in Java is one that is parameterized with (in the case of collections) the type of the objects which will be in the collection. You can parameterize any class or interface as well as methods

The syntax for declaring a variable for a generic type and instantiating it as like below {GenericTypeName}<TYPE> varName = new {GenericTypeName}<TYPE>();

e.g. ArrayList<String> arrList = new ArrayList<String>();

A shorthand can also be used by omitting the right hand side of the TYPE, because the type is known from the declaration, so eventually it will become like below.

ArrayList<String> arrList = new ArrayList<>();.

This is also a preferred way in terms of SonarQube rules. Both of these statements declare a variable of ArrayList, which will house only String objects, and assign a new instance of an ArrayList (typed to String) to the variable. Keep in mind that ArrayList is an object that implements the List interface, and maintains an array.

Another thing to keep in mind regarding generics is that they don’t support primitive data types. So ArrayList<int> list = new ArrayList<>(); is actually invalid.

There are actually two types of ArrayList, the raw (pre-generics) ArrayList and the typed ArrayList. And this is true of many of the Collections classes and interfaces in Java, so let’s compare them side by side.

We will create a new class GenericCasts, code will be quite similar to CastExtras class.

We still have got the two nested classes, BaseClass and NextClass, NextClass extending BaseClass. In the main method, we create ArrayList, both raw and typed, and also a typed list.

We have then added a NextClass instance to both the raw list and typed list, and printing out these list values in Part 1 output. The values do not surprise at all till now.

package maxCode.online.polymorphism;

import java.util.ArrayList;
import java.util.List;

public class GenericCasts {
	static class BaseClass {
		String name = "Base Case";

		public String toString() {
			return getClass().getName();
		}
	}

	static class NextClass extends BaseClass {
		String name = "Next Best Case";

		public String toString() {
			return getClass().getName() + ": " + name;
		}

		public static void testIt(List<BaseClass> baseListParameter) {
			System.out.println(baseListParameter);
		}
	}

	public static void main(String[] args) {
        // Part 1 starts here
		// Raw types
		// Create ArrayList and assign to ArrayList variable
		ArrayList rawList = new ArrayList();
		// Declare a List variable
		List aList;
		// Assign ArrayList object to List variable:
		aList = rawList;

		// Typed generic lists

		// Create a typed ArrayList using BaseClass and assign to
		// ArrayList variable
		ArrayList<BaseClass> baseList = new ArrayList<>();
		// Declare a typed List variable using BaseClass as type
		List<BaseClass> bList;
		// Assign typed ArrayList object to typed List variable
		bList = baseList;

		// Add a NextClass object to the raw ArrayList
		rawList.add(new NextClass());

		// Add a NextClass object to the typed ArrayList
		baseList.add(new NextClass());

		// Print ArrayList and List variables.
		System.out.println("We can use either variable to print the raw ArrayList");
		System.out.println("-- aList = " + aList);
		System.out.println("-- rawList = " + rawList);
		System.out.println("We can use either variable to print the typed ArrayList");
		System.out.println("-- bList = " + bList);
		System.out.println("-- baseList = " + baseList);
        // Part 1 ends here

        // Part 2 starts here
		// // Let's add the StringBuilder object to raw list
		// rawList.add(new StringBuilder("Hello"));
		//
		// // Let's add the StringBuilder object to typed list
		// baseList.add(new StringBuilder("Hello"));
        // Part 2 ends here

        // Part 3 starts here
		// We create variable of NextClass and assign it a new instance
		// NextClass
		NextClass nextClass = new NextClass();

		// We create variable of BaseClass and assign it the nextClass
		// variable - this is a good assignment as we know. You can
		// assign a more specific typed object to a less specifically
		// typed variable.
		BaseClass baseClass = nextClass;

		// We now create an ArrayList of NextClass and assign it to a
		// variable of ArrayList typed to NextClass.
		ArrayList<NextClass> nextList = new ArrayList<>();
		nextList.add(nextClass);

		// We assign nextList to the raw ArrayList variable
		rawList = nextList;
		// We assign nextList to the raw List variable
		aList = nextList;
        
		// // We assign nextList to the typed ArrayList of BaseClass variable
		// baseList = nextList;
		// // We assign nextList to the typed List of BaseClass variable
		// bList = nextList;
        //Part 3 ends here

		// Execute testIt method with List<BaseClass>
		NextClass.testIt(bList);

		// Execute testIt method with ArrayList<BaseClass>
		NextClass.testIt(baseList);

		// Execute testIt method with ArrayList<NextClass>
		// testIt(nextList);

		// Casting doesn't fix it
		// baseList = ( ArrayList<BaseClass> ) nextList;
		//
		// ArrayList<NextClass> anotherTest = ( ArrayList<NextClass> ) rawList;
		//
		// ArrayList<NextClass> anotherTest = ( ArrayList<NextClass> ) baseList;

		// No cast required, retrieving data from ArrayList, typed with
		// NextClass
		NextClass next = nextList.get(0);
		System.out.println("next here is " + next);

		// No cast required, retrieving data from ArrayList, typed with
		// BaseClass
		BaseClass base = baseList.get(0);
		System.out.println("base here is " + base);
		// need a cast still if retrieving more specifically typed object
		next = (NextClass) baseList.get(0);
		System.out.println("next here is " + next);

		// Cast required, retrieving data from raw ArrayList
		next = (NextClass) rawList.get(0);
		System.out.println("next here is " + next);
	}
}

Part 1 Output

We can use either variable to print the raw ArrayList
-- aList = [maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case]
-- rawList = [maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case]
We can use either variable to print the typed ArrayList
-- bList = [maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case]
-- baseList = [maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case]

The part 2 of the code is commented out as defining an ArrayList of type BaseClass informs the compiler that adding anything that doesn’t pass the IS A test for BaseClass is an error, and we will get a compiler error on the second end statement.

In the case of the raw list, the compiler would be quite happy to accept it, so no errors there.

In the third part, we create a NextClass object and are assigning that to both NextClass and BaseClass variables. Then we have a NextClass typed arrayList and assigning it to the raw arraylist and raw list variables. No surprises there, we can assign a specifically typed ArrayList and list to a raw type ArrayList lists. Everything compiles, you can see there’s no errors there. But if we use the next two commented lines, we will get a compiler error. So assigning our ArrayList of type NextClass object to a variable of ArrayList BaseClass is a compiler error, although assigning a NextClass object to a BaseClass variable isn’t. And the same is true for a list of typed BaseClass, it’s a compiler error to assign ArrayList of type NextClass objects (incompatible types).

The next lines of code would give below output.

Part 4 Output

[maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case]
[maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case]
next here is maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case
base here is maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case
next here is maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case
next here is maxCode.online.polymorphism.GenericCasts$NextClass: Next Best Case

So the important points to remember here are

  • You can assign a typed generic object to a raw variable type.
  • But you can’t use the ‘Is A’ test for casting, calling methods or assignments of a parameterized type.
  • An exact match to the parameterized type is required.
  • Using typed ArrayList objects removes the requirement to cast elements before accessing them.
  • A subclass is a superclass, but an array of subclass (subclass[]) cannot be said to be an array of superclass for casting decisions.
  • A generic type to a subclass also cannot be used in place of a generic type to its superclass.

Thats all for Polymorphism, casting and generics. Next we will check out Interfaces in detail.