通用返回类型和lambda函数参数

时间:2016-12-28 16:38:01

标签: java generics lambda

我目前正在研究可以处理任何网格状结构上的操作的抽象代码。代替具体的方法,我一直在尝试开发一个接受lambda表达式的框架,并根据网格元素之间的方向关系提供功能,并且在尝试将泛型类型返回到这些方法时遇到了麻烦。

我有一个类似下面的工作方法,它接受一个lambda函数,它在特定方向找到grid元素,而Consumer则在每个元素上执行一些方法。

public static <E> void forEachClockwise(Direction d, Function<Direction, ? extends E> f, Consumer<? super E> c){
    /*For each of the orthogonal directions, in order:*/
        c.accept(f.apply(d));
    /*end For*/

}

不必每次调用此方法时都必须传递一个元素查找函数,我决定使用一个声明了这样一个函数的接口“Orthogonal”,并重载forEachClockwise方法来接受这个元素。接口

public static <O extends Orthogonal> void forEachClockwise(Direction d, O center, Consumer<O> c){
    forEachClockwise(d, center::findNextByDirection, c);
}

我在这里使用泛型并声明,以便Consumer方法能够访问网格元素的字段而不进行强制转换。我担心这是一个糟糕的设计选择,因为我创建的Orthogonal接口和测试类没有编译而没有警告和没有强制转换,尽管尝试了一些不同的东西。

public interface Orthogonal {
    public <E extends Orthogonal> E findNextByDirection(Direction a);
}

我尝试过的findNextByDirection的两个“有效”实现是:

/* in Coordinate implements Orthogonal: */
@Override
public <E extends Orthogonal> E findNextByDirection(Direction a) {
    return (E) (/*The proper Coordinate*/);
}

并且

@Override
public Coordinate findNextByDirection(Direction a) {
    return /*The proper Coordinate*/;
}
//Warning on return type declaration:
/*Type safety: The return type Element for findNextByDirection(Direction) from the type Element needs unchecked conversion to conform to E from the type Orthogonal*/

最理想的情况是,我希望有一个forEachClockwise(和其他方法)可以接受的方法,它返回一个调用它的同一个类的元素,而不需要强制转换。 Coordinate的findNextByDirection应该返回一个Coordinate,Address应该返回一个Address,Cell应该返回Cell等。我看过的其他问题和答案讨论如何使用泛型返回类型来制作一个方法,但我还没有找到关于如何将这种方法用作lambda参数的任何提示。

我尚未尝试的一些事情是创建一个新的findByDirection方法,它接受某种Orthogonal和Direction作为参数,或者使用Orthogonal中定义的某种方法调用forEachClockwise方法,尽管这些方法听起来和我尝试过的。我看到的另一种方法是为接口提供自己的泛型类型,但是将每个Orthogonal类声明为“MyClass实现Orthogonal”会感觉明显错误和目的失败

我从一开始就设计这个界面是完全错误的,并期望仿制药做一些他们不是为之设计的东西?或者是否有一种更简单的方法可以让类调用正确的方法并返回自己类型的元素?

完整代码:

package test;

import java.util.function.Consumer;
import java.util.function.Function;


public enum Direction {
    NORTH, EAST, SOUTH, WEST;

    public static <O extends Orthogonal> void forEachClockwise(Direction d, O center, Consumer<O> c){
        forEachClockwise(d, center::findNextByDirection, c);
    }

    public static <X> void forEachClockwise(Direction d, Function<Direction, ? extends X> f, Consumer<? super X> c){
        c.accept(f.apply(d));
        forEachExcept(d, f, c);
    }

    public static <X> void forEachExcept(Direction d, Function<Direction, ? extends X> f, Consumer<? super X> c){
        for(int i=0; i<3; i++){
            d = Direction.getClockwise(d);
            c.accept(f.apply(d));
        }
    }

    public static Direction getClockwise(Direction d){
        switch(d){
        case EAST:
            return SOUTH;
        case NORTH:
            return EAST;
        case SOUTH:
            return WEST;
        case WEST:
            return NORTH;
        default:
            return null;
        }
    }

}

interface Orthogonal {
    public <E extends Orthogonal> E findNextByDirection(Direction a);
}






class Coordinate implements Orthogonal{

    int x;
    int y;

    public Coordinate(int x, int y){
        this.x = x;
        this.y = y;
    }

    public int getX(){
        return x;
    }

    public int getY() {
        return y;
    }


    @Override //Warning on "Coordinate" below.  Compiler suggests writing
              //entire <E extends Orthogonal> method signature
    public Coordinate findNextByDirection(Direction a) {
        switch(a){
            case NORTH:
                return new Coordinate(x+1, y);
            case SOUTH:
                return new Coordinate(x-1, y);
            case EAST:
                return new Coordinate(x, y+1);
            case WEST:
                return new Coordinate(x, y-1);
            default:
                return null;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

我们假设有两个类实现Orthogonal - CoordinateArea

public <E extends Orthogonal> E findNextByDirection(Direction a);

您在此处声明的是模板化方法。声明基本上说:“给定一个特定的上下文,此方法将始终返回预期的类型 - 如果要将结果分配给Coordinate变量,该方法将返回Coordinate,如果是Area - 它将返回Area等等 - 只要预期的是实现Orthogonal的类型“

但是,当您尝试在Coordinate中将其作为public Coordinate findNextByDirection(Direction a)实现时,您不再承诺返回给定上下文中的预期内容 - 您现在只返回Coordinate,因此打破界面中声明的合同。更糟糕的是,如果你想到这一点,首先无法真正满足合同。

您应该做的是声明您的接口通用(Orthogonal<E>)并从方法声明中删除<E extends Orthogonal>

如果声明class Coordinate implements Orthogonal<Coordinate>感觉“明显错误”,则不应该。这是常见的做法,广泛用于SDK本身(例如,请参阅Comparable)。

如果您发现findNextByDirection需要返回Orthogonal,则可以将返回类型声明为Orthogonal<E>。您甚至可以在Coordinate中以[{1}}的形式实现您的方法,而不会因Java的类型协方差而发出任何警告。

答案 1 :(得分:0)

我的理解是,从您的findNextByDirection方法,您需要返回与封闭类相同的类型。因此,在您的坐标示例中,您需要返回一个坐标。

如果是这种情况,您可以更进一步使用泛型:

interface Orthogonal<E extends Orthogonal<E>> {
    public E findNextByDirection(Direction a);
}
class Coordinate implements Orthogonal<Coordinate> {
  //rest is the same
  //you should not have any warnings left
}

public static <O extends Orthogonal<O>> void forEachClockwise2(Direction d, O center, Consumer<O> c){
    forEachClockwise(d, center::findNextByDirection, c);
}

注意:两个forEachClockwise方法存在冲突(原始签名相同),因此我将其中一个重命名为forEachClockwise2