感受Lambda之美,推荐收藏,需要时查阅

一、引言

java8最大的特性就是引入Lambda表达式,即函数式编程,可以将行为进行传递。总结就是:使用不可变值与函数,函数对不可变值进行处理,映射成另一个值。

二、java重要的函数式接口

1、什么是函数式接口

函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。使用@FunctionalInterface注解修饰的类,编译器会检测该类是否只有一个抽象方法或接口,否则,会报错。可以有多个默认方法,静态方法。

1.1 java8自带的常用函数式接口。

在这里插入图片描述

public class Test {
    public static void main(String[] args) {
        Predicate<Integer> predicate = x -> x > 185;
        Student student = new Student("9龙", 23, 175);
        System.out.println(
            "9龙的身高高于185吗?:" + predicate.test(student.getStature()));

        Consumer<String> consumer = System.out::println;
        consumer.accept("命运由我不由天");

        Function<Student, String> function = Student::getName;
        String name = function.apply(student);
        System.out.println(name);

        Supplier<Integer> supplier = 
            () -> Integer.valueOf(BigDecimal.TEN.toString());
        System.out.println(supplier.get());

        UnaryOperator<Boolean> unaryOperator = uglily -> !uglily;
        Boolean apply2 = unaryOperator.apply(true);
        System.out.println(apply2);

        BinaryOperator<Integer> operator = (x, y) -> x * y;
        Integer integer = operator.apply(2, 3);
        System.out.println(integer);

        test(() -> "我是一个演示的函数式接口");
    }

    /**
     * 演示自定义函数式接口使用
     *
     * @param worker
     */
    public static void test(Worker worker) {
        String work = worker.work();
        System.out.println(work);
    }

    public interface Worker {
        String work();
    }
}
//9龙的身高高于185吗?:false
//命运由我不由天
//9龙
//10
//false
//6
//我是一个演示的函数式接口

以上演示了lambda接口的使用及自定义一个函数式接口并使用。下面,我们看看java8将函数式接口封装到流中如何高效的帮助我们处理集合。

注意:Student::getName例子中这种编写lambda表达式的方式称为方法引用。格式为ClassNmae::methodName。是不是很神奇,java8就是这么迷人。

示例:本篇所有示例都基于以下三个类。OutstandingClass:班级;Student:学生;SpecialityEnum:特长。

在这里插入图片描述

1.2 惰性求值与及早求值

惰性求值:只描述Stream,操作的结果也是Stream,这样的操作称为惰性求值。惰性求值可以像建造者模式一样链式使用,最后再使用及早求值得到最终结果。

及早求值:得到最终的结果而不是Stream,这样的操作称为及早求值。

1.3 lambda表达式

概念:就是对函数式接口匿名内部类的简写
作用:简化代码【简化为方法形参和方法体】
使用:
语法:

函数式接口  变量名 = (参数1,参数2...) -> {
            	// 方法体
         	}
			(参数1,参数2)表示参数列表;
			->表示连接符;连接符号后是方法体
			{}内部是方法体 

特点说明:
普通方法的写法
1. =右边的类型会根据左边的函数式接口类型自动推断;
2. 如果形参列表为空,只需保留();
3. 如果形参只有1个,()可以省略,只需要参数的名称即可;
4. 如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句;
5. 形参列表的数据类型会自动推断;
6. lambda不会生成一个单独的内部类文件;
7. lambda表达式若访问了局部变量,则局部变量必须是final的,
8. 若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错;
lambda表达式的方法引用:
这是关于方法引用的链接可以看看

4.1. 静态方法引用【掌握】
			语法 : 类名::静态方法名
			注意事项:
				被引用的静态方法参数列表和函数式接口中抽象方法的参数一致!!
				接口的抽象方法没有返回值,引用的静态方法可以有返回值也可以没有
				接口的抽象方法有返回值,引用的静态方法必须有相同类型的返回值!!
			由于满足抽象参数列表与引用参数列表相同,所以可以写成静态方法引用的格式
			
		4.2. 实例方法引用【掌握】
			语法 : 对象名::非静态方法名
			注意事项:
				被引用的实例方法参数列表和函数式接口中抽象方法的参数一致!!
				接口的抽象方法没有返回值,引用的实例方法可以有返回值也可以没有
				接口的抽象方法有返回值,引用的实例方法必须有相同类型的返回值!!
		
		4.3. 构造方法引用【掌握】
			语法 :类名::new
			注意事项:
				被构造方法与函数式接口的抽象方法参数列表一致
			
		4.4. 特定类型的方法引用 【掌握】
			语法 :类名::非静态方法
			注意事项:
				在抽象方法中,参数作为实例方法调用者,就可以简化
				
		4.5. 数组构造方法引用【了解】
			语法 :数据类型[]::new
		
		4.6.类中方法调用父类或本类方法引用 【了解】
			语法 :
				this::方法名
				super::方法名

