Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)——诺诺"涂鸦"记忆

----------   诺诺学习技术交流博客、期待与您交流!  
 ----------

详情请查看:http://blog.csdn.net/sun_promise 

方法引用

(注:此文乃个人查找资料然后学习总结的,若有不对的地方,请大家指出,非常感谢!)

1.方法引用简述

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。

Lambda表达式全文详情地址:http://blog.csdn.net/sun_promise/article/details/51121205

2.作用

方法引用的唯一用途是支持Lambda的简写。

方法引用提高了代码的可读性,也使逻辑更加清晰。(优点)

3.组成

使用::操作符将方法名对象或类的名字分隔开。

::” 是操作符(也可以称作定界符、分隔符)。

eg:

4.分类

1)静态方法引用

组成语法格式:ClassName::staticMethodName

Note:

  • 静态方法引用比较容易理解,和静态方法调用相比,只是把 换为 ::
  • 在目标类型兼容的任何地方,都可以使用静态方法引用。
eg:

-- String::valueOf
  等价于lambda表达式 (s) -> String.valueOf(s)

-- Math::pow 
     等价于lambda表达式  (x, y) -> Math.pow(x, y);

-- 假设需要从一个数字列表中找出最大的一个数字。

方法引用方式:

(max是一Collections里的一个静态方法,它需要传入一个List类型的参数。)

Function<List<Integer>, Integer> maxFn =Collections::max;

maxFn.apply(Arrays.asList(1, 10, 3, 5))。

上面 等价于Lambda表达式 Function<List<Integer>,
Integer> maxFn = (numbers) -> Collections.max(numbers);。

--  以字符串反转为例:

/*
* 函数式接口
* */
interface StringFunc {
    String func(String n);
}
class MyStringOps {
    //静态方法: 反转字符串
    public static String strReverse(String str) {
        String result = "";
        for (int i = str.length() - 1; i >= 0; i--) {
            result += str.charAt(i);
        }
        return result;
    }
}
class MethodRefDemo {
    public static String stringOp(StringFunc sf, String s) {
        return sf.func(s);
    }
    public static void main(String[] args) {
        String inStr = "lambda add power to Java";
        //MyStringOps::strReverse 相当于实现了接口方法func() ,并在接口方法func()中作了MyStringOps.strReverse()操作
        String outStr = stringOp(MyStringOps::strReverse, inStr);
        System.out.println("Original string: " + inStr);
        System.out.println("String reserved: " + outStr);
    }
}

输出结果:

Original string: lambda add power to Java

String reserved: avaJ ot rewop dda adbmal

分析:

在程序中,特别注意下面这行代码:String outStr = stringOp(MyStringOps::strReverse, inStr);

其中将对MyStringOps内声明的静态方法strReverse()的引用传递给stringOp()方法的第一个参数。可以这么做,因为

strReverse与StringFunc函数式接口兼容。因此,表达式MyStringOps::strReverse的计算结果为对象引用,其中,

strReverse提供了StringFunc的func()方法的实现。

--  找到列表中具有最大值的对象

找到集合中最大元素的一种方法是使用Collections类定义的max()方法。对于这里使用的max()版本,必须传递一个集合引用,以及一个实现了Comparator<T>接口的对象的实例。Comparator<T>接口指定如何比较两个对象,它只定义了抽象方法compare(),该方法接受两个参数,其类型均为要比较的对象的类型。如果第一个参数大于第二个参数,该方法返回一个正数;如果两个参数相等,返回0;如果第一个参数小于第二个参数,返回一个负数。

过去,要在max()方法中使用用户定义的对象,必须首先通过一个类显式实现Comparator<T>接口,然后创建该类的一个实例,通过这种方法获得Comparator<T>接口的一个实例,然后,把这个实例作为比较器传递给max()方法。在JDK 8中,现在可以简单地将比较方法的引用传递给max()方法,因为这将自动实现比较器。

