Java中Lambda表达式的使用

2023-05-16

Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体。Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。在Lambda表达式中this是指外围实例,而匿名类中的this是指匿名类实例。如果想在Lambda表达式里面修改外部变量的值也是可以的,可以将变量定义为非局部变量,即为实例变量或者将变量定义为数组。Lambda表达式如果引用某局部变量,则直接将其视为final。1.lambda表达式没有命名,用来像传递数据一样传递操作。2.函数接口指的是只有一个抽象方法的接口,被当做是lambda表达式的类型。最好使@FunctionalInterface 注解,防止其他人在里面添加方法。只需要在想要执行的地方利用传递的对象调用对应的接口中唯一的方法即可。

stream的特性

1.stream不存储数据     2.stream不改变源数据     3.stream的延迟执行特性

通常我们在数组或集合的基础上创建stream,stream不会专门存储数据,对stream的操作也不会影响到创建它的数组和集合,对于stream的聚合、消费或收集操作只能进行一次,再次操作会报错。延迟性是指当stream的终结操作执行的时候,前面的中间操作才执行。

当我们操作一个流的时候,一般并不会修改流底层的集合(即使集合是线程安全的),如果遍历的时候删除和添加会抛出ConcurrentModificationException异常,而ls.stream().foreach()的时候调用ls的set方法是可以的(比如ls。set(0,0))是可以修改原来集合的元素,如果集合里存的是引用类型也可以重新set或者直接改变对象里的字段。

由于stream的延迟执行特性,在聚合操作执行前修改数据源是允许的。并且会影响到流里。

Lambda表达式的语法:
基本语法: (parameters) -> expression 或 (parameters) ->{ statements; }

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s) 

基本的Lambda例子
现在,我们已经知道什么是lambda表达式,让我们先从一些基本的例子开始。 在本节中,我们将看到lambda表达式如何影响我们编码的方式。 假设有一个玩家List ,程序员可以使用 for 语句 ("for 循环")来遍历,在Java SE 8中可以转换为另一种形式:

String[] atp = {"Rafael Nadal", "Novak Djokovic",  
       "Stanislas Wawrinka",  
       "David Ferrer","Roger Federer",  
       "Andy Murray","Tomas Berdych",  
       "Juan Martin Del Potro"};  
List<String> players =  Arrays.asList(atp);  
  
// 以前的循环方式  
for (String player : players) {  
     System.out.print(player + "; ");  
}  
  
// 使用 lambda 表达式以及函数操作(functional operation)  
players.forEach((player) -> System.out.print(player + "; "));  
   
// 在 Java 8 中使用双冒号操作符(double colon operator)  此现象较静态引用。
players.forEach(System.out::println);  
// 1.1使用匿名内部类  
new Thread(new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
}).start();  
  
// 1.2使用 lambda expression  
new Thread(() -> System.out.println("Hello world !")).start();  
  
// 2.1使用匿名内部类  
Runnable race1 = new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
};  
  
// 2.2使用 lambda expression  
Runnable race2 = () -> System.out.println("Hello world !");  
    
race1.run();  
race2.run();
// 1.2 使用 lambda expression 排序 players  
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));  
Arrays.sort(players, sortByName);  
  
// 1.3 也可以采用如下形式:  
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2))); sort方法可以穿传一个comparator的实现对象,而这个类是功能类,所以可以用Lambda。

使用Lambdas和Streams
Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。 在接下来的例子中,我们将探索lambdas和streams 能做什么。 我们创建了一个Person类并使用这个类来添加一些数据到list中,将用于进一步流操作。 Person 只是一个简单的POJO类:

public class Person {  
  
private String firstName, lastName, job, gender;  
private int salary, age;  
  
public Person(String firstName, String lastName, String job,  
                String gender, int age, int salary)       {  
          this.firstName = firstName;  
          this.lastName = lastName;  
          this.gender = gender;  
          this.age = age;  
          this.job = job;  
          this.salary = salary;  
}  
// Getter and Setter   
// . . . . .  
List<Person> javaProgrammers = new ArrayList<Person>() {  
  {  
    add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));  
    add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));  
    add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));  
    add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));  
    add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));  
    add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));  
    add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));  
    add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));  
    add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));  
    add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));  
  }  
}; 

