已给予:

interface Parent1
{
    default Parent1 apply(Consumer<Parent1> consumer) {
        consumer.accept(this);
        return this;
    }
}

interface Parent2
{
    default Parent2 apply(Consumer<Parent2> consumer) {
        consumer.accept(this);
        return this;
    }
}

有没有办法声明interface Child extends Parent1, Parent2而不触发以下编译器错误?'apply(Consumer<? extends Parent2>)' in 'Scratch.Parent2' clashes with 'apply(Consumer<? extends Parent1>)' in 'Scratch.Parent1'; both methods have same erasure, yet neither overrides the other

我理解这个错误的含义,以及为什么类型擦除会导致这个问题.但是,有没有办法按照以下路由手动创建桥接方法:

interface Child extends Parent1, Parent2 {
    @Override
    default Child apply(Consumer<Child> consumer) {
        consumer.accept(this);
        return this;
    }

    @Override
    default Parent1 apply(Consumer<Parent1> consumer) {
        return apply((Consumer<Child>) consumer);
    }

    @Override
    default Parent2 apply(Consumer<Parent2> consumer) {
        return apply((Consumer<Child>) consumer);
    }
}

那么,擦除之后最终会生成以下编译代码吗?

interface Child extends Parent1, Parent2 {
    @Override
    default Child apply(Consumer consumer) {
        consumer.accept(this);
        return this;
    }
}

有没有办法解决这个编译器错误,或者不可能创建一个扩展Parent1和Parent2的接口?

PS:使用通配符(例如Consumer<? extends Parent1>Consumer<? extends Parent2>)也无济于事.

UPDATE:

  • 存在多个不同类型的子项,因此父项不能引用该子项类型.
  • 以下是我目前使用的解决方法:

我已经将所有冲突的方法移到了第三个接口中(比如Parent3):

interface Parent3<S>
{
  default S apply(Consumer<S> consumer);
}

其中S是Self类型,我确保只在子接口中实现这些方法.因此,我们的结论是:

interface Child extends Parent1, Parent2, Parent3<Child>
{
  // Implement conflicting methods here
}

推荐答案

你正在寻找的东西或多或少是可能的,但我们需要在这里解开一些层.

它是CameraGun吗,还是语义上有关系?

现在您有以下情况:两个完全不相关的接口,每个接口声明一个方法,它们的签名完全相同.或者,如果不是一模一样,也可以是近乎一模一样.暂时忘记,由于擦除,它们有so个相同之处(仅在泛型上不同),所以Java不能为您工作.只需考虑这样一个事实,即这些方法签名在两个utterly无关的类型上有nearly个相同.

所以,你所拥有的就相当于这个:

public interface Camera {
  void shoot(Person p);
}

public interface Gun {
  void shoot(Person p);
}

// and with those established, you are attempting to...

class CameraGun implements Camera, Gun {
  void shoot(Person p) {
    // hoo boy I wonder what we are going to write here!
  }
}

因此,只有两种 Select .

  1. 你真的需要一个CameraGun:一个类需要实现一个方法,以某种方式同时做两个utterly件不相关的事情,就像CameraGun需要提供一个方法来实现拍摄一个人的照片和谋杀他们的概念,以及YOU SHOULD OBVIOUSLY BE RUNNING FOR THE HILLS HERE一样.唯一正确的答案是不要玩这个游戏.Do not写这个类.问题是‘CameraGun?’最好的回答是:"什么??没有相机枪!"

  2. 这不是一个摄像机的例子:你的两个不相关的apply个方法实际上并不是不相关的.它们在语义上的意思是一样的.不像那个相机/枪的场景.例如,在相机/枪的场景中,这两种方法都被称为shoot,这或多或少是巧合.相机可以很容易地命名为void capture(Person p),但这个名字对枪接口毫无意义.这是一个提示,相机枪的情况是,嗯,第一个案件.如果在这里不能做到这一点(没有一个适合一个的名字会不适合另一个),那么,整个设计都是错误的.

所以,这条路在这里Forking 了.你要么有一把CameraGun,在这种情况下你就得停下来.正确的.现在.

或者,您有一个‘2个语义等价的概念,由方法声明表示;但是,它们位于无关的类型中’,在这种情况下,我们将继续讨论...

您需要一个通用的超类型

