ホームページ >Java >&#&チュートリアル >Java 8 の新機能: ラムダ式

Java 8 の新機能: ラムダ式

黄舟
黄舟オリジナル
2017-02-23 10:34:571463ブラウズ

要約: ラムダ式は、Java 8 によってもたらされたいくつかの重要な新機能の 1 つです。ラムダ式を借用すると、Java プログラムの設計をより簡潔にすることができます。この記事は Java 8 の最初の新機能であり、動作のパラメーター化、ラムダ式、メソッド参照について説明します。

ラムダ式は、java8 によってもたらされたいくつかの重要な新機能の 1 つです。ラムダ式を借用すると、Java プログラムの設計をより簡潔にすることができます。最近、新しいプロジェクトはバージョン 1.6 を放棄し、完全に Java8 に基づいて開発されています。この記事は Java8 の新機能に関する最初の記事であり、動作のパラメーター化、ラムダ式、メソッド参照について説明します。

1. 動作のパラメータ化

簡単に言うと、関数の本体にはテンプレート クラスの一般的なコードのみが含まれており、ビジネス シナリオに応じて変更される一部のロジックはパラメーターの形式で関数に渡されます。動作パラメータ化により、プログラムの汎用性が高まり、頻繁な変更に対応できるようになります。

プログラムを通じてリンゴをフィルタリングする必要があるとします。最初にリンゴのエンティティを定義します:

/**
 * 苹果实体
 *
 * @author zhenchao.wang 2016-09-17 12:49
 * @version 1.0.0
 */
public class Apple {
    /** 编号 */
    private long id;
    /** 颜色 */
    private Color color;
    /** 重量 */
    private float weight;
    /** 产地 */
    private String origin;
    public Apple() {
    }
    public Apple(long id, Color color, float weight, String origin) {
        this.id = id;
        this.color = color;
        this.weight = weight;
        this.origin = origin;
    }
    // 省略getter和setter
}

ユーザーの最初の要求は単にプログラムを通じて緑色のリンゴをフィルタリングすることであるため、すぐに実装できます。プログラムを通じてそれを実行します:

/**
 * 筛选绿苹果
 *
 * @param apples
 * @return
 */
public static List<Apple> filterGreenApples(List<Apple> apples) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (Color.GREEN.equals(apple.getColor())) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}

しばらくして、ユーザーが新しい要件を提示し、プログラムを通じて赤いリンゴをフィルタリングできるようにしたい場合は、赤いリンゴをフィルタリングする機能を追加しました:

/**
 * 筛选红苹果
 *
 * @param apples
 * @return
 */
public static List<Apple> filterRedApples(List<Apple> apples) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (Color.RED.equals(apple.getColor())) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}

より良い実装は、色をパラメータとして関数に渡すことです。そうすれば、将来ユーザーからのさまざまなカラー フィルター要求に対応できるようになります。

/**
 * 自定义筛选颜色
 *
 * @param apples
 * @param color
 * @return
 */
public static List<Apple> filterApplesByColor(List<Apple> apples, Color color) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (color.equals(apple.getColor())) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}

このように設計した後は、もう心配する必要はありません。ユーザー カラー スクリーニング要件が変更されましたが、残念なことに、ある日、ユーザーが、重量が特定の標準に達するリンゴを選択できるようにするという要件を提起しました。前のレッスンでは、重量標準もパラメータとしてスクリーニング関数に渡しました。それで取得:

/**
 * 筛选指定颜色,且重要符合要求
 *
 * @param apples
 * @param color
 * @param weight
 * @return
 */
public static List<Apple> filterApplesByColorAndWeight(List<Apple> apples, Color color, float weight) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (color.equals(apple.getColor()) && apple.getWeight() >= weight) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}

これは本当にパラメーターを渡す良い方法ですか?フィルタリング条件がますます多くなり、組み合わせモードがますます複雑になる場合、すべての状況を考慮し、それぞれの状況に対応する戦略を用意する必要がありますか? また、これらの関数はフィルタリング条件の一部が異なるだけで、残りは異なります。同じテンプレート コード (コレクションをトラバース) を使用する場合、この時点で動作をパラメーター化できます 。これにより、関数はテンプレート コードのみを保持し、フィルター条件を抽出してパラメーターとして渡します。java8 より前では、これは次のように実現されていました。フィルターインターフェイスの定義:

/**
 * 苹果过滤接口
 *
 * @author zhenchao.wang 2016-09-17 14:21
 * @version 1.0.0
 */
