Can I use a variable as a generic-indicator? - java

Unfortunately, I don't know exactly how to correctly formulate my question.
But I think with an example it becomes clear what I want to know:
I want to create an instance of a generic class. But i don't know the types of the class yet.
So instead of this:
Foo<Type1, Type2> foo = new Foo<>();
i wanto to do something like this:
Class<? extends Type> type = getExpectedType();
Foo<Type1, type> foo = new Foo<>();
I've already tried this and it doesn't work. But is something similar possible?

That's impossible in Java, because type variables are not expressions. More concretely, Java has Type Erasure, meaning that these types don't actually exist during runtime, they are replaced by the compiler with up-casts and down-casts from, and to Object.

If you need to know the type at runtime, you must keep it in a field, and pass it to the constructor:
public Foo(Class<Type2> type2) {
...
this.type2 = type2;
}
then:
Class<? extends Type> type = getExpectedType();
Foo<Type1, ? extends Type> foo = new Foo<>(type);

Related

Why is "? extends" required here in the type declaration

I noticed that this Java 17 code snippet doesn't compile.
class Foo<T> {
public Optional<T> getSomeOptional() {
return Optional.empty();
}
}
class Bar {
List<Foo<?>> fooList = new ArrayList<>();
void bar() {
List<Optional<?>> list = fooList.stream().map(Foo::getSomeOptional).toList();
}
}
but changing class Bar to this works.
class Bar {
List<Foo<?>> fooList = new ArrayList<>();
void bar() {
List<? extends Optional<?>> list = fooList.stream().map(Foo::getSomeOptional).toList();
}
}
Why is ? extends required here in the declaration?
Compilers don't always infer types of complex expressions correctly. In this case, it may help to break them into parts. E.g. in your case, this works:
void bar() {
Stream<Optional<?>> stream = fooList.stream().map(Foo::getSomeOptional);
List<Optional<?>> list = stream.toList();
}
The type guessed by type inference (i.e. when you call a generic method without a type witness) is not always what you want. I think here what happened is that Foo::getSomeOptional takes a Foo<T> and returns an Optional<T>, so the compiler tries to "capture" the wildcard from Foo<?> into an unnamed type variable, so it thinks the result is Optional<some capture>. So you get List<Optional<some capture>>, rather than List<Optional<?>>. Although Optional<some capture> is a subtype of Optional<?>, List<Optional<some capture>> is not a subtype of List<Optional<?>>, since generics are invariant.
If you add the following type witness to tell it that you want Optional<?> (with the plain wildcard) as the result type, it should compile:
List<Optional<?>> list =
fooList.stream().<Optional<?>>map(Foo::getSomeOptional).toList();
This thing you have used is called wildcards.
https://www.geeksforgeeks.org/wildcards-in-java/
Because of Java Type Erasure in the Compile Time, if we don't use a type for our generics then it is considered from the object type, so we use "?" sign or wildcards, by this we basically say that if we pass something in the declaration instead of ?, the compiler should bound the generic to that and don't make it object type.
Note: if you don't bound with anything no matter what type you use, because of java Erasure it's type will become it's super class(the top one).

Are generic type parameters converted to Object for raw types?

