>  기사  >  Java  >  Java Lambda 표현식에 대한 자세한 설명 및 샘플 코드

Java Lambda 표현식에 대한 자세한 설명 및 샘플 코드

高洛峰
高洛峰원래의
2017-01-23 13:24:521538검색

Java 람다 표현식은 Java 8에 도입된 새로운 기능입니다. 함수형 프로그래밍을 시뮬레이션하기 위한 구문 설탕이라고 할 수 있습니다. Javascript의 클로저와 유사하지만 주요 목적은 함수형 구문을 제공하는 것입니다. 코딩을 단순화합니다.

람다 기본 구문

람다의 기본 구조는 (인수) -> (a) -> System.out.println(a)

매개변수가 하나만 있고 유형을 추론할 수 있는 경우 ()를 쓰는 것은 필수가 아닙니다. -> System.out.println(a )

매개변수 유형을 지정할 때는 (int a) -> System.out.println(a)

()와 같이 매개변수가 비어 있을 수 있습니다. -> System .out.println(“hello”)

문이 하나만 있는 경우 본문은 {}를 사용해야 합니다. , {}는 생략 가능합니다.

일반적인 작성 방법은 다음과 같습니다.

(a) -> a * a

(int a, int b) - > a + b

(a, b) -> {return a - b;}

() -> System.out.println(Thread.currentThread().getId())


기능적 인터페이스 FunctionalInterface

개념

Java Lambda 표현 기능적 인터페이스가 기본입니다. 기능적 인터페이스(FunctionalInterface)란 무엇입니까? 간단히 말해서, 하나의 메소드(함수)만 있는 인터페이스입니다. 이 유형의 인터페이스의 목적은 단일 함수에 해당하는 단일 작업을 위한 것입니다. Runnable 및 Comparator와 같은 공통 인터페이스는 기능적 인터페이스이며 @FunctionalInterface로 주석이 추가됩니다.

이해하기 쉽도록 Thread를 예로 들어보겠습니다. Runnable 인터페이스는 스레드 프로그래밍에 일반적으로 사용되는 인터페이스로 스레드의 실행 논리인 void run() 메서드를 포함합니다. 이전 구문에 따르면 새 스레드를 생성할 때 일반적으로 다음과 같이 Runnable 익명 클래스를 사용합니다.

new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println(Thread.currentThread().getId());
  }
 
}).start();

너무 많이 쓰면 지루할 수 있으며, Lambda는 다음과 같이 간결하고 명확해집니다.

new Thread(() -> System.out.println(Thread.currentThread().getId())).start();

Thread의 매개변수에 주의하세요. Runnable의 익명 구현은 다음과 같이 작성하면 이해하는 것이 좋습니다.

Runnable r = () -> .println(Thread.currentThread().getId ());

new Thread(r).start();


물론 Lambda의 목적은 간결하게 작성하는 것뿐만 아니라, 그러나 그것을 이해한 후에 더 높은 수준의 목적을 요약합니다.

비교기의 또 다른 예를 보면 전통적인 작성 방식에 따르면 다음과 같습니다.

Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, new Comparator<Integer>() {
  @Override
  public int compare(Integer o1, Integer o2) {
    return o1 - o2;
  }
});

Lambda 표현식은 다음과 같이 작성됩니다.

정수[] a = {1, 8, 3, 9, 2, 0, 5};

Arrays.sort(a, (o1, o2) -> o1 - o2);


JDK 기능적 인터페이스

기존 클래스 라이브러리에서 Lambda 표현식을 직접 사용하기 위해 기능적 인터페이스로 표시된 일부 인터페이스는 Java 8 이전에 존재했습니다.

java.lang.Runnable

java.util.Comparator

java.util.concurrent.Callable

java.io.FileFilter

java.security.PrivilegedAction

java.beans.PropertyChangeListener

Java 8에는 일반적으로 사용되는 기능적 인터페이스를 제공하는 새로운 패키지 java.util.function이 추가되었습니다.

Function469df1b70b2914a3841e0404ec3e3d5d : 입력 T 출력 R

BiFunction - 함수: 입력 T 및 U 출력 R 객체

술어 - 주장/판단: 입력 T 출력 부울

BiPredicate - 주장/판단: 입력 T 및 U 출력 부울

공급자 - 생산자: 입력 없음, 출력 T

소비자 - 소비자: 입력 T, 출력 없음

BiConsumerabbd655bd3f9f929be0207abcc18a2ef - 소비자: 입력 T 및 U, 출력 없음

UnaryOperator8742468051c85b06f0a0af9e3e506b5c

