Consider the following code:
import java.math.BigDecimal;
class Scratch {
public static class Test<N extends Number> {
public void foo(Class<N> numberClass) {
System.out.println(numberClass);
}
public void bar() {
foo(BigDecimal.class);
}
}
public static void main(String[] args) {
Test<Number> t = new Test<>();
t.bar();
}
}
This fails to compile on line 12 (the call to foo()) with incompatible types: java.lang.Class<java.math.BigDecimal> cannot be converted to java.lang.Class<N>. I don't get it, because the generic N extends Number, and so I should be able to pass BigDecimal.class to a method which takes a Class<N> parameter. TIA for any thoughts!
Suppose a caller did:
var t = new Test<Integer>();
t.bar();
then, bar() would pass a BigDecimal to foo(), which expects an Integer.
The short answer really is that BigDecimal is not necessarily N. It is a little confusing because BigDecimal is within the <N extends Number> bounds, but the current instance's generic type argument can be any type that extends Number, but the bar method assumes BigDecimal.
Check this slightly modified version of the method:
public void bar() {
new Test<BigDecimal>().foo(BigDecimal.class);
}
That method compiles. Why? Becuase Test<BigDecimal>() has BigDecimal as type argument.
When you call foo(BigDecimal.class), you're assuming that this was instantiated with BigDecimal as type argument, which is not always true, and the the compiler is preventing a bug.
The error in your code would be similar to a hypothetical bar() method in ArrayList<T> that does add("string"), which would be wrong for the same reason (the actual array list instance may be created to hold integers, not strings, so the inside code shouldn't make assumptions)
Related
I have the following program:
class MyGenClass{
public <T> void setAge(T ageParam){
Integer age = ageParam;
}
}
class Program{
public static void main(String args[]){
MyGenClass gnClass = new MyGenClass();
gnClass.<Integer>setAge(80);
}
}
In fact, i am passing the Integer then why the ageParam is not assigned to age. And when i do:
class MyGenClass{
public <T> void setAge(T ageParam){
T age = ageParam;
}
}
Why the generic type variable is not assigned to the Integer type variable age in fact the generic type variable ageParam is Integer. Is this compulsory that the ageParam must be assigned to the variable that is of type T? Whats the scenario behind this?
There is not assured that the type T will be compatible with Integer. To make it clear, you have to use the following approach where T would be a subtype of Integer:
public <T extends Integer> void setAge(T ageParam){
age = ageParam;
}
However, I see no point on this. Consider the following approach for the sake of variability:
class MyGenClass {
Number age;
public <T extends Number> void setAge(T ageParam){
age = ageParam;
}
}
Therefore the following is possible (the explicit type arguments can be inferred, thought):
MyGenClass gnClass = new MyGenClass();
gnClass.<Integer>setAge(80);
gnClass.<Long>setAge(80L);
gnClass.<Double>setAge(80.0);
gnClass.<Float>setAge(80.0F);
Look at your MyGenClass in isolation. T could be literally anything. It is not necessarily an Integer. I could call it with a String, or a HashMap or an ArrayList, or literally anything else.
MyGenClass gnClass = new MyGenClass();
gnClass.setAge("hello");
gnClass.setAge(new HashMap<String, String>());
gnClass.setAge(new ArrayList<String>());
In all of these cases, assignment to an Integer variable is invalid, hence the compiler error.
You only happen to be calling it with an Integer in your example. The compiler cannot assert that it will always be this way.
It looks as if you should not be using generics at all. Just change the signature to
public void setAge(Integer ageParam)
package org.my.java;
public class TestTypeVariable {
static <T,A extends T> void typeVarType(T t, A a){
System.out.println(a.getClass());
System.out.println(t.getClass());
}
public static void main(String[] s){
int i= 1;
typeVarType("string", i);
}
}
when run, following is the output :
class java.lang.Integer
class java.lang.String
How can A be of type Integer when it has been already upper-bounded to String?
Please explain me on it.
Two things here:
there is a simple solution to the "bad" typing: T isn't String but Object. And Integer extends Object. But please note: this only works with the "enhanced" type inference capabilities of Java8. With Java7, your input will not compile!
misconception on your end: getClass() happens at runtime, and therefore returns the specific class of the objects passed - independent on what the compiler thinks about generics at compile time.
Having trouble understanding generic programming in Java.
I read some tutorial about it but still quite confused, especially when things get complicated.
Can anyone explain what's happening in this example?
import java.util.Date;
public class Test1 {
public static void main(String[] args) {
P<Cls> p = new P<>(); //<1> //I expect a ClassCastException here, but no. Why? //How does the type inference for class P<E> work?
System.out.println(p.name); //it prints
// System.out.println(p.name.getClass());//but this line throws ClassCastException //why here? why not line <1>?
test1(p);//it runs
// test2(p);//throws ClassCastException//What is going on in method test1&test2?
//How does the type inference for generic methods work in this case?
}
public static<T> void test1(P<? extends T> k){
System.out.println(k.name.getClass());
}
public static<T extends Cls> void test2(P<? extends T> k){
System.out.println(k.name.getClass());
}
}
class P<E>{
E name = (E)new Date();//<2>
}
class Cls{}
P<Cls> p = new P<>();
Remember that Java implements generics by erasure, meaning that the constructor for P doesn't really have any idea what E is at runtime. Generics in Java are purely there to help developers at compile-time.
This means that when you create a new P<>(), a new Date() is created, but it isn't actually cast to any particular type, because the runtime doesn't know anything about E. E does not exist at runtime, as far as the P<E> class is concerned. name is just an Object reference that has a Date inside. However, any time you write code that consumes name in a way where the runtime environment needs to know that it's of a particular type (Cls in this case), the compiler inserts a cast to that type without telling you.
p.name.getClass() gets compiled as ((Cls)p.name).getClass(), which will create a class cast exception.
test2() specifies a type constraint that is not generic (extends Cls). So its call to p.name.getClass() likewise is translated to ((Cls)p.name).getClass().
On the other hand:
System.out.println(p.name) is actually the same as System.out.println((Object)p.name) because println is a non-generic method that takes an object.
test1(p.name) is similar. Because the runtime doesn't actually know what type T is, it basically casts p.name as Object before calling getClass() on it.
In other words, here's your code as it actually gets compiled:
class P{
Object name = new Date();
}
public static void main(String[] args) {
P p = new P();
System.out.println(p.name);
System.out.println(((Cls)p.name).getClass());
test1(p);
test2(p);
}
public static void test1(P k){
System.out.println(k.name.getClass());
}
public static void test2(P k){
System.out.println(((Cls)k.name).getClass());
}
I have a simple generic list class. I'm trying to achieve this: given a list instance of which we know only that it contains instances of a specific subclass of a class, and given an instance of this class, add this instance to the list if it is instance of the contained (subclass) type, otherwise throw an exception (eg. ClassCastException). I tried the following:
class MyList<T>
{
private Class<T> genericClass;
private List<T> list = new ArrayList<>();
public MyList(Class<T> genericClass)
{
this.genericClass = genericClass;
}
public void add(T elem)
{
list.add(elem);
}
//...
public Class<T> getGenericParamClass()
{
return genericClass;
}
}
class A{}
class B extends A{}
class C extends A{}
class Program
{
public static void main(String... args)
{
MyList<B> list1 = new MyList<>(B.class);
MyList<C> list2 = new MyList<>(C.class);
MyList<? extends A> ls = checkStuff() ? list1 : list2;
ls.add(ls.getGenericParamClass().cast(lotsOfStuff())); //ERROR ?!!
}
static boolean checkStuff()
{
Random random = new Random();
return random.nextBoolean();
}
static A lotsOfStuff()
{
return new B();
}
}
I thought that given a Class object whose type parameter is the same as the type of a parameter of a method, I would be able to cast something using the former to be able to pass it to the latter. Alas, it seems I cannot: I get a compile-time error!
I could throw generics out the window, go full unchecked and just say:
A val = lotsOfStuff();
if (myList.getGenericParamClass().isInstance(val))
ls.add(val)
else
throw new SomeException();
But, that would probably create more problems than it would solve, and also it would really bug me.
Am I missing something here, or is it simply not possible the way I thought it out?
Edit:
I understand fully well why something like this cannot work:
List<? extends Number> abc=new ArrayList<Integer>();
abc.add(new Integer(10));
But in my mind, the following transitivity holds: the type of the parameter of add() Is-The-Same-As the type parameter of MyList Is-The-Same-As the type parameter of the Class returned by getGenericParamClass() Is-The-Same-As the return type of the cast() method of that Class. I (as a human) can know that those unknown types are the same, because I am getting them from the same object.
Is there a fault in my logic, or is this a limitation of Java?
The compilation error is:
The method add(capture#2-of ? extends A) in the type MyList is not applicable for the arguments (capture#3-of ? extends A)
To understand that message, recall that ? extends A stands for an unknown type that is a subtype of A. That compiler can not know that the ? extends A returned by lotsOfStuff() is the same (or a subtype of) the ? extends A that the MyList.add method expects, and in fact, your program does not ensure that this is the case (because lotsOfStuff() always returns a B, even if it should be added to a list of C.)
To express that the two are of the same type, we must use a type parameter. The easiest way to get one is to move the code doing the casting and throwing into class MyList<T> (which already has a suitable type parameter), for instance by adding the following method:
void addOrThrow(Object o) {
add(genericClass.cast(o));
}
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));
}