Consider this piece of code:
public class Main {
public static void main(String[] args) {
Cat<Integer> cat = new Cat();
Integer i= cat.meow();
cat.var = 6;
}
}
public class Cat<E> {
public E var;
public E meow() {
return null;
}
}
As per my understanding since I've not specified the type parameter on LHS, it would be taken as Object. And Cat should become Cat<Object> because for the variable declaration to make any sense T must translate to a Class/Interface reference. Is this a correct understanding of how it works? How is type parameter T handled in case of raw types?
I've discussed this on chat and got following explanation which is way over my head:
Generics works because types are erased. Consider T an erasure of
#0-capture-of-Object. When T isn't specified (rawtyped), it is #0-capture-of-(nothing)
What does #0-capture-of-(nothing) mean?
Side note: since generic types are implemented as compile time transformations it would be easier to understand them if one could see the final translated code. Does anyone know a way to see the translated code (not byte code) for a class?
No,
raw types are not as if they are paramterized with Object, nor are they like wildcard types (<?>).
For raw types, generics are turned off.
This code is compiles (with warnings):
Cat c1 = new Cat<String>();
Cat<Integer> c2 = c1;
This code does not:
Cat<? extends Object> c1 = new Cat<String>(); // btw: this is the same as Cat<?>
Cat<Integer> c2 = c1; // error
neither does this:
Cat<Object> c1 = new Cat();
Cat<Integer> c2 = c1; // error
As for the type of var:
the type of the field after compilation is whatever the upper-bound of the parameter is (Object if none is specified). But what does the compiler do if we access var?
Cat<String> c1 = ...
String c1Var = c1.var;
This code compiles without error, but what the compiler will actually compile is this:
Cat c1 = ...
String c1Var = (String) c1.var;
As you can see, var is always treated as a field of type Object, but with generics, the compiler automatically inserts type-safe casts. That's all. If you use raw types, you have to do it yourself. Either way, when you put a Cat that stores an integer in a Cat<String> variable, you will only get a runtime exception if you try to read var.
A quiz
Now look at the declaration of Collections.max. Why do you think the parameter is defined as T extends Object & Comparable<? super T>?
Answer encoded in rot13:
Fb gung nsgre pbzcvyngvba gur erghea glcr vf Bowrpg, abg Pbzcnenoyr. Guvf vf arrqrq sbe onpxjneqf pbzcngvovyvgl (gur zrgubq vgfrys vf byqre guna trarevpf).
Edit:
Here is another good example that I just stumbled upon:
class Foo<T> {
public <V> V bar(V v) { return v; }
}
//compiles
Foo<Object> foo = new Foo<Object>();
Integer i = foo.bar(1);
//compiles
Foo<?> foo = new Foo<String>();
Integer i = foo.bar(1);
// fails
Foo foo = new Foo();
Integer i = foo.bar(1); // error: Object cannot be converted to Integer
Using no parameters disables generics entirely.
This code is valid:
Cat c = new Cat<Integer>();
c is now of the Raw Type Cat.
This is not valid:
Cat<Object> c = new Cat<Integer>(); // Compiler error
So, it's not exactly the same. Though you can, after the first line, do things like:
c.var = 5;
System.out.println(c.var);
c.var = 1;
System.out.println(c.var);
c.var = "test";
System.out.println(c.var);
Outputs:
5
1
test
#Cephalopod has provided the correct answer, however I'd like to expand on that with some of my own explanation.
for the variable declaration to make any sense T must translate to a Class/Interface reference.
That is correct. Generics are a compile time transformation. Runtime system has no notion of abstract types. So before the class is loaded into memory the abstract type T must be replaced by an actual type reference.
Run the following code:
System.out.println(Cat.class.getMethod("meow").getReturnType());
System.out.println(Cat.class.getField("var").getType());
The output is:
class java.lang.Object
class java.lang.Object
The formal type parameter E has been replaced with Object.
Cat should become Cat<Object>
Wrong. Cat will stay Cat. Why? Look at the decompiled class file for Main:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Integer i = (Integer)cat.meow();
cat.var = Integer.valueOf(6);
}
}
The purpose of specifying formal type parameter with <> is to enable compiler to generate explicit casts.
When you say new Cat() it doesn't have to turn into anything, the compiler simply won't generate a cast and the method call would look like:
Integer i = cat.meow(); // No cast at all
Are generic type parameters converted to Object for raw types?
To clarify what is being asked here, the above questions means: Is E replaced with java.lang.Object if I don't specify anything when instantiating Cat.
Actually E would be replaced with java.lang.Object even if you specified <Integer> when instantiating Cat. The replacement/transformation is done at compile time while the instantiation is at runtime. How you use the type isn't going to change its class definition.
Generic types defined in objects like the
Cat c = new Cat<Integer>();
are only intended to provide the compiler with the chance to check that the types will match at runtime.
Generic types assigned in class definitions are retained in the compiled class.
public class Cat<T extends Number>{}
Or
public class Intcat extends Cat<Integer>{}
The runtime knows that the generic argument is bound by Number in the first case and is Integer in the first case.
I have no links to back this up, but I'd rather assume that c becomes raw type Cat, not Cat<Object>.
Raw types don't handle parameter T, which is why they are prone to errors.
javadoc says: A raw type is the name of a generic class or interface without any type arguments.
That chat log seems to mean exactly that, but in a confusing manner.
I actually Do not know How it is actually implemented in the bytecode But from my understanding Cat c = new Cat<Integer>(); Stores the new instance of Cat created by new Cat<Integer>() in the variable c. Now if you query c to know what is the type of var it will reply Integer and not Object because the instance that was created has a type of Integer.
Now If you execute c.var = "Text"; and query c to know what is the type of var. It would reply String. This does not means that by default it is converting <T> to Object. It means that c does not know what is the type of var.
I feel that is why the <?> wild card is used. Cat<?> c = new Cat<Integer>(); it would always convert <T> to Object. That is the reason why it is always advised not the use raw types for generics.
I think Cat c is a RAW type and could be considered as a "wildcard type" like Cat<?>. Since Cat<?> is the supertype of each type of Cat including Cat<Integer>, Cat c may take a new Cat<Integer> object.
This is also mentioned here: Interoperating with Legacy Code
"Most people's first instinct is that Collection really means Collection. However, as we saw earlier, it isn't safe to pass a Collection in a place where a Collection is required. It's more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection."
...
"So raw types are very much like wildcard types, but they are not typechecked as stringently. This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code."

