【问题标题】:Static Method in Interface with Generic signature具有通用签名的接口中的静态方法
【发布时间】:2018-05-05 22:32:50
【问题描述】:

从 Java 8 开始,您可以在接口中实现默认或静态方法,如下所示

public interface DbValuesEnumIface<ID, T extends Enum<T>> {
   T fromId(ID id);

   ID getId();
   static String getDescriptionKey(){
      return "this is a test";
   }
}

我想用具有签名的静态方法声明上述内容,该签名使用实现类定义的边界,因为该方法的实现应该对所有人都相同,唯一不同的是声明的泛型,如下所示:

public interface DbValuesEnumIface<ID, T extends Enum<T>> {

   public static T fromId(ID id) {
        if (id == null) {
            return null;
        }
        for (T en : T.values()) {
            if (en.getId().equals(id)) {
                return en;
            }
        }
    }

    ID getId();

    String getDescriptionKey();
}
...
public enum Statuses implements DbValuesEnumIface<Integer,Statuses>

因为 T 和 ID 不是静态的并且不能从静态上下文中引用而中断。

那么,如何修改以上内容才能成功编译,如果不可能,如何实现以上内容以达到预期目的,同时避免实现类中的代码重复。

【问题讨论】:

  • 不完全确定这个问题是否特别需要与 Java 8 和接口有关。对于尝试使用为 class? 定义的类型参数的 static 方法,相同的规则通常也适用于类?
  • @CKing 在接口中拥有静态方法的能力是 Java8 特有的,并且手头的问题是通过使用具有泛型的接口而发生的。所以相应地添加了标签。 ``` 中断是因为 T 和 ID 不是静态的,并且不能从静态上下文中引用 ``` 如果 DbValuesEnumIface 是一个我只是在子类中扩展的类,但如果这就是你的意思的话,可能仍然会出现。作为示例中的接口,它可能存在一些独特的问题,因此这就是添加此信息的原因。
  • 没有扭结。类或接口。泛型类型参数遵循相同的规则。静态是静态的,实例是实例。此外,T.values 其中T 是类或方法类型参数(不管static)违反了语言的语法规则。当问题与语言无关时,请发布一个特别考虑语言规则的示例?更重要的是,谈论编译错误的问题肯定会消除与直接问题 IMO 无关的所有编译错误。
  • 其实有一个接口具体的区别。对于普通类,你可以像SubClass.method(…)一样调用SuperClassstatic方法,提高了可以使用继承参数化(例如class SubClass extends SuperClass&lt;SpecificType&gt;)的期望,令人失望的是它不起作用。相反,static 接口的方法一开始就没有被继承。所以你不能做Statuses.fromId(…),而只能做DbValuesEnumIface.fromId(…),这显然需要明确提示Statuses是要搜索的类。
  • @Holger “接口的静态方法一开始就没有被继承” 没错。虽然您可以使用默认方法(可能会导致相同的问题)并且 Java 可以解决冲突,但静态方法的处理方式不同。

标签: java generics interface static java-8


【解决方案1】:

由于static 方法与描述实例如何参数化的类的类型参数之间没有关系,因此您必须自己使static 方法泛型。棘手的部分是让声明正确地描述所有需要的约束。正如this answer 已经解释的那样,您需要一个Class 参数,否则,实现没有机会获得实际的类型参数:

public interface DbValuesEnumIface<ID, T extends Enum<T>> {

   public static
   <ID, T extends Enum<T>&DbValuesEnumIface<ID,T>> T fromId(ID id, Class<T> type) {
        if (id == null) {
            return null;
        }
        for (T en : type.getEnumConstants()) {
            if (en.getId().equals(id)) {
                return en;
            }
        }
        throw new NoSuchElementException();
    }

    ID getId();

    String getDescriptionKey();
}

注意static 方法的类型参数独立于类的类型参数。为了清楚起见,您可以考虑给它们起不同的名称。

所以现在,给你enum Statuses implements DbValuesEnumIface&lt;Integer,Statuses&gt;的例子,你可以使用Statuses status = DbValuesEnumIface.fromId(42, Statuses.class);这样的方法


请注意,对于default 方法,可以访问实际类型,因为实现将提供method providing the enum type。您只需要在interface 中声明该方法的存在:

