189 8069 5689

Lambda表达式和方法引用-创新互联

目录

创新互联建站是一家专业从事网站建设、网站设计、网页设计的品牌网络公司。如今是成都地区具影响力的网站设计公司,作为专业的成都网站建设公司,创新互联建站依托强大的技术实力、以及多年的网站运营经验,为您提供专业的成都网站建设、营销型网站建设及网站设计开发服务!

1、Lambda 表达式的使用

(1)方案一:创建符合匹配条件的方法

(2)方案二:创建更通用的匹配方法

(3)方案三:在局部类中指定匹配的条件

(4)方案四:在匿名类中指定匹配的条件

(5)方案五:使用 Lambda 表达式指定需要匹配的条件

(6)方案六:使用 Lambda 表达式的标准函数式接口

(7)方案七:在整个应用程序中使用 Lambda 表达式

(8)方案八:更广泛的使用泛型

(9)方案九:使用 Lambda 表达式作为参数的聚合操作(stream 流)

2、方法引用

(1)静态方法引用

(2)实例方法引用

(3)对特定类型的实例方法引用

(4)构造函数的引用


2022年圣诞节到来啦,很高兴这次我们又能一起度过~

1、Lambda 表达式的使用

匿名内部类的一个问题是,如果匿名类的实现非常简单,比如只包含一个方法的接口,那么匿名类的语法可能显得笨拙和不清楚。在有些情况下,通常试图将方法作为参数传递给另一个方法,那么 Lambda 表达式使你能够做到这一点,将功能作为方法参数,或将代码作为数据进行传递。

尽管匿名内部类比命名类更简洁,但对于只有一个方法的类来说,即使是匿名类也有点过多和麻烦。Lambda 表达式让你可以更紧凑地表达单方法类的实例。

假设你正在创建一个社交网络的应用程序。你希望创建一个特性,使管理员能够对满足特定条件的成员执行任何类型的操作,例如发送消息。这个应用程序的成员由以下Person类表示:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }

    public void printPerson() {
        // ...
    }
}

假设应用程序的成员都存储在 List实例中。本文从一个简单的方法开始。首先使用局部类和匿名类对匹配方法进行改进,最后使用 Lambda 表达式以一种高效而简洁的方式结束该操作。

(1)方案一:创建符合匹配条件的方法

最简单的方案是为每个条件创建不同的匹配方法;每个方法都会匹配某个特征(如性别或年龄)的成员。如下面的方法匹配大于指定年龄的成员:

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

但是,这种方案会使你的程序变得很脆弱。如果 Person 类的结构发生了变更,例如,使用不同的数据类型或算法记录和测量年龄,为了适应这种变化,你将不得不重写大量的 API。另外,该方式也限制了使用的条件,如果想匹配小于某个年龄的成员,那又该怎么办呢?

(2)方案二:创建更通用的匹配方法

下面的方法比 printPersonsOlderThan 更通用;它匹配指定年龄范围内的成员:

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

如果还想指定成员的性别,或者指定性别和年龄范围的组合,该怎么办呢?如果此时更改 Person 类并添加其他属性(如关系状态或地理位置),该怎么办呢?尽管上边这个方法比 printPersonsOlderThan 更加通用,但是尝试为每个可能的匹配条件都创建一个单独的方法仍然会导致脆弱的代码。相反,可以尝试将指定要匹配的条件分离到不同的类中。

(3)方案三:在局部类中指定匹配的条件

下面的方法匹配指定条件的成员:// CheckPerson 为匹配类

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

该方案通过调用方法 tester. test() 检查 List 参数中包含的每个 Person 实例是否满足 CheckPerson.test() 中指定的条件。如果方法 tester. test() 返回一个 true 值,那么在 Person 实例上调用 printPersons 方法。

要指定匹配条件,还需要实现 CheckPerson 接口:

interface CheckPerson {
    boolean test(Person p);
}

下面的类通过对指定方法 test() 的实现,来实现 CheckPerson 接口。该方法用来过滤指定条件的成员:如果其 Person 为男性且年龄在18到25之间,则返回 true:// 把匹配条件写在局部类中

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 () 方法的调用的参数之一是一个匿名类,它过滤指定条件的成员:年龄在18到25岁之间的男性:// 只包含一个方法的接口

printPersons( roster, new CheckPerson() {
        public boolean test(Person p) {
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge()<= 25;
        }
    }
);

这种方法减少了所需的代码量,因为你不必为每个匹配条件都创建一个新的类。然而,考虑到 CheckPerson 接口只包含一个方法,此时匿名类的语法就显得非常庞大。在这种情况下,你可以使用 lambda 表达式来替换匿名类。

(5)方案五:使用 Lambda 表达式指定需要匹配的条件

CheckPerson 接口是一个函数式接口。所谓函数式接口就是只包含一个抽象方法的接口(一个函数式接口可以包含一个或多个默认方法或静态方法)。因为函数式接口只包含一个抽象方法,所以在实现该方法时就可以省略该方法的名称。示例如下所示:

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