现在我们使用forEach方法来迭代输出上述列表:

javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

我们同样使用forEach方法,增加程序员的工资5%:

System.out.println("给程序员加薪 5% :");  
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());  
javaProgrammers.forEach(giveRaise);  

另一个有用的方法是过滤器filter() ,让我们显示月薪超过1400美元的PHP程序员:

System.out.println("下面是月薪超过 $1,400 的PHP程序员:")  
javaProgrammers.stream()  
          .filter((p) -> (p.getSalary() > 1400))  
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));  
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);  
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);  
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));  
  
System.out.println("年龄大于 24岁的女性 Java programmers:");  
javaProgrammers.stream()  
          .filter(ageFilter)  
          .filter(genderFilter)  
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 

 

System.out.println("最前面的3个 Java programmers:");  
javaProgrammers.stream()  
          .limit(3)  
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));  

排序呢? 我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:

System.out.println("根据 name 排序,并显示前5个 Java programmers:");  
List<Person> sortedJavaProgrammers = javaProgrammers  
          .stream()  
          .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))  
          .limit(5)  
          .collect(toList()); 

如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法:

System.out.println("工资最低的 Java programmer:");  
Person pers = javaProgrammers  
          .stream()  
          .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))  
          .get()  
  
System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary()) 

上面的例子中我们已经看到 collect 方法是如何工作的。 结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:

System.out.println("将 javaprogrammers 的 first name 拼接成字符串:");  
String javaDevelopers = javaDevFirstName 
          .stream()  
          .map(Person::getFirstName)  
          .collect(joining(" ; ")); // 在进一步的操作中可以作为标记(token)     
  
System.out.println("将 Java programmers 的 first name 存放到 Set:");  
Set<String> javaDevFirstName = javaProgrammers  
          .stream()  
          .map(Person::getFirstName)  
          .collect(toSet());  
  
System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");  
TreeSet<String> javaDevLastName = javaProgrammers  
          .stream()  
          .map(Person::getLastName)  
          .collect(toCollection(TreeSet::new));

或者收集为一个map
Map<Integer, String> map = per.stream().collect(Collectors.toMap(e -> e.age, e -> e.name));

Stream pipeline 通常是lazy 的: 直到调用终止操作时才会开始计算,对于完成终止操作不需要的数据元素,将永远都不会被计算。正是这种lazy 计算,使无限Stream 成为可能。注意,没有终止操作的Stream pipeline 将是一个静默的无操作指令,因此千万不能忘记终止操作。Stream API 是流式( fluent )的:所有包含pipeline 的调用可以链接成一个表达式。事实上,多个pipeline 也可以链接在一起,成为-个表达式。在默认情况下, Stream pipeline 是按顺序运行的。要使pipelin巳并发执行,只需在该pipeline 的任何Stream 上调用parallel 方法即可,但是通常不建议这么做。

Stream.iterate(1,x->x+2).limit(10).forEach(System.out::println); 指定1 为初始元素, 无限制进行 指定函数操作 limit为显示次数

Stream.of(arr).max(Comparator.comparing(String::length)).ifPresent(System.out::println); max 返回一个Optional对象ifPresent方法为如果有值就执行comsume功能函数。

String str =  Stream.of(arr).parallel().filter(x->x.length()>3).findFirst().orElse("noghing");findFirst返回一个Optional对象orElse方法为如果有值就返回值,没有返回给定的值。

Optional类型

通常聚合操作会返回一个Optional类型,Optional表示一个安全的指定结果类型,所谓的安全指的是避免直接调用返回类型的null值而造成空指针异常,调用optional.ifPresent()可以判断返回值是否为空,或者直接调用ifPresent(Consumer<? super T> consumer)在结果部位空时进行消费操作;调用optional.get()获取返回值。