如果这两个apply方法在语义上相关,则your code needs to reflect that fact.现在它没有,因此,你的代码被 destruct 了.在java中实现这一点的方法是创建一个通用类型来捕获这种共享的语义含义.接口被允许扩展另一个接口,并重新定义你的扩展因此"采用"的方法背后的语义思想-但你不能取消定义,也不能改变任何东西.你只能收紧定义(基本接口可能会说:"节日的事情会发生",而一些扩展它的接口则会说:"五彩纸屑会发生".我们都知道这是一个节日.".子接口并没有改变这个方法是通过做一些节日的东西来实现的.它只是增加了一个兼容的和更具体的附加要求.

这就是你必须在这里建造的.啊,但是,非专利药.嗯,你需要...

超级

你的代码定义甚至在我们到达"它不能编译"部分之前就已经被 destruct 了.这一点:

default Parent1 apply(Consumer<Parent1> consumer) {
        consumer.accept(this);
        return this;
    }

Is just wrong.

这是正确的:

default Parent1 apply(Consumer<? 超级 Parent1> consumer) {
        consumer.accept(this);
        return this;
    }

区别在于? 超级.这个消费者的重点是消费this.Consumer<GrandParent>号和Consumer<Object>号都能做到.没有理由不写这个.

一旦你修好了,你就需要..

泛型'self 类型'黑客.

不幸的是,Java没有自类型.你需要养成的第一件事就是接受事实.它无法真正修复,只能四处破解.这意味着拥有随时返回方法的层次 struct 是相关的is just wrong,并且是您需要停止使用的代码样式.我有时会觉得这很诱人.但是,在这里,考虑到类型层次 struct clearly扮演了一个角色,让这些方法返回自己是一个非常糟糕的 idea .只需使用void,调用代码将不得不放弃能够链接的便利.

然而,如果真的需要自己的类型,sometimes,你可以破解它.这并不是没有问题,不能应用向后兼容性,所以,您必须立即考虑它.它看起来是这样的:

interface Grandparent<G extends Grandparent<G>> {
  @SuppressWarnings({"all", "unchecked"})
  default G self() {
    return (G) this;
  }
}

interface Parent1 extends Grandparent<Parent1> {}

interface Parent2<P extends Parent2<P>> extends Grandparent<P> {}

class Child1 implements Parent2<Child1> {
 public static void main(String[] args) {
  Child1 c = new Child1();
  Child1 d = c.self();
  assert c == d;
 }
}

正如您所看到的,此黑客攻击具有与其相关的各种问题:

  1. 您必须明确地编写self()方法,并且不添加一些@SuppressWarnings就不能编写它.至少,一旦您这样做了,任何其他方法都不再需要进入@SW区域;它们只需在需要时调用self()即可.
  2. 层次 struct 中的任何子类型都必须继续拼凑起来,并添加这奇怪的<X extends Self<X>>个东西.你必须预先决定你是不是层级 struct 中的最后一种类型.
  3. 即使是这样,任何实现都需要编写class Foobar extends SelfHackeryType<Foobar>个代码.
  4. 任何使用这种实例的人都会看到泛型,并可能会问:见鬼,这是怎么回事?

但是,如果你愿意跳过所有这些障碍,你就会得到你想要的:你可以拨打child1.someSelfReturningMethod(),而这个表达式的类型是‘Child1’.这意味着如果‘Child1’有一个你想要链接的方法setName(它返回自身),而Groud1(extends Child1)有setBirthdate(Child1没有这个方法),你仍然可以写new Grandchild1().setName("Jake").setBirthdate(someDate);并拥有那个‘work’--而如果没有这个攻击,它就不会工作,因为setName被声明返回Child1,而does not havesetBirthdate方法.

把它放在一起

import java.util.function.*;

interface Grandparent<G extends Grandparent<G>> {
  @SuppressWarnings({"all", "unchecked"})
  default G self() {
    return (G) this;
  }

  default G apply(Consumer<? 超级 G> consumer) {
    consumer.accept(self());
    return self();
  }
}

interface Parent1<P extends Parent1<P>> extends Grandparent<P> {}
interface Parent2<P extends Parent2<P>> extends Grandparent<P> {}

class Child implements Parent1<Child>, Parent2<Child> {
  String name;
}

class Example {
  public static void main(String[] args) {
    Child c = new Child();
    c.name = "Jake";
    Consumer<Object> printer = System.out::println;
    Consumer<Child> announcer = x -> System.out.println("Welcome to the world, " + x.name + "!!");
    Child d = c.apply(printer);
    Child e = c.apply(announcer);
    assert c == d;
    assert c == e;
  }
}

Java相关问答推荐

更新我们的一个文物后出现了严重的符号引用错误

AlarmManager没有在正确的时间发送alert

@org.springframework.beans.factory.annotation.Autowired(required=true)-注入点有以下注释:-SpringBoot

确定Java中Math.Ranb()输出的上限

DTO到实体,反之亦然,控制器和服务之间的哪一层应该处理转换?

FALSE:它应该在什么时候使用?

具有多种令牌类型和段的复杂Java 17正则表达式

try 在Android Studio中的infoWindow中使用EditText(Java)

支持MySQL 5.6的最新Hibernate版本

如果按钮符合某些期望,如何修改它的文本?

Java页面筛选器问题

来自外部模块的方面(对于Java+Gradle项目)不起作用

Java创建带有扩展通配符的抽象处理器

带有可选部分的Java DateTimeForMatter

在Java中将.GRF转换为图像文件

在Spring Boot中使用咖啡因进行缓存-根据输出控制缓存

PhantomReference无法访问时会发生什么?

Maven创建带有特定类的Spring Boot jar和普通jar

Hibernate 命名策略导致 Java Spring Boot 应用程序中出现未知列错误

元音变音字符:如何在 Java 中将Á<0x9c>转换为Ü?