Java8学习:打卡第44天
内容管理 :java8构造器引用
在昨天的分享中,了解到Lambda表达式的作用就是创建函数式接口的实例,并且使用方法引用来更加简化Lambda表达式,但是方法引用要做到引用的方法恰当,只要创建实例之后,直接就可调取其中的方法来完成相关的操作
Lambda表达式和方法引用回顾🏷
昨天的分享中,已经明确了这两种结构的用法,这里先来简单再回顾一下
Supplier<String> supply = () -> "Cfeng";
System.out.println(supply.get());
昨天提到过,消费型接口是对传入的对象进行操作,而供应型接口就是获得一个T类型的对象
上面这个例子的Lambda表达式有些许简单,再来看一个例子,其中例子中的Employee类就是之前的博客中的Employee🌴
Employee employee = new Employee(12, "Cfeng", 2, 30);
Supplier<String> supply = () -> employee.getDetails();//这就是常规Lambda表达式使用其他的方法的方式
System.out.println(supply.get());
//方法引用的方式
Supplier<String> con2 = employee :: getDetails; //使用方法引用来进行优化
System.out.println(con2.get());//和C不一样,不是地址,而是地址指代的对象的字符串表示,还是有所区别
方法使用的要求
接口中的参数列表和返回值类型和调用方法的参数列表和返回值类型相同
昨天的博客中就已经提到过了,方法引用的方式有
-
类 :: 静态方法 -----例子就是 Integer :: compare
-
对象 :: 实例方法 -----例子就是上面的 employee :: getDetails
⚠上面的要求只适用于前两种情况,第三种情况不是很适合⚠️ -
类 :: 实例方法
传入的两个对象中,第一个对象是实例方法的调用者,所以就可以直接省略,写成方法引用的形式
这第三种看着好像会很疑惑,因为和普通的.引用的方式有点脱,看一个例子
使用的函数式接口为 Comparator中的int compare(T t1, T t2)
想调用的实现方法为String中的 boolean t1.compareTo(t2)
这里的参数列表是不匹配的,看看是怎么回事
Comparator<String> com1 = (t1,t2) -> t1.compareTo(t2);
System.out.println(com1.compare("abc", "abd"));
//传入的两个对象中,第一个对象是实例方法的调用者,所以就可以直接省略,写成方法引用的形式
Comparator<String> com = String :: compareTo;
System.out.println(com.compare("abc", "abd"));
这里通过Lambda表达式到方法引用的变化,相信你就可以知道为什么是类 :: 实例方法了;这里其实是使用传入的第一个对象来调用的实例方法,并没有违背规则
- 使用还是有要求的
那就是这里调取的方法的参数个数比functionalinterface中的abstract方法少一个,并且抽象方法参数类表中的第一个必须是实例方法的类类型的
- 那为什么要使用类名来调用呢?
这是因为调取的时候调取实例方法的对象是该类的一个对象,并且不能使用 对象 :: 实例方法,因为这里是因为如果使用对象那么这里的参数就只能有一个,而不是两个,所以不能使用第二种方式
构造器引用🏷
其实构造器引用就是方法引用的一个特例,既然我们可以通过类名来调用一个静态方法,那么也可以使用类名来调用一个类的构造器,只是注意写法 :类名 :: new
但是这里需要注意的地方就是接口中的方法的参数列表和构造器的参数列表需要相同
抽象方法的返回值类型就是构造的类类型的
举几个例子来说明一下
- 空参构造器
Supplier<Employee> emp = () -> new Employee();
emp.get();
//换成方法引用的方式
Supplier<Employee> emp1 = Employee :: new;
emp1.get();
这里获取的对象就是一个创建的Employee对象,为了检验是否执行,在构造器中加一个打印语句,就可以执行
- 含参构造器
这里可以使用Function接口R apply(T t) ; 传入一个T类型的t,返回一个R类型的对象
Function<String, Employee> func1 = name -> new Employee(name);
System.out.println(func1.apply("张三"));
//看一下方法引用的方式
Function<String, Employee> func = Employee :: new;
System.out.println(func.apply("李四"));
打印的结果是
0 张三 0 0.0
0 李四 0 0.0
java可以自动识别相应的构造器来进行方法引用,所以就和之前的一样的
同理,这里还可以找到其他的含有多个参数的接口方法,比如BiFunction
Function<String, Employee> func1 = name -> new Employee(name);
System.out.println(func1.apply("张三"));
//看一下方法引用的方式
Function<String, Employee> func = Employee :: new;
System.out.println(func.apply("李四"));
BiFunction<String, Integer, Employee> member3 = (name,age) -> new Employee(name, age);
System.out.println(member3.apply("王五", 30));
//使用方法引用来做
BiFunction<String, Integer, Employee> member = Employee :: new;
System.out.println(member.apply("孙六", 27));
运行之后的输出结果
0 张三 0 0.0
0 李四 0 0.0
0 王五 30 0.0
0 孙六 27 0.0
数组引用🏷
其实就是构造器引用,因为数组其实也是一种特殊的类,直接举例子看一下就可
Function<Integer,int[]> instance = lenth -> new int[lenth];
System.out.println(Arrays.toString(instance.apply(5)));
//使用数组引用
Function<Integer,int[]> instance1 = int[] :: new;
System.out.println(Arrays.toString(instance1.apply(6)));
输出的结果是
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0]
所以数组引用就可以看作是构造器引用,方式都是类似的
Stream API🏷
java8两个最重要的改变,一个是引入了Lambda表达式,另外一个就是引入了Stream API
- 什么是Stream API?
String API 在java.until中
String 是java8中处理集合的关键抽象概念,可以指定你希望对集合进行的操作,可以执行复杂的查找,过滤,映射操作,使用Stream API 对集合进行操作,就类似使用数据库进行数据库查询,也就是Stream API提供了高效使用数据的方式
- 为什么要使用Stream API?
实际开发中,我们需要从数据库中获取数据,比如在前台想要直接获取最近三个月的数据,那么会将命令发送到java处理层,但是java层原来的数据处理能力很弱,进入连接的数据库中进行相关数据的过滤,所以就只能使用MySOL和Oracle等数据库,因为其他的数据库不能自己进行数据的操作,要在java层面处理;引入了Stream API之后这里就可以使用其他的NoSQL数据源比如MongDB和Radis
- Stream和Collection的区别?
Collection是一种静态的内存结构,而Stream是有关于计算的,前者面向内存,后者面向CPU,通过CPU进行计算
- Stream的特点
Stream是数据的渠道,用来操作数据源所生成的元素序列
1
: Stream自己不会存储数据,只是计算,类似于Itreator ⛺️
2
: Stream不会改变源对象,相反会返回一个持有结果的新Stream ,类似于String类 ⛺️
3
: Stream操作是延迟执行的,它们会等到需要结果时才执行【很实用】
- 操作Stream的三个步骤
- 创建Stream :一个数据源,获取一个流
- 中间操作 :中间操作链,对数据源的数据进行处理
- 终止操作(终端操作) 一旦执行终止操作,就执行中间擦欧总链,并产生结果,之后,不能再使用 — 意味着如果想用需要再重新写一个流
今天时间有点紧,对于Stream的操作实例明天会和大家详细分析🌳
再次讲述一下Stream的使用,Stream关注的是数据的计算,和CPU打交道,而集合关注的数据的存储,和内存打交道,关系型数据库比如MySQL就不需要Stream,但是非关系型noSQL数据库 Redis就需要Stream进行数据的计算处理,Stream不会改变源来的数据,和String一样,会新创建一个Stream
Stream执行,首先先创建一个Stream【实例化】,有一系列中间操作【filter,sorted,map,collect】,还有一个结束操作,只有执行结束操作,那么才会执行中间链,就像一个一次性button,并且只能执行一次,该流就不能再执行,只有再创建,就像对象的finallize也只有一次机会
创建一个Stream对象
通过集合创建
java8的Collection接口被扩展了,提供了两个获取流的方法
- default Stream< E> stream(); 返回一个顺序流
- default Stream< E> parallelStream(); 返回一个并行流
数据源就会自己适配这种方法
Stream java.util.Collection.stream()
Stream java.util.Collection. parallelStream()
我们通过之前的Employees来看一下这里的方法
List<Employee> employees = Arrays.asList(new NameListService().getAllEmployees());//Arrays类很好用,asList
//创建一个顺序流【数据按照源数据的顺序】
Stream<Employee> stream1 = employees.stream();
//创建一个并行流【数据同时取用,就像多线程一样】
Stream<Employee> parallStream = employees.parallelStream();
顺序流就是执行中间操作时,还是按照源数据的方式,一个一个的取用操作;而并行流就是同时取用数据;这里将数组转化为集合使用的时Arrays中的asList方法
通过数组创建
既然集合可以是数据源,那么数组也可以是数据源,创建的方法和上面是一样的,所有的数据源都有该方法
java8的Arrays中的静态方法stream()可以获取数组流
- static < T> Stream(T[] array) 返回一个流
- 重载形式,能够处理对应基本类型的数组
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array)
Employee[] emp = new NameListService().getAllEmployees();
Stream<Employee> stream2 = Arrays.stream(emp);
通过Stream的of的方法
Stream类中提供了方法of()来创建一个流,该方法为静态方法
Stream<Employee> stream3 = Stream.of(emp);
通过创建无限流
可以使用Stream类的静态方法Stream.itreate()和Stream.generate(),创建无限流
- 迭代 public static< T> Stream< T>itrerat(final T seed, final UnaryOperator< T> f)
Returns an infinite sequential ordered Stream produced by iterativeapplication of a > > > function f to an initial element seed,producing a Stream consisting of seed, f(seed), f(f(seed)), etc.
The first element (position 0) in the Stream will bethe provided seed. For n > 0, the element at position n, will be the result of applying the function f to theelement at position n - 1.
就是给一个种子,之后就像离散数学中的一样开始自动迭代
这里第二个是一个函数式接口,里面有个apply方法可以返回一个传入的对象的对象,使用了lambda表达式
可以使用foreach来看一下这个流
对于foreach方法
void java.util.stream.Stream.forEach(Consumer<? super Integer> action)
Performs an action for each element of this stream.
This is a terminaloperation.
The behavior of this operation is explicitly nondeterministic.For parallel stream pipelines, this operation does notguarantee to respect the encounter order of the stream, as doing >sowould sacrifice the benefit of parallelism. For any given element, theaction may be >performed at whatever time and in whatever thread thelibrary chooses. If the action >accesses shared state, it isresponsible for providing the required synchronization.
里面的参数就是一个消费者函数式接口,那这里我们使用方法引用来看一下
Stream.iterate(0, t-> t + 2);//以0为种子,生成了一个无限的偶数列流
Stream.iterate(0, t-> t + 2).forEach(System.out :: println);
//这样子就可以生成一个无限偶数流
//为了方便,我们对无限流加一个限制
Stream.iterate(0, t-> t + 2).limit(10).forEach(System.out :: println);
//这样之后就只会产生10个偶数的流
- 生成 public static< T> Stream< T>generate(Supplier< T> s) 里面是一个提供者函数式接口
Returns an infinite sequential unordered stream where each element isgenerated by the provided Supplier. This is suitable forgenerating constant streams, streams of random elements, etc.
Stream.generate(Math :: random).limit(10).forEach(System.out :: println);
里面是一个提供型接口,可以产生对象,这里就用Math类的random方法来代替
对于剩下的部分明天再更新🌳