Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)

简介: Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)

方法引用


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


1.方法引用简述


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


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


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


2.作用


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


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


3.组成


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



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


eg:


image.png

image.png

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]



目录
相关文章
|
9天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
33 17
|
2天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
8 2
|
6天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
4天前
|
Java Spring
JAVA获取重定向地址URL的两种方法
【10月更文挑战第17天】本文介绍了两种在Java中获取HTTP响应头中的Location字段的方法:一种是使用HttpURLConnection,另一种是使用Spring的RestTemplate。通过设置连接超时和禁用自动重定向,确保请求按预期执行。此外,还提供了一个自定义的`NoRedirectSimpleClientHttpRequestFactory`类,用于禁用RestTemplate的自动重定向功能。
|
8天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
79 38
|
5天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
9天前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
25 1
[Java]线程生命周期与线程通信
|
7天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
1天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
8 1