【问题标题】:Generic return type of interface method [closed]接口方法的通用返回类型
【发布时间】:2018-02-23 21:59:34
【问题描述】:

我有一个带有方法parse(String value) 的接口,它可能有不同的实现,返回<String, Integer> <String, String> 或任何东西的映射。我怎样才能使这个足够通用,以便我扩展不同的返回类型?

目前,我这样做:

public interface Parser <K,V> { 

    Map<K,V> parse(String document);
}

但这将使其仅适用于地图。有人可以告诉我有没有一种方法可以使它适用于不同的返回类型?

【问题讨论】:

  • Parser&lt;T&gt; 也许?
  • 喜欢public interface Parser&lt;E&gt; { E parse(String document); }?
  • @4castle 我是界面新手,你能解释一下它是如何工作的(你在这里提到的那个)
  • "...有没有一种方法可以使它适用于不同的返回类型?"你的意思是什么返回类型?您是否还有其他方法也需要返回与parse 方法返回的类型无关的泛型值?还是别的什么?
  • en.wikipedia.org/wiki/Generics_in_Java ... 通读。应该可以极大地帮助您理解接口/类的泛型

标签: java generics interface java-8 return


【解决方案1】:

如果您希望它返回任何类型,只需使用一种泛型类型来定义它,例如 T:

public interface Parser <T> { 

    <T> parse(String document);
}

这是可能的,但我担心你以后会遇到新的挑战。 Java 目前有办法从泛型类型实例化一个类,因此您还必须将该类类型作为参数传递:

public interface Parser <T> { 

    <T> parse(Class<T> clazz, String document);
}

你可以这样做,但我认为应该进一步设计你的架构。如果文档的返回类型可以是任何东西,在大多数情况下,这是一种弱设计的味道,并且会导致意大利面条式代码。

【讨论】:

  • "Map&lt;T&gt;..." -> 这不会编译。 Map 有两个泛型参数。
  • 感谢您的关注。我纠正了例子。
【解决方案2】:

如果你想让你的接口在返回类型中通用,我建议对 JoeC 的评论进行扩展。

从 Java 8 开始,有java.util.function-package,为基本转换提供接口。特别是,接口Function 可用于满足您的目的。我会建议这样的实现:

// file: Parser.java
import java.util.function.Function;

public abstract class Parser<R> implements Function<String, R> {

    @Override
    public final R apply(String document) {
        return (this.parse(document));
    }

    abstract public R parse(String document);
}

上述示例的实例化如下所示:

String document             = ...;
Parser<Map<K, V>> mapParser = ...; // instantiate a fitting Parser
Map<K, V> result            = mapParser.parse(document);

(鉴于KV 是此代码块中已知的通用参数)。

您可以进一步指定接口以获得更简单的语法:

// file: MapParser.java
import java.util.Map;

public abstract class MapParser<K, V> extends Parser<Map<K, V>> {}

有了这个(空)接口,你可以将上面的代码重写为:

String document           = ...;
MapParser<K, V> mapParser = ...; // instantiate a fitting MapParser
Map<K, V> result          = mapParser.parse(document);

正如@matoni 所说,可以编写interfacesIParserIMapParser 并在它们之上设置抽象类ParserMapParser

// file: IParser.java:
import java.util.function.Function;

public interface IParser<R> extends Function<String,R> {

    @Override
    default public R apply(String document) {
        return (this.parse(document));
    }

    public R parse(String document);
}

// file: IMapParser.java:
import java.util.Map;

public interface IMapParser<K, V> extends IParser<Map<K, V>> {}

// file: Parser.java:
public abstract class Parser<R> implements IParser<R> {

    @Override
    public final R apply(String document) {
        return (this.parse(document));
    }
}

// file: MapParser.java:
import java.util.Map;

public abstract class MapParser<K, V> extends Parser<Map<K, V>>
        implements IMapParser<K, V> {}

接口为用户提供了更大的灵活性,因为一个class 可以实现多个interfaces,但只能实现extends 另一个class。然而,不利的一面是,接口IParserIMapParser 的开发人员无法强制方法apply(...) 不能被覆盖。因此,理论上,Parser 的实现者可以以不同的方式实现apply(...)parse(...),这可能导致意外行为。当使用抽象类 ParserMapParser 时,开发人员会强制 apply(...) 调用 parse(...) 并因此保持一致的行为。

【讨论】:

  • 我已经删除了我的答案,因为它和你的一样 - 你更快:)。考虑用接口 Parser 替换抽象 Parser。 Java 8 引入了default 方法,与抽象类中的非抽象方法基本相同。
  • @matoni 是的,Java 8 可以。但是对于抽象类,可以强制apply(...) 不会在其他地方被覆盖并调用parse(...),从而保证一致的行为,例如如果Parser 的实例被用作流调用的一部分。
  • 当然,但在 Java 中一切都是虚拟的,因此可以被覆盖。 final 关键字很少使用。拥有一个接口更加灵活,因为您可以实现多个接口,而使用抽象类您只能扩展一个父类。
  • "...在 Java 中一切都是虚拟的..." 你的意思是抽象的?仅当您将其声明为抽象时,它才是抽象的。这与动态绑定不同。 “final关键字很少使用。”我倾向于不同意。看看API。 “有一个界面更灵活......”当然,但如果我能防止一些错误,我愿意为了正确性而牺牲灵活性。
  • “虚拟”是指可覆盖的。在 C++ 中,只有标记为虚拟的方法可以被覆盖(这同样适用于 C#),相反的 Java 方法默认是虚拟的。如果您不设计库 API,我会选择灵活性。
【解决方案3】:

cmets 已经给了你很好的提示,但我想你需要一个例子。

// imports elided

interface Parser<T> {

    T parse(String document);

    Parser<Map<String, Integer>> static mapParser() {
        // replace with actual parsing code
        return document -> {
            Map<String, Integer> result = new Hashmap<>();
            result.put(document, document.length());
            return result;
         }

    Parser<List<String>> static listParser() {
        return document -> Collections.singletonList(document);
    }
}

请注意,这些实现只是占位符 - 它们只是为了说明您可以创建的 Parser 类型。我还使用了一个更简洁的 lambda,因为您的接口只有一个实例方法 parse(String document),它将其限定为 FunctionalInterface,允许您在实现命名接口方法时替换匿名 lambda 表达式。

然后调用者可以通过以下方式调用:

String document = "abc";
Map<String, Integer> lookup = Parser.mapParser().parse(document);
List<String> list = Parser.listParser().parse(document);

【讨论】:

    猜你喜欢
    • 2017-05-09
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 1970-01-01
    • 2017-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多