递归泛型和流畅接口

9

tl;dr

试图实现一种分层流畅接口,使我可以同时合并节点子类以及独立的类,但是出现了类型参数超出其范围的错误。

Details

我正在尝试实现一种解决方案,以便我可以创建一些东西,比如可以这样做:

farm
    .animal()
        .cat()
            .meow()
            .findsHuman()
                .saysHello()
                .done()
            .done()
        .dog()
            .bark()
            .chacesCar()
            .findsHuman()
                .saysHello()
                .done()
            .done()
        .done()
    .human()
        .saysHello()
        .done();

同时还能够实现以下功能:

Human human = new Human()
    .saysHello()

我已经尝试了多种方法,但仍无法达到所描述的灵活性。

我目前的尝试使用以下类:

abstract class Base<T extends Base<T>>{

    private T parent;

    Base(){

    }

    Base( T parent ){
        this.parent = parent;
    }

    public T done() throws NullPointerException{
        if ( parent != null ){
            return (T) parent;
        }

        throw new NullPointerException();
    }   
}

class Farm<T extends Base<T>> extends Base{

    private Animal<Farm<T>> animal;
    private Human<Farm<T>> human;

    public Farm(){
        super();
        this.animal = new Animal( this );
        this.human = new Human( this );
    }

    public Animal<Farm> animal(){
        return this.animal;
    }

    public Human<Farm<T>> human(){
        return this.human;
    }
}

class Animal <T extends Base<T>> extends Base{

    private Cat<Animal<T>> cat;
    private Dog<Animal<T>> dog;

    public Animal(){
        super();
        init();
    }

    public Animal( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.cat = new Cat(this);
        this.dog = new Dog(this);
    }

    public Cat<Animal<T>> cat(){
        return cat;
    }

    public Dog<Animal<T>> dog(){
        return dog;
    }
}

class Human<T extends Base<T>> extends Base{

    public Human<T> saysHello(){
        System.out.println("human says hi");
        return this;
    }
}

class Cat <T extends Base<T>> extends Base{

    private Human<Cat> human;

    public Cat(){
        super();
        init();
    }

    public Cat( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.human = new Human();
    }

    public Cat<T> meow(){
        System.out.println("cat says meow");
        return this;
    }

    public Human<Cat<T>> findsHuman(){
        return this.human;
    }
}


class Dog <T extends Base<T>> extends Base{

    private Human<Dog> human;

    public Dog(){
        super();
        init();
    }

    public Dog( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.human = new Human();
    }


    public Dog<T> bark(){
        System.out.println("dog says woof");
        return this;
    }

    public Dog<T> chacesCar(){
        System.out.println("cat drinks milk");
        return this;
    }

    public Human<Dog<T>> findsHuman(){
        return this.human;
    }

}

我看到的错误通常是:

Animal.java:4: 类型参数Animal不在其范围内 private Cat cat; Animal.java:5: 类型参数Animal不在其范围内 private Dog dog;

应用于所有类似引用,并且也与我所需示例相关:

无法找到符号 符号:方法dog() 位置:类Base.dog()

我尝试使用以下解决方案来解决类似的问题,但没有成功,因此欢迎任何支持和帮助。
参考资料:

1
在我看来,你正在使用Java泛型来完成其不应完成的任务。这在C++中可能没问题,但是Java泛型并非如此。泛型是有害的,这就是其中一个例子。当然,除了你所粘贴的“编程艺术”之外,肯定还有其他软件工程解决方案可以满足你的需求。当答案变得如此复杂时,很可能出现了一些问题。 - Alfonso Nishikawa
我想我找到了一个解决方案。我已相应地更新了我的回答。 - morpheus05
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - SS44
@morpheus05 谢谢您的更新,我将仔细查看所有答案,看看是否能找到最好的解决方法。 - SS44
7个回答

4
下面的代码似乎可以正常工作,不需要任何@SuppressWarnings。关键概念是要理解你的T参数实际上是你的对象父类的类,但是T的父类可以是任何东西。因此,你想要的是T extends Base<?>而不是T extends Base<T>
输出结果为:
cat says meow
human says hi
dog says woof
cat drinks milk
human says hi
human says hi