当然,你还可以使用标准的函数式接口来代替 CheckPerson 接口,这样就进一步减少了所需的代码量。

(6)方案六:使用 Lambda 表达式的标准函数式接口

  原有的 CheckPerson 接口如下:

interface CheckPerson {
    boolean test(Person p);
}

这是一个非常简单的接口。因为它只包含一个抽象方法,所以它是一个函数式接口。接口中,该方法接受一个参数并返回一个布尔值。因为这个方法非常简单,可能都不值得在应用程序中去定义一个。因此,JDK 定义了几个标准的函数式接口,你可以在 java.util.function 包中找到它们。

例如,你可以使用 Predicate接口来代替 CheckPerson。此接口包含一个方法:boolean test(T t):

interface Predicate{
    boolean test(T t);
}

接口 Predicate是泛型接口的一个例子。此接口仅包含一个泛型的参数 T。当你使用实际的类型声明或实例化泛型类型时,你就拥有了具体的参数化类型。例如,参数化类型Predicate如下所示:

interface Predicate{
    boolean test(Person t);
}

此参数化类型包含一个方法,该方法具有与 CheckPerson 相同的返回类型和参数。因此,你可以使用 Predicate来代替 CheckPerson,如下所示:

public static void printPersonsWithPredicate(
    Listroster, Predicatetester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

因此,下面方法的调用与方式三中调用 printPersons 时的方式相同:

printPersonsWithPredicate( roster, p ->p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge()<= 25
);
(7)方案七:在整个应用程序中使用 Lambda 表达式

  接下来重新考虑 printPersonsWithPredicate 方法,看看还有哪些地方可以使用 Lambda 表达式:

public static void printPersonsWithPredicate(Listroster, 
                                        Predicatetester){
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

假设你想要一个类似于 printPerson 的 Lambda 表达式,该表达式接受一个参数(Person类型的对象)并返回 void。记住,要使用 Lambda 表达式,你需要实现一个函数式接口。在这种情况下,你需要一个抽象方法,该方法可以接受一个 Person 类型的参数并返回 void。Consumer接口包含一个方法 void accept(T t),该方法具有这些特征。下面将调用 p.printPerson() 方法替换为调用 Consumer实例的 accept() 方法:

public static void processPersons(Listroster, Predicatetester, 
        Consumerblock) {
            for (Person p : roster) {
                if (tester.test(p)) {
                    block.accept(p);
            }
        }
}

因此,下面方法的调用与方式三中调用 printPersons 时的方式相同:

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

如果还想对成员做更多的事情而不是仅仅把它们的资料打印出来呢?假设你想验证成员的配置文件或得到他们的联系信息,在这种情况下,你需要一个包含返回值的抽象方法的函数式接口。Function接口包含方法 apply(T t) 可以满足这一要求,下面的代码展示了这一点:

public static void processPersonsWithFunction( Listroster,
    Predicatetester, Functionmapper, Consumerblock) {
        for (Person p : roster) {
            if (tester.test(p)) {
                String data = mapper.apply(p);
                block.accept(data);
        }
    }
}

下边代码从列表中选择符合条件的成员,并打印符合条件成员的电子邮件地址:

processPersonsWithFunction(
    roster,
    p ->p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge()<= 25,
    p ->p.getEmailAddress(),
    email ->System.out.println(email)
);
(8)方案八:更广泛的使用泛型

  接下来,再重新考虑下 processPersonsWithFunction 方法。以下是它的泛型版本,它接受任何数据类型的元素的集合作为参数:

public staticvoid processElements(
    Iterablesource,
    Predicatetester,
    Functionmapper,
    Consumerblock) {
    for (X p : source) {
        if (tester.test(p)) {
            Y data = mapper.apply(p);
            block.accept(data);
        }
    }
}

下边代码从列表中选择符合条件的成员,并打印符合条件成员的电子邮件地址:

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

因此,你还可以使用聚合操作来替换这些操作中的每一个步骤。

(9)方案九:使用 Lambda 表达式作为参数的聚合操作(stream 流)

以下示例使用聚合操作,从列表中筛选符合条件的成员,并打印电子邮件地址:

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));

filter()、map() 和 forEach() 操作都是聚合操作。聚合操作用来处理来自 stream 的元素,而不是直接来自集合的元素(这就是本例中调用的第一个方法是 stream 的原因)。流是元素的序列,与集合不同,它不是存储元素的数据结构。相反,流通过管道携带来自 source (例如集合)的值。管道是一个流操作序列,在本例中为 filter - map - forEach。此外,聚合操作通常接受 lambda 表达式作为参数,使你能够自定义它们的行为。

2、方法引用

