Java Generics - Confusing behavior - java

I'm having trouble understanding why I'm getting a compilation error here. Let me share some simple code. The following block of code works fine:
public class Test {
public static void main(String[] args) {
String[] arr = new String[0];
MethodA(arr);
}
public static <E> void MethodA(E[] array) {
Integer[] intArray = new Integer[0];
MethodB(array, intArray);
}
public static <E> void MethodB(E[] array, E[] secondArray) {
//Stuff
}
}
The problem arises when I add a new generic List parameter to MethodB, calling it from MethodA:
public class Test {
public static void main(String[] args) {
String[] arr = new String[0];
MethodA(arr);
}
public static <E> void MethodA(E[] array) {
Integer[] intArray = new Integer[0];
List<E> someList = new ArrayList<E>();
MethodB(array, intArray, someList);
}
public static <E> void MethodB(E[] array, E[] secondArray, List<E> list) {
//Stuff
}
}
Which gives me the following error:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method MethodB(E[], E[], List) in the type Test is not applicable for the arguments (E[], Integer[], List)
It seems to be telling me to change the parameter from E[] to Integer[], which is weird because it did not complain about such a thing until after I introduced the List parameter. Again, I feel like I must be making a silly mistake somewhere, but I can't figure it out. Any help would be appreciated! Thanks!

In the first example, you're calling MethodB with a String[] and an Integer[].
Since arrays are "covariant" - meaning, for example, you can cast a String[] to an Object[], it calls the version of MethodB with Object for E.
In the second example, it's similar, but you also have a List<E>. Generic classes do not work the same way of arrays - you cannot cast a List<String> to a List<Object>. So it would be invalid for E to be Object (or anything other than whatever E is in MethodA) since then the third parameter couldn't be converted, and it would also be invalid for E to be String since then the first parameter couldn't be converted. So there is no type that works for E.
Note: If you changed String to Integer in main, it still wouldn't compile, even though E could be Integer. That's because the compiler doesn't know that MethodA is never called with anything other than Integer.

In method B declaration You use the same generic type(E) for all three parameters.
It meens that you may use a parameter of any kind (E) but it must be the same for all 3 parameters.
Try adding another generic type(T) like this:
public class Test {
public static void main(String[] args) {
String[] arr = new String[0];
MethodA(arr);
}
public static <E> void MethodA(E[] array) {
Integer[] intArray = new Integer[0];
List<E> someList = new ArrayList<E>();
MethodB(array, intArray, someList);
}
public static <E, T> void MethodB(E[] array, T[] secondArray, List<E> list) {
//Stuff
}
}
Or if there is a need you can add third so that List does not require the E to be the same type as in E[] array.

MethodB requires all three parameters be of same type. But you are calling it with E and Integer. Try E[] intArray = null; and compiler will not compalin

Related

Two methods for creating generic arrays with the same result, but only one works

I recently tried to create an array of generics and found out it is not allowed:
Cannot create generic array of OptionSet<T>
I decided to make a test class and found out that there is a different method with exactly the same result that does work:
public class Test {
#SuppressWarnings("unchecked")
public static final void main(String[] args) {
A<String> a = null;
A<String> b = null;
A<?>[] array1 = array(a, b); // fine, only a warning
A<?>[] array2 = new A<String>[] {a, b}; // Error: Cannot create a generic array of Test.A<String>
}
#SuppressWarnings("unchecked")
private static final <T> A<T>[] array(A<T>... a) {
return a; // fine, only a warning
}
private static final class A<T> {}
}
Why is that? Both methods have exactly the same result, but for some reason one throws an error and the other works fine, although it gives a warning.
It's because of casting is happening in case of method call via return type private static final <T> A<T>[] array(A<T>... a) which is not in case of A<?>[] array2 = new A<String>[] {a, b}; where you're directly creating the instance.
to make it work You need to cast explicitly like below:
A<?>[] array2 = (A<String>[])(new A<?>[] { a, b });
What's happening is the elements are A<String> but not the array itself; it's still generic A<?> until you cast it explicitly.

How to read this: public <T> T[] toArray(T[] array);

