Angelo Oparah Angelo Oparah - 7 months ago 14
Java Question

Oracle Java generics tutorial Box class explanation

I was reading the Oracle tutorial about generics and could not figure out what is wrong with the example provided. (The tutorial can be found @ https://docs.oracle.com/javase/tutorial/java/generics/types.html)

Quoting the tutorial:


Begin by examining a non-generic Box class that operates on objects of any type. It needs only to provide two methods: set, which adds an object to the box, and get, which retrieves it:

public class Box {
private Object object;

public void set(Object object) { this.object = object; }
public Object get() { return object; }
}


Since its methods accept or return an
Object
, you are free to pass in whatever you want, provided that it is not one of the primitive types. There is no way to verify, at compile time, how the class is used. One part of the code may place an Integer in the box and expect to get Integers out of it, while another part of the code may mistakenly pass in a
String
, resulting in a runtime error."


I tried to figure out why a runtime error would be the result of such operation but cannot come up with an explanation, so I recreated the situation on my IDE, ran the code, but did not get any error.
Is it a mistake? Did I misinterpret the situation?

Here is the sample code I created to test the situation:

Box class definition

public class Box
{
private Object ob;

public Object getObject(){
return ob;
}

public void setObject(Object ob){
this.ob = ob;
}
}


Testing the Box class

public class BoxTest
{
public static void run(){

String sPar = "hello";
Integer iPar = 45;
Box box = new Box();

box.setObject(iPar);
System.out.println(box.getObject());

box.setObject(sPar);
System.out.println(box.getObject());
}
}


I also tried to pass in a primitive type and it still works fine...so why would a runtime error occur in such a situation?

Answer

It is referring to the fact that you can write:

Box box = new Box();
box.setObject(Integer.valueOf(1));
String value = (String) box.getObject();

This code compiles fine, but gives an error at runtime.

In general, there is no way of knowing - short of testing every possible type - what type Box.getObject() will return.

The use of the cast may seem a bit unusual - surely you would always know what you'd put into the box. It would not be possible to know, if, say, you have a method which expects a Box parameter containing a String:

void doSomethingToStringBox(Box box) {
  String value = (String) box.getValue();
}

You can call this from anywhere in your code (at least from where it is visible), so you can't be 100% sure that the person writing that code knew that Box had to contain a String.


If you had correctly used the generic version of the class:

Box<Integer> box = new Box<>();
box.setObject(Integer.valueOf(1));
// Compiler error - Integer cannot be cast to String.
String value = (String) box.getObject();

or

Box<String> box = new Box<>();
// Compiler error - Integer cannot be converted to String 
box.setObject(Integer.valueOf(1));
String value = box.getObject();

I emphasize the word "correctly" above, since it is still possible to use genericized Box without the generics:

Box box = new Box();

and then you're back in the same type-unsafe situation as the very first example.

This is called a raw type, and you should not use them in any code you write now. They are permitted for backwards compatibility with legacy code, and may be unsupported in the future.

Comments