ホームページ  >  記事  >  Java  >  Java でのラムダ式の簡単な使用例

Java でのラムダ式の簡単な使用例

高洛峰
高洛峰オリジナル
2017-01-23 15:08:301527ブラウズ

Java のラムダ式に関する私の見解は非常に複雑です:

私の考えの 1 つは次のとおりです: ラムダ式は Java プログラムの読み取りエクスペリエンスを軽減します。 Java プログラムの表現力は決して優れたものではありません。それどころか、Java の人気を高めている要因の 1 つは、その安全性と保守性です。注意を払えば、初心者でも堅牢で保守しやすいコードを作成できます。ラムダ式は開発者にとって比較的高い要件を必要とするため、メンテナンスがより困難になります。

私がもう 1 つ考えているのは、プログラマーとして、言語の新しい機能を学び、受け入れる必要があるということです。読書体験が乏しいという理由だけでその表現上の利点を放棄すると、三眼表現ですら理解するのが難しいと感じる人もいるでしょう。言語も発達しており、ついていけない人は自ら置いていかれることになります。

取り残されたくない。しかし、選択を迫られるとしたら、私の決断はまだ比較的保守的です。Java 言語ではラムダを使用する必要はありません。ラムダを使用すると、現在の Java サークルの多くの人がラムダに慣れなくなり、人件費の増加が発生します。 。非常に気に入った場合は、scala の使用を検討してください。

とにかく、私は Lambda をマスターしようと始めました。結局のところ、仕事で保守されているコードのいくつかは Lambda を使用しています (信じてください、私は徐々に削除します)。私が勉強したチュートリアルは、Oracla-Java 公式 Web サイトにある関連チュートリアルです。

——————————

あなたは現在ソーシャルネットワーキングアプリケーションを作成していると仮定します。 1 つの機能は、管理者が指定された基準を満たすメンバーに対してメッセージの送信などの特定のアクションを実行できることです。次の表で、この使用例を詳しく説明します。

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: 指定された基準を満たすメンバーを見つけるメソッドを 1 つずつ作成します

これは、上記のケースを実装するための最も単純かつ大まかな解決策です。複数のメソッドを作成し、各メソッドが基準 (年齢や年齢など) をチェックするだけです。性別)。次のコードは、経過時間が指定された値より大きいかどうかをチェックします。

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 メソッドの 1 つのパラメーターは匿名クラスです。この匿名クラスの機能は、解決策 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 までご連絡ください。