class MyClass {
    private int val;
    MyClass(int v) {
        val = v;
    }
    public int getValue() {
        return val;
    }
}
class UseMethodRef {
    public static int compareMC(MyClass a, MyClass b) {
        return a.getValue() - b.getValue();
    }
    public static void main(String[] args) {
        ArrayList<MyClass> a1 = new ArrayList<MyClass>();
        a1.add(new MyClass(1));
        a1.add(new MyClass(4));
        a1.add(new MyClass(2));
        a1.add(new MyClass(9));
        a1.add(new MyClass(3));
        a1.add(new MyClass(7));
        //UseMethodRef::compareMC生成了抽象接口Comparator定义的compare()方法的实例。
        MyClass maxValObj = Collections.max(a1, UseMethodRef::compareMC);
        System.out.println("Maximum value is: " + maxValObj.getValue());
    }
}

输出结果:

Maximum value is: 9

分析:

在程序中,注意MyClass即没有定义自己的比较方法,也没有实现Comparator接口。但是,通过调用max()方法,仍然可以获得MyClass对象列表中的最大值,这是因为UseMethodRef定义了静态方法compareMC(),它与Comparator定义的compare()方法兼容。因此,没哟必要显式的实现Comparator接口并创建其实例。

2)实例方法引用

这种语法与用于静态方法的语法类似,只不过这里使用对象引用而不是类名。

实例方法引用又分以下三种类型

a.实例上的实例方法引用

组成语法格式:instanceReference::methodName

Note:

对于具体(或者任意)对象的实例方法引用,在实例方法名称和其所属类型名称间加上分隔符

与引用静态方法引用相比,都换为实例对象的而已。

eg:

-- Function<String, String> upper = String::toUpperCase;

--

/*
* 函数式接口
* */
interface StringFunc {
    String func(String n);
}
class MyStringOps {
    //普通方法: 反转字符串
    public String strReverse(String str) {
        String result = "";
        for (int i = str.length() - 1; i >= 0; i--) {
            result += str.charAt(i);
        }
        return result;
    }
}
class MethodRefDemo2 {
    public static String stringOp(StringFunc sf, String s) {
        return sf.func(s);
    }
    public static void main(String[] args) {
        String inStr = "lambda add power to Java";
        MyStringOps strOps = new MyStringOps();//实例对象
        //MyStringOps::strReverse 相当于实现了接口方法func() ,并在接口方法func()中作了MyStringOps.strReverse()操作
        String outStr = stringOp(strOps::strReverse, inStr);

        System.out.println("Original string: " + inStr);
        System.out.println("String reserved: " + outStr);
    }
}

输出结果:

Original string: lambda add power to Java

String reserved: avaJ ot rewop dda adbmal

分析:

这里使用了类的名称,而不是具体的对象,尽管指定的是实例方法。使用这种形式时,函数式接口的第一个参数匹配调用对象,第二个参数匹配方法指定的参数。

-- 定义了一个方法counter(),用于统计某个数组中,满足函数式接口MyFunc的fun()方法定义的条件的对象个数。本例中,统计HighTemp类的实例个数。

interface MyFunc<T> {
    boolean func(T v1, T v2);
}
class HighTemp {
    private int hTemp;
    HighTemp(int ht) {
        hTemp = ht;
    }
    public boolean sameTemp(HighTemp ht2) {
        return hTemp == ht2.hTemp;
    }
    public boolean lessThanTemp(HighTemp ht2) {
        return hTemp < ht2.hTemp;
    }
}
class InstanceMethWithObjectRefDemo {
    public static <T> int counter(T[] vals, MyFunc<T> f, T v) {
        int count = 0;

        for (int i = 0; i < vals.length; i++) {
            if (f.func(vals[i], v)) count++;
        }
        return count;
    }
    public static void main(String[] args) {
        int count;
        HighTemp[] weekDayHighs = {
                new HighTemp(89), new HighTemp(82),
                new HighTemp(90), new HighTemp(89),
                new HighTemp(89), new HighTemp(91),
                new HighTemp(84), new HighTemp(83)};
        //HighTemp::sameTemp 为实例方法引用
        count = counter(weekDayHighs, HighTemp::sameTemp, new HighTemp(89));
        System.out.println(count + " days had a high of 89");
        HighTemp[] weekDayHighs2 = {
                new HighTemp(31), new HighTemp(12),
                new HighTemp(24), new HighTemp(19),
                new HighTemp(18), new HighTemp(12),
                new HighTemp(-1), new HighTemp(13)};

        count = counter(weekDayHighs2, HighTemp::sameTemp, new HighTemp(12));
        System.out.println(count + " days had a high of 12");

        count = counter(weekDayHighs, HighTemp::lessThanTemp, new HighTemp(89));
        System.out.println(count + " days had a high less than 89");

        count = counter(weekDayHighs2, HighTemp::lessThanTemp, new HighTemp(19));
        System.out.println(count + " days had a high of less than 19");
    }
}