BinaryOperator - 이진 연산: 입력 T 및 T 출력 T

또한 기본 유형 처리를 위해 :BooleanSupplier, DoubleBinaryOperator, DoubleConsumer, DoubleFunction<를 포함한 보다 구체적인 기능과 인터페이스가 추가되었습니다. ;R>, DoublePredicate, DoubleSupplier, DoubleToIntFunction, DoubleToLongFunction, DoubleUnaryOperator, IntBinaryOperator, IntConsumer, IntFunction, IntPredicate, IntSupplier, IntToDoubleFunction, IntToLongFunction, IntUnary 연산자, LongBinaryOperator, LongConsumer,LongFunction& lt;R>, LongPredicate, LongSupplier, LongToDoubleFunction, LongToIntFunction , LongUnaryOperator, ToDoubleBiFunction, ToDoubleFunction, ToIntBiFunction, ToIntFunction, ToLongBiFunction, ToL ongFunction. 위의 기능적 인터페이스와 결합하면 이러한 기본 유형의 기능적 인터페이스의 기능을 클래스 이름을 통해 한눈에 알 수 있습니다.


기능적 인터페이스 만들기

때로는 함수형 인터페이스를 직접 구현해야 하는 경우도 있습니다. 방법도 매우 간단합니다. 먼저 이 인터페이스가 하나의 함수 작업만 가질 수 있는지 확인해야 합니다. 인터페이스 유형을 @FunctionalInterface로 표시하면 됩니다.

유형 파생

유형 파생은 람다 표현식의 기본이며 유형 파생 프로세스는 람다 표현식의 컴파일 프로세스입니다. 다음 코드를 예로 들어 보겠습니다.

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);
编译期间,我理解的类型推导的过程如下:

先确定目标类型 Function

Function 作为函数式接口,其方法签名为:Integer apply(String t)

检测 str -> Integer.parseInt(str) 是否与方法签名匹配(方法的参数类型、个数、顺序 和返回值类型)

如果不匹配,则报编译错误

这里的目标类型是关键,通过目标类型获取方法签名,然后和 Lambda 表达式做出对比。

方法引用

方法引用(Method Reference)的基础同样是函数式接口,可以直接作为函数式接口的实现,与 Lambda 表达式有相同的作用,同样依赖于类型推导。方法引用可以看作是只调用一个方法的 Lambda 表达式的简化。

方法引用的语法为: Type::methodName 或者 instanceName::methodName , 构造函数对应的 methodName 为 new。

例如上面曾用到例子:

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);

对应的方法引用的写法为

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::parseInt;

根据方法的类型,方法引用主要分为一下几种类型,构造方法引用、静态方法引用、实例上实例方法引用、类型上实例方法引用等

构造方法引用

语法为: Type::new 。 如下面的函数为了将字符串转为数组

方法引用写法

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::new;

Lambda 写法

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> new Integer(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
  @Override
  public Integer apply(String str) {
    return new Integer(str);
  }
};

数组构造方法引用

语法为: Type[]::new 。如下面的函数为了构造一个指定长度的字符串数组

方法引用写法

Function440e1640c9faa3393c37ba0de3f32bfa fixedArray = String[]::new;

方法引用写法

Function440e1640c9faa3393c37ba0de3f32bfa fixedArray = length -> new String[length];

传统写法

Function<Integer, String[]> fixedArray = new Function<Integer, String[]>() {
  @Override
  public String[] apply(Integer length) {
    return new String[length];
  }
};

静态方法引用

语法为: Type::new 。 如下面的函数同样为了将字符串转为数组

方法引用写法

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::parseInt;

Lambda 写法

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
  @Override
  public Integer apply(String str) {
    return Integer.parseInt(str);
  }
};

实例上实例方法引用

语法为: instanceName::methodName 。如下面的判断函数用来判断给定的姓名是否在列表中存在

Listf7e83be87db5cd2d9a8a0b8117b38cd4 names = Arrays.asList(new String[]{"张三", "李四", "王五"});
Predicatef7e83be87db5cd2d9a8a0b8117b38cd4 checkNameExists = names::contains;
System.out.println(checkNameExists.test("张三"));
System.out.println(checkNameExists.test("张四"));

类型上实例方法引用

语法为: Type::methodName 。运行时引用是指上下文中的对象,如下面的函数来返回字符串的长度

Function<String, Integer> calcStrLength = String::length;
System.out.println(calcStrLength.apply("张三"));
List<String> names = Arrays.asList(new String[]{"zhangsan", "lisi", "wangwu"});
names.stream().map(String::length).forEach(System.out::println);<br>

   

