>  기사  >  Java  >  Java에서 람다 표현식의 간단한 사용 사례

Java에서 람다 표현식의 간단한 사용 사례

高洛峰
高洛峰원래의
2017-01-23 15:08:301535검색

Java의 람다 표현식에 대한 나의 견해는 매우 복잡합니다.

제 생각 중 하나는 람다 표현식이 Java 프로그램의 읽기 경험을 감소시킨다는 것입니다. Java 프로그램은 표현력이 뛰어난 적이 없습니다. 반대로 Java를 인기 있게 만드는 요인 중 하나는 안전성과 보수성입니다. 초보자도 주의를 기울이면 강력하고 유지 관리하기 쉬운 코드를 작성할 수 있습니다. 람다 식은 개발자에 대한 요구 사항이 상대적으로 높기 때문에 유지 관리가 더 어렵습니다.

또 제가 생각하는 점은 코더로서 언어의 새로운 기능을 배우고 수용하는 것이 필요하다는 것입니다. 단지 읽기 경험이 좋지 않다는 이유로 표현의 장점을 포기한다면 어떤 사람들은 삼안식 표현조차 이해하기 어려울 것입니다. 언어도 발전하는데, 따라가지 못하는 사람은 자발적으로 뒤처지게 된다.

뒤쳐지고 싶지 않아요. 그러나 선택을 해야 한다면 내 결정은 여전히 ​​상대적으로 보수적입니다. Java 언어에서는 람다를 사용할 필요가 없습니다. 이로 인해 현재 Java 서클의 많은 사람들이 익숙하지 않게 되고 인건비가 증가하게 됩니다. . 매우 마음에 든다면 스칼라 사용을 고려해 볼 수 있습니다.

아무튼, 저는 Lambda를 마스터하려고 노력하기 시작했습니다. 결국 직장에서 유지 관리하는 코드 중 일부는 Lambda를 사용합니다(믿어주세요. 점진적으로 제거하겠습니다). 제가 공부한 튜토리얼은 Oracla-Java 공식 홈페이지에 있는 관련 튜토리얼입니다.

——————————

현재 소셜 네트워킹 애플리케이션을 만들고 있다고 가정해 보겠습니다. 한 가지 기능은 관리자가 지정된 기준을 충족하는 구성원에 대해 메시지 보내기와 같은 특정 작업을 수행할 수 있다는 것입니다. 다음 표에서는 이 사용 사례를 자세히 설명합니다.

Java에서 람다 표현식의 간단한 사용 사례

다음 Person 클래스를 사용하여 소셜 네트워크의 회원 정보를 나타냅니다.

public class Person {
  
  public enum Sex {
    MALE, FEMALE
  }
  
  String name;
  LocalDate birthday;
  Sex gender;
  String emailAddress;
  
  public int getAge() {
    // ...
  }
  
  public void printPerson() {
    // ...
  }
}

모든 멤버가 List 인스턴스에 저장되어 있다고 가정합니다.

이 섹션에서는 매우 간단한 방법으로 시작한 다음 로컬 클래스와 익명 클래스를 사용하여 구현해 보고 마지막에는 점차적으로 람다 표현식의 강력함과 효율성을 경험하게 됩니다. 전체 코드는 여기에서 찾을 수 있습니다.

옵션 1: 메소드를 하나씩 생성하여 지정된 기준을 충족하는 멤버를 찾습니다

위에서 언급한 사례를 구현하는 가장 간단하고 대략적인 솔루션입니다. 여러 메소드를 생성하면 됩니다. 각 방법은 기준(예: 연령 또는 성별)을 확인합니다. 다음 코드 조각은 age가 지정된 값보다 큰지 확인합니다.

public static void printPersonsOlderThan(List<person> roster, int age) {
  for (Person p : roster) {
    if (p.getAge() >= age) {
      p.printPerson();
    }
  }
}

이것은 매우 취약한 솔루션이며 애플리케이션이 실행되지 않을 가능성이 매우 높습니다. 작은 업데이트. Person 클래스에 새 멤버 변수를 추가하거나 표준에서 연령 측정 알고리즘을 변경하면 이 변경 사항에 적응하기 위해 많은 코드를 다시 작성해야 합니다. 게다가 여기의 제한은 너무 엄격합니다. 예를 들어 특정 연령보다 어린 멤버를 인쇄하려면 어떻게 해야 할까요? 새로운 메소드 printPersonsYoungerThan을 추가하시겠습니까? 이는 분명히 어리석은 방법이다.

옵션 2: 보다 일반적인 방법 만들기

다음 방법은 printPersonsOlderThan보다 더 적합합니다. 이 방법은 지정된 연령 그룹 내의 회원 정보를 인쇄합니다.

public static void printPersonsWithinAgeRange(
    List<person> roster, int low, int high) {
  for (Person p : roster) {
    if (low <= p.getAge() && p.getAge() < high) {
      p.printPerson();
    }
  }
}