输出结果:

3 days had a high of 89

2 days had a high of 12

3 days had a high less than 89

5 days had a hign less than 19

分析:

注意HighTemp有两个实例方法:someTemp()和lessThanTemp()。如果两个HighTemp对象包含相同的温度,sameTemp()方法返回true。如果调用对象的温度小于被传递的对象的温度,lessThanTemp()方法返回true。这两个方法都有一个HighTemp类型的参数,并且都返回布尔结果。因此,这两个方法都与MyFunc函数式接口兼容,因为调用对象类型可以映射到func()的第一个参数,传递的实参可以映射到func()的第二个参数。因此,这个表达式:HighTemp::sameTemp

被传递给counter()方法时,会创建函数式接口的一个实例,其中第一个参数的参数类型就是实例方法的调用对象的类型,也就是HighTemp。第二个参数的类型也是HighTemp,因为这是sameTemp()方法的参数。对于lessThanTemp(),这也是成立的。

Note:

上面程序中函数式接口中的函数boolean func(T v1,T v2)中含有两个参数,而HighTemp中函数sameTemp(HighTemp ht2)含有一个参数,但是能够兼容的原因是:

其实HighTemp类中的sameTemp(HighTemp ht2)其实包含两个参数,默认隐藏调用这个函数的引用this。

故,当使用类的实例方法作为方法引用时,函数式接口的第一个参数匹配类的实例方法的调用对象,第二个参数才匹配方法指定的参数。

b.超类上的实例方法引用

组成语法格式:super::methodName

方法的名称由methodName指定

通过使用super,可以引用方法的超类版本。

eg: super::name

Note:还可以捕获this 指针

this :: equals  等价于lambda表达式
 x -> this.equals(x);

c.类型上的实例方法引用

组成语法格式:ClassName::methodName

Note:

若类型的实例方法是泛型的,就需要在::分隔符前提供类型参数,或者(多数情况下)利用目标类型推导出其类型。

静态方法引用和类型上的实例方法引用拥有一样的语法。编译器会根据实际情况做出决定。

一般我们不需要指定方法引用中的参数类型,因为编译器往往可以推导出结果,但如果需要我们也可以显式在::分隔符之前提供参数类型信息。

eg:

String::toString 等价于lambda表达式 (s) -> s.toString()

这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。

在泛型类或泛型方法中,也可以使用方法引用。

interface MyFunc<T> {
    int func(T[] als, T v);
}
class MyArrayOps {
    public static <T> int countMatching(T[] vals, T v) {
        int count = 0;
        for (int i = 0; i < vals.length; i++) {
            if (vals[i] == v) count++;
        }
        return count;
    }
}
class GenericMethodRefDemo {
    public static <T> int myOp(MyFunc<T> f, T[] vals, T v) {
        return f.func(vals, v);
    }
    public static void main(String[] args){
        Integer[] vals = {1, 2, 3, 4, 2, 3, 4, 4, 5};
        String[] strs = {"One", "Two", "Three", "Two"};
        int count;
        count=myOp(MyArrayOps::<Integer>countMatching, vals, 4);
        System.out.println("vals contains "+count+" 4s");
        count=myOp(MyArrayOps::<String>countMatching, strs, "Two");
        System.out.println("strs contains "+count+" Twos");
    }
}

