How do I fix this Java Generics wildcard/abstract ambiguity problem? - java

The code below is a simplified version of the pattern my project is using. The standard pattern we use is to have a Writer for each object type. For the subtypes of one abstract type (in this example Animal), I'd like an enum to serve as a lookup for the correct writer.
abstract class Writer<T> {
abstract void write(T value);
}
abstract class Animal {
abstract AnimalType getType();
}
class Cat extends Animal {
AnimalType getType() { return AnimalType.CAT; }
}
class CatWriter extends Writer<Cat> {
void write(Cat value) { }
}
// The AnimalType stores a reference to the correct writer for the Animal subclass
enum AnimalType {
CAT(new CatWriter());
Writer<? extends Animal> writer;
Writer writerThatWorksWithWarning;
Writer<Animal> writerThatWorksButCantBeAssigned;
AnimalType(Writer<? extends Animal> writer) {
this.writerThatWorksWithWarning = writer;
this.writer = writer;
// ERROR: Incompatible Types
this.writerThatWorksButCantBeAssigned = writer;
}
}
Sample use case:
class Test {
public static void main(String... args) {
Animal value = new Cat();
// ERROR: write (capture<? extends Animal) in Writer cannot be applied to (Animal)
value.getType().writer.write(value);
// WARNING: Unchecked call
value.getType().writerThatWorksWithWarning.write(value);
// This line works fine here - but can't be assigned above
value.getType().writerThatWorksButCantBeAssigned.write(value);
}
}
I think that my problem is similar to the problem in this question: Java Generics with wildcard, however I can't tell how to solve it.
I've put the inline errors and warnings I get in the comments.
Any ideas?

I think the issue here is that you can't represent a type hierarchy with an enum, so there's no way to tell the type system that for enum { CAT, DOG; } the CAT should type to CAT extends Animal and the DOG types to DOG extends Animal. So But since you have a class hierarchy already, why not use that? i.e. something like :
public interface Writer<T> {
public void write(T t);
}
public abstract class Animal<T extends Animal<T>> {
public abstract Writer<T> getWriter()...
}
public class Cat extends Animal<Cat> {
#Override
public Writer<Cat> getWriter()...
}
It seems to me that what you're really using the enum for is something more like a hashmap of <Class, Writer<Class>>, sort of a built in singleton. You can do this, but only by hiding the types.

I would have animals unaware of writers. they are animals after all.
You can have a Map<Class,Writer>, and for each entry in it, you maintain that the key Class<X> and value Writer<X> are about the same type X. We can't express that relation in types, so casts must be done at some places. If looking up fails for a type (say Cat), try looking up again with its super types (Animal)
A type safe public API can be designed like
static public <T> void registerWriter(Class<T> type, Writer<T> writer)
static public <T> Writer<? super T> getWriter(Class<T> type)
Suppose we don't have a Writer directly mapped to Cat, but we do have a Writer<Animal> for Animal, then that writer will be returned for Cat.class. That is ok, because that writer does accept all animals.
This convenient method can be provided:
static public static void write(Object obj)
from the type of the object, a suitable writer can be found, and the writer will accept the object.

Try this instead,
enum AnimalType {
CAT(new CatWriter());
private Writer<? extends Animal> writer;
AnimalType(Writer<? extends Animal> writer) {
this.writer = writer;
}
public Writer<Animal> getWriter() {
return (Writer<Animal>)writer;
}
}
Moreover, I am not sure what are you up to. But I believe that Visitor pattern will come handy in this case.
Problem with the above solution, the code below will break the thing.
Animal cat = new Cat();
Animal dog = new Dog();
cat.getType().getWriter().write(cat);
// java.lang.ClassCastException in the write() method's argument
cat.getType().getWriter().write(dog);

Related

Java generics code cannot compile