이때 새로운 아이디어가 떠오릅니다. 특정 성별의 회원정보를 출력하고 싶거나, 특정 성별과 연령을 모두 만족하는 회원정보를 출력하고 싶다면? Person 클래스를 조정하고 친근감이나 위치와 같은 속성을 추가하면 어떨까요? 이 메소드를 작성하는 것이 printPersonsYoungerThan보다 더 일반적이지만 가능한 모든 쿼리에 대해 메소드를 작성하면 코드가 불안정해집니다. 표준 검사 코드를 새로운 클래스로 분리하는 것이 좋습니다.

옵션 3: 로컬 클래스에서 표준 검사 구현

다음 메소드는 검색 기준을 충족하는 회원 정보를 인쇄합니다.

public static void printPersons(List<person> roster, CheckPerson tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

CheckPerso 개체 테스터는 프로그램에서 List 매개변수 로터의 각 인스턴스를 확인하는 데 사용됩니다. tester.test()가 true를 반환하면 printPerson() 메서드가 실행됩니다. 검색 기준을 설정하려면 CheckPerson 인터페이스를 구현해야 합니다.

다음 클래스는 CheckPerson을 구현하고 테스트 메서드의 특정 구현을 제공합니다. 이 클래스의 테스트 방법은 미국에서 군 복무 요건을 충족하는 구성원의 정보를 필터링합니다. 즉, 성별은 남성이고 연령은 18~25세입니다.

class CheckPersonEligibleForSelectiveService implements CheckPerson {
  public boolean test(Person p) {
    return p.gender == Person.Sex.MALE &&
        p.getAge() >= 18 &&
        p.getAge() <= 25;
  }
}

이 클래스를 사용하려면 인스턴스를 만들고 printPersons 메서드를 트리거하세요.

printPersons(
    roster, new CheckPersonEligibleForSelectiveService());

이제 코드가 덜 취약해 보입니다. Person 클래스 구조의 변경으로 인해 코드를 다시 작성할 필요가 없습니다. 그러나 여전히 추가 코드가 있습니다. 새로 정의된 인터페이스와 애플리케이션의 각 검색 기준에 대해 정의된 내부 클래스입니다.

CheckPersonEligibleForSelectiveService는 인터페이스를 구현하기 때문에 표준별로 내부 클래스를 정의하는 대신 익명 클래스를 사용할 수 있다.

옵션 4: 익명 클래스를 사용하여 표준 검사 구현

아래 호출된 printPersons 메서드의 매개 변수 중 하나는 익명 클래스입니다. 이 익명 클래스의 기능은 솔루션 3의 CheckPersonEligibleForSelectiveService 클래스와 동일합니다. 두 필터 멤버 모두 성별이 남성이고 18세에서 25세 사이입니다.

아아아아

这个方案减少了编码量,因为不再需要为每个要执行的检索方案创建新类。不过这样做仍让人有些不舒服:尽管CheckPerson接口只有一个方法,实现的匿名类仍是有些冗长笨重。此时可以使用Lambda表达式替换匿名类,下面会说下如何使用Lambda表达式替换匿名类。

方案五:使用Lambda表达式实现标准检查

CheckPerson接口是一个函数式接口。所谓的函数式接口就是指任何只包含一个抽象方法的接口。(一个函数式接口中也可以有多个default方法或静态方法)。既然函数式接口中只有一个抽象方法,那么在实现这个函数式接口的方法的时候可以省略掉方法的方法名。为了实现这个想法,可以使用Lambda表达式替换匿名类表达式。在下面重写的printPersons方法中,相关的代码做了高亮处理:

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

在这里还可以使用一个标准的函数接口来替换CheckPerson接口,从而进一步简化代码。

方案六:在Lambda表达式中使用标准函数式接口

再来看一下CheckPerson接口:

interface CheckPerson {
  boolean test(Person p);
}

这是一个非常简单的接口。因为只有一个抽象方法,所以它也是一个函数式接口。这个抽象方法只接受一个参数并返回一个boolean值。这个抽象接口太过简单了,以至于我们会考虑是否有必要在应用中定义一个这样的接口。此时可以考虑使用JDK定义的标准函数式接口,可以在java.util.function包下找到这些接口。

在这个例子中,我们就可以使用Predicate接口来替换CheckPerson。在这个接口中有一个boolean test(T t)方法:

interface Predicate<t> {
  boolean test(T t);
}

Predicate接口是一个泛型接口。泛型类(或者是泛型接口)使用一对尖括号()指定了一个或多个类型参数。在这个接口中只有一个类型参数。在使用具体类声明或实例化一个泛型类时,就会获得一个参数化类。比如说参数化类Predicate就是这样的:

interface Predicate<person> {
  boolean test(Person t);
}

在这个参数化类中有一个与CheckPerson.boolean test(Person p)方法的参数和返回值都一致的方法。因此就可以同如下方法所演示的一样使用Predicate接口来替换CheckPerson接口:

public static void printPersonsWithPredicate(
    List<person> roster, Predicate<person> tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

然后使用下面的代码就可以像方案三中一样筛选适龄服兵役的会员了:

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

有没有注意到,这里使用Predicate作为参数类型时并没有显式指定参数类型。这里并不是唯一适用lambda表达式的地方,下面的方案会介绍更多lambda表达式的用法。

方案七:在整个应用中使用lambda表达式

再来看一下printPersonsWithPredicate 方法,考虑是否可以在这里使用lambda表达式:

public static void printPersonsWithPredicate(
    List<person> roster, Predicate<person> tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

在这个方法中使用Predicate实例tester检查了roster中的每个Person实例。如果Person实例符合tester中定义的检查标准,将会触发Person实例的printPerson方法。

除了触发printPerson方法,满足tester标准的Person实例还可以执行其他的方法。可以考虑使用lambda表达式指定要执行的方法(私以为这个特性很好,解决了java中方法不能作为对象传递的问题)。现在需要一个类似printPerson方法的lambda表达式——一个只需要一个参数且返回为void的lambda表达式。记住一点:要使用lambda表达式,需要先实现一个函数式接口。在这个例子中需要一个函数式接口,其中只包含一个抽象方法,这个抽象方法有个类型为Person的参数,且返回为void。可以看一下JDK提供的标准函数式接口Consumer,它有一个抽象方法void accept(T t)正好满足这个要求。在下面的代码中使用一个Consumer的实例调用accept方法替换了p.printPerson():

public static void processPersons(
    List<person> roster,
    Predicate<person> tester,
    Consumer<person> block) {
  for (Person p : roster) {
    if (tester.test(p)) {
      block.accept(p);
    }
  }
}

对应这里,可以使用如下代码筛选适龄服兵役的会员:

processPersons(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.printPerson()
);

如果我们想做的事情不只是打印会员信息,而是更多的事情,比如验证会员身份、获取会员联系方式等等。此时,我们需要一个有返回值方法的函数式接口。JDK的标准函数式接口Function就有一个这样的方法R apply(T t)。下面的方法从参数mapper中获取数据,并在这些数据上执行参数block指定的行为:

public static void processPersonsWithFunction(
    List<person> roster,
    Predicate<person> tester,
    Function<person  , string> mapper,
    Consumer<string> block) {
  for (Person p : roster) {
    if (tester.test(p)) {
      String data = mapper.apply(p);
      block.accept(data);
    }
  }
}

下面的代码获取了roster中适龄服兵役的所有会员的邮箱信息并打印出来:

processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

方案八:多使用泛型

再来回顾一下processPersonsWithFunction方法。下面是这个方法的泛型版本,新方法在参数类型上要求更为宽容:

public static <x  , Y> void processElements(
    Iterable<x> source,
    Predicate<x> tester,
    Function<x  , Y> mapper,
    Consumer<y> block) {
  for (X p : source) {
    if (tester.test(p)) {
      Y data = mapper.apply(p);
      block.accept(data);
    }
  }
}

 

要打印适龄服兵役的会员信息可以像下面这样调用processElements方法:

processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

在方法的调用过程中执行了如下行为:

从一个集合中获取对象信息,在这个例子里是从集合实例roster中获取Person对象信息。
过滤能够匹配Predicate实例tester的对象。在这个例子里,Predicate对象是一个lambda表达式,它指定了过滤适龄服兵役的条件。

将过滤后的对象交给一个Function对象mapper处理,mapper会为这个对象匹配一个值。在这个例子中Function对象mapper是一个lambda表达式,它返回了每个会员的邮箱地址。

由Consumer对象block为mapper匹配的值指定一个行为。在这个例子里,Consumer对象是一个lambda表达式,它的作用是打印一个字符串,也就是Function实例mapper返回的会员邮件地址。

方案九:使用将lambda表达式作为参数的聚集操作

下面的代码中使用了聚集操作来打印roster集合中适龄服兵役会员的邮件地址:

roster.stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

分析下如上代码的执行过程,整理如下表:

Java에서 람다 표현식의 간단한 사용 사례

表中的filter、map和forEach操作都是聚集操作。聚集操作处理的元素来自Stream,而非是直接从集合中获取(就是因为这示例程序中调用的第一个方法是stream())。Stream是一个数据序列。和集合不同,Stream并没有用特定的结构存储数据。相反的,Stream从一个特定的源获取数据,比如从集合获取数据,通过一个pipeline。pipeline是一个Stream操作序列,在这个例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表达式作为参数,这也给了我们许多自定义的空间。

更多Java에서 람다 표현식의 간단한 사용 사례相关文章请关注PHP中文网!

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