java8 lambda this_java8 Lambda

本文详细介绍了Java 8中的重要特性——Lambda表达式和Stream API。Lambda表达式允许以简洁的方式传递功能接口,而Stream API则提供了对集合数据的高效处理。文章通过实例展示了Lambda表达式的使用,包括参数、主体、this的引用以及修改外部变量等,并解释了Stream的延迟执行特性和常用操作,如filter、map、limit、sorted等。同时,还讨论了方法引用和函数式接口的概念,以及如何在实际编程中应用这些新特性提高代码效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

stream的特性

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

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

当我们操作一个流的时候,并不会修改流底层的集合(即使集合是线程安全的),如果想要修改原有的集合,就无法定义流操作的输出。

由于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 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 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 javaProgrammers = new ArrayList() {

{

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 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 ageFilter = (p) -> (p.getAge() > 25);

Predicate salaryFilter = (p) -> (p.getSalary() > 1400);

Predicate 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 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 javaDevFirstName = javaProgrammers

.stream()

.map(Person::getFirstName)

.collect(toSet());

System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");

TreeSet javaDevLastName = javaProgrammers

.stream()

.map(Person::getLastName)

.collect(toCollection(TreeSet::new));

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的迭代。

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 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 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 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默认是非线程安全的,可能带来结果的不确定性。

————————————————

版权声明:本文为优快云博主「weixin_39407066」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.youkuaiyun.com/weixin_39407066/java/article/details/88805123

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值