Java 8在使用继承和泛型时试图添加Function FunctionalInterface调用的问题

时间:2016-06-22 11:09:52

标签: java generics inheritance functional-programming

我一直在学习Java 8的功能,到目前为止已经成功实现了它们,但是我的最新代码引发了两个问题。

第一个问题可以通过强制转换来解决,但这不应该在这里发生,因为类型继承应该是合理的;这是在接受一个参数构造函数参数时,其中构造函数是Function FunctionalInterface方法参考。

相关问题是,如果在调用Method Reference时将此类型用作构造函数参数的类型,则在使用“方法引用”填充映射时,Java无法将泛型参数的具体声明子类型解析为有效。

下面的代码使用分离接口继承树,接口规范和接口实现,用于两种类型的对象Node和Edge。

边缘具有通用参数,用于定义创建方法引用时指定的起始节点和结束节点。

节点和边缘都可以有自定义子类型,它们具有由实现子类型实现的派生接口(子类型)。

节点包含作为集合的关系,可以是具体节点引用或具体边缘引用。 (startNode应该与持有边缘集的父对象匹配,但是作为边缘的成员需要,这是我无法控制的)

为避免大量重复代码,可通过操作界面进行访问

Operations接口还有一个独立的接口和具体实现,并且有两种类型(Node和Edge)来支持Edges所需的泛型,一个是为每个ImplExtendedNode中具有关系的每个关系集创建的。

此修改是将创建者方法引用的存储移回到知道要使用哪些具体类型的类中,并且具有访问集的权限,而不是将自定义方法引用传递到每个Operations构造函数中,然后传递它回来调用。

问题非常特定于Edge(构造函数)方法参考,并且无法正确解析类型是否有效并满足泛型类型约束的要求。

系统是批处理过程的一部分。我已经把所有东西都拿出来了,我非常感谢帮助我了解更多关于泛型和java 8的新功能,以便了解我的错误。

//Node Interfaces

public interface Node {
    ...
}

public interface DefaultNode extends Node {
    ...
    public <S extends Object & DefaultNode> Optional<S> createRelationship(NodeOperations nodeOps);
    public <R extends Object & DefaultEdge, T extends Object & DefaultNode> Optional<R> createRelationship(EdgeOperations edgeOps);
    ...
}

//Edge Interfaces

public interface Edge<S extends Object & Node, T extends Object & Node> {
    ...
}

public interface DefaultEdge<S extends Object & Node, T extends Object & Node> extends Edge<S, T> {
    ...
}

//Implementation Classes 

//(base objects)
public abstract class ImplNode implements Node {
    ...
}

public abstract class ImplEdge<S extends Object & Node, T extends Object & Node> implements Edge<S, T> {
    ...
    //constructor(s)
    public ImplEdge(S s) {
        ...
    }
    ...
}

//(provide basic functions)
public abstract class ImplDefaultNode extends ImplNode implements DefaultNode {

    //holds method reference to custom node/edge constructors in child implementation class definitions (multiple per class) 
    protected Map<NodeOperations, Supplier> nodeSuppliers;
    protected Map<EdgeOperations, Function> edgeSuppliers;

    //this works fine
    @Override
    public <S extends Object & DefaultNode> Optional<S> createRelationship(NodeOperations nodeOps) {
        ...
        Supplier<S> f = this.nodeSuppliers.get(nodeOps);
        S relationship = f.get();
        ...
    }

    //issue one, cannot automatically cast type of "this" to expected type of T in f.apply(this) - I know this should be possible in most cases and usually points to an issue with the code
    @Override
    public <R extends Object & DefaultEdge, T extends Object & DefaultNode> Optional<R> createRelationship(EdgeOperations edgeOps) {
        ...
        Function<T, R> f = this.edgeSuppliers.get(edgeOps);
        R relationship = f.apply(this);
        ...
    }
}

public abstract class ImplDefaultEdge<S extends Object & Node, T extends Object & Node> extends ImplEdge<S, T> implements DefaultEdge<S, T> {
    private S startNode;
    private T endNode;
    ...
}