I have the following code that give me trouble compiling.
Give me this error
Error:(20, 22) java: incompatible types: com.company.Main.Impl cannot be converted to T
I only need that interface to work in this function, and I don't want to change the function body much.
Why doesn't this work?
and
How could I make this work?
package com.company;
public class Main {
class Impl implements someinterface, anotherinterface{
#Override
public Integer getInteger() {
return 0;
}
}
class BigObject{
public Impl get(){
return new Impl();
}
}
private <T extends someinterface & anotherinterface> Integer myfunc(BigObject bg){
T xy = bg.get(); // this line will not compile????
if (xy.isOK()) // from someinterface
return xy.getInteger(); // from anotherinterface
return null;
}
public static void main(String[] args) {
// write your code here
}
}
It won't compile because in Java, generics are invariant, see related post.
With the following line of code:
<T extends SomeInterface> Integer myfunc(BigObject bg) { ... }
you are saying that T is something that is some kind of SomeInterface, or, more precisely, a certain type that is a subtype of SomeInterface. The compiler complains about T xy = bg.get(), because bg.get() returns a certain subtype of T, but that type may or may not be the same as Impl.
As an analogy, you are saying something like this:
class Cat extends Animal { }
class AnimalObj {
public Cat get() {
return new Cat();
}
}
private <T extends Animal> Integer myfunc(AnimalObj bg) {
T xy = bg.get();
...
}
T could be a Cat, but it could also be a Dog. Who knows. So that's why the compiler complains.
If you don't care about the subtype, you should drop generics, and write this instead:
private Integer myfunc(AnimalObj bg) {
Animal xy = bg.get();
...
}
Since myFunc accepts a BigObject, which is able to deliver a concrete Impl, you could just replace <T extends someinterface & anotherinterface> by Impl.
Read more
Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?
Generics – The Java Tutorials
Lets change the myfunc a little bit so that it will return the generic type T:
private static <T extends someinterface & anotherinterface> T myfunc(BigObject bg){
return bg.get();
}
Now you can call it like this:
Impl i1 = myfunc( new BigObject() );
and like this:
Impl2 i2 = myfunc( new BigObject() );
But if BigObject.get would have only returned Impl you would have error with second call that expected Impl2.

Java class information is lost when extend class with more generics

This seems to be a compiler issue, or maybe this is there by design.
ClassA is a class with two generics. ClassB will extend ClassA by providing one solid generic type, but still expose another one.
In the following example, E will be passed in type that will extend ClassA, so when any method is called, then returned type will still be the subtype which enables to call the subtype method if needed. The motivation behinds this is to do a builder pattern, e.g.
ClassB b = new ClassB<String>().m1().m2().m3().m4()......
public class ClassA<E, T> {
public final E e;
public final T t;
public ClassA(T t) {
this.e = (E)this;
this.t = t;
}
public E printA() {
System.out.println("AAAAAA");
return e;
}
}
public class ClassB<T> extends ClassA<ClassB, T> {
public ClassB(T t) {
super(t);
}
public ClassB printB() {
System.out.println("BBBBBB");
return this;
}
public static void main(String[] args) {
ClassB<String> classB = new ClassB<>("Hello World");
// classB.printA().printA().printA(); // This will fail, after the second printA() return Object type instance instead of ClassB.
System.out.println(classB.printA().printA().getClass()); // This will print "class ClassB", so the class information it still there.
((ClassB)classB.printA().printA()).printA(); // Casting the instance to ClassB again will make it work again.
}
}
The problem is that when you call the method two times, the return instance type will be lost, so it will be an Object type, and cannot call any ClassA/B method without casting it. This is super weird.
Any idea?
Your class ClassB is a generic one, but you are opting out of generics when not providing a type parameter.
And you are doing exactly that here
public class ClassB<T> extends ClassA<ClassB, T>
^^^^^^
and here
public ClassB printB()
^^^^^^
So simply change these lines to
public class ClassB<T> extends ClassA<ClassB<T>, T>
^^^
and
public ClassB<T> printB()
^^^
Then it will work.

Why a generic method of an interface can be implemented as non-generic in Java?

