问题
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