Java 8-Lambda表达式、方法引用、标准函数接口与流操作、管道操作之间的关系

public void run() {

System.out.println(“hi”);

}

});

t1.start();

Thread t2 = new Thread(() -> System.out.println(“hi”));

t2.start();

t1与t2完成相同的功能。t2中的Lambda表达式() -> System.out.println("hi")Runnable接口中的方法public abstract void run();的形式一样:

  1. 没有返回值。

  2. 没有传入参数。

下面一个例子中

String[] arr = {“111”,“22”,“3”};

Arrays.sort(arr,new Comparator() {

@Override

public int compare(String o1, String o2) {

return o1.length()-o2.length();

}

});

Arrays.sort(arr, (o1,o2)->o1.length()-o2.length());

(o1,o2)->o1.length()-o2.length()的形式与Compartor中public int compare(String o1, String o2) 形式也一样:

  1. 两个入参。

  2. 一个返回值,且返回值为int类型。

2.Lambda表达式、匿名内部类与this

======================

Lambda表达式在用法上看起来很像匿名内部类,但其并不是匿名内部类。比如,在以下代码中,在Lambda表达式中不能获得this。

Thread t1 = new Thread(new Runnable() {

public void run() {

System.out.println(this);//打印匿名内部类

}

});

t1.start();

Thread t2 = new Thread(()->{

//System.out.println(this);//无法编译通过

});

t2.start();

观察如下代码,会发现Lambda表达式中的this与所处环境有关,在这里this是对外部对象的引用。

class Foo{

Runnable r1 = ()->{

System.out.println(this);

};

Runnable r2 = ()->{

System.out.println(this);

};

void test(){

r1.run();

r2.run();

}

}

//测试代码如下

Foo foo = new Foo();

System.out.println(foo);

foo.test();

从输出可以看出,输出了三个对象实际上是同一个对象。

Foo@87aac27

Foo@87aac27

Foo@87aac27

3. 标准函数式接口与方法引用

================

3.1 函数式接口


Java8中为Iterable引入了默认实现方法default void forEach(Consumer<? super T> action)。用法如下:

List strs = Arrays.asList(“1”,“222”,“33”); //List接口间接继承了Iterable接口,所以strs也会有forEach方法。

strs.forEach(e->System.out.println(e)); //将strs中的每个元素迭代输出

为什么可以将Lambda表达式e->System.out.println(e)作为Consumer<? super T> action类型的参数。

先看一下Consumer的代码

@FunctionalInterface

public interface Consumer {

void accept(T t);

//其他代码

}

可以看到void accept(T t)e->System.out.println(e)形式上是一致的,所以可以将该Lambda表达式作为输入参数。

注意:这里使用了@FunctionalInterface标注该结构为函数式接口。也可以自己创建函数式接口。但要注意函数接口只能有一个抽象方法

如下代码可以通过:

@FunctionalInterface

interface MyFuncInterface{

void test();

}

但如下代码却无法编译通过

@FunctionalInterface

interface MyFuncInterface{

void test();

void test1();

}

JDK中大量使用了几个常用的标准函数接口。如下所示:

public interface Consumer {//无返回值,消费传入的T。可接受e->System.out.println(e)或System.out::println

void accept(T t);

//其他代码

}

public interface Function<T, R> {//将t转化为r。可接受e->Integer.parseInt或Integer::parseInt,将String类型转化为int

R apply(T t);

//其他代码

}

public interface Predicate {//根据传入t判断真假。可接受x->x>3或String::equals(与传入String对象比较,返回True或False)

boolean test(T t);

//其他代码

}

public interface Supplier {//无输入参数,直接获取T。可接受()->Arrays.asList(“1”,“2”,“3”}或

T get();

}

3.2 方法引用


前面出现的System.out::println就是方法引用。下面的代码中,strs.forEach的入参类型为Consumer<? super T> action

前面已经提到可以使用e->System.out.println(e)作为入参,同时我们知道System.out.println方法签名中返回值为void、

无入参也符合要求,所以我们可以使用System.out::println来替代e->System.out.println(e)。注意:要使用::来引用相关

的方法。

···

List strs = Arrays.asList(“1”,“222”,“33”);

strs.forEach(e->System.out.println(e));

strs.forEach(System.out::println);

···

方法引用不仅可以引用jdk中已有类的方法,还可以引用自定义类的相关方法。比如:

class Foo{

void myPrintX(T t) { //必须创建Foo对象才能对非static进行方法引用

System.out.println(“x=”+t);

}

static void myPrint(T t) {

System.out.println(“element=”+t);

}

}

//测试代码

List strs = Arrays.asList(“1”,“222”,“33”);

strs.forEach(Foo::myPrint);

strs.forEach(new Foo()::myPrintX);

输出结果为

element=1

element=222

element=33

x=1

x=222

x=33

4.Lambda、方法引用、标准函数接口与Stream

===========================

从Java 8起,可以将集合中数据放入流并进行管道式操作。

管道式操作包含3部分:

  1. 数据源(集合、数组等)

  2. 0个或多个中间操作(filter、map等)

  3. 终端操作(forEach、collect、average, sum, min, max, and count)。

中间操作产生的还是流,那么通过filter得到的流还可以继续进行filter。

终端操作产生的就不是流了(可能是一个List、Map或int等),对一个流进行终端操作后,就不能在进行任何其他中间操作。

对一个流一旦进行完终端操作,就不能再进行中间操作,运行如下代码

List intList = Arrays.asList(1, 2, 3, 4, 5);

Stream stream1 = intList.stream().filter(e->e>3);