2、常用的流

2.1 collect(Collectors.toList())

将流转换为list。还有toSet(),toMap()等。及早求值。

public class TestCase {
    public static void main(String[] args) {
        List<Student> studentList = Stream.of(new Student("路飞", 22, 175),
                new Student("红发", 40, 180),
                new Student("白胡子", 50, 185)).collect(Collectors.toList());
        System.out.println(studentList);
    }
}
//输出结果
//[Student{name='路飞', age=22, stature=175, specialities=null}, 
//Student{name='红发', age=40, stature=180, specialities=null}, 
//Student{name='白胡子', age=50, stature=185, specialities=null}]

2.2 filter

顾名思义,起过滤筛选的作用。内部就是Predicate接口。惰性求值。
在这里插入图片描述
比如我们筛选出出身高小于180的同学。

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        List<Student> list = students.stream()
            .filter(stu -> stu.getStature() < 180)
            .collect(Collectors.toList());
        System.out.println(list);
    }
}
//输出结果
//[Student{name='路飞', age=22, stature=175, specialities=null}]

filter实现的是

Stream<T> filter(Predicate<? super T> predicate);
符合函数式接口,作用于抽象方法中的test(),返回一个Boolean

2.3 map

转换功能,内部就是Function接口。惰性求值
在这里插入图片描述

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        List<String> names = students.stream().map(student -> student.getName())
                .collect(Collectors.toList());
        System.out.println(names);
    }
}
//输出结果
//[路飞, 红发, 白胡子]

例子中将student对象转换为String对象,获取student的名字。

 <R> Stream<R> map(Function<? super T, ? extends R> mapper);

2.4 flatMap

将多个Stream合并为一个Stream。惰性求值
在这里插入图片描述

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        List<Student> studentList = Stream.of(students,
                asList(new Student("艾斯", 25, 183),
                        new Student("雷利", 48, 176)))
                .flatMap(students1 -> students1.stream()).collect(Collectors.toList());
        System.out.println(studentList);
    }
}
//输出结果
//[Student{name='路飞', age=22, stature=175, specialities=null}, 
//Student{name='红发', age=40, stature=180, specialities=null}, 
//Student{name='白胡子', age=50, stature=185, specialities=null}, 
//Student{name='艾斯', age=25, stature=183, specialities=null},
//Student{name='雷利', age=48, stature=176, specialities=null}]

调用Stream.of的静态方法将两个list转换为Stream,再通过flatMap将两个流合并为一个。
理解:就是说把多个list流合到一个流进行操作,也就是Stream.of(list,list)

2.5 max和min

我们经常会在集合中求最大或最小值,使用流就很方便。及早求值。

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        Optional<Student> max = students.stream()
            .max(Comparator.comparing(stu -> stu.getAge()));
        Optional<Student> min = students.stream()
            .min(Comparator.comparing(stu -> stu.getAge()));
        //判断是否有值
        if (max.isPresent()) {
            System.out.println(max.get());
        }
        if (min.isPresent()) {
            System.out.println(min.get());
        }
    }
}
//输出结果
//Student{name='白胡子', age=50, stature=185, specialities=null}
//Student{name='路飞', age=22, stature=175, specialities=null}

max、min接收一个Comparator(例子中使用java8自带的静态函数,只需要传进需要比较值即可。)并且返回一个Optional对象,该对象是java8新增的类,专门为了防止null引发的空指针异常。

可以使用max.isPresent()判断是否有值;可以使用max.orElse(new Student()),当值为null时就使用给定值;也可以使用max.orElseGet(() -> new Student());这需要传入一个Supplier的lambda表达式。也可以是max.orElseGet(Student:new)

2.6 count

统计功能,一般都是结合filter使用,因为先筛选出我们需要的再统计即可。及早求值

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        long count = students.stream().filter(s1 -> s1.getAge() < 45).count();
        System.out.println("年龄小于45岁的人数是:" + count);
    }
}
//输出结果
//年龄小于45岁的人数是:2

2.7 reduce

reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count 、 min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。及早求值。
在这里插入图片描述