采用Optional.empty()创建一个空的Optional,使用Optional.of()创建指定值的Optional。同样也可以调用Optional对象的map方法进行Optional的转换,调用flatMap方法进行Optional的迭代。

connect方法

Stream.concat(set1.stream(), set2.stream()).forEach(System.out::println);可以把两个stream合并在一起,如果是两个set的stream合在一起,就算有重复的元素在集合里也不会合并,合并的流中一样不可以对对应的set做删除和增加操作。

map和flatmap的区别

map只是一维 1对1 的映射,而flatmap可以将一个两层集合映射成一层,相当于他映射的深度比map深了一层 ,所以名称上就把map加了个flat 叫flatmap。 map:转换流,将一种类型的流转换为另外一种流。flapMap:拆解流,将流中每一个元素拆解成一个流,最后合并流,也就是说flatMap方法最终会把所有返回的stream合并。

map操作:

flatmap操作:

 

方法引用

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。Java 8 对方法引用的支持只是编译器层面的支持,虚拟机执行引擎并不了解方法引用。编译器遇到方法引用的时候,会像上面那样自动推断出程序员的意图,将方法引用还原成接口实现对象,或者更形象地说,就是把方法引用设法包装成一个接口实现对象,这样虚拟机就可以无差别地执行字节码文件而不需要管什么是方法引用了。函数式接口:有且仅有一个抽象方法,Object的public方法除外,用@FunctionalInterface的注解。

注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。有以下几种情况:

1.类名::静态方法名

public class Student {
    private String name;
    private int score;

    public Student(){

    }

    public Student(String name,int score){
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public static int compareStudentByScore(Student student1,Student student2){
        return student1.getScore() - student2.getScore();
    }

    public static int compareStudentByName(Student student1,Student student2){
        return student1.getName().compareToIgnoreCase(student2.getName());
    }
}
Student student1 = new Student("zhangsan",60);
Student student2 = new Student("lisi",70);
Student student3 = new Student("wangwu",80);
Student student4 = new Student("zhaoliu",90);
List<Student> students = Arrays.asList(student1,student2,student3,student4);

students.sort((o1, o2) -> o1.getScore() - o2.getScore());
students.forEach(student -> System.out.println(student.getScore()));

使用类名::静态方法名 方法引用替换lambda表达式

students.sort(Student::compareStudentByScore);
students.forEach(student -> System.out.println(student.getScore()));

第二种 对象::实例方法名

我们再自定义一个用于比较Student元素的类

public class StudentComparator {
    public int compareStudentByScore(Student student1,Student student2){
        return student2.getScore() - student1.getScore();
    }
}

StudentComparator中定义了一个非静态的,实例方法compareStudentByScore,同样该方法的定义满足Comparator接口的compare方法定义,所以这里可以直接使用 对象::实例方法名 的方式使用方法引用来替换lambda表达式

StudentComparator studentComparator = new StudentComparator();
students.sort(studentComparator::compareStudentByScore);
students.forEach(student -> System.out.println(student.getScore()));

3. 对象的超类方法引用语法: super::methodname

超类方法语法使用例子:
public class Example extends BaseExample{
    public void test() {
        List<String> list = Arrays.asList("aaaa", "bbbb", "cccc"); 
        //对象的超类方法语法: super::methodName 
        list.forEach(super::print);
    }
}

class BaseExample {
    public void print(String content){
        System.out.println(content);
    }
}

4. 类构造器引用语法: classname::new 例如:ArrayList::new

public class Example {
   private String name;
   Example(String name){
       this.name = name;
   }  
   public static void main(String[] args) {
       InterfaceExample com =  Example::new;
       Example bean = com.create("hello world");
       System.out.println(bean.name);
   }
}
interface InterfaceExample{
   Example create(String name);
}

5. 数组构造器引用语法: typename[]::new 例如: String[]:new

public class Example {
    public static void main(String[] args) {
        Function <Integer, Example[]> function = Example[]::new;
        Example[] array = function.apply(4);    //这里的4是数组的大小
        
        for(Example e:array){
            System.out.println(e);    //如果输出的话,你会发现会输出4个空对象(null)
        }
    }
}

6.注意:类名::实例方法名 
这种方法引用的方式较之前两种稍微有一些不好理解,因为无论是通过类名调用静态方法还是通过对象调用实例方法这都是符合Java的语法,使用起来也比较清晰明了。

现在再看一下Student类中静态方法的定义

public static int compareStudentByScore(Student student1,Student student2){
    return student1.getScore() - student2.getScore();
}

虽然这个方法在语法上没有任何问题,可以作为一个工具正常使用,但是有没有觉得其在设计上是不合适的或者是错误的。这样的方法定义放在任何一个类中都可以正常使用,而不只是从属于Student这个类,那如果要定义一个只能从属于Student类的比较方法下面这个实例方法更合适一些

public int compareByScore(Student student){
    return this.getScore() - student.getScore();
}

接收一个Student对象和当前调用该方法的Student对象的分数进行比较即可。现在我们就可以使用 类名::实例方法名 这种方式的方法引用替换lambda表达式了

students.sort(Student::compareByScore);
students.forEach(student -> System.out.println(student.getScore()));

这里非常奇怪,sort方法接收的lambda表达式不应该是两个参数么,为什么这个实例方法只有一个参数也满足了lambda表达式的定义(想想这个方法是谁来调用的)。这就是 类名::实例方法名 这种方法引用的特殊之处:当使用 类名::实例方法名 方法引用时,一定是lambda表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去。
结合本例来看,最初的lambda表达式是这样的

students.sort((o1, o2) -> o1.getScore() - o2.getScore());

那使用 类名::实例方法名 方法引用时,一定是o1来调用了compareByScore实例方法,并将o2作为参数传递进来进行比较。是不是就符合了compareByScore的方法定义。这也解释了下面在非流中的使用。

parallelStream 并行流

1.parallelStream提交的任务会被ForkJoinPool中的通用线程池处理。

2.parallelStream并行执行是无序的。

3.parallelStream提供了更简单的并发执行的实现,但并不意味着更高的性能,它是使用要根据具体的应用场景。如果cpu资源紧张parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能。

4.任务之间最好是状态无关的,因为parallelStream默认是非线程安全的,可能带来结果的不确定性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中Lambda表达式的使用 的相关文章