stream1.forEach(System.out::println);

Stream stream2 = stream1.filter(e->e>4);

stream2.forEach(System.out::println);

会提示stream has already been operated upon or closed

4.1 stream的的filter、map方法与Predicate、Function接口


Predicate接口(boolean test(T t))的作用是根据传入参数t判断真假。

Function接口(R apply(T t);)的作用是将T类型的t转换成R类型。

观察如下代码:

List strs = Arrays.asList(“1”,“222”, null, “33”);

Stream stream = strs.stream().filter(e -> e != null).map(Integer::parseInt);

stream.forEach(e -> System.out.println(1 + e));

输出

2

223

34

其中strs.stream().filter(e->e!=null)的filter方法声明如下Stream<T> filter(Predicate<? super T> predicate);,即这里需要一个Predicate<? super T> predicate类型的参数。前面可以看到Predicate接口中的方法为boolean test(T t);,即接受一个t返回

boolean值。e->e!=null符合这样的要求。

strs.stream().filter(e->e!=null).map(Integer::parseInt);中的map方法声明如下Stream<R> map(Function<? super T, ? extends R> mapper)

即这里需要一个Function<? super T, ? extends R> mapper)类型的参数。前面可以看到Function接口中的方法为R apply(T t);

即接受一个类型为T的元素,将其转换为元素R。在这里实际上就是将String类型元素转化成int类型元素。Integer::parseInt刚好

符合这种要求。

4.2 Optional中map方法与Function接口


从刚才的例子中,我们可以看Function接口的作用可以将一个类型的转换成另外一个类型。比如

Student s1 = new Student(“zhang san”);

String name = s1.getName(); //对应的方法引用是Student::getName()

中Student::getName()相当于Student类型转换成String类型。

如下代码中,一个Course有很多Student(stuList),每个Student有都可以getName()。现在想要获取该Course中某个学生的姓名。

以往的代码如果使用course.getStuList().get(i).getName()来获取某个学生的姓名,看起来代码风格固然流畅,然而却没有正确处理:

course1为null,get(i)为null,getName为null的情况。那么必须在整个处理过程编写大量的判断null的代码。

可以使用Optional进行改进,即保持了流畅的编码风格,又可以正确处理null。

以下代码中:Optional.ofNullable方法可以将给定值转化为Optional类型(可包含代表给定值的Optional对象,也可包含代表null的Optional对象)

import java.util.ArrayList;

import java.util.List;

import java.util.Optional;

class Student{

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Student(String name) {

this.name = name;

}

}

class Course{//课程

private String name;

private List stuList;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public void addStudent(Student… stus) {

for (Student student : stus) {

stuList.add(student);

}

}

public Student getStu(int i) {

return stuList.get(i);

}

public List getStuList() {

return stuList;

}

public Course(String name) {

this.name = name;

stuList = new ArrayList<>();

}

}

public class TestOptional {

public static void main(String[] args) {

Course course = new Course(“数学”);

Student s0 = new Student(“s1”);

Student s1 = new Student(null);

Student s2 = null;

course.addStudent(s0, s1, s2);

String result = findStudent(course, 0);// orElse,当处理过程中过程中有一个null时,即返回orElse中的值

System.out.println(“均不为空情况下姓名为:” + result);

result = findStudent(course, 1);

System.out.println(“student的name为null的情况:” + result);

result = findStudent(course, 2);

System.out.println(“student为null的情况:” + result);

Course courseNull = null;

result = findStudent(courseNull, 3);

System.out.println(“course为null的情况:” + result);

}

private static String findStudent(Course course, int i) {

Optional courseNullable = Optional.ofNullable(course);

String result = courseNullable.map(e -> e.getStu(i)).map(Student::getName).orElse(“查询不到”);

return result;

}

}

注意:

  1. Optional的map方法入参为Function类型,所以map(e->e.getStu(0))map(Student::getName)形式均可执行。

  2. Optional的map方法返回值为Optional类型,所以可以以链式风格map(e->e.getStu(2)).map(Student::getName)流畅的编写对应代码。

  3. 该例子中不考虑stuList为null的情况,因为只要创建了Course,默认就创建了stuList。

  4. 这里没有对不同种的null情况(student为null,course为null)进行处理,返回的结果统一是查询不到,会造成理解上的混淆。

4.3 stream的mapToInt方法与ToIntFunction函数式接口


List strs = Arrays.asList(“1”, “222”, null, “33”);

IntStream intStream = strs1.stream().filter(e -> e != null).mapToInt(e -> e.length());

intStream.forEach(System.out::println);

mapToInt(e -> e.length())的mapToInt方法参数类型为ToIntFunction<? super T> mapper,查询源代码ToIntFunction包含方法int applyAsInt(T value);,即需要一个方法接受T类型输入参数,然后将其转化为int。在这里,e -> e.length()起到了这个作用。

代码的作用就是要将求得流中每个非null的字符串的长度,然后放入intStream中。

4.4 stream的reduce方法与BinaryOperator函数式接口


int[] arr = {1,2,3,4,5};

int x = 0;

for (int i = 0; i < arr.length; i++) {

x = x + arr[i];

}

System.out.println(x);

这段代码每回从数组中取出一个元素,然后与前一个元素相加。最后求的所有元素值的和。这类操作经常使用,可以使用stream中的

reduce方法来简化实现。

List intList = Arrays.asList(1, 2, 3, 4, 5);

Stream intStream1 = intList.stream();

Optional result = intStream1.reduce((a, b) -> a + b);

Integer integer = result.get();

System.out.println(integer);// 15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值