public class TestCase {
    public static void main(String[] args) {
        Integer reduce = Stream.of(1, 2, 3, 4).reduce(0, (acc, x) -> acc+ x);
        System.out.println(reduce);
    }
}
//输出结果
//10
/*
首先初始化:0+(1+2)=3
第二次:(3+3)=6
第三次:(6+4)=10
注:0只是作为初始化才开始累加的
如:x+y那么所得的值就会替换掉x,y就是下一个要累加的值,然后继续累加
*/

我们看得reduce接收了一个初始值为0的累加器,依次取出值与累加器相加,最后累加器的值就是最终的结果。
底层实现的是

T reduce(T identity, BinaryOperator<T> accumulator);
实现的是二元操作,比如求两个数的相加.

理解,这里的.reduce(0, (acc, x) -> acc+ x);
表示会依次传进两个参数,而这两个参数在内部进行相加的运算方法,然后依次穿进去相加然后相加完再将0也带进去进行运算,最后得出结果返回

三、高级集合类及收集器

3.1 转换成值

收集器,一种通用的、从流生成复杂值的结构。只要将它传给 collect 方法,所有的流就都可以使用它了。标准类库已经提供了一些有用的收集器,以下示例代码中的收集器都是从 java.util.stream.Collectors 类中静态导入的。

public class CollectorsTest {
    public static void main(String[] args) {
        List<Student> students1 = new ArrayList<>(3);
        students1.add(new Student("路飞", 23, 175));
        students1.add(new Student("红发", 40, 180));
        students1.add(new Student("白胡子", 50, 185));

        OutstandingClass ostClass1 = new OutstandingClass("一班", students1);
        //复制students1,并移除一个学生
        List<Student> students2 = new ArrayList<>(students1);
        students2.remove(1);
        OutstandingClass ostClass2 = new OutstandingClass("二班", students2);
        //将ostClass1、ostClass2转换为Stream
        Stream<OutstandingClass> classStream = Stream.of(ostClass1, ostClass2);
        OutstandingClass outstandingClass = biggestGroup(classStream);
        System.out.println("人数最多的班级是:" + outstandingClass.getName());

        System.out.println("一班平均年龄是:" + averageNumberOfStudent(students1));
    }

    /**
     * 获取人数最多的班级
     */
    private static OutstandingClass biggestGroup(Stream<OutstandingClass> outstandingClasses) {
        return outstandingClasses.collect(
                maxBy(comparing(ostClass -> ostClass.getStudents().size())))
                .orElseGet(OutstandingClass::new);
    }

    /**
     * 计算平均年龄
     */
    private static double averageNumberOfStudent(List<Student> students) {
        return students.stream().collect(averagingInt(Student::getAge));
    }
}
//输出结果
//人数最多的班级是:一班
//一班平均年龄是:37.666666666666664

maxBy或者minBy就是求最大值与最小值。

3.2 转换成块

常用的流操作是将其分解成两个集合,Collectors.partitioningBy帮我们实现了,接收一个Predicate函数式接口。
在这里插入图片描述将示例学生分为会唱歌与不会唱歌的两个集合。

public class PartitioningByTest {
    public static void main(String[] args) {
        //省略List<student> students的初始化
        Map<Boolean, List<Student>> listMap = students.stream().collect(
            Collectors.partitioningBy(student -> student.getSpecialities().
                                      contains(SpecialityEnum.SING)));
    }
}

3.3 数据分组

数据分组是一种更自然的分割数据操作,与将数据分成 ture 和 false 两部分不同,可以使用任意值对数据分组。Collectors.groupingBy接收一个Function做转换。
在这里插入图片描述
如图,我们使用groupingBy将根据进行分组为圆形一组,三角形一组,正方形一组。

例子:根据学生第一个特长进行分组

public class GroupingByTest {
    public static void main(String[] args) {
        //省略List<student> students的初始化
         Map<SpecialityEnum, List<Student>> listMap = 
             students.stream().collect(
             Collectors.groupingBy(student -> student.getSpecialities().get(0)));
    }
}

Collectors.groupingBy与SQL 中的 group by 操作是一样的。

3.4 字符串拼接

如果将所有学生的名字拼接起来,怎么做呢?通常只能创建一个StringBuilder,循环拼接。使用Stream,使用Collectors.joining()简单容易。

public class JoiningTest {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

         String names = students.stream()
             .map(Student::getName).collect(Collectors.joining(",","[","]"));
        System.out.println(names);
    }
}
//输出结果
//[路飞,红发,白胡子]