输出结果:

vals contains 3 4s

strs contains 2 Twos

分析:

在程序中,MyArrayOps是非泛型类,包含泛型方法countMatching()。该方法返回数组中与指定值匹配的元素的个数。注意这里如何指定泛型类型参数。例如,在main()方法中,对countMatching()方法的第一次调用如下所示:count = myOp(MyArrayOps::<Integer>countMatching,vals,4);

这里传递了类型参数Integer。

注意,参数传递发生在::的后面。这种语法可以推广。当把泛型方法指定为方法引用时,类型参数出现在::之后、方法名之前。但是,需要指出的是,在这种情况(和其它许多情况)下,并非必须显示指定类型参数,因为类型参数会被自动推断得出。对于指定泛型类的情况,类型参数位于类名的后面::的前面。

3)构造方法引用

构造方法引用又分构造方法引用和数组构造方法引用。

a.构造方法引用 (也可以称作构造器引用)

组成语法格式:Class::new

构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字

eg:

-- String::new, 等价于lambda表达式 () -> new
String()

--

List<String> strings = new ArrayList<String>();
strings.add("a");
strings.add("b");
Stream<Button> stream = strings.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());

-- 可以把这个引用赋值给定义的方法与构造函数兼容的任何函数式接口的引用

interface MyFunc {
    MyClass func(int n);
}
class MyClass {
    private int val;
    MyClass(int v) {
        val = v;
    }
    MyClass() {
        val = 0;
    }
    public int getValue() {
        return val;
    }
}
class ConstructorRefDemo {
    public static void main(String[] args) {
        MyFunc myClassCons = MyClass::new;
        MyClass mc = myClassCons.func(100);
        System.out.println("val in mc is: " + mc.getValue());
    }
}

输出结果:

val in mc is: 100

b.数组构造方法引用:

组成语法格式:TypeName[]::new

eg:

-- int[]::new 是一个含有一个参数的构造器引用,这个参数就是数组的长度。

等价于lambda表达式  x -> new int[x]。

-- 假想存在一个接收int参数的数组构造方法

IntFunction<int[]> arrayMaker = int[]::new;

int[] array = arrayMaker.apply(10) // 创建数组 int[10]

----------   诺诺学习技术交流博客、期待与您交流!  
 ----------

详情请查看:http://blog.csdn.net/sun_promise 

时间: 2024-10-01 07:54:26

Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)——诺诺"涂鸦"记忆的相关文章

Java 8 新特性1-函数式接口