@FunctionalInterface
public interface AppleFilter {
    /**
     * 筛选条件抽象
     *
     * @param apple
     * @return
     */
    boolean accept(Apple apple);
}
/**
 * 将筛选条件封装成接口
 *
 * @param apples
 * @param filter
 * @return
 */
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (filter.accept(apple)) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}

上記の動作を抽象化した後、特定の呼び出し場所でフィルター条件を設定し、その条件をパラメータとしてメソッドに渡すことができます:



public static void main(String[] args) {
    List<Apple> apples = new ArrayList<>();
    // 筛选苹果
    List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
        @Override
        public boolean accept(Apple apple) {
            // 筛选重量大于100g的红苹果
            return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
        }
    });
}

上記の動作 パラメータ化メソッドは次を使用して実装されます。この設計は、


java.util.Comparator

java.util.concurrent.Callable

などの jdk 内でもよく使用されます。このタイプのインターフェイスを使用する場合、特定の呼び出し場所で匿名クラスを使用して、特定の実行ロジックを指定できます。この関数は、上記のコード ブロックから判断すると、非常にマニアックではありますが、十分に簡潔ではありません。Java8 では、ラムダ式を使用して簡素化できます。



// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);

ラムダ式を使用して大幅に簡素化されています。 java のラムダ式~



2. ラムダ式の定義

まずラムダ式を明確にする必要がありますが、式は本質的には関数に属しません。特定のクラスには、パラメータ リスト、関数本体、戻り値の型があり、例外をスローする機能があります。第二に、ラムダ式には特定の関数名がありません。ラムダ式はパラメータのように渡すことができます。コードの記述が大幅に簡素化されます。形式は次のように定義されます:

形式 1: パラメータ リスト-> 式 形式 2: パラメータ リスト-> {式セット} ラムダ式は return キーワードを意味するので、単一の In式の場合、明示的に return キーワードを記述する必要はありませんが、式がステートメントのコレクションである場合は、明示的に return を追加し、複数の式を中括弧

{ }

で囲む必要があります。例をいくつか見てみましょう。

//返回给定字符串的长度,隐含return语句
(String s) -> s.length() 
// 始终返回42的无参方法
() -> 42 
// 包含多行表达式,则用花括号括起来
(int x, int y) -> {
    int z = x * y;
    return x + z;
}

3. 関数型インターフェースに依存してラムダ式を使用する


ラムダ式の使用には関数型インターフェースの助けが必要です。つまり、関数型インターフェースが出現した場合にのみ、ラムダ式を使用して式を表現できます。式が簡素化されます。

カスタム関数型インターフェース


関数型インターフェースは、
1 つの抽象メソッド

のみを持つインターフェースとして定義されます。 Java 8 のインターフェース定義の改良点は、デフォルト・メソッドの導入です。これにより、デフォルト・メソッドがいくつあっても、抽象メソッドが 1 つだけ存在する限り、インターフェース内のメソッドにデフォルト実装を提供できるようになります。 (上記の AppleFilter を引用):

/**
 * 苹果过滤接口
 *
 * @author zhenchao.wang 2016-09-17 14:21
 * @version 1.0.0
 */
@FunctionalInterface
public interface AppleFilter {
    /**
     * 筛选条件抽象
     *
     * @param apple
     * @return
     */
    boolean accept(Apple apple);
}
AppleFilter
には 1 つの抽象メソッド
accept(Apple apple)

のみが含まれており、インターフェースを定義する際には、これを関数インターフェースとみなします。追加されました
@FunctionalInterface

注解,用于标记该接口是函数式接口,不过这个接口是可选的,当添加了该接口之后,编译器就限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。

jdk自带的函数式接口

jdk为lambda表达式已经内置了丰富的函数式接口,如下表所示(仅列出部分):




函数式接口 函数描述符 原始类型特化
Predicate8742468051c85b06f0a0af9e3e506b5c T -> boolean IntPredicate, LongPredicate, DoublePredicate
Consumer8742468051c85b06f0a0af9e3e506b5c T -> void IntConsumer, LongConsumer, DoubleConsumer
Funcation469df1b70b2914a3841e0404ec3e3d5d T -> R IntFuncation0f1763a9dfcc95d54eac98034f0cdcdd, IntToDoubleFunction, IntToLongFunction0f1763a9dfcc95d54eac98034f0cdcdd, LongFuncation…
Supplier8742468051c85b06f0a0af9e3e506b5c () -> T BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator8742468051c85b06f0a0af9e3e506b5c T -> T IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator8742468051c85b06f0a0af9e3e506b5c (T, T) -> T IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicateedf640cc243cef8451ed278a13e0f190 (L, R) -> boolean
BiConsumerabbd655bd3f9f929be0207abcc18a2ef (T, U) -> void
BiFunction96b8c9ba1c8b330d622a8468a6113c2e (T, U) -> R