我认为这是正确的,尽管您可能需要更改您的Dog.chacesCar()方法,以便它不会输出cat drinks milk!而且应该是chases而不是chaces

希望这能帮到您!

abstract class Base<T extends Base<?>> {

    private final T parent;

    Base() {
        this.parent = null;
    }

    Base(T parent) {
        this.parent = parent;
    }

    public T done() throws NullPointerException {
        if (parent != null) {
            return parent;
        }

        throw new NullPointerException();
    }
}

class Farm<T extends Base<?>> extends Base<T> {

    private final Animal<Farm<T>> animal;
    private final Human<Farm<T>> human;

    public Farm() {
        super();
        this.animal = new Animal<>(this);
        this.human = new Human<>(this);
    }

    public Animal<Farm<T>> animal() {
        return this.animal;
    }

    public Human<Farm<T>> human() {
        return this.human;
    }
}

class Animal<T extends Base<?>> extends Base<T> {

    private Cat<Animal<T>> cat;
    private Dog<Animal<T>> dog;

    public Animal() {
        super();
        init();
    }

    public Animal(T parent) {
        super(parent);
        init();
    }

    private void init() {
        this.cat = new Cat<>(this);
        this.dog = new Dog<>(this);
    }

    public Cat<Animal<T>> cat() {
        return cat;
    }

    public Dog<Animal<T>> dog() {
        return dog;
    }
}

class Human<T extends Base<?>> extends Base<T> {
    public Human() {
        super();
    }

    public Human(T parent) {
        super(parent);
    }

    public Human<T> saysHello() {
        System.out.println("human says hi");
        return this;
    }
}

class Cat<T extends Base<?>> extends Base<T> {

    private Human<Cat<T>> human;

    public Cat() {
        super();
        init();
    }

    public Cat(T parent) {
        super(parent);
        init();
    }

    private void init() {
        this.human = new Human<>(this);
    }

    public Cat<T> meow() {
        System.out.println("cat says meow");
        return this;
    }

    public Human<Cat<T>> findsHuman() {
        return this.human;
    }
}

class Dog<T extends Base<?>> extends Base<T> {

    private Human<Dog<T>> human;

    public Dog() {
        super();
        init();
    }

    public Dog(T parent) {
        super(parent);
        init();
    }

    private void init() {
        this.human = new Human<>(this);
    }

    public Dog<T> bark() {
        System.out.println("dog says woof");
        return this;
    }

    public Dog<T> chacesCar() {
        System.out.println("cat drinks milk");
        return this;
    }

    public Human<Dog<T>> findsHuman() {
        return this.human;
    }

}

测试代码:

public static void main(String[] args) {
    Farm<?> farm = new Farm<>();
    farm
        .animal()
            .cat()
                .meow()
                .findsHuman()
                    .saysHello()
                    .done()
                .done()
            .dog()
                .bark()
                .chacesCar()
                .findsHuman()
                    .saysHello()
                    .done()
                .done()
            .done()
        .human()
            .saysHello()
            .done();

    Human human = new Human()
            .saysHello();
}

请问您能否分享一下您的主方法?我在尝试运行时遇到了“找不到符号”的错误。 - SS44
我已将其添加到答案中(它几乎是您发布的确切测试代码)。 - Constantinos
它找不到哪个符号? - Constantinos
谢谢,看起来我的问题是我在没有 <?> 的情况下运行了 Farm,这导致 done 返回 Base。这似乎解决了我的问题并给了我所需的方向。 - SS44
其他回答中关于SuppressWarnings的事情是为了解决类层次结构和模板方法的问题。例如,您必须在CatDog中重新实现findsHuman()而不是在Animal类中实现它。尽管如此,赞一个从另一方面考虑问题的方式。 - Pavel Horal
1
更明确地说,您已经改变了通用参数的语义,从“作为叶子子类型”变为“作为拥有构建器类型”(这就是我所说的“从另一个角度来看”的方法)。 - Pavel Horal

