Yet another java generics confusion - java

Let us have the following code:
public class TestGenerics {
static <T> void mix(ArrayList<T> list, T t) {
System.out.println(t.getClass().getName());
T item = list.get(0);
t = item;
System.out.println(t.getClass().getName());
}
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<Object>();
list.add(new Integer(3));
mix(list, "hello world");
}
}
In the output I get:
java.lang.String
java.lang.Integer
It's nonsense - we've just assigned Integer to String without getting ClassCastException! We can't write it like that:
String s = new Integer(3);
but this is what we have just done here.
Is it a bug or something?

In your case as list is an ArrayList<Object>, T is considered as an Object so you can see things as :
static void mix(ArrayList<Object> list, Object t)
So you assigned an Integer to an Object (which was a String before).
Object obj = new Integer(3);
obj = "Hello"; //No CCE here

You have a list of Object. One of the objects in the list is a String, the other is an Integer. getClass returns the runtime type of the object, not the static type. There is no casting involved here.

This seems to me as if getClass() returns the dynamic (runtime) type of an object, while generics deals only with static (compile-time) type information.
What you print on the console is the actual, dynamic types.
However, in your example code, T would be type Object, therefore t = item is a perfectly valid assignment.

Related

Type erasure Generics

An error occurs at new T[5] during compile-time saying => error: generic array creation
and according to my understanding, the array is created during compile-time and since we don't know the type of T at compile-time we cannot instantiate an array.
But
if T gets erased at compile-time and changes to Object then still why this error occurs ? because we can create an array of Object.
// Before Compiling
public class GenericClass<T> {
GenericClass(){
T[] obj = new T[5];
}
}
// After Compiling
public class GenericClass {
GenericClass() {
Object[] obj = new Object[5];
}
}
Similar case, like,
public class GenericClass<T> {
GenericClass(){
T obj = new T(); }}
/* error :required: class
found: type parameter T
where T is a type-variable:
T extends Object declared in class GenericClass*/
according to my understanding, the array is created during compile-time
No, the array is created at runtime.
nd since we don't know the type of T at compile-time we cannot instantiate an array.
Correct.
But if T gets erased at compile-time and changes to Object then still why this error occurs ?
Because "it is erased at compile time and changes to Object" is oversimplified.
Also, generics and arrays don't play nice with each other. The problem is, where the generics part is erased, arrays do not work like that. You can do this:
String[] x = new String[10];
tellMeTheTypeOfMyArray(x);
void tellMeTheTypeOfMyArray(Object[] o) {
System.out.println("Your array's component type is: " + o.getClass().getComponentType());
}
This code will compile and work fine, without error, and prints:
Your array's component type is: java.lang.String
Contrast to generics where you cannot write such a method. You cannot possibly make this:
List<String> x = new ArrayList<String>();
tellMeTheTypeOfMyList(x);
void tellMeTheTypeOfMyList(List<?> o) {
System.out.println("Your list's component type is: " + ??????);
}
work. There's no java code possible here, nothing you can write in place of the ?????? to print String, because that information simply is not there at runtime anymore.
Imagine this code:
// This is written by Wahab today.
class Example<T> {
protected T[] arr;
Example() {
this.arr = new T[10];
}
}
and it worked like you wanted. Then I do:
// Written by me, a year later
class Uhoh extends Example<String> {
Uhoh() {
super();
}
void hmmm() {
System.out.println(this.arr.getComponentType());
}
}
I would obviously expect, nay, demand - that this prints java.lang.String, but it could not possibly do so. Because this is weird and confusing, java has a rule: If you compile your code and you do not see any warnings about generics problems (and did not #SuppressWarnings them away), then this kind of confusion is not likely to happen.
Allowing you to write new T[] and having that just be a silly way to write new Object[] is considered too far gone for this.
So how do I use arrays with generics types?
The same way java.util.ArrayList does it: Do not use generics here. Arrays should pretty much never have T types if you intend to create them inside the generic code. If you have a T[] anywhere in your codebase, then that means you should never be new-ing up anything for it - let the caller of your code do it for you. If you do want to new up new arrays yourself, don't use T, use Object[] as type, and cast to T where needed. This is literally how java's built-in ArrayList class works. Some excerpts copy-pasted straight from its source:
transient Object[] elementData; // non-private to simplify nested class access
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
#SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
Here's an example, again straight from ArrayList's sources (or rather, java.util.Collection defines this, and ArrayList inherits it), where you let the caller provide you with code to make arrays:
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
Here the caller provides a function that transforms an int into a T[] - it takes the concept of doing new String[10] and turns it into a function, that you then pass along to the toArray method which will then use it (feel free to ignore how it uses it here, it's a bit of a bizarre solution. It works, just - not sure you should be learning lessons about that part).
You use it like this:
List<String> listOfStrings = ...;
String[] convertedToArray = listOfStrings.toArray(String[]::new);
Java arrays know their component type at runtime. When you create an array, you must provide the component type at runtime. But in your GenericClass, it cannot do that because it does not know what T is at runtime. If it creates an Object[], that object will have the wrong runtime class, and that instance is not compatible with the type T[] if T is anything other than Object. You are correct that, within the class, nothing is immediately wrong. But if the claim that the variable is T[] is exposed to an outside scope which expects T to be a more specific type, it can cause a ClassCastException:
// Before type erasure
class GenericClass<T> {
T[] obj;
GenericClass() {
obj = new T[5]; // if hypothetically you could do this
}
T[] getObj() {
return obj;
}
}
class MyCode {
public static void main(String[] args) {
GenericClass<String> foo = new GenericClass<>();
String[] strings = foo.getObj(); // no casts needed; no warnings
}
}
// After type erasure
class GenericClass {
Object[] obj;
GenericClass() {
obj = new Object[5];
}
Object[] getObj() {
return obj;
}
}
class MyCode {
public static void main(String[] args) {
GenericClass foo = new GenericClass();
String[] strings = (String[]) foo.getObj(); // ClassCastException at runtime
}
}

Can an object of a generic class be created with any type, T?

I learnt basic idea about generic class is one that can be parameterized with another class
(or type). The example I am given is ArrayList<E>.
Does generic class specifically refer to ArrayList, or any type?
All classes declared with < .. > are generic classes.
An ArrayList of some given type T is declared as:
ArrayList<T> list = new ArrayList<T>()
ArrayList holding Integers (when intending to hold int) should be
ArrayList<Integer> list = new ArrayList<Integer>()
You can also make your own generic classes (here is a Node for some singly-linked list holding some unspecified type E)
class Node<E> {
private E element;
private Node<E> next;
public Node(E element) {
this.element = element;
this.next = null;
}
public E getElement() {
return this.element;
}
public Node<E> getNext() {
return this.next;
}
}
Note: T cannot be a primitive type such as char, int, boolean, double. You have to use wrappers such as Character, Integer, Boolean, or Double.
Generics in Java is similar to templates in C++. The idea is to allow type (Integer, String, … etc and user defined types) to be a parameter to methods, classes and interfaces. For example, classes like HashSet, ArrayList, HashMap, etc use generics very well. We can use them for any type.
Generic Class:
To create objects of generic class, we use following syntax.
// To create an instance of generic class
BaseType obj = new BaseType ()
Note: In Parameter type we can not use primitives like
'int','char' or 'double'.
// A Simple Java program to show working of user defined
// Generic classes
// We use < > to specify Parameter type
class Test<T>
{
// An object of type T is declared
T obj;
Test(T obj) { this.obj = obj; } // constructor
public T getObject() { return this.obj; }
}
// Driver class to test above
class Main
{
public static void main (String[] args)
{
// instance of Integer type
Test <Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());
// instance of String type
Test <String> sObj =
new Test<String>("GeeksForGeeks");
System.out.println(sObj.getObject());
}
}
We can also pass multiple Type parameters in Generic classes.
// A Simple Java program to show multiple
// type parameters in Java Generics
// We use < > to specify Parameter type
class Test<T, U>
{
T obj1; // An object of type T
U obj2; // An object of type U
// constructor
Test(T obj1, U obj2)
{
this.obj1 = obj1;
this.obj2 = obj2;
}
// To print objects of T and U
public void print()
{
System.out.println(obj1);
System.out.println(obj2);
}
}
// Driver class to test above
class Main
{
public static void main (String[] args)
{
Test <String, Integer> obj =
new Test<String, Integer>("GfG", 15);
obj.print();
}
}
Generic Functions:
We can also write generic functions that can be called with different types of arguments based on the type of arguments passed to generic method, the compiler handles each method.
// A Simple Java program to show working of user defined
// Generic functions
class Test
{
// A Generic method example
static <T> void genericDisplay (T element)
{
System.out.println(element.getClass().getName() +
" = " + element);
}
// Driver method
public static void main(String[] args)
{
// Calling generic method with Integer argument
genericDisplay(11);
// Calling generic method with String argument
genericDisplay("GeeksForGeeks");
// Calling generic method with double argument
genericDisplay(1.0);
}
}
Advantages of Generics:
Programs that uses Generics has got many benefits over non-generic code.
Code Reuse: We can write a method/class/interface once and use for any type we want.
.
Type Safety : Generics make errors to appear compile time than at run time (It’s always better to know problems in your code at compile time rather than making your code fail at run time). Suppose you want to create an ArrayList that store name of students and if by mistake programmer adds an integer object instead of string, compiler allows it. But, when we retrieve this data from ArrayList, it causes problems at runtime.
// A Simple Java program to demonstrate that NOT using
// generics can cause run time exceptions
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creatinga an ArrayList without any type specified
ArrayList al = new ArrayList();
al.add("Sachin");
al.add("Rahul");
al.add(10); // Compiler allows this
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
// Causes Runtime Exception
String s3 = (String)al.get(2);
}
}
Output :
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String
at Test.main(Test.java:19)
How generics solve this problem?
At the time of defining ArrayList, we can specify that this list can take only String objects.
// Using generics converts run time exceptions into
// compile time exception.
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();
al.add("Sachin");
al.add("Rahul");
// Now Compiler doesn't allow this
al.add(10);
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
String s3 = (String)al.get(2);
}
}
Output:
15: error: no suitable method found for add(int)
al.add(10);
^
.
Individual Type Casting is not needed: If we do not use generics, then, in the above example every-time we retrieve data from ArrayList, we have to typecast it. Typecasting at every retrieval operation is a big headache. If we already know that our list only holds string data then we need not to typecast it every time.
// We don't need to typecast individual members of ArrayList
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();
al.add("Sachin");
al.add("Rahul");
// Typecasting is not needed
String s1 = al.get(0);
String s2 = al.get(1);
}
}
.
Implementing generic algorithms: By using generics, we can implement algorithms that work on different types of objects and at the same they are type safe too.