使用 Lambda 表达式可以创建匿名方法。然而,有时 Lambda 表达式除了调用现有方法外什么也不做。在这些情况下,通过名称引用现有方法通常会显得语义更加清楚。方法引用使你能够做到这一点;对于已有名称的方法,它们是更紧凑、更易于阅读的 lambda 表达式。

再来看下 Lambda 表达式一节中的 Person 类:

public class Person {
    // ...
    LocalDate birthday;
    
    public int getAge() {
        // ...
    }
    
    public LocalDate getBirthday() {
        return birthday;
    }   

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
    // ...
}

假设你应用程序的成员包含在一个数组中,并且你希望按年龄对数组进行排序。可以使用如下代码:

// 数组
Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

// 定义排序规则
class PersonAgeComparator implements Comparator{
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}

// 调用排序方法
Arrays.sort(rosterAsArray, new PersonAgeComparator());

调用 sort() 的方法签名如下:

staticvoid sort(T[] a, Comparatorc)

注意,接口 Comparator 是一个函数式接口。因此,你可以使用 lambda 表达式,而不是定义并创建一个实现 Comparator 类的新实例:

Arrays.sort(rosterAsArray,
    (Person a, Person b) ->{
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

但是,比较两个 Person 实例的出生日期的方法已经作为 Person. compareByAge() 存在。所以,你可以直接在 lambda 表达式的主体中调用这个方法:

Arrays.sort(rosterAsArray,
    (a, b) ->Person.compareByAge(a, b)
);

因为这个 Lambda 表达式调用了一个现有的方法,所以你可以使用一个方法引用来替代 Lambda 表达式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用 Person::compareByAge 在语义上与 lambda 表达式:(a, b) ->Person.compareByAge(a, b) 相同。它们都具有以下特征:

  • 它的形式参数列表复制自 Comparator.compare,即(Person, Person)。
  • 它的主体调用 Person.compareByAge 方法。

Java 有四种形式的方法引用:

类型语法例子
引用静态方法ContainingClass::staticMethodNamePerson::compareByAge
引用特定对象的实例方法ContainingObject::instanceMethodNamemyComparisonProvider::compareByName
myApp::appendStrings2
对特定类型任意对象的实例方法的引用ContainingType::methodNameString::compareToIgnoreCase
String::concat
对构造函数的引用ClassName::new HashSet::new

下面的例子,MethodReferencesExamples,包含了前三种类型的方法引用示例:

import java.util.function.BiFunction;

public class MethodReferencesExamples {
    
    public staticT mergeThings(T a, T b, BiFunctionmerger) {
        return merger.apply(a, b);
    }
    
    public static String appendStrings(String a, String b) {
        return a + b;
    }
    
    public String appendStrings2(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {
        
        MethodReferencesExamples myApp = new MethodReferencesExamples();

        // Calling the method mergeThings with a lambda expression
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", (a, b) ->a + b));
        
        // 引用静态方法
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        // 引用特定对象的实例方法      
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", myApp::appendStrings2));
        
        // 对特定类型任意对象的实例方法的引用
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", String::concat));
    }
}

BiFunction 是 java.util.function 包中众多的函数式接口之一。BiFunction 函数接口可以表示接受两个参数并产生一个结果的 lambda 表达式或方法引用。

(1)静态方法引用

方法引用 Person::compareByAge 和 MethodReferencesExamples::appendStrings 是静态方法引用。

(2)实例方法引用

下面是一个引用特定对象的实例方法的例子:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用 myComparisonProvider::compareByName 调用方法 compareByName,它是对象 myComparisonProvider 的一部分。JRE 可以推断出方法的参数类型,在本例中为(Person, Person)。

(3)对特定类型的实例方法引用

下面是对特定类型的任意对象的实例方法的引用示例:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法引用 String::compareToIgnoreCase,表示该引用将调用方法 a.compareToIgnoreCase(b)。

(4)构造函数的引用

通过使用名称 new,能够使用与静态方法相同的方式引用构造函数。下面的方法将展示元素从一个集合复制到另一个集合:

public static, DEST extends Collection>DEST transferElements( SOURCE sourceCollection, SuppliercollectionFactory) {
    DEST result = collectionFactory.get();
    for (T t : sourceCollection) {
        result.add(t);
    }
    return result;
}

函数式接口 Supplier 包含一个 get() 方法,该方法不接受参数并返回一个对象。因此,你可以用 Lambda 表达式调用 transferElements 方法,如下所示:

SetrosterSetLambda = transferElements(roster, () ->{ return new HashSet<>(); });

你也可以使用构造函数引用来代替 Lambda 表达式,如下所示:

SetrosterSet = transferElements(roster, HashSet::new);

Java 编译器会推断你想要创建一个包含 Person 类型元素的 HashSet 集合。或者,你也可以这样指定:

SetrosterSet = transferElements(roster, HashSet::new);

至此,方法引用介绍完毕。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


网站名称:Lambda表达式和方法引用-创新互联
当前路径:http://cdxtjz.com/article/ehgdj.html

其他资讯