3
我想到的最好的方法如下:

我所想到的最佳方案是:

new Animal()
    .cat()
      .meow()
      .findsHuman()
        .<Cat>done()
      .<Animal>done()
    .dog()
      .bark()
        .findHuman()
          .<Dog>done()
      .done();

使用以下基类:

public abstract class Base<T extends Base<T>>{

  private Base<?> backRef;

  public Base() {}

  public Base(Base<?> backRef) {
    this.backRef = backRef;
 }

  @SuppressWarnings("unchecked")
    protected T self() {
    return (T)this;
  }

  @SuppressWarnings("unchecked")
  public <U extends Base<U>> U done() {
    return (U)backRef;
  }
}

如果您将backRef声明为T类型,则其他类不允许,因为它们不是彼此的子类,因此您必须指定不同的类型,但由于这种类型是上下文相关的(有时是Cat,有时是Dog),我认为没有别的选择,只能通过传递提示来解决。
我找到了一个解决方案:
new Animal()
    .cat()
      .meow()
      .findsHuman()
        .done()
      .done()
    .dog()
      .bark()
        .findHuman()
          .done()
    .done();



public abstract class Base<T extends Base<T,P>, P>{

  private P backRef;
  public Base() {}

  public Base(P backRef) {
    this.backRef = backRef;
  }

  @SuppressWarnings("unchecked")
  protected T self() {
    return (T)this;
  }

  public P done() {
    return backRef;
 }
}

有人建议,我们为父类添加一个额外的类型。

现在看一下基类:

public final class Cat extends Base<Cat, Animal>{

  public Cat() {}

  public Cat(Animal backRef) {
    super(backRef);
  }

  public Cat meow() {
    System.out.println("Meeeoooww");
    return self();
  }

  public Human<Cat> findsHuman() {
    return new Human<Cat>(this);
  }
}

正如您所看到的,Cat明确指定了它应该使用哪种基本类型。而对于人类来说,则可能会根据上下文而改变类型:

public final class Human<P> extends Base<Human<P>, P> {

  public Human() {}

  public Human(P backRef) {
    super(backRef);
  }

人类指定了一个额外的通用参数,调用者(猫、狗)在它们的findHuman()方法中指定。


这是类型提示的相当有趣的用法(尽管其类型安全性不是很好)。+1 - Pavel Horal
恭喜你。我希望我永远不需要像这样的东西 ;) 但是做得很好。就我个人而言,我无法理解这个解决方案。 - Alfonso Nishikawa

2

这是我们在一个项目中所做的事情:

public abstract class Parent<T extends Parent<T>> {

    /**
     * Get {@code this} casted to its subclass.
     */
    @SuppressWarnings("unchecked")
    protected final T self() {
        return (T) this;
    }

    public T foo() {
        // ... some logic
        return self();
    }

    // ... other parent methods

}

public class Child extends Parent<Child> {

    public Child bar() {
        // ... some logic
        return self();
    }

    // ... other child methods

}

允许子类拥有自己的子类将会是这样的:

注:此处为IT技术相关内容,无需解释,请保留HTML标签。

public class Child<T extends Child<T>> extends Parent<T> {

    public T bar() {
        // ... some logic
        return self();
    }

}

1
self 的实现应该是 final 的,这样就不会有其他人搞乱它。 - SpaceTrucker
这对于 OtherChild extends Parent<Child> 不起作用-self() 中的 (T) this 将失败。 - andrius.velykis

1

在这行中:

class Farm<T extends Base<T>>

编译器将第二个类型参数视为具体类。例如,如果您用以下代码替换该行:
class Farm<T extends Base<Double>>

'Double'是一个具体类。当编译器扫描它时,无法区分您的T和Double,并将它们都视为具体类,而不是类型参数。让编译器知道T是类型参数的唯一方法是这样:

class Farm<T extends Base<T>, T>