Is-a relationship with parameterized references

This has probably been asked before but i haven't been able to find a question.
I would like to understand the underlying reasons why does the following block of code not compile:
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
Box<Integer> i = new Box<Integer>(13);
// does not compile
Box<Object> o = i;
}
}
One way to look at it is, let's assume Box defined a void setValue(T val) method.
Box<Object> o = i;
o.setValue("a string");
Integer x = i.getValue(); // ?!
The problem is mentioned in java documentation here (it is similar to Vlad's answer):
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
Let's test your understanding of generics. Is the following code snippet legal?
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object. Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it. As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.
Firstly you cannot cast parameterized types. Check this oracle doc.
Typically, you cannot cast to a parameterized type unless it is
parameterized by unbounded wildcards. For example:
List li = new ArrayList<>();
List ln = (List) li; // compile-time error
Hence the line Box<Object> o = i; causes compile time error.
Even though while creating Box object you have not specified the generic parameter, but using constructor parameter type java Type inference the constructing object's type.
You can assign any reference to an Object reference but when generics kicks in, the exact generic type is matched at compile time. Hence it's not working. If you change to Box<? extends Object> o = i; it'll work since the generic type matches the contained type.

ArrayList type mismatch

I made a little program to illustrate my question. If I strip the casting to BigInteger in the line of the return (12th line), I get a compiling error of "Incompatible types". But if I do the casting, the line before (11th) prints out that the type of the returned value is BigInteger. If it's a BigInteger, why do I have to cast?
public class ProbArrBig {
public static void main (String args[]) {
}
private static BigInteger myFunction() {
ArrayList myArr = new ArrayList();
BigInteger myNumber = BigInteger.valueOf(23452439);
myArr.add(myNumber);
System.out.println("Type of myArr.get(0): "+myArr.get(0).getClass().getName());
return (BigInteger) myArr.get(0); //Doesn't work without (BigInteger)
}
}
It should be using
ArrayList<BigInteger> myArr = new ArrayList<BigInteger>()
This is called generics and indicate that the list contains BigInteger
objects, therefore when a value it is retrieved from the list you are indicating that it will be of that specific type.
The ArrayList class is designed to take a generics type <E> as follows:
ArrayList<Integer> myArr = new ArrayList<Integer>();
Since you did not provide the actual type for <E> for your particular instance, what you actually have is
ArrayList<Object> myArr = newArrayList<Object>();
Therefore, your return of myArr does not match your method signature unless you cast BigInteger, which you are able to do because everything inherits from the Object class. If you change your method to return a type Object, you will see that the code will compile without error, without the cast.

Is a String array subclass of an Object array?

I think I'm missing something basic here. Any explanation or pointers to previously asked questions will be very helpful.
import java.util.Arrays;
import java.util.List;
public class St {
public static void bla(Object[] gaga) {
gaga[0] = new Date(); // throws ArrayStoreException
System.out.println(gaga[0]);
}
public static void bla(List<Object> gaga) {
System.out.println(gaga.get(0));
}
public static void main(String[] args) {
String[] nana = { "bla" };
bla(nana); // Works fine
List<String> bla1 = Arrays.asList(args);
bla(bla1); // Wont compile
System.out.println(new String[0] instanceof Object[]); // prints true
System.out.println(nana.getClass().getSuperclass().getSimpleName()); // prints Object
}
}
So, it seems like a List<String> is not a subclass of a List<Object> but a String[] is a subclass of Object[].
Is this a valid assumption? If so, why? If not, why?
Thanks
Java arrays are covariant, i.e. they allow Object[] foo = new String[2];. But this doesn't mean they are subclasses. String[] is a subclass of Object (although instanceof returns true, String[].class.getSuperclass() returns Object)
Yes, your assumption is valid. As said by #Bozho arrays are covariant, whereas generic collections (such as generic List) are not covariant.
Covariance in arrays is risky:
String[] strings = new String[] { "a", "b" }
Object[] objects = strings;
objects[0] = new Date(); // <-- Runtime error here
String s = strings[0];
s.substring(5, 3); // ????!! s is not a String
The third line fires a runtime exception. If it weren't firing this exception then you could get a String variable, s, that references a value that is not a String (nor a subtype thereof): a Date.
(new String[0] instanceof Object[]) // => true
You are correct. Array types are covariant in Java by design, but a Foo<Sub> is-not-a Foo<Super>.
String[] is a subclass of Object[]
Correct, see 4.10.3 Subtyping among Array Types:
If S and T are both reference types, then S[] >1 T[] iff S >1 T.
Since String >1 Object so String[] >1 Object[]
That is, String[] is a direct subtype of Object[]
Object >1 Object[]
Therefor Object > String[]; String[] is a (indirect?) subtype of Object
No such relationship exists for generics, so List<String> > List<Object> is not true.
Now, consider the following simple example:
import java.util.*;
class G {
interface I {
void f();
}
class C implements I {
public void f() {}
}
void allF(List<I> li) {
for (I i : li) { i.f(); }
}
void x(List<C> lc) {
allF(lc);
}
}
It does not compile, because x is invoking allF with a List<C> which is not a List<I>. To be able to use List<C> the signature has to change slightly:
void allF(List<? extends I> li) {
Now it compiles. Informally, li is a List of some type that extends/implements I. So List<C> is assignable to List<? extends I>. What you can do with such a list is limited. Essentially, you can read/access it but cannot write/modify it.

Categories

Resources