又比如下面的函数已指定的分隔符分割字符串为数组

BiFunction505cb6255f356d4ffe44ba9665547740 split = String::split;
String[] names = split.apply("zhangsan,lisi,wangwu", ",");
System.out.println(Arrays.toString(names));

Stream 对象

概念

什么是 Stream ? 这里的 Stream 不同于 io 中的 InputStream 和 OutputStream,Stream 位于包 java.util.stream 中, 也是 java 8 新加入的,Stream 只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。什么是聚合操作?简单举例来说常见的有平均值、最大值、最小值、总和、排序、过滤等。

Stream 的几个特征:

单次处理。一次处理结束后,当前Stream就关闭了。
支持并行操作
常见的获取 Stream 的方式

从集合中获取

Collection.stream();
Collection.parallelStream();

静态工厂

Arrays.stream(array)
Stream.of(T …)
IntStream.range()
这里只对 Stream 做简单的介绍,下面会有具体的应用。要说 Stream 与 Lambda 表达式有什么关系,其实并没有什么特别紧密的关系,只是 Lambda 表达式极大的方便了 Stream 的使用。如果没有 Lambda 表达式,使用 Stream 的过程中会产生大量的匿名类,非常别扭。

举例

以下的demo依赖于 Employee 对象,以及由 Employee 对象组成的 List 对象。

public class Employee {
 
  private String name;
  private String sex;
  private int age;
 
  public Employee(String name, String sex, int age) {
    super();
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  public String getName() {
    return name;
  }
 
  public String getSex() {
    return sex;
  }
  public int getAge() {
    return age;
  }
  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("Employee {name=").append(name).append(", sex=").append(sex).append(", age=").append(age)
        .append("}");
    return builder.toString();
  }
}
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("张三", "男", 25));
employees.add(new Employee("李四", "女", 24));
employees.add(new Employee("王五", "女", 23));
employees.add(new Employee("周六", "男", 22));
employees.add(new Employee("孙七", "女", 21));
employees.add(new Employee("刘八", "男", 20));

   

打印所有员工

Collection 提供了 forEach 方法,供我们逐个操作单个对象。

employees.forEach(e -> System.out.println(e)); 
或者
employees.stream().forEach(e -> System.out.println(e));

按年龄排序

Collections.sort(employees, (e1, e2) -> e1.getAge() - e2.getAge());
employees.forEach(e -> System.out.println(e));
或者
employees.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(e -> System.out.println(e));
打印年龄最大的女员工

max/min 返回指定排序条件下最大/最小的元素

Employee maxAgeFemaleEmployee = employees.stream()
    .filter(e -> "女".equals(e.getSex()))
    .max((e1, e2) -> e1.getAge() - e2.getAge())
    .get();
System.out.println(maxAgeFemaleEmployee);

   

打印出年龄大于20 的男员工

filter 可以过滤出符合条件的元素

employees.stream()
        .filter(e -> e.getAge() > 20 && "男".equals(e.getSex()))
        .forEach(e -> System.out.println(e));
打印出年龄最大的2名男员工

limit 方法截取有限的元素

employees.stream()
    .filter(e -> "男".equals(e.getSex()))
    .sorted((e1, e2) -> e2.getAge() - e1.getAge())
    .limit(2)
    .forEach(e -> System.out.println(e));

   

打印出所有男员工的姓名,使用 , 分隔

map 将 Stream 中所有元素的执行给定的函数后返回值组成新的 Stream

String maleEmployeesNames = employees.stream()
    .map(e -> e.getName())
    .collect(Collectors.joining(","));
System.out.println(maleEmployeesNames);

   

统计信息

IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics 包含了 Stream 中的汇总数据。

IntSummaryStatistics stat = employees.stream()
    .mapToInt(Employee::getAge).summaryStatistics();
System.out.println("员工总数:" + stat.getCount());
System.out.println("最高年龄:" + stat.getMax());
System.out.println("最小年龄:" + stat.getMin());
System.out.println("平均年龄:" + stat.getAverage());

   

总结

Lambda 表达式确实可以减少很多代码,能提高生产力,当然也有弊端,就是复杂的表达式可读性会比较差,也可能是还不是很习惯的缘故吧,如果习惯了,相信会喜欢上的。凡事都有两面性,就看我们如何去平衡这其中的利弊了,尤其是在一个团队中。

以上就是对Java8 JavaLambda 的资料整理,后续继续补充相关资料谢谢大家对本站的支持!

更多Java Lambda 表达式详解及示例代码相关文章请关注PHP中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.