java class declaration with the generic type disable method returning another generic type

I have this little problem and I want to find out whay is this happening to me:
I made class like this:
public class SomeSimpleClass {
//and I had method who returns generic. it can be instance of whatever, and for sure can be casted to U:
public <U>U giveMeSomething(String arg) {
return (U)SomewhereSomething.getValueCastingIsOK(arg);
}
}
and then I am using it like this:
// give me some number:
Integer number = instanceSomeSimpleClass.giveMeSomething("I want integer value");
OR
String text = instanceSomeSimpleClass.giveMeSomething("I want text now");
and everything is ok. getValueCastingIsOK from SomewhereSomething returns me what I want, and I dont need type cast on the giveMeSomething. Looks like the U is replaced by the type from the associative variable.. and everything is cool..
BUT then I create a new class:
public class SomeOtherClass <T extends Something> {
//And I have the exactly same method here:
public <U>U giveMeSomething(String arg) {
return (U)SomewhereSomething.getValueCastingIsOK(arg);
}
}
and now when I want to use it in the same way, the compiler (I am using Java 1.7) is telling me, that I have to use typecast, and start to using the giveMeSomething like this, because U is now the Object, and nothing else...
Integer number = (Integer)instanceSomeSimpleClass.giveMeSomething("I want integer value");
:(
Why in the second case the compiler cant get the U type in the same way, as in the first case. And what I have to do, to be that this way?
Thank for an answers, suggestions.. ;)
The problem is that you're using a raw type of the class SomeOtherClass, but not supplying any type parameters to your declaration:
SomeOtherClass instanceSomeSimpleClass = new SomeOtherClass();
When you do this, the Java compiler will replace ALL generics in the class with the type erasure version, even unrelated generics such as your generic method. The type erasure of the method
public <U>U giveMeSomething(String arg) {
becomes
public Object giveMeSomething(String arg) {
which requires the cast to Integer.
You must either supply a type parameter to SomeOtherClass (useless, unless you've eliminated unnecessary code that uses T to post the class code here):
SomeOtherClass<Something> instanceSomeSimpleClass = new SomeOtherClass<Something>();
Or you can remove the type parameter on the class definition, as you had it originally:
public class SomeSimpleClass {
Either will keep the generic method intact and eliminate the need to cast the return type of giveMeSomething.
You're missing the java syntax for explicit typing:
public <U>U giveMeSomething(String arg) {
return SomewhereSomething.<U>getValueCastingIsOK(arg);
}
Notice the <U> between the dot and the method name. Notice also that casting is now not required.
That's how you explicitly declare what the type is for a typed method. Without this, java must infer the type, which it can't here, so (unsafe) casting is required.
Another example to illustrate. In this case, java can infer the type is Integer because of the type that the result is being assigned to:
Integer number = instanceSomeSimpleClass.giveMeSomething("I want integer value");
But if you were to use the value without assigning it, you'd need to pass to type:
someIntegerRequired(instanceSomeSimpleClass.<Integer>giveMeSomething("I want integer value"));

How to instantiate generic variable like Class<Foo<T>>?

I need to instantiate generic variable like Class>.
For example,
Class<? extends List<String>> = ...
Variant 1:
Class<? extends List<String>> clazz = LinkedList.class;
don't work - "Incompatible types".
Variant 2:
Class<? extends List> clazz = LinkedList.class;
work, but in this case List is a raw type - not good.
How to instantiate such variables?
There is not really a point in having Class<List<String>>, since it would be equivalent to Class<List<Integer>> (both being a reference to the raw List due to erasure).
Having said that, if your intention is to represent the List<String> type, this is possible, see TypeLiteral of Google Guice.
TypeLiteral<List<String>> type = new TypeLiteral<List<String>>() {};
System.out.println(type.getType()); //should print List<String>
The mechanism that makes it possible is reflection on static type declarations, in either class definition (class X extends Foo<Y>, Y is available through reflection, it is preserved in the bytecode), method definitions, etc. TypeLiteral uses the former, you may notice that it creates a brand new class, so the net effect is that your type parameter gets preserved. It's a nice workaround when you are really fighting against erasure.
You can't do it properly. Either use the raw type or use an unsafe cast.
List<String> myList = new ArrayList<String>();
Class<? extends List<String>> var = (Class<? extends List<String>>)myList.getClass();
You can write:
class ListString extends java.util.LinkedList<String> { }
Class<? extends java.util.List<String>> clazz = ListString.class;
However, given what Class represents, there isn't that much point. You are better off avoiding reflection wherever possible.

Generics in Java, using wildcards

I have a question about Generics in Java, namely using wildcards. I have an example class GenClass like this:
public class GenClass<E> {
private E var;
public void setVar(E x) {
var = x;
}
public E getVar() {
return var;
}
}
I have another simple class:
public class ExampleClass {
}
I have written the following test class:
public class TestGenClass {
public static void main(String[] str) {
ExampleClass ec = new ExampleClass();
GenClass<ExampleClass> c = new GenClass<ExampleClass>();
c.setVar(ec);
System.out.println(c.getVar()); // OUTPUT: ExampleClass#addbf1
}
}
Now, if I use a wildcard and write in the test class this:
GenClass<?> c = new GenClass<ExampleClass>();
on the place of:
GenClass<ExampleClass> c = new GenClass<ExampleClass>();
the compiler has no problem with this new statement, however, it complains about
c.setVar(ec);
It says that "the method (setVar()) is not applicable for the arguments (ExampleClass)". Why do I get this message?
I thought that the way I have used the wildcard, makes the reference variable c be of type GenClass, which would accept as parameter any class - on the place of E I would have any class. This is just the declaration of the variable. Then I initialize it with
new GenClass<ExampleClass>()
which means that I create an object of type GenClass, which has as parameter a class of type ExampleClass. So, I think that now E in GenClass will be ExampleClass, and I would be able to use the method setVar(), giving it as argument something of type ExampleClass.
This was my assumption and understanding, but it seems that Java does not like it, and I am not right.
Any comment is appreciated, thank you.
This exact situation is covered in the Java Generics Tutorial.
Notice that [with the wildcard], we can still read elements from [the generic Collection] and give them type Object. This is always safe, since whatever the actual type of the collection, it does contain objects. It isn't safe to add arbitrary objects to it however:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
Since we don't know what the element type of c stands for, we cannot add objects to it. The add() method takes arguments of type E, the element type of the collection. When the actual type parameter is ?, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don't know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type.
(emphasis mine)
mmyers has the correct answer, but I just wanted to comment on this part of your question (which sounds like your rationale for wanting to use the wildcard):
I thought that the way I have used the wildcard, makes the reference variable c be of type GenClass, which would accept as parameter any class - on the place of E I would have any class. This is just the declaration of the variable. Then I initialize it with
If you really want to accomplish this, you could do something like without compilation errors:
GenClass<Object> gc = new GenClass<Object>();
gc.setVar(new ExampleClass());
But then again, if you want to declare an instance of GenClass that can contain any type, I'm not sure why you'd want to use generics at all - you could just use the raw class:
GenClass raw = new GenClass();
raw.setVar(new ExampleClass());
raw.setVar("this runs ok");

Categories