Java 8 新特性1-函数式接口 (原) Lambda表达式基本结构: (param1,param2,param3) -> {代码块} 例1: package com.demo.jdk8; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; public class Test2 { public static void main(String[] args) { for_test

[转]Java 8新特性探究(lambda)

原文地址:http://my.oschina.net/benhaile/blog/175012 目录[-] 函数式接口 Lambda语法 方法引用 总结 函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是只包含一个方法的接口.比如Java标准库中的java.lang.Runnable和 java.util.Comparator都是典型的函数式接口.java 8提供 @FunctionalInterface作为注解,这个注解

Java 8新特性-4 方法引用

对于引用来说我们一般都是用在对象,而对象引用的特点是:不同的引用对象可以操作同一块内容! Java 8的方法引用定义了四种格式: 引用静态方法     ClassName :: staticMethodName 引用对象方法:  Object:: methodName 引用特定类型方法: ClassName :: methodName 引用构造方法: ClassName  :: new 静态方法引用示例 /** * 静态方法引用 * @param <P> 引用方法的参数类型 * @param

Java 8 新特性概述

Oracle 在 2014 年 3 月发布了 Java 8 正式版,该版本是一个有重大改变的版本,对 JAVA 带来了诸多新特性.其中主要的新特性涵盖:函数式接口.Lambda 表达式.集合的流式操作.注解的更新.安全性的增强.IO\NIO 的改进.完善的全球化功能等.本文将对 Java 8 中几个重要新特性进行介绍. 函数式接口 Java 8 引入的一个核心概念是函数式接口(Functional Interfaces).通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行.如果一个接口

Java 8新特性

现在,是时候把所有Java8的重要特性收集整理成一篇单独的文章了,希望这篇文章能给你带来阅读上的乐趣.开始吧! 目录结构 介绍 Java语言的新特性 2.1 Lambdas表达式与Functional接口 2.2 接口的默认与静态方法 2.3 方法引用 2.4 重复注解 2.5 更好的类型推测机制 2.6 扩展注解的支持 Java编译器的新特性 3.1 参数名字 Java 类库的新特性 4.1 Optional 4.2 Streams 4.3 Date/Time API (JSR 310) 4.

Java 8新特性之旅:使用Stream API处理集合

在这篇“Java 8新特性教程”系列文章中,我们会深入解释,并通过代码来展示,如何通过流来遍历集合,如何从集合和数组来创建流,以及怎么聚合流的值. 在之前的文章“遍历.过滤.处理集合及使用Lambda表达式增强方法”中,我已经深入解释并演示了通过lambda表达式和方法引用来遍历集合,使用predicate接口来过滤集合,实现接口的默认方法,最后还演示了接口静态方法的实现. 源代码都在我的Github上:可以从 这里克隆. 内容列表 使用流来遍历集合. 从集合或数组创建流. 聚合流中的值. 1.

Java 8新特性终极指南

目录结构 介绍 Java语言的新特性 2.1 Lambdas表达式与Functional接口 2.2 接口的默认与静态方法 2.3 方法引用 2.4 重复注解 2.5 更好的类型推测机制 2.6 扩展注解的支持 Java编译器的新特性 3.1 参数名字 Java 类库的新特性 4.1 Optional 4.2 Streams 4.3 Date/Time API (JSR 310) 4.4 JavaScript引擎Nashorn 4.5 Base64 4.6 并行(parallel)数组 4.7

Java 8新特性前瞻

快端午小长假了,要上线的项目差不多完结了,终于有时间可以坐下来写篇博客了. 这是篇对我看到的java 8新特性的一些总结,也是自己学习过程的总结. 几乎可以说java 8是目前为止,自2004年java 5发布以来的java世界中最大的事件了.它带来了java语言层面上的诸多改变,主要包括下面一些方面:语法.编译器.库.工具和运行时. 一,语法层面: 1,Lambda表达式. lambda表达式是一种可调用对象,它允许我们将函数作为函数参数传入.诸如C++.Groovy.Scala都已经支持la

Java 8 新特性:Java 类库的新特性之 Stream类 ——诺诺&quot;涂鸦&quot;记忆

----------   诺诺学习技术交流博客.期待与您交流!    ---------- 详情请查看:http://blog.csdn.net/sun_promise  Java 类库的新特性之 Stream类 (注:此文中涉及到的一部分图片为网络图片,若有问题,请联系我将其删除.) 一.Java8对IO/NIO 的改进 Java 8 对 IO/NIO 也做了一些改进,主要包括: 改进了java.nio.charset.Charset 的实现,使编码和解码的效率得以提升: 精简了jre/lib

Java 8 新特性 – 终极手册整理

1.简介 毫无疑问,Java 8是自Java  5(2004年)发布以来Java语言最大的一次版本升级,Java 8带来了很多的新特性,比如编译器.类库.开发工具和JVM(Java虚拟机).在这篇教程中我们将会学习这些新特性,并通过真实例子演示说明它们适用的场景. 本教程由下面几部分组成,它们分别涉及到Java平台某一特定方面的内容: 语言 编译器 类库 开发工具 运行时(Java虚拟机) 2.Java的新特性 总体来说,Java 8是一个大的版本升级.有人可能会说,Java 8的新特性非常令人