  • 透明平开窗

    我有一点JWindow上面有一个标志 用户可以将东西拖到上面 我主要在 OS X 上开发我的应用程序 为了获得我使用的透明窗口 setBackground new Color 0 0 0 0 在 Mac 上 这工作得很好 但在 Window
  • 多线程环境下如何更好的使用ExecutorService?

    我需要创建一个库 其中包含同步和异步方法 executeSynchronous 等待直到有结果 返回结果 executeAsynchronous 立即返回一个 Future 如果需要 可以在其他事情完成后进行处理 我的图书馆的核心逻辑 客户
  • 如何让Spring RabbitMQ创建一个新的队列?

    根据我对rabbit mq的 有限 经验 如果您为尚不存在的队列创建新的侦听器 则会自动创建该队列 我正在尝试将 Spring AMQP 项目与rabbit mq 一起使用来设置侦听器 但出现错误 这是我的 xml 配置
  • Java:高性能消息传递(单生产者/单消费者)

    我最初问这个问题here https stackoverflow com questions 3367192 java is while true loop in a thread bad whats the alternative 但我意
  • 静态方法的 Java 内存模型

    我来自操作系统和 C 语言背景 在代码编译时 世界很简单 需要处理和理解堆栈 堆文本部分等 当我开始学习 Java 时 我确实了解 JVM 和垃圾收集器 我对静态方法感到很有趣 根据我的理解 类的所有实例都会在堆中创建 然后被清理 但是 对
  • 使用 jpql 和 jpa 从日期字段中提取年份

    我想从数据库中的一行中提取年份部分 以便将其与值进行比较 这是我的功能 public List
  • firebase推送通知错误Spring Boot服务器端

    我正在尝试从 Spring Boot 服务器端发送通知到客户端 android 服务器运行良好 一切都很好 2020 09 01 08 13 07 691 INFO 18941 restartedMain e DevToolsPropert
  • 尝试在java中的Arraylist中查找对象的所有出现

