java8新特性
主要内容(哔哩哔哩上的视频)
1.Lambda 表达式
为什么使用Lambda表达式?
- Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使java的语言表达能力得到了提升。
Lambda表达式的基础语法:java8中引入了一个新的操作符"->"该操作符称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆成两部分。
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需执行的功能,即Lambda体。
所谓的Lambda表达式就是对接口的一个实现,左侧的参数列表对应的就是接口中的抽象方法的参数列表,右侧就是对接口抽象方法实现的一个功能。
什么是函数式接口?
这个接口中只有一个抽象方法的接口就叫函数式接口,可以使用注解@FunctionalInterface修饰,Lambda表达式需要函数式接口的支持。
Lambda表达式语法格式
语法格式一:无参数,无返回值。
"->"操作符左边是接口的参数,右边是接口中抽象方法的实现。
举例:
public class TestMain {
@Test
public void test1(){
Runnable r = () -> {
int sum = 0;
for(int i=1;i<100;i++){
sum += i;
}
System.out.println(sum);
};
Thread t1 = new Thread(r);
t1.start();
}
}
语法格式二:有一个参数,无返回值。
(x) -> System.out.println(x);
@Test
public void test2(){
Consumer<String> con = (x) -> System.out.println(x);
con.accept("sssxxxx");
}
语法格式三:若只有一个参数,小括号可以忽略不写。
x -> System.out.println(x);
语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句。
@Test
public void test3(){
Comparator<Integer> com = (x, y) ->{
System.out.println("函数式接口");
return Integer.compare(x,y);
};
int num = com.compare(10,11);
System.out.println(num);
}
语法格式五:有两个以上参数,有返回值,并且Lambda体中只有一条语句,return和大括号都可以省略不写。
@Test
public void test4(){
Comparator<Integer> com = (x,y)->Integer.compare(x,y);
Integer num = com.compare(10,11);
System.out.println(num);
}
语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即"类型推断"。
(Integer x,Integer y) ->Integer.Compare(x,y);
自定义函数式接口并使用
//自定义一个函数式接口
@FunctionalInterface
public interface MyFun<T> {
T getValue(T t);
}
//使用自定义的函数式接口
@Test
public void test5(){
Integer num = operate(10,x->x*x);
System.out.println(num);
}
private Integer operate(Integer num,MyFun<Integer> myFun){
return myFun.getValue(num);
}
练习:
1.调用Collections.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同按姓名比),使用lambda作为参数传递。
@Data
public class Employee {
private Integer id;
private String name;
private Integer age;
private Double salary;
public Employee(Integer id, String name, Integer age, Double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
}
@Test
public void test1(){
Collections.sort(emps,(s1,s2)->{
if(s1.getAge().equals(s2.getAge())){
return s1.getName().compareTo(s2.getName());
}else{
return Integer.compare(s1.getAge(),s2.getAge());
}
});
for(Employee e : emps){
System.out.println(e);
}
}
2.①声明式函数接口,接口中声明抽象方法,public String getValue(String str);
②声明类TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值。
③再将一个字符串的第二个和第四个索引位置进行截取子串。
@FunctionalInterface
public interface MyFunction<T> {
T getValue(T str);
}
@Test
public void test2(){
String str1 = this.operate("adccc",x->x.toUpperCase());
System.out.println(str1);
System.out.println("==============================");
String str2 = this.operate("abcdefg",x->x.substring(1,4));
System.out.println(str2);
}
private String operate(String str,MyFunction<String> myFunction){
return myFunction.getValue(str);
}
3.①声明一个带两个泛型的函数式接口,泛型类型为<T,R> T为参数,R为返回值
②接口中声明对应抽象方法
③在TestLambda类中声明方法,使用接口作为参数,计算两个long型参数的和
④再计算两个long型参数的乘积。
@FunctionalInterface
public interface MyFunctionFirst<T,R> {
R sum(T t1,T t2);
}
@Test
public void test3(){
Long sum = this.operate(34l,56l,(x,y)->x+y);
System.out.println(sum);
System.out.println("================");
Long sum1 = this.operate(10L,10L,(x,y)->x*y);
System.out.println(sum1);
}
private Long operate(Long t1,Long t2,MyFunctionFirst<Long,Long> my){
return my.sum(t1,t2);
}
2.函数式接口
java内置四大核心函数式接口
Consumer<T> : 消费型接口
void accept(T t);
@Test
public void test5(){//消费型接口
this.happy(10000.0,x-> System.out.println("一晚上消费"+x+"元"));
}
private void happy(Double money, Consumer<Double> con){
con.accept(money);
}
Supplier<T> : 供给型接口
T get();
@Test
public void test6(){//供给型接口
Supplier<Integer> su = ()-> (int) (Math.random()*100);
System.out.println(su.get());
}
Function<T,R> : 函数型接口
R apply(T t);
@Test
public void test7(){//函数型接口
Function<Long,Long> fun = (x) ->{int i =0;i=10;x = x+i;return x;};
Long i = fun.apply(100L);
System.out.println(i);
}
Predicate<T> : 断言型接口
boolean test(T t);
@Test
public void test8(){//断言型接口
Predicate<Integer> pre = (x) -> x > 10;
Boolean bo = pre.test(20);
System.out.println(bo);
}
3.方法引用与构造器引用
方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用"方法引用"(可以理解为方法引用是Lambda表达式的另外一种表现方式)
主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
4.Stream API
什么是Stream?
是数据渠道,用于操作数据源(集合、数组)所生成的元素序列。集合讲的是数据,流讲的是计算。
注意:
Stream 自己不会存储元素
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream的操作三个步骤
- 创建Stream
一个数据源(如:集合、数组),获取一个流 - 中间操作
一个中间操作链,对数据源的数据进行处理 - 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果。
可以对集合或者数组中的数据进行操作,流在对集合或者数组中的数据进行操作的时候,类似于数据的传输,也得有个数据传输的管道,流在管道传输的过程中,会做一系列流水线式的中间操作,比如过滤、排序、切片。做完这些操作之后,原来的数据源是不会改变的,会产生一个新的流。
//创建Stream
@Test
public void test9(){
//1.可以通过Collection系列提供的Stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2.通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);
//3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa","bb","cc");
//4.创建无线流
Stream<Integer> stream4 = Stream.iterate(0,(x)->x+2);
stream4.limit(10).forEach(System.out::println);
//生成
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
}
Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为"惰性求值"。
帅选与切片
filter(Predicate p) 接收Lambda,从流中排除某些元素。
List<Employee> emps = Arrays.asList(
new Employee(101,"张三",18,9999.99),
new Employee(102,"李四",59,6666.66),
new Employee(103,"王五",28,3333.33),
new Employee(104,"赵六",8,7777.77),
new Employee(105,"田七",38,5555.55)
);
//内部迭代: 迭代操作由Stream API完成
@Test
public void test10(){
//中间操作,不会执行任何操作
Stream<Employee> stream = emps.stream().filter(s-> {
System.out.println("Stream API 的中间操作");
return s.getAge()>35;
});
//终止操作,一次性执行全部内容,即"惰性求值"
stream.forEach(System.out::println);
}
//以下是打印情况,当没有终止操作的时候,是不会有打印结果的,当有终止操作时,才会一次性执行全部内容。
Stream API 的中间操作
Stream API 的中间操作
Employee(id=102, name=李四, age=59, salary=6666.66)
Stream API 的中间操作
Stream API 的中间操作
Stream API 的中间操作
Employee(id=105, name=田七, age=38, salary=5555.55)
distinct() 帅选,通过流所生成元素的hashCode()和equals()去除重复元素。
limit(long maxSize) 截断流,使其元素不超过给定数量。
//短路 可以在某个方面提高效率 并不是遍历所有 而是发现两个的话就返回两个
@Test
public void test11(){
emps.stream().filter(e->{
System.out.println("短路!");
return e.getSalary()>5000;
}).limit(2)
.forEach(System.out::println);
}
skip(long n) 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补。
映射
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDoublie(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
mapToLong(ToLangFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连成一个流。
@Test
public void test12(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
Stream<Character> stream = list.stream().flatMap(e->filterCharacter(e));
stream.forEach(System.out::print);
}
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for(Character ch : str.toCharArray()){
list.add(ch);
}
return list.stream();
}
排序
sorted() 产生一个新流,其中按自然顺序排序。
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序。
allMatch 检查是否匹配所有元素
anyMatch 检查是否至少匹配一个元素
noneMatch 检查是否没有匹配所有元素
findFirst 返回第一个元素
findAny 返回当前流中的任意元素
count 返回流中元素的个数
max 返回流中最大的值
min 返回流中最小值
List<Employee> emps = Arrays.asList(
new Employee(101,"张三",18,9999.99, Employee.State.BUSY),
new Employee(102,"李四",59,6666.66, Employee.State.FREE),
new Employee(103,"王五",28,3333.33,Employee.State.FREE),
new Employee(104,"赵六",8,7777.77,Employee.State.VOCATION),
new Employee(105,"田七",38,5555.55,Employee.State.VOCATION)
);
@Test
public void test13(){
boolean b = emps.stream().allMatch(e->e.getState().equals(Employee.State.BUSY));
System.out.println(b);
boolean b1 = emps.stream().anyMatch(e->e.getState().equals(Employee.State.BUSY));
System.out.println(b1);
boolean b2 = emps.stream().noneMatch(e->e.getState().equals(Employee.State.BUSY));
System.out.println(b2);
Optional<Employee> optional = emps.stream().sorted((e1, e2)->e1.getSalary().compareTo(e2.getSalary())).findFirst();
System.out.println(optional.get());
Optional<Employee> op = emps.stream().filter(e->e.getSalary()>5000).findAny();
System.out.println(op.get());
Long count = emps.stream().count();
System.out.println(count);
Optional<Employee> e = emps.stream().max((e1,e2)->e1.getSalary().compareTo(e2.getSalary()));
System.out.println(e.get());
Optional<Employee> op1 = emps.stream().min((e1,e2)->e1.getAge().compareTo(e2.getAge()));
System.out.println(op1.get());
}
归约
reduce(T identity,BinaryOperator) 可以将流中元素反复结合起来,得到一个值。
reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个值。返回Optional
@Test
public void test14(){
Integer sum = emps.stream().map(e->e.getAge()).reduce(0,(x,y)-> x+y);
System.out.println(sum);
Optional<Double> dsum = emps.stream().map(e->e.getSalary()).reduce(Double::sum);
System.out.println(dsum.get());
}
备注:map和reduce的连接通常称为map-reduce模式,因Google用它进行网络搜索而出名。
收集
collect(Collector c) 将流转化为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。
Collector接口方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表
@Test
public void test15(){
//总数
Long count = emps.stream().collect(Collectors.counting());
System.out.println(count);
//平均值
Double avg = emps.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
//总和
Double sum = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sum);
//最大值
Optional<Employee> op = emps.stream().collect(Collectors.maxBy((x1,x2)->x1.getSalary().compareTo(x2.getSalary())));
System.out.println(op.get());
//工资的最小值
Optional<Double> minOp = emps.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compareTo));
System.out.println(minOp.get());
//分组
Map<Employee.State,List<Employee>> map = emps.stream().collect(Collectors.groupingBy(Employee::getState));
System.out.println(map);
//多级分组(先按状态分组,再按年龄段分组,可以无线分下去)
Map<Employee.State,Map<String,List<Employee>>> map1 = emps.stream().collect(Collectors.groupingBy(Employee::getState,Collectors.groupingBy(e->{
if(e.getAge()<=35){
return "青年";
}else if(e.getAge() <=50){
return "青年";
}else{
return "老年";
}
})));
System.out.println(map1);
//分区
Map<Boolean,List<Employee>> map2= emps.stream().collect(Collectors.partitioningBy(e->e.getSalary()>10000));
System.out.println(map2);
//获取组函数(统计函数)
DoubleSummaryStatistics statistics = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
//获取平均值
Double avg1 = statistics.getAverage();
//获取个数
long count1 = statistics.getCount();
//获取最大值
statistics.getMax();
//获取最小值
statistics.getMin();
//获取总值
statistics.getSum();
//连接字符串
String joinStr = emps.stream().map(Employee::getName).collect(Collectors.joining("-"));
}
并行流与顺序流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换。
Fork/join框架与传统线程池的区别
采用"工作窃取"模式:
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架中实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。
Optional类
Optional类是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
常用方法:
Optional.of(T t) :创建一个Optional实例
Optional.empty():创建一个空的Optional实例
Optional.ofNullable(T t):若t 不为null,创建Optional实例,否则创建空实例
isPresent() : 判断是否包含值
orElse(T t):如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) 如果调用对象包含值,返回该值 ,否则返回s获取的值
map(Function f) 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
flatMap(Function mapper) 与map类似,要求返回值必须是Optional。