This question already has answers here:
Java generics type erasure: when and what happens?
(7 answers)
Closed 5 years ago.
I'm trying to learn about the use of generic types and I've noticed something weird when I was experimenting with some lines of code.
The first piece of code is inside a class named "A":
public void func(int k, List list) {
list.add(9);
list.add(true);
list.add("a string");
}
The second piece of code is in a different class, inside the main function:
List<Integer> arr = new ArrayList<Integer>();
arr.add(14);
System.out.println(arr.toString());
a.func(8, arr);
System.out.println(arr.toString());
Running the code results in this lines being printed:
[14]
[14, 9, true, a string]
This got me pretty confused since arr is an ArrayList of type Integer, how can it contain objects of type boolean and String? Is there a transformation of the list in the function func to a raw type (which mean it becomes of generic type Object)? And if so how is it possible since you cannot do this for example: List<Integer> arr = new ArrayList<Object>();?
Would love some clarification on this, maybe it will help me grasp this subject of generic types better. Thanks!
Java does not allow the creation of generic Arrays. The Java Collection Classes are mainly implemented using Object arrays. The ArrayList class may look like the following
public class ArrayList<T> implements List<T>, Serializable {
private transient Object[] data;
// more content...
}
When creating a new Instance of the ArrayList a new Object[] array is created that can hold objects of any type. Typesafety is only achieved through using the generic Type Parameter.
Since List did not provide any Type parameter it makes use of the rawtype and anything can be added to the list. Therefore always make sure to infer template arguments to keep the typesafety.
public void func(int k, List<Integer> list) {
list.add(9); // works
list.add(true); // compile error
list.add("a string"); // compile error
}
You should never use rawtypes. Depending on your compiler settings warnings will be omitted.
It's better to use (bound/unbound) wildcards.
Reason behind such kind of output is that you are passing List as an parameter to func( int k , List list ) .
And list in func method is non- generic which allows you to add string as well so you are getting such output.
List<Integer> arr = new ArrayList<Integer>();
arr.add(14);
System.out.println(arr.toString());
a.func(8, arr); // here you are passing a list
System.out.println(arr.toString());
public void func(List list) { // here List in non-generic
list.add(9);
list.add(true);
list.add("a string");
}
The whole point of a Generic code in Java is to provide type-safety before the actual code compilation. The compiled code have no type, that's why we don't face any problem in our code becuase when actually func() method is called, the List arr doesn't have any type.
When we're calling System.out.println(arr.toString()), we're just printing an object. This will fine on any type (i.e., int, boolean, String etc...). That's why we don't encounter any exception.
However, just try to assign a value from this list and we'll get java.lang.ClassCastException
...
func(8, arr);
System.out.println(arr.toString());
int a = arr.get(2);
Related
I am experimenting with making my own custom Hashtable as a way of understanding the data structure, and have run into what many other people seem to have run into; that you cannot create a generic array the same way you would make another array. I understand the reasons for this, but also know that Java's HashMap itself uses an array to store Entry items. If my understanding is correct, Java's HashMap creates an object[] and then casts each element of the object to the Entry class each time the put or get method is called. Is this correct?
I have read answers about generic arrays saying it is possible to instead do something like having Entry[] table as a class variable and then using table = (Entry[]) new Object[size]; in the constructor as a way of avoiding having to do the casting in both the put and get methods, but this does leads to a ClassCastException, which is understandable since it would have to check each element of the Object array anyway in order to make sure they are the Entry class. Does this mean I cannot use this method in my custom Hashtable?
Finally, another way of creating the Entry array seems to be checking the class type in the constructor and using Entry[] table = (Entry[]) Array.newInstance(c, s); Is this method perhaps more suitable?
Below is a snippet of my own code relevant to this question. I am wondering if my interpretation of everything above is correct, and if this is an acceptable way of going about my own Hashtable. I also understand my method of using determining the index for the given hashCode may be incorrect, but that is outside the scope of my question :), and my put and get methods are definitely incomplete!
public class HashTable<K, V> {
Object[] buckets;
HashTable(int size) {
buckets = new Object[size];
this.size = size;
}
void put(K key, V value) {
int i = key.hashCode()%size;
buckets[i] = (Entry) new Entry(key, value, (Entry) buckets[i]);
}
K get(K key) {
int i = key.hashCode()%size;
Entry entry = (Entry) buckets[i];
return entry.key;
}
}
If my understanding is correct, Java's HashMap
creates an object[] and then casts each element of the object to the
Entry class each time the put or get method is called. Is this
correct?
The standard library's source is available. You could check it for yourself. If you did, you would find that no, that's not quite what java.util.HashMap does.
I have read answers about generic arrays saying it is possible to
instead do something like having Entry[] table as a class variable and
then using table = (Entry[]) new Object[size];
To the extent that such answers recommended exactly what you describe, they are wrong. I suspect, however, that your "something like" does not capture the key elements of the answers you saw.
There are two potential issues
Creating an array whose element type is drawn from a type parameter:
class MyClass<T> {
// CAN'T DO THIS:
T[] array = new T[2];
// can do this:
T[] array = (T[]) new Object[2];
// or this:
Object[] array = new Object[2]; // (and cast later)
}
Creating an array whose element type is parameterized
class MyOtherClass<T> {
// CAN'T DO THIS, EITHER:
SomeType<T>[] array = new SomeType<T>[2];
// can do this:
SomeType<T>[] array = (SomeType<T>) new SomeType[2];
// or this:
SomeType[] array = new SomeType[2]; // (and cast later)
}
As you will have seen in the JDK source (you did follow the above link, right?), HashMap's issue is of the second type, and what it does is create an array of the appropriate raw type, and then cast that to the desired parameterized type -- which will trip the compiler's type safety warnings, but is in fact perfectly type safe as long as no other, raw or differently parameterized, reference escapes.
in the constructor as a
way of avoiding having to do the casting in both the put and get
methods, but this does leads to a ClassCastException [...]. Does this
mean I cannot use this method in my custom Hashtable?
Yes, of course it does. The method you describe and demonstrate is invalid, as the exception tells you. An Object[] is not an Entry[]. But that's not what the answers you reviewed were suggesting you do.
Finally, another way of creating the Entry array seems to be checking
the class type in the constructor and using Entry[] table = (Entry[])
Array.newInstance(c, s); Is this method perhaps more suitable?
Rarely is reflection a better answer for anything. It only makes sense when you don't have all the type information you need at compile time, and that is not your case.
It looks like your Entry class is an inner class, which I'd recommend against because it makes things more complicated. First, let's just assume that we don't have an inner class.
For the illustration, we have a simple generic class:
class Foo<T> {}
There's a difference between these two generic array types:
class Container<T> {
// creating an array with erasure of T[]
// vvvvvvvvvvvvv
T[] arrA = (T[]) new Object[N];
// creating an array with erasure of Foo<T>[]
// vvvvvvvvvv
Foo<T>[] arrB = (Foo<T>[]) new Foo[N];
// Note that the following would be slightly
// better because it doesn't use a raw type,
// but it doesn't work for this illustration
// because it's not the erasure of Foo[]:
// (Foo<T>[]) new Foo<?>[N];
}
Casting checks the erasure of type, so suppose we create a new container and assign those arrays to something in the outside world:
Container<String> c = new Container<String>();
String[] arrA1 = c.arrA;
Foo<String>[] arrB1 = c.arrB;
// After erasure these assignments become:
String[] arrA1 = (String[]) arrA;
Foo[] arrB1 = arrB;
The first assignment, arrA1 = c.arrA throws a ClassCastException, but the second assignment, arrB1 = c.arrB does not. This is because in the first case the conversion is from Object[] to String[] whereas in the second case there is no checked cast because all parameterizations of Foo<T> just become Foo after erasure.
This is all to explain my next point which is that creating an array of a parameterized type is more acceptable than creating an array of a type variable. In the case of the type variable array we have an Object[] masquerading as a T[] but in the case of the parameterized type we actually do have an array of Foo[], it's just that there is no checking for the type arguments to Foo. In other words:
Container<String> c = new Container<String>();
// Recall that this assignment doesn't throw a ClassCastException
Foo<String> arrB = c.arrB;
Object[] arrBAsOBj = arrB;
// This assignment throws an ArrayStoreException
arrBAsObj[0] = new StringBuilder();
// This assignment does not throw an ArrayStoreException
arrBAsObj[0] = new Foo<Integer>();
Although, I'd like to note that you should never expose a generic array to the outside world. I'm just doing that to illustrate the explanation.
Anyway, if you're writing something like a hash table, it's acceptable to create an unchecked array of a parameterized type. I usually write a helper method like this:
private static <K, V> Map.Entry<K, V>[] createUncheckedArray(int length) {
#SuppressWarnings("unchecked")
final Map.Entry<K, V>[] unchecked =
(Map.Entry<K, V>[]) new Map.Entry<?, ?>[length];
return unchecked;
}
Just don't return it to the outside world, because we still don't actually have a generic array, just an array of Map.Entry with unchecked type arguments.
Really Java should just have a simple class like Array<T> for this sort of case when we actually need a fixed-length container.
For an inner class you have to use a parameterized type as a qualifier, something like this:
private Entry[] createUncheckedArray(int length) {
#SuppressWarnings("unchecked")
final Entry[] unchecked =
(Entry[]) new HashTable<?, ?>.Entry[length];
return unchecked;
}
I am attempting to convert an ArrayList of class SomeClass to an ArrayList of class Object. This new ArrayList of Object will then be passed to a function. I currently have done the following:
// convert ArrayList<SomeClass> to generic ArrayList<Object>
Object[] objectArray = someClassList.toArray();
ArrayList<Object> objects = new ArrayList<Object>();
for (int i = 0; i < objectArray.length; i++) {
objects.add(objectArray[i]);
}
someFunction(objects);
public void someFunction(ArrayList<Object> objects) {
// do something with objects
}
Is there a more efficient or "standard" way of doing this? Is what I am doing "wrong" in the first place?
The purpose of converting it to ArrayList of class Object is that I have created an external library to process ArrayList of generic Objects.
If you are able to change the function's signature to taking an ArrayList<? extends Object> objects or an ArrayList<?> objects (or even better, List instead of ArrayList), you will be able to pass your ArrayList<SomeClass> directly.
The reason an ArrayList<SomeClass> is not an ArrayList<Object> is that an ArrayList<Object> would accept that you add() any kind of Object into it, which is not something you can do with an ArrayList<SomeClass>. On the other hand, an ArrayList<? extends Object> will allow you to retrieve elements from the list, but not add elements, so ArrayList<SomeClass> can safely be assigned to it.
Since you created the external library, I think it would be easier to modify the function signature to accept lists of any type. This can be accomplished using the unbounded wildcard ?:
public static void someFunction(List<?> objects) {
// whatever
}
Then you don't need to make any conversions to call it:
public static void main(String[] args) {
List<String> words = new ArrayList<>();
someFunction(words);
}
Also, unless you have a good reason not to, it would be better to accept any List in someFunction instead of limiting your input to ArrayLists. This makes your code more flexible and easier to change in the future.
A simple way to convert a List<SubFoo> to a List<Foo> is to use Collections.unmodifiableList(listOfSubFoos), which is perfectly type-safe and actually enforces that you can't do anything bad with it (like adding a DifferentSubFoo).
It is possible to transform the type parameters of a type in arbitrary ways with two casts:
ArrayList<SomeClass> l1 = ...;
ArrayList<Object> l2 = (ArrayList<Object>) (Object) l1;
But, as Aasmund Eldhuset also says in his answer: This is probably not a good idea! It is better to give a more suitable type to l2 instead, like ArrayList<?>.
This code gives you an compile warning saying Type safetyThat: Unchecked cast from Object to ArrayList<Object> for a reason. If for example a String is added to l2 and then someone reads l1 and expects a SomeClass they will get a very unexpected ClassCastException.
Why can't I create an array of List ?
List<String>[] nav = new List<String>[] { new ArrayList<String>() };
Eclipse says "Cannot create a generic array of List"
or
ArrayList<String>[] nav = new ArrayList<String>[] { new ArrayList<String>() };
Eclipse says "Cannot create a generic array of ArrayList"
or
List<String>[] getListsOfStrings() {
List<String> groupA = new ArrayList<String>();
List<String> groupB = new ArrayList<String>();
return new List<String>[] { groupA, groupB };
}
But I can do this:
List[] getLists() {
return new List[] { new ArrayList(), new ArrayList() };
}
Eclipse says that List and ArrayList are raw types but it compiles...
Seems pretty simple, why won't it work?
Well, generics tutorial give the answer to your question.
The component type of an array object
may not be a type variable or a
parameterized type, unless it is an
(unbounded) wildcard type.You can
declare array types whose element type
is a type variable or a parameterized
type, but not array objects.
This is
annoying, to be sure. This restriction
is necessary to avoid situations like:
// Not really allowed.
List<String>[] lsa = new List<String>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Unsound, but passes run time store check
oa[1] = li;
// Run-time error: ClassCastException.
String s = lsa[1].get(0);
If arrays of parameterized type were
allowed, the previous example would
compile without any unchecked
warnings, and yet fail at run-time.
We've had type-safety as a primary
design goal of generics.
You can't create arrays of generic types, generally.
The reason is that the JVM has no way to check that only the right objects are put into it (with ArrayStoreExceptions), since the difference between List<String> and List<Integer> are nonexistent at runtime.
Of course, you can trick the compiler by using the raw type List or the unbound wildcard type List<?>, and then cast it (with a unchecked cast) to List<String>. But then it is your responsibility to put only List<String> in it and no other lists.
No exact answer, but a tip:
Last example has a raw type warning because you omitted the typization of the list; it is generally a better (type safe) approach to specify which object types are contained in the list, which you already did in the previous examples (List<String> instead of List).
Using arrays is not best practice, since their use contains errors most times; Using Collection classes (List, Set, Map,...) enables use of typization and of convenient methods for handling their content; just take a look at the static methods of the Collections class.
Thus, just use the example of the previous answer.
Another solution is to extend LinkedList<String> (or ArrayList<String>, etc.), then create an array of the subclass.
private static class StringList extends LinkedList<String> {}
public static void main(String[] args)
{
StringList[] strings = new StringList[2];
strings[0] = new StringList();
strings[1] = new StringList();
strings[0].add("Test 1");
strings[0].add("Test 2");
strings[1].add("Test 3");
strings[1].add("Test 4");
System.out.println(strings[0]);
System.out.println(strings[1]);
}
I have the class:
class SomeClass<T extends SomeInterface>{
private T[] myArray;
public SomeClass()
{
// I want to initialize myArray in here to a default size of 100
myArray = new T[100]; // this gives an error
}
}
I know I can fix that by requiring a parameter in the constructor as:
class SomeClass<T extends SomeInterface>{
private T[] myArray;
public SomeClass(Class<T> clazz)
{
myArray= (T[]) Array.newInstance(clazz, 100);
}
}
but it makes no scene having to pass the generic parameter twice.
in other words in order to instantiate an object from the class SomeClass I will have to do something like:
SomeClass<SomeOtherClass> obj =
new SomeClass<SomeOtherClass>(SomeOtherClass.class);
I program in c# and Java does not seem to be friendly. I don't even understand why it is not possible to cast Object[] array to SomeOtherClass[] array. In c# that will be possible...
so my question is how can I avoid having to pass the SomeOtherClass.class parameter in order to be able to construct an array of the generic type in the constructor of the class...
While Shakedown listed the fix to your problem, allow me to explain why it is not typesafe to create a generic array.
I will illustrate why with an example from Effective Java 2nd Ed.
// Why generic array creation is illegal - won't compile!
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
Let’s pretend that line 1, which creates a generic array, is legal.
Line 2 creates and initializes a List<Integer> containing a single
element.
Line 3 stores the List<String> array into an Object array
variable, which is legal because arrays are covariant.
Line 4 stores the List<Integer> into the sole element of the Object array, which
succeeds because generics are implemented by erasure: the runtime type
of a List<Integer> instance is simply List, and the runtime type of a
List<String>[] instance is List[], so this assignment doesn’t generate
an ArrayStoreException. Now we’re in trouble. We’ve stored a
List<Integer> instance into an array that is declared to hold only
List<String> instances.
In line 5, we retrieve the sole element from
the sole list in this array. The compiler automatically casts the
retrieved element to String, but it’s an Integer, so we get a
ClassCastException at runtime. In order to prevent this from
happening, line 1 (which creates a generic array) generates a
compile-time error.
Yes, you would have to pass in the .class like that in order to make this work.
You could avoid all of this and just use an ArrayList<T> instead. When you need it in the form of an array you can use: (T[]) myArrayList.toArray()
All and every type in java is a subclass of Object class so this can be achieved using this.
public StackArray(int size){
dataStack = (T[])new Object[size];
}
Quick Question...
Can collections in Java hold more than one type? Or do they all have to be the same type?
thanks
Simple answer
Yes.
More detailed answer
You can either use generic collection, without <T> value, for example:
ArrayList a = new ArrayList();
a.add(2);
a.add("String");
Using collections without <T> is a bad habit and most IDEs / compilers give a warning here. You can circumvent it by using a collection of Object, i.e.:
ArrayList<Object> a = new ArrayList<Object>();
Or you can find some common interface or supertype that these element must have in, for example ArrayList<Number> - and you can store various objects that have common Number superclass, i.e. BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short:
ArrayList<Number> a = new ArrayList<Number>();
a.add(2); // integer
a.add(42L); // long
a.add(123.45d); // double
System.out.println(a.toString()); // => [2, 42, 123.45]
Note that it essentially means that a elements are of Number class — i.e. you can't ask to execute subclass-specific methods (for example, Double#isInfinite(), which doesn't exist in Number superclass), although you can typecast in run-time if you somehow know it's safe to typecast:
a.get(2).isInfinite() // compile-time error
((Double) a.get(2)).isInfinite() // => false
((Double) a.get(1)).isInfinite() // run-time error (ClassCastException)
Run-time typecasting is also generally frowned upon, as it effectively circumvents proper compile-time type safety.
Also note that it's impossible to assign (or use) ArrayList<Number> in place of ArrayList<Integer> and vice-versa, i.e. this will fail to compile:
public void printNumbers(ArrayList<Number> list) {
list.forEach(System.out::println);
}
ArrayList<Integer> a = new ArrayList<Integer>();
printNumbers(a); // "incompatible types"
as well as this:
public void printIntegers(ArrayList<Integer> list) {
list.forEach(System.out::println);
}
ArrayList<Number> a = new ArrayList<Number>();
printIntegers(a); // "incompatible types"
To declare a variable to be able to accept both ArrayList<Number> or any of its subclasses, one can use ArrayList<? extends Number> or ArrayList<? super Number> syntax. extends is generally used when you're going to consume (i.e. read) from the object in your method, super is used when you're going to produce (i.e. write). Given that printout is consuming, it's safe to use extends:
public void printNumbers(ArrayList<? extends Number> list) {
list.forEach(System.out::println);
}
ArrayList<Integer> listInt = new ArrayList<Integer>();
printNumbers(listInt); // works
ArrayList<Double> listDbl = new ArrayList<Double>();
printNumbers(listDbl); // also works
There is a good answer in
Difference between <? super T> and <? extends T> in Java for more in-depth explanation.
If you want them to hold any more than one type, use Collection<Object>. However, you won't know what you're getting without doing some if (x instanceof MyType) calls, which are rather inefficient.
They have to be of the same Supertype. So if you have objects of type A, then a Collection<A> can store objects of type A and of every subtype of A.
If you want to allow arbitrary types, then use Collection<Object>, otherwise take the most general appropriate super-class.
However, you will then have to manually cast from the most general type (Object) to the specific type you have in mind. You can use the typeof operator to find out what the type is.
Every Collection classes can contains heterogeneous objects except TreeSet and TreeMap. Since TreeSet and TreeMap stores elements according to some sorting order. so, if objects are of different type it will not be able to sort it because comparison between the objects will not be possible for sorting.
Yes they can but they should not (that's why generics have been put in place since 5th version of jdk) in general store different types, as this is the straight way to errors.
Yes collections in java can hold more than one type as below. But it will throw an exception if done using the following way.
ArrayList al = new ArrayList();
al.add(1);
al.add("name");
al.add(1.2f);
Iterator itr =al.iterator();
while(itr.hasNext())
{
System.out.println(itr.next());
}
Hence it's better to mention the type that you're using. To get rid of the exception the above program can be modified as below.
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(2);
al.add(3);
Iterator itr =al.iterator();
while(itr.hasNext())
{
System.out.println(itr.next());
}
ArrayList<String> al1 = new ArrayList<String>();
al1.add("Words");
al1.add("Names");
al1.add("Characters");
Iterator itr1 =al1.iterator();
while(itr1.hasNext())
{
System.out.println(itr1.next());
}
You can also use more than these types.
Yes,
My mistake the correct code is this one and
ArrayList<Elements>()=new ArrayList();
or
ArrayList<E>()=new ArrayList();
should be the correct declaration if you want to use Generics in Collection.
class Test
{
public static void main(String[] args)
{
// For Generic class of List
ArrayList<E> arrL1 = new ArrayList<E>();
arrL1.add("stackoverflow");
arrL1.add(1);
Iterator itr1=list.iterator();
while(itr1.hasNext())
{
System.out.println(itr1.next());
}
// for Particular datatype in List
ArrayList<String> list=new ArrayList<String>(); // Creating arraylist
list.add("Ravi"); // Adding object in arraylist
list.add("Vijay");
list.add("Ravi");
list.add("Ajay");
// transversing the values
Iterator itr=list.iterator();
while(itr.hasNext())
{
System.out.println(itr.next());
}
}
}
Output 1
stackoverflow
1
Output 2
Ravi
Vijay
Ravi
Ajay
I believe you can also use Collection<?>.
Yes, you can have more than one datatype in ArrayList of Collection.
class Test
{
public static void main(String[] args)
{
// For Generic class of List
ArrayList<> arrL1 = new ArrayList<>();
arrL1.add("stackoverflow");
arrL1.add(1);
// for Particular datatype in List
ArrayList<String> list=new ArrayList<String>(); // Creating arraylist
list.add("Ravi"); // Adding object in arraylist
list.add("Vijay");
list.add("Ravi");
list.add("Ajay");
// transversing the values
Iterator itr=list.iterator();
while(itr.hasNext())
{
System.out.println(itr.next());
}
}
}
Output 1:
stackoverflow
1
Output 2:
Ravi
Vijay
Ravi
Ajay