【问题标题】:Lambda expression and generic interfaceLambda 表达式和泛型接口
【发布时间】:2016-01-10 12:05:26
【问题描述】:

所以,我目前正在研究 lambda 表达式,刚刚遇到了一个带有通用函数接口的示例:

@FunctionalInterface
public interface Mapper<T> {
    int map(T source);

    public static <U> int[] mapToInt(U[] list, Mapper<? super U> mapper) {
        int[] mappedValues = new int[list.length];

        for(int i = 0; i< list.length; i++) {
            mappedValues[i] = mapper.map(list[i]);
        }

        return mappedValues;
    }
}

和测试代码

public static void main(String[] args) {
        System.out.println("Mapping names to their lengths:");
        String[] names = {"David", "Li", "Doug"};
        int[] lengthMapping = Mapper.mapToInt(names, (String name) -> name.length());
        printMapping(names, lengthMapping);

    }

    public static void printMapping(Object[] from, int[] to) {
        for(int i = 0; i < from.length; i++) {
            System.out.println(from[i]  + " mapped to " + to[i]);
        }
    }

所以,困扰我的是,在这个示例中,我们将Mapper 功能接口的实例作为第二个参数传递给测试代码中的mapToInt 方法。但是,在这个例子中,Mapper 的形参 &lt;T&gt; 的值是多少?这似乎可行,但是如果从未指定形参的参数,编译器怎么能解决这个问题?

可能是因为,抽象方法:

int map(T source);

使用 T 参数,并基于 lambda 表达式参数,将其用作 T 的真实类型?

另外,mapToInt 中的第二个参数:Mapper&lt;? super U&gt; mapper,同样的问题,在此示例中,Mapper&lt;? super U&gt; 的类型的值是多少?

【问题讨论】:

    标签: java generics lambda


    【解决方案1】:

    类型 U 与类型 T 无关。

    类型 T 允许定义一个功能通用接口,其中它的单个抽象方法需要实现一个函数 T -&gt; int

    所以这个表达式之一将是完全有效的:

    Mapper<Boolean> mapper = b -> b.hashCode(); //a function Boolean -> int
    

    U 类型允许你定义一个泛型方法。因此,当您使用String[] 作为第一个参数调用mapToInt 时,编译器推断出的类型U 是String

    因此,您需要提供一个函数 String -&gt; int,这正是 name -&gt; name.length() 所做的(您可以省略 lambda 表达式的类型参数)。

    【讨论】:

      【解决方案2】:

      这似乎可行,但是编译器怎么能解决这个问题 如果从未指定形参的实参?

      这称为类型推断。确切的过程是extremely complicated,但在这种情况下,编译器能够轻松推断类型,因为您确实明确指定了它:(String name) -&gt; name.length()。所以你说的是真的:

      基于 lambda 表达式参数,将其用作真实类型 吗?

      但是,在这种情况下并不完全需要。这也可以:

      int[] lengthMapping = Mapper.mapToInt(names, name -> name.length());
      

      在这种情况下,请注意编译器知道函数mapToInt 的类型参数是什么。实际上,使用第一个参数U[] list 声明的函数,并且由于您将String[] names 作为第一个参数传递,编译器能够推断出无论U 是什么,String[] 都必须通过a 转换为U[] widening reference conversion。也就是说,UStringString 的超类。

      现在,第二个参数是一个函数接口的实例,它的方法接受UU 的超类(这就是Mapper&lt;? super U&gt; 的含义)。此时编译器基本上决定“好吧,它可能只是String”,因为它是String 不会与迄今为止推断的任何内容相矛盾。请注意,您实际上可以更改 lambda 表达式的类型:

      int[] lengthMapping = Mapper.mapToInt(names, (Object name) -> name.hashCode());
      

      现在T 将变为Object,因为您已明确指定它。但是由于T 必须是UU 本身的超类,所以现在还不清楚U 到底是什么。 U 可能是 String,但也可能是 Object。但猜猜怎么了?没关系!因为在编译时它没有任何效果,而在运行时它将是Object,因为类型擦除。但如果你真的想要,你也可以明确地澄清这一点:

      int[] lengthMapping = Mapper.<Object>mapToInt(names, name -> name.hashCode());
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-30
        • 1970-01-01
        相关资源
        最近更新 更多