public interface DbValuesEnumIface<ID, T extends Enum<T>&DbValuesEnumIface<ID,T>> {

    public default T fromId(ID id) {
        if (id == null) {
            return null;
        }
        for (T en : getDeclaringClass().getEnumConstants()) {
            if (en.getId().equals(id)) {
                return en;
            }
        }
        throw new NoSuchElementException();
    }
    Class<T> getDeclaringClass();//no needed to implement it, inherited by java.lang.Enum
    ID getId();
    String getDescriptionKey();
}

但是,明显的缺点是您需要一个目标实例来调用该方法,即Statuses status = Statuses.SOME_CONSTANT.fromId(42);

【讨论】:

  • hm..但是那意味着在接口级别和方法级别声明的泛型和不相关的权利?
  • @Leon:是的。您唯一能做的就是为每个enum 添加一个简单的委托方法,例如将public static Statuses fromId(Integer id) { return DbValuesEnumIface.fromId(id, Statuses.class); } 添加到Statuses,这仍然比在每个enum 中重复整个实现要好...
  • @Eugene:对,方法的类型参数是不相关的(这就是为什么我什至建议给它们起不同的名字);这是约束,即 X extends Enum&lt;X&gt;&amp;DbValuesEnumIface&lt;ID,X&gt; 强制类型参数具有兼容的继承,因为只有 enum 类型通过适当的参数化实现 DbValuesEnumIface 才能满足约束。
  • @Eugene:我刚刚意识到 default 方法的解决方案不需要额外的 Class 参数(更新了我的答案),但是,我不知道是否有在现有实例上调用方法比提供类文字作为参数更好......
  • @Eugene: 任何人都可以实现这个接口,但是没有人可以为T 提供一个不是enum 的类型一个适当的实现同时界面。因此,如果您创建了一个不合适的接口,它仍然必须指向T 的接口的适当实现。你不能写防弹软件,但你甚至不应该尝试。将实施者引导到正确的方向就足够了。
【解决方案2】:

据我所知,没有简单的方法,首先你需要将你的方法更改为default,你可以阅读更多here 为什么你不能在静态上下文中使用泛型。

但是即使你把它改成default,事情还是不行,因为你需要把枚举的一个实例或者类类型传递给那个方法,像这样:

public default T fromId(ID id, Class<T> t) {
        if (id == null) {
            return null;
        }
        for (T en : t.getEnumConstants()) {
            // dome something
        }
        return null;
}

现在你遇到了另一个问题,在 fromId 内部 - 你唯一知道的是 T 扩展了 enum - 可能不是你的枚举,因此 getId (似乎你的枚举有)编译器根本不知道。

除了声明一个接口之外,我不知道有什么简单的方法可以完成这项工作,例如:

interface IID {
    public int getId();
} 

让你的enum 实现它:

static enum My implements IID {
    A {

        @Override
        public int getId() {
            // TODO Auto-generated method stub
            return 0;
        }

    };
}

并将声明更改为:

public interface DbValuesEnumIface<ID, T extends Enum<My> & IID>

【讨论】:

  • 非常彻底的答案,我会阅读提供的链接以及您的建议。如果今天没有更好的结果,我会确保接受这个答案。谢谢。
  • 如果你有Class参数,你已经有了你需要的,没有理由将方法从static更改为default。您只需要声明Class 必须是enum 并实现所需的接口,具有讽刺意味的是,您在最后一行展示了如何做这些事情。
【解决方案3】:

你可以把static改成default就可以编译成功了。

default EvaluationStatuses fromId(Integer id)

【讨论】:

  • 我的错,我不小心粘贴了错误的方法签名。界面中的右边应该是 T fromId(ID id) 。我确实尝试将静态更改为默认值,但它无法识别 for (T en : T.values()) 中的 values() 方法。不过,您可能在这里有所了解,我也会进一步研究这种方法。
  • @Leon 它无法识别 values 方法,因为T 是类型参数而不是引用。这与接口或默认方法或静态方法无关。也许先阅读泛型语法,然后再转向更棘手的主题,比如这个?
猜你喜欢
  • 2018-01-16
  • 2017-12-04
  • 1970-01-01
  • 1970-01-01
  • 2020-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多