Why does this compile?
B is used in A without any generic parameters, and this compiled in Java. What is going on here?
interface B<T>
{
public T Foo(T value);
}
public class A
{
public B What()
{
return null;
}
public void Foo()
{
B x = What();
x.Foo(123);
}
}
This is for compatibility with pre-J2SE 5.0 Java. You should get a rawtypes warning (take notice of the compiler warnings).
You're just using a raw type of B here. Just like
List list = new ArrayList(); // defined as: public interface List<E>
Perfectly, valid; not recommended though.
Related
I was playing around with generics and found that, to my surprise, the following code compiles:
class A {}
class B extends A {}
class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}
public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}
public static <T> void fArray(T[] a, Generic<? extends T> b) {
a[0] = b.get();
}
}
I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?
T seems to be inferred to Object, since I can pass a Generic<Object> as well.
Moreover, when actually running the code, it throws an ArrayStoreException on the a[0] = b.get(); line.
I'm not using any raw generic types. I feel like this exception could have been avoided with a compile time error, or at least a warning, if T was actually inferred to be B.
When further testing with the List<...> equivalent:
public static void main(String[] args) {
fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected
}
public static <T> void fList(List<T> a, Generic<? extends T> b) {
a.add(b.get());
}
This does produce and error:
The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>)
As does the more generic case:
public static <T> void fList(List<? extends T> a, Generic<? extends T> b) {
a.add(b.get()); // <-- Error here
}
The compiler correctly recognizes that the first ? might be further down the inheritance hierarchy than the second ?.
e.g. If the first ? were B and the second ? were A then this isn't type safe.
So why doesn't the first example produce a similar compiler error? Is it just an oversight? Or is there a technical limitation?
The only way I could produce an error is by explicitly providing a type:
Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable
I didn't really find anything through my own research, except for this article from 2005 (before generics), which talks about the dangers of array covariance.
Array covariance seems to hint towards an explanation, but I can't think of one.
Current jdk is 1.8.0.0_91
Consider this example:
class A {}
class B extends A {}
class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}
public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}
public static <T> void fArray(T[] a, Generic<? extends T> b) {
List<T> list = new ArrayList<>();
list.add(a[0]);
list.add(b.get());
System.out.println(list);
}
}
As you can see, the signatures used to infer the type parameters are identical, the only thing that's different is that fArray() only reads array elements instead of writing them, making the T -> A inference perfectly justifiable at runtime.
And there is no way for the compiler to tell what your array reference will be used for in the implementation of the method.
I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?
T is not inferred to be B, it is inferred to be A. Since B extends A, B[] is a subtype of A[], and therefore the method call is correct.
Contrary to generics, the element type of an array is available at runtime (they are reified). So when you try to do
a[0] = b.get();
the runtime environment knows that a is actually a B array and cannot hold an A.
The problem here is that Java is extending dynamically. Arrays are there since the first version of Java, while generics were added only in Java 1.5. In general, Oracle tries to make new Java versions backward compatible, and therefore errors made in earlier versions (the array covariance, for example) are not corrected in newer versions.
I'm struggling with this aspect of Generics in Java. Hopefully someone can help me see the ways.
I have a class that holds a List of objects. This code works, but I want to get rid of the cast. How can I make this more generic?
public class Executor {
List<BaseRequest<BaseObj>> mRequests = new ArrayList<BaseRequest<BaseObj>>();
public Executor() {
}
#SuppressWarnings("unchecked")
public <T extends BaseObj> void add(final BaseRequest<T> request) {
mRequests.add((BaseRequest<BaseObj>) request);
}
public void execute() {
for (BaseRequest<BaseObj> r : mRequests) {
// DO SOMETHING WITH r
}
}
}
In the posted snippet you need the cast because BaseRequest<? extends BaseObj> is not a subtype of BaseRequest<BaseObj>, and the cast can't be checked at runtime because of type erasure, and that's why the compiler warns you. But if you change the declaration of mRequests:
public class Executor {
List<BaseRequest<? extends BaseObj>> mRequests = new ArrayList<>();
public Executor() {
}
public <T extends BaseObj> void add(final BaseRequest<T> request) {
mRequests.add(request);
}
public void execute() {
for (BaseRequest<? extends BaseObj> r : mRequests) {
// DO SOMETHING WITH r
}
}
}
class BaseRequest<T> {}
class BaseObj {}
Let's resolve the problem step-by-step. You want to be able to call
req.add(new BaseRequest<ExtObj1>());
req.add(new BaseRequest<ExtObj2>());
req.add(new BaseRequest<ExtObj3>());
where ExtObj[1|2|3] extends BaseObj. Given the List interface:
List<T> {
void add(T el);
}
we need to find a common supertype for BaseRequest<ExtObj1>, BaseRequest<ExtObj2> and BaseRequest<ExtObj3>. One supertype is BaseRequest<?> and another one is BaseRequest<? extends BaseObj>. I picked the second one because it's the most restrictive possible. You should know that in Java BaseRequest<ExtObj1> is not a subtype of BaseRequest<BaseObj> because generics are invariant.
Now that we have the right declaration for mRequests, finding the API for Executor.add() is straightforward. BTW, if the method body you need is really that simple, you don't even need the type parameter:
public void add(BaseRequest<? extends BaseObj> request) {
mRequests.add(request);
}
Warnings are not errors. Warnings are there so you check if you have an error because it may not be checked automatically. You should check it and then use the annotation to note that warning was already checked.
In your case it warns BaseRequest<T> is not equivalent to BaseRequest<BaseObj>.
Example:
public class NumberWrapper<N extends Number> {
private N value;
public void setValue(N value) {
this.value = value;
}
}
public class MainClazz {
private NumberWrapper<Integer> wrappedNumber = new NumberWrapper<Integer>();
public void run() {
Number n = externalSource.getNumber();
wrappedNumber.setValue(n); // <-- Error. What if getNumber returns a double?
}
}
You can have this error ir not depending on how you complete/integrate the code you are showing.
Can anybody explain me why I get an error only when creating B in the following code:
public class factory {
public <T> void createA(List<I<T>> c) {
A a = new A(c);//warning here
}
public <T> void createB(List<I<T>> c) {
B b = new B(c);//error here: The constructor B(List<I<T>>) is undefined
}
}
interface I<T> {
}
class B implements I<Integer> {
public B(List<I<?>> c) {
}
}
class A<T> implements I<T> {
public A(List<I<?>> c) {
}
}
B class is generic and A is not, but I have no idea why it matters in that case.
public B(List<I<?>> c) {
}
The ? is the unknown type. It is not a wild card for any other type. The compiler tells the truth, there is no constructor for the type List<I<T>> (T is not ?)
It doesn't really work for the other method too. You simply exchanged the compile error by a "unchecked conversion" warning. Because class A is parametized, you'd have to call it like that:
public <T> void createA(List<I<T>> c) {
A<T> a = new A<T>(c);
}
And voilĂ , say hello to the same error.
A is a generic class, which means that when you use A by itself, it is a raw type. When you use a raw type, you turn off all the generics for its methods and constructors. So for example, the following will compile:
A a = new A(new ArrayList<String>());
B is not a generic class, so using B by itself is not a raw type, and does not turn off generics.
I don't understand why this confuses the compiler. I'm using the generic type T to hold an object that's not related to the put and get methods. I always thought GenericClass and GenericClass<Object> were functionally identical, but I must be mistaken. When compiling the DoesntWork class I get incompatible types - required: String - found: Object. The Works class does what I expect. What's going on here?
public class GenericClass<T> {
public <V> void put(Class<V> key, V value) {
// put into map
}
public <V> V get(Class<V> key) {
// get from map
return null;
}
public static class DoesntWork {
public DoesntWork() {
GenericClass genericClass = new GenericClass();
String s = genericClass.get(String.class);
}
}
public static class Works {
public Works() {
GenericClass<Object> genericClass = new GenericClass<Object>();
String s = genericClass.get(String.class);
}
}
}
The thing about how raw types work -- generic types that you've left out the arguments for -- is that all generics for them and their methods are erased as well. So for a raw GenericClass, the get and put methods also lose their generics.
This is because when you work with a generic class without the extra type information you work with what is sometimes called the degenerate form of the class. The degenerate form has ALL generic type information removed.
Essentially - your class becomes something like:
public class GenericClass {
public void put(Class key, Object value) {
// put into map
}
public Object get(Class key) {
// get from map
return null;
}
...
}
The compiler response you are seeing is therefore expected behaviour.
It's mentioned in a Java Puzzlers.
If you don't use Java Generics, I believe it's not possible to have two methods in the same class that differ only in their return type.
In other words, this would be illegal:
public HappyEmotion foo(T emotion) {
// do something
}
public SadEmotion foo(T emotion) {
// do something else
}
Is the same true when overloading methods that return a generic type that may implement different interfaces, such as if the following two methods were present in the same class definition:
public <T extends Happy> T foo(T emotion) {
// do something
}
public <T extends Sad> T foo(T emotion) {
// do something else
}
Would this be illegal?
This is legal since the input parameter too differs based on the type..
For this reason, following is legal,
public <T extends Happy> T foo(T emotion) {
// do something
}
public <T extends Sad> T foo(T emotion) {
// do something else
}
But following is not,
public <T extends Happy> String foo(T emotion) {
// do something
}
public <T extends Happy> T foo(T emotion) {
// do something else
}
Thanks...
This ran just fine.
public class Main {
public static void main(String[] args){
Main main = new Main();
main.foo("hello");
main.foo(new Integer(5));
}
public <T extends String> T foo(T emotion) {
return (T) "test";
}
public <T extends Integer> T foo(T emotion) {
Integer integer = 5;
return (T) integer;
}
}
It will compile, but where you get into problems is if either Happy or Sad is a superclass of the other.
For instance, the following compiles:
public <T extends Number> T sayHi() {
System.out.println("number");
return null;
}
public <T extends Integer> T sayHi() {
System.out.println("integer");
return null;
}
However, you run into problems when you try to compile the following:
Integer test = sayHi();
In this case, you simply cannot add <Integer> to the front because Integer is still both a Number and an Integer.
However the following compiles
Double test2 = <Double>sayHi();
so basically as long as a Sad object cannot be an instance of a Happy object and visa versa, your code should work as long as you call it with the or in front of the method name.
You can use generic to distinguish the method in Java. The JVM doesn't see this type however provided the argument or return type is different it will still compile in the Sun/Oracle compiler. This doesn't compile for the IBM/eclipse compiler.
This shows you want is happening at the byte code level. http://vanillajava.blogspot.com/2011/02/with-generics-return-type-is-part-of.html
This is legal, as others have said. However, I want to point out what happens when types extend each other.
Let's say we have two interfaces (works for classes as well just change the signature):
interface Emotion {}
interface Happy extends Emotion {}
And two functions:
<T extends Emotion> void foo(T obj) {} // Referred as foo1
<T extends Happy> void foo(T obj) {} // Referred as foo2
If an object conforms to Emotion the JVM will choose foo1.
If an object conforms to Happy the JVM will choose foo2 and not foo1.
Notice the order of precedence. That is how the JVM resolves the ambiguity. However, this is only valid when you pass the generic parameter as an argument.