I had read documentation about generics types and I know use it but I have a problem "reading" two methods.
I use "toArray()" and I use "asList()" but I don't understand how write the method solving types.
Example 1
public Iterator<E> iterator();
public Iterator<String> iterator();
Example 2
public E get(int location);
public String get(int location);
Example 3(Here is that I don't understand)
public static <T> List<T> asList(T... array) {
return new ArrayList<T>(array);
}
public static <String> List<String> asList(String... array) {
return new ArrayList<String>(array);
}
[modifiers] [return type] [¿What is this?] [type param]
public static <String> List<String> asList(String... array) {
v[type param for returned ArrayList]
return new ArrayList<String>(array);
}
Example 4(the same that Example 3)
public <T> T[] toArray(T[] array);
[modifier] [return type] [what is this?] [param type]
public <String> String[] toArray(String[] array);
¿Is this a return type?
<String> List<String>
I had read the forum but I don't find some that explain this. All responses are explanations of how to use it but I know how to use it.
Thanks in advanced!
===================== EDIT 1 =============================
I have a test:
import java.util.List;
public class ClassTest {
public static void main(String[] args) {
ClassTest.testClass(new String[]{"1","2","3"});
}
public static <String> List<String> testClass(String[] array){
System.out.println("** public static <String> List<String> testClass(String[] array){");
return null;
}
public static List<String> testClass(String[] array){
System.out.println("** public static List<String> testClass(String[] array){");
return null;
}
}
If I execute the test I have this trace:
** public static List<String> testClass(String[] array){
If I delete the second method I have this trace:
** public static <String> List<String> testClass(String[] array){
In both case, it works.
Maybe is not the same that my first question but I think so.
The compiler think that the methods are diferent from the other because if y delete
<String>
in the first method compiler says that the method is repeat.
I can't appreciate your response.
===================== EDIT 2 =============================
I have some new information... In Oracle doc exist this:
https://docs.oracle.com/javase/tutorial/java/generics/methods.html
https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html
Exactly, what I ask is called: Type Inference(I don't know, at the moment...) related with "Generic Methods".
At present, I'm reading this documentation...
I will be back when I read it.
Thanks for all!
PD: I don't forget completly this thread because maybe I come back to ask.
===================== EDIT 3 =============================
I've read the Oracle's documentation and I think I understand it a little bit more.
I think this is the best way to understand it...
In a class you can define a dynamic type so,
public class Box<T>
The same way to do this in a method is like this:
public <U> void test()
The different between this is the place of the "Type Parameter", but in general, is the same.
Correct me if I'm wrong, please.
Next step... A little bit more complicate:
public class Box<T> {
private T variable;
....
public <U> void test(){
U variable = null;
...
One thing that confused me, is that I never used a parameterised method, only like this:
public class Box<T> {
private T variable;
public void test(T variable){
...
On this way I never needed to use in a method.
Now something more complicate. I don't understand why this below code works. Is a very strange example, I know, but is because I can't see it.
public class TestBox<Mike> {
public static void main(String[] args) {
TestBox<Charles> xb = new TestBox();
}
public static <Peter> void get(TestBox u){
}
}
This words, every time are... ¿¿suggestions?? I don't have any class call "Mike" or "Peter"
<Mike>
<Peter>
The previous code compile if I left . I guess that is because If I put something between <> might be a real type.
Then I need to confirm, that the word after static(or public if isn't a static method) is just a "SUGGESTION".
Thanks!!
===================== EDIT 4 =============================
New Question related with previos doubt.
public <String> void testClass(String[] array){
System.out.println("** public <String> void testClass(String[] array){");
}
public void testClass(String[] array){
System.out.println("** public void testClass(String[] array){");
}
Why if I change in the first Method the word String for Integer, I have error...?
Integer is "WhatEverIWant"
public <Integer> void testClass(String[] array){
System.out.println("** public <Integer> void testClass(String[] array){");
}
And Why if I change the parameter String[] for Integer[], it works again?
public <Integer> void testClass(Integer[] array){
System.out.println("** public <Integer> void testClass(String[] array){");
}
I think is ilogical, respect before comments, Ufff...
<T> is not a return type, it's a declaration of generic type parameter T, and it may appear before the return type.
In public <T> T[] toArray(T[] array)
T[] is the return type, and it means that toArray accepts a generic array and returns a generic array of the same type.
In <String> List<String> toArray(String[] array), <String> is a generic type parameter (List<String> is the return type), which is confusing, since it hides the java.lang.String class.
It's completely equivalent to <T> List<T> toArray(T[] array).
EDIT:
public static List<String> testClass(String[] array)
is a method that accepts a String array (i.e. an array whose element type is java.lang.String) and returns a List of Strings.
public static <String> List<String> testClass(String[] array)
is a method that accepts an array of some reference type (it has a generic type parameter called String, which has no relation to java.lang.String) and returns a List of the same type.
The two methods have a different argument, even though it looks like they don't.
When you call testClass with a String[] argument, the non-generic method that accepts a String array will be called, since its arguments are a better fit to the array you are passing than the generic method. If you remove the non-generic method, the generic method is called instead, since the generic method accepts any non-primitive array.
Maybe it will help you better understand if you try to call the method with different types of arrays :
String[] strarray = {"a","b"};
testClass(strarray); // calls the first (non generic) method
Integer[] intarray = {1,2,3,4};
testClass(intarray); // calls the second (generic) method
If you edit your test code in a IDE like 'idea' or others, you will see the color of 'String' is different between first method and second. That means the compiler treat the first 'String' not 'java.lang.String'. It is just a word like T,E or any other word.I think maybe Java should let compiler check the generic type word to avoid confused with Java's keywords.

Why can't I have two methods with ArrayList parameters?

Why can't I make two overloaded methods whose parameters are both array lists, but with different data types?
public class test {
public static void main(String[] args){
ArrayList<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.add(4);
ints.add(5);
showFirst(ints);
ArrayList<Double> dubs = new ArrayList<Double>();
dubs.add(1.1);
dubs.add(2.2);
dubs.add(3.3);
dubs.add(4.4);
dubs.add(5.5);
showFirst(dubs);
}
public static void showFirst(ArrayList<Integer> a)
{
System.out.println(a.remove(0));
}
public static void showFirst(ArrayList<Double> a)
{
System.out.println(a.remove(0));
}
}
I am in eclipse, and it underlines the problem causing code in red and gives this message: Method showFirst(ArrayList<Integer>) has the same erasure showFirst(ArrayList<E>) as another method in type test
The only way I could get it to work is my adding other parameters, such as , int b after showFirst(ArrayList<Integer> a and , int b after showFirst(ArrayList<Double> a.
Is there any way to make this code work the way I intended? If not, I'd like to know why this is happening.
Running the program generates the following error message:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method showFirst(ArrayList<Integer>) in the type test is not applicable for the arguments (ArrayList<Double>)
at test.test.main(test.java:25)
Edit:
Using or , what if I wanted to do things where I need the data type such as:
public static int[] reverseInArray(ArrayList<Integer> a)
{
int n = a.size();
int[] b = new int[n];
while(n > 0)
{
b[n] = a.remove(0);
n--;
}
return b;
}
public static double[] reverseInArray(ArrayList<Double> a)
{
double n = a.size();
double[] b = new int[n];
while(I > 0)
{
b[n] = a.remove(0);
n--;
}
return b;
}
At run time, every ArrayList<Whatever> will be converted to ArrayList (raw) due to type erasure. So, just have one method that receives List<? extends Number>.
//renamed to show what the method really does
public static void removeFirst(List<? extends Number> a) {
System.out.println(a.remove(0));
}
Note that the method above will work only for Lists (ArrayList, LinkedList and other implementations of List) which declares to hold a class that extends from Number. If you want/need a method to remove the first element from List that holds any type, use List<?> instead:
public static void removeFirst(List<?> a) {
System.out.println(a.remove(0));
}
Remember to always program to interfaces instead of specific class implementation.
Generics are only enforced at compile time. At runtime an ArrayList is an ArrayList.
You can combine the two methods in this particular case, though:
public static void showFirst(ArrayList<? extends Number> a)
{
System.out.println(a.remove(0));
}
Because generics are erased at runtime. In other words, your code doesn't know that the two methods are different. In fact, you have another compiler error that you're not telling us about:
Method showFirst(ArrayList<Integer>) has the same erasure showFirst(ArrayList<E>) as another method in type Main
... because of type erasure, your generic parameters are unknown at runtime, hence your overridden methods share an ambiguous signature.
Have this method only, because, generics are available only in compile, so, both of your methods are compiled to same signature. So it's ambiguous which method to be called
public static void showFirst(ArrayList<? extends Number> a)
{
System.out.println(a.remove(0));
}

tricky static generic method with generic return type which itself could be a generic

I have a class as follows:
public class MyConverter {
public <T> T convert (Object o, String typeidentifier, T dummy)
{
... do some conversions such as a java array to an ArrayList or vice versa
... based on a typeidentifier syntax similar to Class.getName() but which
... embeds information about generic subtypes
}
}
and want to be able to do something general like this:
int[] ar = {...};
ArrayList<Integer> dummy = null;
Integer elem = MyConverter.convert(ar, "java.util.ArrayList<Integer>", dummy)
.get(15);
That is, the T in convert may itself be a generic instance, and I found that to get this goal to work, I have to pass a fully typed dummy, as ArrayList.class won't give the java compiler enough information that it is an ArrayList<Integer> if I used Class<T> dummycls instead of T dummy.
Am I missing something? Is there a way to both write and invoke convert without requiring a dummy?
Specify the type on your call, rather than letting java infer the type:
Integer elem = MyConverter.<ArrayList<Integer>>convert(ar, "java.util.ArrayList<Integer>");
This link describes this (cool) syntax.
This kind of looks like Arrays.asList, it will take a native array and convert it to an ArrayList.
An implementation could like the following:
public static <T> List<T> asList(T... a) {
ArrayList<T> arr = new ArrayList<T>();
for (T item: a) {
arr.add(item);
}
return arr;
}
You can write the logic whatever you want to convert in convert method and cast it to generic type and return.
public class MyConverter {
public static void main(String[] args) {
Integer i = MyConverter.convert();
System.out.println(i);
}
#SuppressWarnings("unchecked")
private static <T> T convert() {
Integer i = new Integer(10);
return (T)i;
}
}

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