下面分别就

Predicate<T>

Consumer<T>

Function<T, R>

的使用示例说明。

Predicate8742468051c85b06f0a0af9e3e506b5c

@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

Predicate的功能类似于上面的

AppleFilter

,利用我们在外部设定的条件对于传入的参数进行校验,并返回验证结果

boolean

,下面利用

Predicate

对List集合的元素进行过滤:

/**
 * 按照指定的条件对集合元素进行过滤
 *
 * @param list
 * @param predicate
 * @param 
 * @return
 */
public  List filter(List list, Predicate<T> predicate) {
    List newList = new ArrayList();
    for (final T t : list) {
        if (predicate.test(t)) {
            newList.add(t);
        }
    }
    return newList;
}

利用上面的函数式接口过滤字符串集合中的空字符串:

demo.filter(list, (String str) -> null != str && !str.isEmpty());
Consumer<T>
@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

Consumer提供了一个accept抽象函数,该函数接收参数,但不返回值,下面利用

Consumer

遍历集合:

/**
 * 遍历集合,执行自定义行为
 *
 * @param list
 * @param consumer
 * @param 
 */
public  void filter(List list, Consumer<T> consumer) {
    for (final T t : list) {
        consumer.accept(t);
    }
}

利用上面的函数式接口,遍历字符串集合,并打印非空字符串:

demo.filter(list, (String str) -> {
        if (StringUtils.isNotBlank(str)) {
            System.out.println(str);
        }
    });

Function469df1b70b2914a3841e0404ec3e3d5d

@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

Funcation执行转换操作,输入是类型T的数据,返回R类型的数据,下面利用

Function

对集合进行转换:

/**
 * 遍历集合,执行自定义转换操作
 *
 * @param list
 * @param function
 * @param 
 * @param 
 * @return
 */
public  List filter(List list, Function<T, R> function) {
    List newList = new ArrayList();
    for (final T t : list) {
        newList.add(function.apply(t));
    }
    return newList;
}

下面利用上面的函数式接口,将一个封装字符串(整型数字的字符串表示)的接口,转换成整型集合:

demo.filter(list, (String str) -> Integer.parseInt(str));
上面这些函数式接口还提供了一些逻辑操作的默认实现,留到后面介绍java8接口的默认方法时再讲吧~

使用过程中需要注意的一些事情

类型推断

在编码过程中,有时候可能会疑惑我们的调用代码会去具体匹配哪个函数式接口,实际上编译器会根据参数、返回类型、异常类型(如果存在)等做正确的判定。

在具体调用时,在一些时候可以省略参数的类型,从而进一步简化代码:

/

/ 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// 某些情况下我们甚至可以省略参数类型,编译器会根据上下文正确判断
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);

局部变量

上面所有例子我们的lambda表达式都是使用其主体参数,我们也可以在lambda中使用局部变量,如下:

int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);

该例子中我们在lambda中使用了局部变量weight,不过在lambda中使用局部变量必须要求该变量 显式声明为final或事实上的final ,这主要是因为局部变量存储在栈上,lambda表达式则在另一个线程中运行,当该线程视图访问该局部变量的时候,该变量存在被更改或回收的可能性,所以用final修饰之后就不会存在线程安全的问题。

四. 方法引用

采用方法引用可以更近一步的简化代码,有时候这种简化让代码看上去更加的直观,先看一个例子:

/* ... 省略apples的初始化操作 */
// 采用lambda表达式
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// 采用方法引用
apples.sort(Comparator.comparing(Apple::getWeight));

方法引用通过

::

将方法隶属和方法自身连接起来,主要分为三类:

静态方法

(args) -> ClassName.staticMethod(args)


转换成

ClassName::staticMethod


参数的实例方法

(args) -> args.instanceMethod()


转换成

ClassName::instanceMethod  // ClassName是args的类型


外部的实例方法

(args) -> ext.instanceMethod(args)


转换成

ext::instanceMethod(args)

 以上就是Java8 新特性之 Lambda 表达式 的内容,更多相关内容请关注PHP中文网(www.php.cn)!



声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。