Chaining, returning base objects, and type mismatch to extended classes

为君一笑 提交于 2019-12-11 20:23:52

问题


I have come across a class like this. It holds a "with" method that lets people chain things together.

public class Bear {
    protected List<String> names = new ArrayList<String>();
    protected List<String> foods = new ArrayList<String>();

    public Bear withName(String name) {
        names.add(name);
        return this;
    }

    public Bear withFood(String food) {
        foods.add(food);
        return this;
    }
}

// ...somewhere else
Bear b = new Bear().withName("jake").withName("fish");

I found two classes that shared 90% of the same code. So, I created a base class between them, and transferred the 25 or so "with" methods to it (with the member variables and all.) Like so:

public abstract class Animal {
    protected List<String> names = new ArrayList<String>();

    public Animal withName(String name) {
        names.add(name);
        return this;
    }
}

public class Bear extends Animal {
    protected List<String> foods = new ArrayList<String>();

    public Bear withFood(String food) {
        foods.add(food);
        return this;
    }
}

However, this now breaks everything (And there is a lot of places that use this with design for these two classes).

Bear b = new Bear().withName("jake"); // Breaks
bear b2 = new Bear().withFood("fish"); // Fine

The error given:

Type mismatch: cannot convert from Animal to Bear

Apparently, when you return the base class this, it's returning a Bear type, and does not do any sort of automatic conversion.

What are my options to solve/bypass this issue?


回答1:


You're looking for the CRTP:

public abstract class Animal<T extends Animal<T>> {
    protected List<String> names = new ArrayList<String>();

    public T withName(String name) {
        names.add(name);
        return (T)this;
    }
}

This will give an unavoidable unchecked cast warning, since the type system cannot prevent you from writing class Cat extends Animal<Dog> {} class Dog extends Animal<Dog>.

If you have multiple builder methods in the base class, you can isolate the warning by writing private T returnThis() { return (T)this; }.




回答2:


Your Bear class extends Animal and therefore inherits the withName method which is declared as

public Animal withName(String name) ...

Your method invocation is validated at compile time

Bear b = new Bear().withName("jake");  // Breaks

and all the compiler knows is that Animal#withName(String) returns an Animal. It cannot know that at run-time you are actually returning a Bear. So it cannot let you assign that value to a Bear.

You can do what SLaks suggests or override the method in Bear class and change its return type to Bear.

@Override
public Bear withName(String name) {
    names.add(name); // or invoke super method
    return this;
}

If you call the method on a reference of type Bear, the method will have a return type of Bear. See here for why this works.



来源:https://stackoverflow.com/questions/20638749/chaining-returning-base-objects-and-type-mismatch-to-extended-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!