//(provides custom extensions which rely on ImplDefaultNode)

//custom implementation of interface derived and defined from DefaultNode 
public class ImplExtendedNode extends ImplDefaultNode implements ExtendedNode {
    ...
    private Set<DifferentImplExtendedNode1> nodeRels1;
    ...
    private Set<ImplExtendedEdge<ImplExtendedNode, DifferentImplExtendedNode2>> edgeRels1;

    //this works when called via create function in superclass through generic map lookup
    @Override
    public NodeOperations morphNodeRels1() {
      ...
      this.nodeSuppliers.put(i, DifferentImplExtendedNode1::new);
      ...
    }

    //this throws compiler error - 
    //Cannot convert java.lang.Object to ImplExtendedNode 
    //It only works if the first generic type is ImplNode, or ImplDefaultNode I think)
    @Override
    public EdgeOperations morphEdgeRels1() {
        ...
        this.edgeSuppliers.put(i, ImplExtendedEdge<ImplExtendedNode, DifferentImplExtendedNode2>::new);
        ...
    }
}

1 个答案:

答案 0 :(得分:1)

你的问题与Java 8无关,它们源于对某些通用声明含义的基本误解。

如果使用类型参数定义类或方法,则意味着代码使用这些类或方法可以使用替换这些参数的任意类型,并且您的通用代码将独立于使用代码实际上已经选择了类型参数。

您可以定义如下界面:

public interface DefaultNode extends Node {
    ...
    public <S extends Object & DefaultNode> Optional<S>
        createRelationship(NodeOperations nodeOps);
    public <R extends Object & DefaultEdge, T extends Object & DefaultNode> Optional<R>
        createRelationship(EdgeOperations edgeOps);
    ...
}

只要调用者选择的类型是Optional<WhatEverTheCallerChooses>的子类型,第一种方法就会返回DefaultNode。当然,那是行不通的。为了证明这一点,以下代码编译时没有错误:

public static void demonstrating(DefaultNode n, NodeOperations o) {
    abstract class FooBar implements DefaultNode {}
    Optional<FooBar> opt = n.createRelationship(o);
    if(opt.isPresent()) {
        FooBar fb = opt.get();
    }
}

createRelationship方法的通用签名承诺以某种方式返回FooBar的具体子类,甚至不知道本地类。当然,在这种情况下,它总是可以返回一个空的可选项,但是一个方法,其唯一有效的实现是什么都不返回,没有任何意义。

第二种方法甚至添加了另一个类型参数T,它不会出现在方法签名的其他地方,这使得它完全无用。您在方法实现中尝试使用它表明您不理解类型参数是调用者和方法实现之间的契约的一部分:

@Override
public <R extends Object & DefaultEdge, T extends Object & DefaultNode> Optional<R>
                                              createRelationship(EdgeOperations edgeOps) {
    ...
    Function<T, R> f = this.edgeSuppliers.get(edgeOps);
    R relationship = f.apply(this);
    ...
}

由于RT是方法的类型参数,调用者决定用实际类型替换它们。 edgeSuppliers.get返回原始类型 Function,因此编译器接受Function<T, R>的赋值,尽管它完全错误。您不知道调用者为TR选择了哪些实际类型,因此您无法知道该函数是否符合合同,请参见上文T FooBar }}

然后你调用f.apply(this),但是你怎么能期望编译器接受它?如上所示,T可以是本地类型FooBar,而您的this引用不指向FooBar的实例。当然,你的地图不可能包含一个实际上需要一个完全不同的上下文的FooBar实例的函数,但这只表明先前未经检查的操作已经错误。 T 可能 FooBar,因此此函数不能<{1}}。

但是如上所述,Function<T,R>根本没有在方法声明中使用,所以在方法中也没有使用它是没有意义的。如果断言所有函数都可以在此处使用T,则它们应该可以赋值给this,并且您应该在类中使用适当的声明,包括Function<? super ImplDefaultNode, …>的声明。但是,无法保留在调用时返回调用者所希望的任何内容的承诺。你最好在这个地方没有任何类型参数,并声明保证的最小值,例如: Map