Let's say we have a few test interfaces/classes like this:
abstract class Plant {
public abstract String getName();
}
interface Eatable { }
class Apple extends Plant implements Eatable {
#Override
public String getName() {
return "Apple";
}
}
class Rose extends Plant {
#Override
public String getName() {
return "Rose";
}
}
interface Animal {
<T extends Plant & Eatable> void eat(T plant);
}
You can see Animal.eat is a generic method with constraints. Now I have my Human class like this:
class Human implements Animal {
#Override
public void eat(Plant plant) {
}
}
which compiles fine. You can see Human.eat is less constrained than Animal.eat because the Eatable interface is lost.
Q1: Why doesn't the compiler complain about this inconsistency?
Q2: If Plant&Eatable downgrades to Plant is acceptable for the compiler, why it complains on eat(Object plant)?
Lesson: Generics by Gilad Bracha
according to him
public static <T extends Object & Comparable<? super T>> T max(Collection<T> coll)
This is an example of giving multiple bounds for a type parameter,
using the syntax T1 & T2 ... & Tn. A type variable with multiple
bounds is known to be a subtype of all of the types listed in the
bound. When a multiple bound is used, the first type mentioned in the
bound is used as the erasure of the type variable.
so your example <T extends Plant & Eatable> void eat(T plant); will be erased to void eat(Plant plant); so when you override it the compiler doesn't complain
Ahmed answer is right,by the way,if you wanna put constraint the implementation of the interface of Animal, you should declare it as this:
interface Animal<T extends Plant & Eatable> {
void eat(T plant);
}
Then, if you implements the Animal interface without providing the type information, the compiler will using the least surprise policy to infer the T as a Plant type. But if you provide the necessary type information, the compiler works fine.
class Human implements Animal<Rose> // won't compile
class Human implements Animal<Apple> // compile

Type safety in Generics

I have the below hierarchy of classes which just implement the imaginery functionality of encoding and decoding the animals from the given bytearrays.
public abstract class Animal {
}
class Tiger extends Animal{
private String name;
public void setName() {
}
public String getName() {
return this.name;
}
}
abstract class AnimalTransformer {
public static <T> T decodeAnimalFromBytes(byte[] animalInBytes) {
return null;
}
public static byte[] encodeAnimalInBytes(Animal animal) {
return null;
}
}
class TigerTransformer extends AnimalTransformer{
public static Tiger decodeAnimalFromBytes(byte[] animalInBytes) {
return new Tiger();
}
public static byte[] encodeAnimalinBytes(Tiger tiger) {
return new byte[0];
}
}
On overriding method from the AnimalTransformer abstract class in the TigerTransformer class which extends AnimalTransformer , i get the following warning
Type safety: The return type Tiger for decodeAnimalFromBytes(byte[]) from the type
TigerTransformer needs unchecked conversion to conform to T
from the type AnimalTransformer
I understand the cause of this warning but unfortunately i am not able to solve it as i am new to generics. Can someone briefly explain how can this warning be remedied?
Note that there's no use in making the methods of AnimalTransformer static. Static methods don't override each other by inheritance. Furthermore, you are not binding the data type the transformer works on to the type of the decoded object (e.g., TigerTransformer can return Horse objects).
I would do the following instead, which I consider to be more type safe:
abstract class AnimalTransformer <T> {
public abstract T decodeAnimalFromBytes(byte[] animalInBytes);
public abstract byte[] encodeAnimalInBytes(T animal);
}
First of all, don't try to 'override' with static methods. It does not work the way you think it does - the 'overridden' static methods can still be accessed from the subclass in devious (accidental) ways. If you want overriding behavior, use local methods.
The warning is being generated because the Tiger method signature is not compatible with what the super-class method promises - that any class <T> chosen by the client can be decoded to, which is a very big, unfulfillable promise.
A better method would be:
abstract class AnimalTransformer<T extends Animal> {
public T decodeAnimalFromBytes(byte[] animalInBytes) {
return null;
}
public static byte[] encodeAnimalInBytes(T animal) {
return null;
}
}
class TigerTransformer extends AnimalTransformer<Tiger> {
public Tiger decodeAnimalFromBytes(byte[] animalInBytes) {
return new Tiger();
}
public byte[] encodeAnimalinBytes(Tiger tiger) {
return new byte[0];
}
}
This represents what you are trying to model more clearly - an AnimalTransformer provides transformation methods for some subclass of Animal, and it is up to the subclass, or anonymous implementation, to clarify which.

Generics Subtyping issue in java

