Java8新特性:Lambda表达式之方法引用详解

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]

到此这篇关于Java8新特性:Lambda表达式之方法引用详解的文章就介绍到这了,更多相关Java8 Lambda表达式之方法引用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java8中lambda表达式的应用及一些泛型相关知识

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public class Employee { public String name; public int age; public char sex; public String time; public int salary; } 我们有一列此类数据 List<Employee> data = Arrays.asL

  • Java8新特性lambda表达式有什么用(用法实例)

    我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值.现有接口迁移成为lambda风格的问题已经通过default methods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulk operation),解开lambda最强作用的神秘面纱. 1.关于JSR335 JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其

  • Java8新特性Lambda表达式的一些复杂用法总结

    简介 lambda表达式是JAVA8中提供的一种新的特性,它支持Java也能进行简单的"函数式编程". 它是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数. 本文将介绍关于Java8 Lambda表达式的一些复杂用法,分享出来供大家参考学习,下面来一起看看详细的介绍: 复杂用法实例 传入数组ids,在list<Obj>上操作,找出Obj中id想匹配的,并且按

  • Java8 用Lambda表达式给List集合排序的实现

    Lambda用到了JDK8自带的一个函数式接口Comparator<T>. 准备一个Apple类 public class Apple { private int weight; private String color; public Apple(){} public Apple(int weight) { this.weight = weight; } public Apple(int weight, String color) { this.weight = weight; this.c

  • Java8深入学习系列(一)lambda表达式介绍

    前言 最近在学习java8,所以接下来会给大家介绍一系列的Java8学习内容,那么让我们先从lambda表达式开始. 众所周知从java8出现以来lambda是最重要的特性之一,它可以让我们用简洁流畅的代码完成一个功能. 很长一段时间java被吐槽是冗余和缺乏函数式编程能力的语言,随着函数式编程的流行java8种也引入了 这种编程风格.在此之前我们都在写匿名内部类干这些事,但有时候这不是好的做法,本文中将介绍和使用lambda, 带你体验函数式编程的魔力. 什么是lambda? lambda表达

  • Java8新特性之Lambda表达式浅析

    说到java 8,首先会想到lambda(闭包)以及虚拟扩展方法(default method),这个特性早已经被各大技术网站炒得沸沸扬扬了,也是我们java 8系列开篇要讲的第一特性(JEP126 http://openjdk.java.net/jeps/126),jdk8的一些库已经应用了lambda表达式重新设计了,理解他对学习java 8新特性有着重要的意义. 一.函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是

  • 如何更好的使用Java8中方法引用详解

    前言 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法.方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文.计算时,方法引用会创建函数式接口的一个实例. 当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些.方法引用是一种更简洁易懂的Lambda表达式. 注意:方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::". 在Java8中,使用方法引用非常简单,如String

  • Java8 Lamda方法引用和构造引用原理

    一方法引用概述 方法引用是特定Lamda表达式的一种简写,其思路就是能替换Lamda表达式就直接调用函数使用方法名. 其语法格式:类名 :: 方法名. 二3种方法引用 1 指向静态方法的引用 语法格式: 静态类名(ClassName)::方法名(MethodName) 示例: // 1 Lamda静态方法 @Test public void LamdaSTest(){ String youku1327 = "1327"; Function function = s -> Obje

  • 30分钟入门Java8之方法引用学习

    前言 之前两篇文章分别介绍了Java8的lambda表达式和默认方法和静态接口方法.今天我们继续学习Java8的新语言特性--方法引用(Method References). 在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法.然而,有时候我们仅仅是调用了一个已存在的方法.如下: Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2)); 在Java8中,我们可以直接通过方法引用来简写lambda表达式中已

  • Java8中如何通过方法引用获取属性名详解

    前言 在我们开发过程中常常有一个需求,就是要知道实体类中Getter方法对应的属性名称(Field Name),例如实体类属性到数据库字段的映射,我们常常是硬编码指定 属性名,这种硬编码有两个缺点. 1.编码效率低:因为要硬编码写属性名,很可能写错,需要非常小心,时间浪费在了不必要的检查上. 2.容易让开发人员踩坑:例如有一天发现实体类中Field Name定义的不够明确,希望换一个Field Name,那么代码所有硬编码的Field Name都要跟着变更,对于未并更的地方,是无法在编译期发现的

随机推荐