我希望这个回答(或者至少是相关的)能够回答您的问题。
编辑 当我在输入时,帖子已经被编辑了,所以我想这个回答不再相关了。

1
您的问题在于done方法应该返回父类,但是父类不一定是T而只是一个Base。另一个问题是无论是哪个类,done方法都应该始终返回相同的类。但以下是您提出的类的微小变化。首先是Base,声明其具体类和具体父类:
abstract class Base<T extends Base<T, P>, P>{

    private P parent;

    Base(){

    }

    Base( P parent ){
        this.parent = parent;
    }

    public P done() throws NullPointerException{
        if ( parent != null ){
            return parent;
        }

        throw new NullPointerException();
    }   
}

完成上述操作后,派生的具体类将变为:
class Farm extends Base<Farm, Object>{

    private Animal animal;
    private Human human;

    public Farm(){
        super();
        this.animal = new Animal( this );
        this.human = new Human( this );
    }

    public Animal animal(){
        return this.animal;
    }

    public Human human(){
        return this.human;
    }
}

class Animal extends Base<Animal, Farm>{

    private Cat cat;
    private Dog dog;

    public Animal(){
        super();
        init();
    }

    public Animal( Farm parent ){
        super( parent );
        init();
    }

    private void init(){
        this.cat = new Cat(this);
        this.dog = new Dog(this);
    }

    public Cat cat(){
        return cat;
    }

    public Dog dog(){
        return dog;
    }
}

class Human extends Base<Human, Farm>{

    public Human() {

    }

    public Human(Farm farm) {
        super(farm);
    }

    public Human saysHello(){
        System.out.println("human says hi");
        return this;
    }

}

class CatOrDog extends Base<Cat, Animal>{

    protected Human human;

    public CatOrDog(){
        super();
        init(null);
    }

    public CatOrDog( Animal parent ){
        super( parent );
        init(parent);
    }

    private void init(Animal parent){
        Animal parent = done();
        Farm farm = (parent == null) ? null : parent.done();
        this.human = new Human(farm);
    }

    public Human findsHuman(){
        return this.human;
    }
}


class Cat extends CatOrDog{

    public Cat(){
        super();
    }

    public Cat( Animal parent ){
        super( parent );
    }

    public Cat meow(){
        System.out.println("cat says meow");
        return this;
    }
}


class Dog extends CatOrDog {

    public Dog(){
        super();
    }

    public Dog( Animal parent ){
        super( parent );
    }

    public Dog bark(){
        System.out.println("dog says woof");
        return this;
    }

    public Dog chacesCar(){
        System.out.println("cat drinks milk");
        return this;
    }
}

有了这个,我就可以毫无错误或警告地编写:

Farm farm = new Farm();
farm.animal()
    .cat()
        .meow()
        .findsHuman()
            .saysHello()
            .done()
        .animal()
    .dog()
        .bark()
        .chacesCar()
        .findsHuman()
            .saysHello()
            .done()
        .animal()
    .done()
.human()
    .saysHello()
    .done();

请注意,我不得不将两个done调用替换为animals调用。

编辑:

我添加了一个新的类CatOrDog来因式分解Human的处理。由于Human的父类是Farm,如果存在正确的父类,我将使用正确的父类初始化新的human。这样,不仅上述源代码可以编译而不出现错误或警告,而且它也可以在没有任何问题的情况下运行并打印:

cat says meow
human says hi
dog says woof
cat drinks milk
human says hi
human says hi

1

你也可以玩转接口,这样就可以模拟多重继承。虽然有点啰嗦,但没有危险的强制类型转换,我认为非常易于理解。


定义可用的方法:
public interface AnimalIn {
    AnimalOut animal();
}

public interface CatIn {
    CatOut cat();
}

public interface MeowIn {
    CatOut meow();
}

public interface DogIn {
    DogOut dog();
}

public interface BarkIn {
    DogOut bark();
}

public interface ChacesCarIn {
    DogOut chacesCar();
}

public interface FindsHumanIn<T> {
    HumanOut<T> findsHuman();
}

public interface HumanIn {
    HumanOut<FarmOut> human();
}