I am trying to learn Subtyping in Java and I am not an better person in generics so I am getting this issue or doubt-
import java.util.ArrayList;
import java.util.Collection;
interface Animal<T>{}
class Lion implements Animal<Lion>{}
class Butterfly implements Animal<Butterfly>{}
class Cage<T>{
public <T> void addAnimal(T t){
}
}
interface CageAnimal<E> extends Collection<E>{}
public class SubType<T> {
public <T> SubType() {
Lion lion = new Lion();
Butterfly butterfly = new Butterfly();
/**
* **Here inside Lion cage, we added Butterfly : WRONG**
*/
Cage<Lion> cageLion = new Cage<Lion>();
cageLion.addAnimal(lion);
cageLion.addAnimal(butterfly);
CageAnimal<Lion> cageAnimalLion = (CageAnimal<Lion>) new ArrayList<Lion>();
cageAnimalLion.add(lion);
//cageAnimalLion.add(butterfly);//Butterfly is Not Supposed to add here as it is the cage of Lion
}
}
In the above example when I declare Cage , why I am able to add Butterfly and in the Same case when I made CageAnimal type, I am not able to add any Buttefly
Cage<Lion> cageLion = new Cage<Lion>();
cageLion.addAnimal(lion);
cageLion.addAnimal(butterfly);
and in case of Cage
Cage<Animal> cageAnimalLion = new Cage<Lion>();
cageAnimalLion.addAnimal(lion);
cageAnimalLion.addAnimal(butterfly); //Throwing Compile Error
This line
public <T> void addAnimal(T t){
should probably be
public void addAnimal(T t){
Declare Cage class like this:
class Cage<T extends Animal> {
public void addAnimal(T t) { ... }
}
If you declare the addAnimal method in the following way...
public void <T> addAnimal(T t)
... you are "hiding" the T type parameter with a different type parameter with the same name. It is the same as if you declared the method like this:
class Cage<T extends Animal> {
public void <X> addAnimal(X t) { ... }
}
...which is obviously not doing its job. On the other hand, in the first version I wrote, both the T in declaration of the class and the method are the same.
Moreover declaring <T extends Animal> bound ensures that the cage can only be of type that extends an Animal, i.e. Cage<Lion>, Cage<Butterfly>, but Cage<String> is illegal.
And of course, you cannot cast an ArrayList to CageAnimal, that will fail at runtime with a ClassCastException, because ArrayList in not a subtype of CageAnimal.
Because CageAnimal and Cage are very different things. Looks how you've defined generic parameter for Cage:
public <T> void addAnimal(T t){
}
This <T> you put on the method, means that method has its own generic parameter, different from the one you've defined in class. If you remove it from method signature it will use generic parameter of the class.
E.g.
public void addAnimal(T t)
Your problem is that fundamentally your Cage will accept any T, and therefore any Animal. The various T's don't all refer to the same value of T, they're variables local to the class or method.
What you could write is something like this:
public class Cage<T> {
public void addAnimal(Animal<T> caged) {
}
}
Now you will at least get compiler errors in the common case of:
Cage<Lion> c=new Cage<Lion>();
c.add(new Butterfly()); // should error AFAIK
However it will be reduced to a warning in case of:
Animal butterfly=new Butterfly();
Cage<Lion> c=new Cage<Lion>();
c.add(butterfly); // warning about raw types... IIRC
Because, fundamentally Cage will still accept any Animal.
EDIT: Note that the earlier mentioned answer of removing the <T> local to the addAnimal method will work better for this purpose.
By declaring public <T> void addAnimal(T t) you're parameterising the method as well as the class Cage. This T has no relation to the T in Cage<T>.
You can either have:
class Cage<T extends Animal<T>> {
public void addAnimal(T animal) {
}
}
or, if you want the Animal returned then have:
class Cage<T extends Animal<T>> {
public T addAnimal(T animal) {
}
}
class Cage<T>{
public <T> void addAnimal(T t){
}
}
The Cage class has a generic method addAnimal. The generic type associated with the method causes the generic type associated with the class to be ignored and the type of the parameter to be used as the generic type for the method.
Try executing the following example to see what is happening:
public class TestCage {
/**
* #param args
*/
public static void main(String[] args) {
Cage<String> cage1 = new Cage<String>();
cage1.addAnimal(new String("test1"));
cage1.addAnimal(new Integer(1));
cage1.addAnimal2(new String("test2"));
//cage1.addAnimal2(new Integer(1)); //Uncomment to throw error
}
}
class Cage<T>{
public <T> void addAnimal(T t){
System.out.println("T: " + t.getClass().getName());
}
public void addAnimal2(T t){
System.out.println("T: " + t.getClass().getName());
}
}
In summary, by adding a generic method to the class, the generic type parameter of the class is ignored and the type of the parameter passed into the method is used as the generic type parameter of the method.
Try to take the <T> out of public <T> void addAnimal(T t).

Categories