    我有一个 Java ArrayList 我需要查找其中出现的所有特定对象 ArrayList indexOf Object 方法只找到一次出现 所以看来我还需要其他东西 我认为你不需要太花哨 以下应该可以正常工作 static
  • 列表推导式和 for 循环中的 Lambda 表达式[重复]

    这个问题在这里已经有答案了 我想要一个 lambda 列表 作为一些繁重计算的缓存 并注意到这一点 gt gt gt j for j in lambda i for i in range 10 9 9 9 9 9 9 9 9 9 9 Alt
  • 以编程方式设置 Logback Appender 路径

    我正在尝试以编程方式设置 Logback 附加程序路径 滚动文件附加器 http logback qos ch apidocs ch qos logback core rolling RollingFileAppender html准确地说
  • 不要模拟值对象:过于通用的规则,没有解释

    以下是 Mockito 单元测试框架的引用 不要模拟值对象 为什么有人会想要这样做呢 因为实例化对象太痛苦了 gt 无效 原因 如果创造新的装置太困难 那就是一个迹象 代码可能需要一些认真的重构 另一种方法是创建 价值对象的构建者 有一些工
  • 使用Java开发跨平台,不同平台字体缩放不同

    我正在为我的大学制作一些软件 需要一个 GUI 在它的第一个版本中 我让它使用系统外观 因此它看起来像 Linux Mac Windows 中的本机应用程序 我发现这很麻烦 因为我必须根据操作系统使所有 JLabel 具有不同的大小 无论分
  • 是否可以为 azure blob 存储中的给定目录生成具有写入权限的 SAS(共享访问签名)

    我们的 blob 存储帐户结构 容器名称 simple 在这个容器内我们有 blob aa one zip aa two zip bb ss zip bb dd zip 是否可以生成对aa 目录 有写权限 但对bb 目录 没有访问权限的SA
  • 如何使用云打印打印Android活动显示

    我正在尝试将 Google 云打印实现到应用程序中 遵循集成指南 https developers google com cloud print docs android 我试图通过打印 google com 来保持基本 单击我创建的打印按
  • 我们可以有虚假中断吗?

    我正在创建一个任务轮询器 每分钟都会查找任务 它看起来像这样 public class Poller private final ExecutorService e Executors newSingleThreadExecutor pub
  • Selenium - 等待网络流量

    我们将 Selenium 与 Java API 和一些 Javascript 用户扩展一起使用 我们在应用程序中使用了大量 AJAX 调用 我们的许多测试随机失败 因为有时 AJAX 调用完成得比其他时候慢 因此页面未完全加载 我们通过等待
  • JMockit - 初始化问题

    当我使用以下测试时 我收到警告 警告 JMockit 是按需初始化的 这可能会导致某些测试失败 请检查文档以获取更好的初始化方法 这是我的测试实现 package test import static mockit Mockit impor
  • 编写自定义 Eclipse 调试器

    EDIT 一定有某种方法可以解决这个问题 而无需编写全新的调试器 我目前正在研究在现有 java 调试器之上构建的方法 如果有人对如何获取 Java 调试器已有的信息 有关堆栈帧 变量 原始数据等 有任何想法 那将非常有帮助 我想要做的是我
  • 如何在java 1.8中从org.jboss.jca.adapters.jdbc.jdk8.WrappedConnectionJDK8转换为oracle.jdbc.OracleConnection

    如何在 java 1 8 中从 org jboss jca adapters jdbc jdk8 WrappedConnectionJDK8 转换为 oracle jdbc OracleConnection 目前我正在这样使用并得到以下异常
  • 我找不到 IntelliJ 快捷方式

    我使用 vim 一段时间 我知道有一个 intellij vim 插件 我很好奇内置的 IntelliJ 文本导航存在什么 如何打开实时模板来创建模板 如何查看以 tr 开头的现有模板列表 如何进行全局搜索并在当前文档中进行搜索 然后转到下

随机推荐