joining接收三个参数,第一个是分界符,第二个是前缀符,第三个是结束符。也可以不传入参数Collectors.joining(),这样就是直接拼接。
这里我自己抄写的代码:

		//lambda
        List<Student> students  = Stream.of(new Student("路飞", 50, 175), new Student("红发", 40, 180), new Student("白胡子", 50, 185)).collect(Collectors.toList());

        //筛选filter
        List<Student> collect = students.stream().filter(student -> student.getHeight() < 180).collect(Collectors.toList());
        System.out.println("collect = " + collect);

        //转换 map
        List<String> mapToName = students.stream().map(Student::getName).collect(Collectors.toList());
        System.out.println("mapToName = " + mapToName);

        //flatMap 多个流转换为一个流
        List<Student> flatMapStudent = Stream.of(students, asList(new Student("艾斯", 25, 183), new Student("雷利", 48, 176))).flatMap(Collection::stream).collect(Collectors.toList());
        System.out.println("flatMapStudent = " + flatMapStudent);

        //max
        Student student = flatMapStudent.stream().max(Comparator.comparing(Student::getAge)).orElseThrow(() -> new RuntimeException("自定义异常"));
        Student orElseGet = flatMapStudent.stream().min(Comparator.comparing(Student::getAge).thenComparing(Student::getHeight)).orElseGet(Student::new);
        int studentMax = flatMapStudent.stream().mapToInt(Student::getAge).max().getAsInt();
        int studentMax1 = flatMapStudent.stream().mapToInt(Student::getAge).reduce(0,Integer::sum);
        System.out.println("orElseGet = " + orElseGet);
        System.out.println("studentMax = " + studentMax);
        System.out.println("studentMax1 = " + studentMax1);
        System.out.println("student = " + student);

        //reduce
        Integer reduce = Stream.of(1, 2, 3, 4).reduce(10, (acc, x) -> acc * x);
        System.out.println("reduce = " + reduce);

        //Collectors
        OutstandingClass class1 = new OutstandingClass("一班", students);
        OutstandingClass class2 = new OutstandingClass("一班", asList(new Student("娜美", 20, 190)));
        System.out.println(" =================================== ");
        Double aDouble = students.stream().collect(averagingInt(Student::getAge));
        System.out.println("aDouble = " + aDouble);
        OutstandingClass outstandingClass = Stream.of(class1,class2).collect(maxBy(Comparator.comparing(x -> x.getStudentList().size()))).orElseGet(OutstandingClass::new);
        System.out.println("outstandingClass = " + outstandingClass);

关于Collect中的Collectors的使用我这里推荐一篇文章以后可以来看看

四、总结

本篇主要从实际使用讲述了常用的方法及流,使用java8可以很清晰表达你要做什么,代码也很简洁。本篇例子主要是为了讲解较为简单,大家可以去使用java8重构自己现有的代码,自行领会lambda的奥妙。

本文说的Stream要组合使用才会发挥更大的功能,链式调用很迷人,根据自己的业务去做吧。

五、数值流

@Test
public void testToInt() {
    final ArrayList<Dish> dishes = Lists.newArrayList(
            new Dish("pork", false, 800, Type.MEAT),
            new Dish("beef", false, 700, Type.MEAT),
            new Dish("chicken", false, 400, Type.MEAT),
            new Dish("french fries", true, 530, Type.OTHER),
            new Dish("rice", true, 350, Type.OTHER),
            new Dish("season fruit", true, 120, Type.OTHER),
            new Dish("pizza", true, 550, Type.OTHER),
            new Dish("prawns", false, 300, Type.FISH),
            new Dish("salmon", false, 450, Type.FISH)
    );

    IntStream intStream = dishes.stream()
            .mapToInt(Dish::getCalories);转换数值流
}

转换为对象流
我们虽然会使用数值流进行计算,但经常需要回归到对象,那么就需要将int stream装箱为Integer stream. 可以使用boxed()方法。

Stream<Integer> boxed = intStream.boxed();

其中有一个数值范围流:
有时候需要生成一个数值范围,比如1到30. 可以使用for循环,也可以直接使用数值流。

创建一个包含两端的数值流,比如1到10,包含10:

IntStream intStream = IntStream.rangeClosed(1, 10);

创建一个不包含结尾的数值流,比如1到9:

IntStream range = IntStream.range(1, 9);

关于流的操作这里也记录几个链接,方便以后查看:
获取一个Collector实现类对象方式:通过Collectors工具类中的方法
Stream中的详细用法说明
Stream的常用方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值