Java中Lambda表达式的使用「建议收藏」

2022-09-08 10:26:01 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

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

代码语言:javascript复制
// 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中可以转换为另一种形式:

代码语言:javascript复制
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);  
代码语言:javascript复制
// 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();
代码语言:javascript复制
// 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类:

代码语言:javascript复制
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   
// . . . . .  
代码语言:javascript复制
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%:

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

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

代码语言:javascript复制
System.out.println("下面是月薪超过 $1,400 的PHP程序员:")  
javaProgrammers.stream()  
          .filter((p) -> (p.getSalary() > 1400))  
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));  
代码语言:javascript复制
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())); 
代码语言:javascript复制
System.out.println("最前面的3个 Java programmers:");  
javaProgrammers.stream()  
          .limit(3)  
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));  

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

代码语言:javascript复制
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方法:

代码语言:javascript复制
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中:

代码语言:javascript复制
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功能函数。

代码语言:javascript复制
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.类名::静态方法名

代码语言:javascript复制
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());
    }
}
代码语言:javascript复制
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表达式

代码语言:javascript复制
students.sort(Student::compareStudentByScore);
students.forEach(student -> System.out.println(student.getScore()));

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

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

代码语言:javascript复制
public class StudentComparator {
    public int compareStudentByScore(Student student1,Student student2){
        return student2.getScore() - student1.getScore();
    }
}

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

代码语言:javascript复制
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类中静态方法的定义

代码语言:javascript复制
public static int compareStudentByScore(Student student1,Student student2){
    return student1.getScore() - student2.getScore();
}

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

代码语言:javascript复制
public int compareByScore(Student student){
    return this.getScore() - student.getScore();
}

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

代码语言:javascript复制
students.sort(Student::compareByScore);
students.forEach(student -> System.out.println(student.getScore()));

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

代码语言:javascript复制
students.sort((o1, o2) -> o1.getScore() - o2.getScore());

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

parallelStream 并行流

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

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

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

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

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/157061.html原文链接:https://javaforall.cn

0 人点赞