public interface SaysHelloIn<T> {
    HumanOut<T> saysHello();
}

public interface DoneIn<T> {
    T done();
}

您可能需要在接口中拥有多个方法,但我尚未遇到这种需求。例如,如果您必须有两种meow

public interface MeowIn {
    CatOut meowForFood();
    CatOut meowForMilk();
    CatOut meowForStrokes();
}

定义输出类型:

Farm 提供 AnimalHuman

public interface FarmOut extends AnimalIn, HumanIn {
    // no specific methods
}

"Animal" 提供 "Cat"、"Dog" 或 "Done":
public interface AnimalOut extends CatIn, DogIn, DoneIn<FarmOut> {
    // no specific methods
}

"Cat" 提供 "Meow"、"FindsHuman" 或 "Done":
public interface CatOut extends MeowIn, FindsHumanIn<CatOut>, DoneIn<AnimalOut> {
    // no specific methods
}

Dog 提供 Bark, ChacesCar, FindsHumanDone:

(保留HTML)
public interface DogOut extends BarkIn, ChacesCarIn, FindsHumanIn<DogOut>, DoneIn<AnimalOut> {
    // no specific methods
}

Human提供了SayHelloDone

public interface HumanOut<T> extends SaysHelloIn<T>, DoneIn<T> {
    // no specific methods
}

简单实现 *Out 接口:
public class Farm implements FarmOut {

    @Override
    public AnimalOut animal() {
        return new Animal(this);
    }

    @Override
    public HumanOut<FarmOut> human() {
        return new Human<FarmOut>(this);
    }

}

public class Animal implements AnimalOut {

    private FarmOut chain;

    public Animal(FarmOut chain) {
        this.chain = chain;
    }

    @Override
    public CatOut cat() {
        return new Cat(this);
    }

    @Override
    public DogOut dog() {
        return new Dog(this);
    }

    @Override
    public FarmOut done() {
        return chain;
    }

}

public class Dog implements DogOut {

    private AnimalOut chain;

    public Dog(AnimalOut chain) {
        this.chain = chain;
    }

    @Override
    public DogOut bark() {
        System.out.println("bark");
        return this;
    }

    @Override
    public DogOut chacesCar() {
        System.out.println("chaces car");
        return this;
    }

    @Override
    public HumanOut<DogOut> findsHuman() {
        return new Human<DogOut>(this);
    }

    @Override
    public AnimalOut done() {
        return chain;
    }

}

public class Cat implements CatOut {

    private AnimalOut chain;

    public Cat(AnimalOut chain) {
        this.chain = chain;
    }

    @Override
    public CatOut meow() {
        System.out.println("meow");
        return this;
    }

    @Override
    public HumanOut<CatOut> findsHuman() {
        return new Human<CatOut>(this);
    }

    @Override
    public AnimalOut done() {
        return chain;
    }

}

public class Human<T> implements HumanOut<T> {

    private T chain;

    public Human(T chain) {
        this.chain = chain;
    }

    @Override
    public HumanOut<T> saysHello() {
        System.out.println("hello");
        return this;
    }

    @Override
    public T done() {
        return chain;
    }

}

这些实现即使没有接口也可以工作:删除implements *Out@Override,并将任何*Out替换为*(例如,AnimalOut替换为Animal)。话虽如此,使用接口更容易维护:只需更新它们并修复编译错误即可。使用接口还更容易找到DSL解决方案(正如您所看到的),有时它们是必不可少的。

演示:

new Farm()
.animal()
    .cat()
        .meow()
        .findsHuman()
            .saysHello()
            .done()
        .done()
    .dog()
        .bark()
        .chacesCar()
        .findsHuman()
            .saysHello()
            .done()
        .done()
    .done()
.human()
    .saysHello()
    .done();

输出:

meow
hello
bark
chaces car
hello
hello

0

没有一种“安全”的方法来做这件事,但是以下代码应该可以编译:

class Dog extends Base{

 <T extends Dog> T bark(){
    return (T) this;
 } 

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接