Java 8 新特性——实践篇
参考
重要更新:Lambda 表达式和Stream API
Lambda 表达式
Lambda 表达式引入之前:
举个场景例子:当我们要对一个班级里的学生对象里各种成绩进行过滤时,比如大于85分获得A的学生集合,最初的方式是写不同的方法处理不同的科目成绩过滤;再后面就可以用策略模式,声明一个接口ScoreFilterStrategy,针对不同的科目实现不同的策略算法。再优化一下,我们可以对策略模式进行升级,直接用匿名内部类实现我们的接口ScoreFilterStrategy,自定义策略实现。但基于其代码的繁琐性,我们可以使用Lambda 表达式进行函数式编程优化,更可以对集合进行Stream API流的调用处理来实现想要的效果。参考如下:
Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。
Lambda 表达式语法
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为“->” ,该操作符被称为Lambda 操作符或剪头操作符。它将Lambda 分为两个部分:
左侧:指定了Lambda 表达式需要的所有参数。
右侧:指定了Lambda 体,即Lambda 表达式要执行的功能。
Lambda 表达式需要函数式接口的支持。
示例:
public class LambdaTest {
//语法格式一:无参,无返回值,Lambda 体只需一条语句
@Test
public void test1() {
Runnable r = () -> System.out.println("helo world");
r.run();
}
//语法格式二:Lambda 需要一个参数
@Test
public void test2() {
Consumer<String> consumer = (str) -> System.out.println(str);
consumer.accept("I am Batman");
}
//语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略
@Test
public void test3() {
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("I am Batman");
}
//语法格式四:Lambda 需要两个参数,并且有返回值,并且lambda体中有多条语句时要加{}
@Test
public void test4() {
Comparator<Integer> comparator = (x, y) -> {
System.out.println("比较数据");
return Integer.compare(x, y);
};
comparator.compare(1, 2);
}
//语法格式五:当Lambda 体只有一条语句时,return 与大括号可以省略
@Test
public void test5() {
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
comparator.compare(1, 2);
}
//语法格式六:Lambda 表达式的参数列表上的参数类型可以省略不写,因为JVM编译器(javac)会通过程序上下文推动出数据类型,
// 即类型推断。这是JDK1.8的新特性,在JDK1.7上写类型推断的代码是编译不通过的。
@Test
public void test6() {
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
comparator.compare(1, 2);
}
//类型推断实例
@Test
public void test7() {
String[] strings = {"a", "b", "ddd"};
//如果像下面这样写就无法通过上下文进行类型推断
// String[] string2;
// string2 = {"a", "b", "ddd"};
//后面的new ArrayList<>();尖括号里面也可以省略不写也是通过类型推断得来的。
List<String> list = new ArrayList<>();
//创建的new HashMap<>()尖括号里面也可以省略不写也是通过类型推断得来的,他会采用operate方法里的泛型格式
operate(new HashMap<>());
}
private void operate(Map<String,String> map){
}
}
函数式接口
接口中只包含一个抽象方法的接口,称为函数式接口。我们可以使用@FunctionalInterface注解修饰该接口,用于检测是否是函数式接口。
示例:
@FunctionalInterfacepublic interface MyFunc { Integer getValue(Integer t);}
@FunctionalInterfacepublic interface CustomFunc { String execute(String str);}
@FunctionalInterface
public interface CauclateFunc<T,R> {
R getValue(T t1,T t2);
}
public class LambdaTest2 {
@Test
public void test() {
System.out.println(operate(100, x -> x * x));
}
//函数式接口作为方法参数传递,这样我们便能在调用方法时自定义我们的lambda函数的操作
private Integer operate(Integer t, MyFunc myFunc) {
return myFunc.getValue(t);
}
@Test
public void test2() {
System.out.println(concate("hello",str -> str + " world"));
String concate = concate("I", str -> {
System.out.println("prepare...");
return str + " am " + "batman";
});
System.out.println(concate);
}
private String concate(String str,CustomFunc customFunc){
return customFunc.execute(str);
}
@Test
public void test3() {
Long calcute = calcute(100L, 200L, (x, y) -> x * y);
System.out.println(calcute);
Long calcute2 = calcute(100L, 200L, (x, y) -> x - y);
System.out.println(calcute2);
}
private Long calcute(Long l1, Long l2, CauclateFunc<Long, Long> cauclateFunc) {
return cauclateFunc.getValue(l1, l2);
}
}
输出:
hello world
prepare...
I am batman
20000
-100
Java 内置四大核心函数式接口
其他子接口是在四大核心函数式接口的基础上进行个性化的参数添加和处理定义:
四大核心函数式接口使用示例:
public class FunctionalInterfaceTest {
/**
* Consumer<T> :消费型接口:接收一个参数进行消费处理,无返回
* void accept(T t)
*/
@Test
public void test() {
call("麻包锅", name -> System.out.println(name + "被老师点名了!"));
}
//麻包锅被老师点名了!
private void call(String name, Consumer<String> consumer) {
consumer.accept(name);
}
/**
* Supplier<T> :供给型接口 :返回一些东西
* T get();
*/
@Test
public void test2() {
//注意加括号,int强转是针对整个只而不是Math.random()返回的,否则为0
List<Integer> list = supply(5, () -> (int) (Math.random() * 100));
System.out.println(list);
}
//[78, 96, 66, 20, 0]
private List<Integer> supply(Integer num, Supplier<Integer> supplier) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(supplier.get());
}
return list;
}
/**
* Function<T, R>:函数型接口 :对T类型参数对象处理返回R类型对象
* R apply(T t);
*/
@Test
public void test3() {
String concate = concate("希望耗子尾汁!", str -> "老同志," + str);
System.out.println(concate);
String filterStr = filterStr("偷袭, 骗我老同志! ", s -> s.replace(" ",""));
System.out.println(filterStr);
}
//老同志,希望耗子尾汁!
//偷袭,骗我老同志!
private String concate(String str, Function<String,String> function) {
return function.apply(str);
}
private String filterStr(String str, Function<String,String> function) {
return function.apply(str);
}
/**
* Predicate<T> :断言型接口
* boolean test(T t);
*/
@Test
public void test4() {
List<Integer> numList = new ArrayList<>();
numList.add(1);
numList.add(13);
numList.add(23);
numList.add(67);
List<Integer> list = check(numList, n -> n > 15);
System.out.println(list);
}
//[23, 67]
List<Integer> check(List<Integer> list,Predicate<Integer> predicate){
List<Integer> newList = new ArrayList<>();
for (Integer num : list) {
if (predicate.test(num)){
newList.add(num);
}
}
return newList;
}
}
方法引用与构造器引用
方法引用
当要传递给Lambda体的操作,如果Lambda 体中的内容有方法实现了,就可以使用“方法引用”。即方法引用是lambda表达式的另一种表现形式。
有以下三种语法形式:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
注意:
- lambda体中调用方法的参数列表和返回值类型,要与函数式接口中的抽象方法的参数列表和返回值类型保持一致。
- 如果lambda参数列表中的第一个参数是示例方法的调用者,第二个参数是示例方法的参数时,可以使用ClassName::methodName。
构造器引用
格式:ClassName::new
构造器引用与函数式接口相结合,自动与函数式接口中方法兼容。
注意:
- 当我们把构造器引用赋值给定义的方法时,需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表一致!
数组引用
格式:type[] :: new
示例:
public class MethodRefTest {
/**
* 对象::实例方法
* 注意方法引用使用时:lamda表达式需要实现的抽象方法的参数类型和返回值要与当前调用的方法的参数类型和返回值保持一致
* Consumer<T> : void accept(T t);
* PrintStream : void println(String x);
* 当调用方法有一个参数无返回值时适用于Consumer的消费型接口
*/
@Test
public void test() {
Consumer<String> consumer = System.out::println;
consumer.accept("朋友们好啊!");
}
/**
* Supplier<T> T get();
* 调用方法无参数有返回值时适用于Supplier的供给型接口
* 典型的如对象的get方法
*/
@Test
public void test2() {
User user = new User();
Supplier<String> supplier = user::getName;
System.out.println(supplier.get());
}
/**
* 类::静态方法
* Comparator<T> : int compare(T o1, T o2);
* Integer : static int compare(int x, int y)
*/
@Test
public void test3() {
List<Integer> numList = new ArrayList<>();
numList.add(111);
numList.add(13);
numList.add(2);
numList.add(67);
// Comparator<Integer> comparator = Integer::compare;
// Collections.sort(numList,comparator);
Collections.sort(numList, Integer::compare);
System.out.println(numList);
}
//[2, 13, 67, 111]
/**
* 类::实例方法
* 使用条件:如果lambda参数列表中的第一个参数是示例方法的调用者,第二个参数是示例方法的参数时
* BiPredicate<T, U> : boolean test(T t, U u);
* String : boolean equals(Object anObject)
*/
@Test
public void test4() {
boolean isEqual = checkEqual("music", "Music", String::equals);
System.out.println(isEqual);
}
//false
private boolean checkEqual(String a, String b, BiPredicate<String, String> biPredicate) {
return biPredicate.test(a, b);
}
/**
* 构造器引用
* 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表一致
* Supplier<T> : T get(); 对于无参构造器
* Function<T, R> : R apply(T t); 对于一个参数构造器
* BiFunction<T, U, R> : R apply(T t, U u); 对于两个参数构造器
*/
@Test
public void test5() {
//函数式编程写法
Supplier<User> supplier1 = () -> new User();
//构造器引用
Supplier<User> supplier = User::new;
//一个参数构造器
Function<String, User> function = User::new;
User user = function.apply("年轻人");
System.out.println(user);//User{id=0, age=0, name='年轻人'}
BiFunction<Integer, String, User> biFunction = User::new;
User user1 = biFunction.apply(69, "老同志");
System.out.println(user1);//User{id=0, age=69, name='老同志'}
}
/**
* 数组引用
*/
@Test
public void test6() {
Function<Integer, String[]> function1 = x -> new String[x];
Function<Integer, String[]> function = String[]::new;
String[] strings = function.apply(5);
System.out.println(strings.length);//5
}
}
public class User {
private int id;
private int age;
private String name;
public User() {
}
public User(int age, String name) {
this.age = age;
this.name = name;
}
public User(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
Stream API
Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
集合讲的是数据,Stream流讲的是计算。
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行。
Stream 的操作有三个步骤:
-
创建Stream
一个数据源(如:集合、数组),获取一个流 -
中间操作
一个中间操作链,对数据源的数据进行处理 -
终止操作
一个终止操作,执行中间操作链,并产生结果
创建Stream
创建Stream流的4种方式示例:
public class StreamTest {
//创建Stream流的4种方式
@Test
public void test() {
//1、集合创建流:通过Collection系列集合提供的stream()或parallelStream()方法创建流
List<String> list = new ArrayList<>();
//串行流
Stream<String> stream = list.stream();
//并行流
Stream<String> parallelStream = list.parallelStream();
//2、数组创建流:通过Arrays.stream(T[] array)静态方法获取数组流
User[] users = new User[5];
Stream<User> userStream = Arrays.stream(users);
//3、Stream创建流: 通过Stream.of(T... values)静态方法创建流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 66, 75);
//4、创建无限流
// A:通过Stream.iterate(final T seed, final UnaryOperator<T> f)传入seed和一元函数式接口实现无限流
Stream<Integer> stream1 = Stream.iterate(1, x -> x + 1);
stream1.limit(5).forEach(System.out::println);
// B:通过Stream.igenerate(Supplier<T> s) 提供一个供给型接口实现无限流
Stream<Double> stream2 = Stream.generate(()->Math.random());
stream2.limit(5).forEach(System.out::println);
}
}
输出:
1
2
3
4
5
0.19737667335799347
0.4379300542517345
0.626269580987739
0.8557261379085842
0.09320455087266999
中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!
而在终止操作时一次性全部处理,称为“惰性求值”或者叫延迟加载。
中间操作示例:
public class StreamTest2 {
List<User> users = Arrays.asList(new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
new User(2, 47, "池寒枫", Arrays.asList("shanghai", "beijing")),
new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
new User(5, 380, "霍恩斯", Arrays.asList("shanghai", "beijing")),
new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")),
new User(5, 320, "霍恩斯1", Arrays.asList("shanghai", "beijing")),
new User(5, 600, "霍恩斯2", Arrays.asList("guangzhou", "shengzhen")));
//中间操作
@Test
public void test() {
//filter: boolean test(T t); 通过断言型接口参数过滤
//forEach 是内部迭代,由Stream api 完成,相对的外部迭代就是我们自己写迭代代码iterator
users.stream().filter(user -> user.getAge() > 23).forEach(System.out::println);
}
@Test
public void test2() {
//limit 截断流,截取maxSize个,当截取到足够的个数后便会短路,不再迭代下去
users.stream().filter(user -> user.getAge() > 23)
.limit(2).forEach(System.out::println);
}
/**
* User{id=1, age=27, name='大青山'}
* User{id=2, age=47, name='池寒枫'}
*/
@Test
public void test3() {
//skip 跳过n个
//distinct 去重 如果是对象需要重写hashCode和equal方法
users.stream().filter(user -> user.getAge() > 23)
.skip(2).distinct().forEach(System.out::println);
}
/**
* User{id=3, age=25, name='艾米'}
* User{id=5, age=500, name='霍恩斯'}
*/
@Test
public void test4() {
//map 映射 参数是函数式接口Function<? super T, ? extends R>
users.stream().map(user -> user.getName()).forEach(System.out::println);
}
/**
* 大青山
* 池寒枫
* 艾米
* 池傲天
* 霍恩斯
* 霍恩斯
*/
@Test
public void test5() {
//flatMap 映射成流,将流中的每个值都映射成流,把所有的流连接成一个流。
// 参数是函数式接口Function<? super T, ? extends Stream<? extends R>> mapper ,注意出参必须是个Stream流对象。因此lambda表达式的返回值要是个流
Stream<String> stream = users.stream().flatMap(user -> user.getAddress().stream());
// List<String> collect = stream.collect(Collectors.toList());
// System.out.println(collect);
stream.forEach(System.out::println);
}
@Test
public void test6() {
//sorted() 排序:沒有传参数默认自然排序: Comparable :注意,要排序的对象必须实现Comparable才能调用自然排序sorted()
/**像String就能调用sorted() 排序,因为他实现了Comparable。
* 否则会报错:java.lang.ClassCastException: com.self.practice.lambda.User cannot be cast to java.lang.Comparable
* public final class String
* implements java.io.Serializable, Comparable<String>, CharSequence
*/
//sorted(Comparator<? super T> comparator); 定制排序 Comparator
Stream<User> stream = users.stream().sorted((e1, e2) -> {
if (e1.getName().equals(e2.getName())) {
return e1.getAge().compareTo(e2.getAge());//倒序取反就行,加个负号 -
} else {
return e1.getName().compareTo(e2.getName());
}
});
stream.forEach(System.out::println);
}
}
/**
* User{id=1, age=27, name='大青山'}
* User{id=4, age=18, name='池傲天'}
* User{id=2, age=47, name='池寒枫'}
* User{id=3, age=25, name='艾米'}
* User{id=5, age=380, name='霍恩斯'}
* User{id=5, age=500, name='霍恩斯'}
* User{id=5, age=320, name='霍恩斯1'}
* User{id=5, age=600, name='霍恩斯2'}
*/
终止操作
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。
示例:
public class StreamTest {
//创建Stream流的4种方式
@Test
public void test() {
//1、集合创建流:通过Collection系列集合提供的stream()或parallelStream()方法创建流
List<String> list = new ArrayList<>();
//串行流
Stream<String> stream = list.stream();
//并行流
Stream<String> parallelStream = list.parallelStream();
//2、数组创建流:通过Arrays.stream(T[] array)静态方法获取数组流
User[] users = new User[5];
Stream<User> userStream = Arrays.stream(users);
//3、Stream创建流: 通过Stream.of(T... values)静态方法创建流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 66, 75);
//4、创建无限流
// A:通过Stream.iterate(final T seed, final UnaryOperator<T> f)传入seed和一元函数式接口实现无限流
Stream<Integer> stream1 = Stream.iterate(1, x -> x + 1);
stream1.limit(5).forEach(System.out::println);
// B:通过Stream.igenerate(Supplier<T> s) 提供一个供给型接口实现无限流
Stream<Double> stream2 = Stream.generate(()->Math.random());
stream2.limit(5).forEach(System.out::println);
}
}
public class StreamTest2 {
List<User> users = Arrays.asList(new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
new User(2, 47, "池寒枫", Arrays.asList("shanghai", "beijing")),
new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
new User(5, 380, "霍恩斯", Arrays.asList("shanghai", "beijing")),
new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")),
new User(5, 320, "霍恩斯1", Arrays.asList("shanghai", "beijing")),
new User(5, 600, "霍恩斯2", Arrays.asList("guangzhou", "shengzhen")));
//中间操作
@Test
public void test() {
//filter: boolean test(T t); 通过断言型接口参数过滤
//forEach 是内部迭代,由Stream api 完成,相对的外部迭代就是我们自己写迭代代码iterator
users.stream().filter(user -> user.getAge() > 23).forEach(System.out::println);
}
@Test
public void test2() {
//limit 截断流,截取maxSize个,当截取到足够的个数后便会短路,不再迭代下去
users.stream().filter(user -> user.getAge() > 23)
.limit(2).forEach(System.out::println);
}
/**
* User{id=1, age=27, name='大青山'}
* User{id=2, age=47, name='池寒枫'}
*/
@Test
public void test3() {
//skip 跳过n个
//distinct 去重 如果是对象需要重写hashCode和equal方法
users.stream().filter(user -> user.getAge() > 23)
.skip(2).distinct().forEach(System.out::println);
}
/**
* User{id=3, age=25, name='艾米'}
* User{id=5, age=500, name='霍恩斯'}
*/
@Test
public void test4() {
//map 映射 参数是函数式接口Function<? super T, ? extends R>
users.stream().map(user -> user.getName()).forEach(System.out::println);
}
/**
* 大青山
* 池寒枫
* 艾米
* 池傲天
* 霍恩斯
* 霍恩斯
*/
@Test
public void test5() {
//flatMap 映射成流,将流中的每个值都映射成流,把所有的流连接成一个流。
// 参数是函数式接口Function<? super T, ? extends Stream<? extends R>> mapper ,注意出参必须是个Stream流对象。因此lambda表达式的返回值要是个流
Stream<String> stream = users.stream().flatMap(user -> user.getAddress().stream());
// List<String> collect = stream.collect(Collectors.toList());
// System.out.println(collect);
stream.forEach(System.out::println);
}
@Test
public void test6() {
//sorted() 排序:沒有传参数默认自然排序: Comparable :注意,要排序的对象必须实现Comparable才能调用自然排序sorted()
/**像String就能调用sorted() 排序,因为他实现了Comparable。
* 否则会报错:java.lang.ClassCastException: com.self.practice.lambda.User cannot be cast to java.lang.Comparable
* public final class String
* implements java.io.Serializable, Comparable<String>, CharSequence
*/
//sorted(Comparator<? super T> comparator); 定制排序 Comparator
Stream<User> stream = users.stream().sorted((e1, e2) -> {
if (e1.getName().equals(e2.getName())) {
return e1.getAge().compareTo(e2.getAge());//倒序取反就行,加个负号 -
} else {
return e1.getName().compareTo(e2.getName());
}
});
stream.forEach(System.out::println);
}
/**
* User{id=1, age=27, name='大青山'}
* User{id=4, age=18, name='池傲天'}
* User{id=2, age=47, name='池寒枫'}
* User{id=3, age=25, name='艾米'}
* User{id=5, age=380, name='霍恩斯'}
* User{id=5, age=500, name='霍恩斯'}
* User{id=5, age=320, name='霍恩斯1'}
* User{id=5, age=600, name='霍恩斯2'}
*/
}
public class StreamTest3 {
List<User> users = Arrays.asList(new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
new User(2, 47, "池寒枫", Arrays.asList("shanghai", "beijing")),
// new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
// new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
// new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
// new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
// new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
// new User(5, 380, "霍恩斯", Arrays.asList("shanghai", "beijing")),
new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")));
//终止操作
@Test
public void test() {
//anyMatch(Predicate<? super T> predicate) 检查是否至少匹配一个元素
boolean b = users.stream().anyMatch(user -> user.getAge() > 600);
System.out.println(b);//false
//allMatch(Predicate<? super T> predicate) 检查是否匹配所有元素
boolean b1 = users.stream().allMatch(user -> user.getAge() < 600);
System.out.println(b1);//true
//noneMatch(Predicate<? super T> predicate) 检查是否所有元素都没有匹配到
boolean b2 = users.stream().noneMatch(user -> "雷葛".equals(user.getName()));
System.out.println(b2);//true
//先排序再获取第一个最大或最小的值
//Optional<T> findFirst() 返回第一个元素 ,注意因为集合可能为空因此返回了Optional对象
Optional<User> user = users.stream().sorted(Comparator.comparing(User::getAge)).findFirst();
user.ifPresent(System.out::println);
//先过滤出符合条件的随便找一个
// Optional<T> findAny() 返回当前流中的任意元素,总感觉不是返回任意一个?而是返回第一个符合条件的
Optional<User> userOptional = users.stream().filter(u -> u.getAge() < 30).findAny();
userOptional.ifPresent(System.out::println);
long count = users.stream().count();
System.out.println(count);
//Optional<T> max(Comparator<? super T> comparator) 返回流中最大值
Optional<User> max = users.stream().max(Comparator.comparing(User::getAge));
System.out.println(max);
//Optional<T> min(Comparator<? super T> comparator) 返回流中最小值
Optional<String> min = users.stream().map(u -> u.getName()).min(String::compareTo);
System.out.println(min);
}
//归约
@Test
public void test1(){
/**
* 归约,可以将流中的元素反复结合起来,得到一个值。像我们下面可以对各个值进行累加操作,也可以进行累乘等操作
* identity作为起始值作为x,然后把流中的元素作为y进行操作;得到的结果继续作为x,继续对流的元素填充y进行操作
* T reduce(T identity, BinaryOperator<T> accumulator);
*BinaryOperator<T> :R apply(T t, U u);
*/
// Integer totalAge = users.stream().map(user -> user.getAge()).reduce(0, (x, y) -> x + y);
//get和累加的两种写法
// Integer totalAge = users.stream().map(User::getAge).reduce(0, Integer::sum);
//如果没有identity作为起始值,那么返回的是Optional,因为集合可能为空
//map-reduce连接称为map-reduce模式,网络搜索模式(大数据搜索)
Optional<Integer> totalAge = users.stream().map(User::getAge).reduce( Integer::sum);
System.out.println(totalAge.get());//617
//归约拼接字符串
Optional<String> stringOptional = users.stream().map(User::getName).reduce(String::concat);
System.out.println(stringOptional.get());//艾米大青山池寒枫池傲天霍恩斯
}
//收集
@Test
public void test2(){
/**
* collect将流转化为其他的形式,接收一个collector实现,用于给stream中元素做汇总的方法
*/
List<String> list = users.stream().map(User::getName).collect(Collectors.toList());
list.forEach(System.out::print);//艾米大青山池寒枫池傲天霍恩斯
//set去重
Set<String> set = users.stream().map(User::getName).collect(Collectors.toSet());
set.forEach(System.out::print);//艾米大青山池寒枫池傲天霍恩斯
//把流中元素收集到创建的集合.如HashSet::new
HashSet<String> hashSet = users.stream().map(User::getName).collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::print);//艾米大青山池寒枫池傲天霍恩斯
//收集统计
Long total = users.stream().collect(Collectors.counting());
System.out.println(total);//5
//平均值。年龄平均值
Double avarage = users.stream().collect(Collectors.averagingInt(User::getAge));
System.out.println(avarage);//123.4
//总和
Integer sum = users.stream().collect(Collectors.summingInt(User::getAge));
System.out.println(sum);//617
//最大值
// Optional<User> max = users.stream().max((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge()));
Optional<User> max = users.stream().collect(Collectors.maxBy((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge())));
System.out.println(max.get());//User{id=5, age=500, name='霍恩斯'}
//最小值.注意不是对象就可以用Integer::compare方法引用替换,对象的话还是要写函数式方法
Optional<Integer> min = users.stream().map(User::getAge).collect(Collectors.minBy(Integer::compare));
System.out.println(min.get());//18
}
//分组
@Test
public void test3(){
//按名字分组
Map<String, List<User>> map = users.stream().collect(Collectors.groupingBy(User::getName));
System.out.println(map);
//{霍恩斯=[User{id=5, age=500, name='霍恩斯'}], 艾米=[User{id=3, age=25, name='艾米'}], 池傲天=[User{id=4, age=18, name='池傲天'}], 大青山=[User{id=1, age=27, name='大青山'}], 池寒枫=[User{id=2, age=47, name='池寒枫'}]}
//多级分组
Map<String, Map<String, List<User>>> collect = users.stream().collect(Collectors.groupingBy(user -> {
if (user.getAge() < 20) {
return "young";
} else if (user.getAge() < 50) {
return "adult";
} else {
return "old";
}
}, Collectors.groupingBy(User::getName)));
System.out.println(collect);
//{young={池傲天=[User{id=4, age=18, name='池傲天'}]}, old={霍恩斯=[User{id=5, age=500, name='霍恩斯'}]}, adult={艾米=[User{id=3, age=25, name='艾米'}], 大青山=[User{id=1, age=27, name='大青山'}], 池寒枫=[User{id=2, age=47, name='池寒枫'}]}}
}
//分区,即满足与不满足条件的元素区分
@Test
public void test4(){
//年龄大于50的为true,满足条件
Map<Boolean, List<User>> map = users.stream().collect(Collectors.partitioningBy(user -> user.getAge() > 50));
System.out.println(map);
//{false=[User{id=3, age=25, name='艾米'}, User{id=1, age=27, name='大青山'}, User{id=2, age=47, name='池寒枫'}, User{id=4, age=18, name='池傲天'}], true=[User{id=5, age=500, name='霍恩斯'}]}
}
@Test
public void test5(){
//获取统计,收集流中Integer属性的统计值。
IntSummaryStatistics statistics = users.stream().collect(Collectors.summarizingInt(User::getAge));
System.out.println(statistics.getAverage());//123.4
System.out.println(statistics.getMax());//500
System.out.println(statistics.getSum());//617
}
//连接流中每个字符串
@Test
public void test6(){
String str = users.stream().map(User::getName).collect(Collectors.joining());
//加分隔符,
String str1 = users.stream().map(User::getName).collect(Collectors.joining("-"));
//加前缀、后缀
String str2 = users.stream().map(User::getName).collect(Collectors.joining("-","start","end"));
System.out.println(str);//艾米大青山池寒枫池傲天霍恩斯
System.out.println(str1);//艾米-大青山-池寒枫-池傲天-霍恩斯
System.out.println(str2);//start艾米-大青山-池寒枫-池傲天-霍恩斯end
}
}
public class StreamPractice {
List<User> users = Arrays.asList(new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
new User(2, 47, "池寒枫", Arrays.asList("shanghai", "beijing")),
new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")));
@Test
public void test() {
List<Integer> list = Arrays.asList(1, 3, 5, 8);
List<Integer> collect = list.stream().map(e -> e * e).collect(Collectors.toList());
System.out.println(collect);//[1, 9, 25, 64]
}
@Test
public void test1() {
Optional<Integer> total = users.stream().map(user -> 1).reduce(Integer::sum);
System.out.println(total);//5
}
}
并行流
Fork/Join 框架与传统线程池的区别
采用“工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能.
示例:
@Test
public void test2() {
Instant start = Instant.now();
OptionalLong aLong = LongStream.rangeClosed(0, 100000000).parallel().reduce(Long::sum);
System.out.println(aLong);
Instant end = Instant.now();
System.out.println("耗时长:" + Duration.between(start, end).toMillis());
}
//OptionalLong[5000000050000000]
//耗时长:55
Optional——容器类
Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。
示例:
public class OptionalTest {
/**
* 常用方法:
* 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
*/
@Test
public void test(){
//Optional.of(T t) : 创建一个Optional 实例
Optional<User> userOptional = Optional.of(new User());
User user = userOptional.get();
System.out.println(user);//User{id=null, age=null, name='null'}
Optional<User> empty = Optional.empty();
// User user1 = empty.get();//java.util.NoSuchElementException: No value present
// System.out.println(user1);
//Optional.ofNullable(T t):若t 不为null,创建Optional 实例,否则创建空实例
//是of和empty方法的组合使用
Optional<User> userOptional2 = Optional.ofNullable(new User());
//isPresent() : 判断是否包含值
if (userOptional2.isPresent()){
System.out.println(userOptional2.get());
}
//ifPresent(Consumer<? super T> consumer) 判断存在包含值,存在则进行Consumer消费,一般选择这个处理Optional
userOptional2.ifPresent(System.out::print);
//orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数值t
User user1 = empty.orElse(new User());
//orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则调用供给型函数获取值,
User user2 = empty.orElseGet(User::new);
//ap(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
// Optional<String> s = userOptional2.map(e -> e.getName());
//flatMap要求返回值必须是Optional,因此必须对返回值进行Optional.of包装
Optional<String> optionalS = userOptional2.flatMap(e -> {
return Optional.of(e.getName());
});
}
@Test
public void test1(){
Optional<User> optional = Optional.ofNullable(null);
String fanName = getFanName(optional);
System.out.println(fanName);//null
}
//注意方法参数列表同样可以使用Optional包装
public String getFanName(ser){
//Optional使用Optional封装属性值对象及orElse来避免过多的空指针判断
return user.orElse(new User())
.getFan()
.orElse(new Fan())
.getName();//null
}
}
接口中的默认方法与静态方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰。
接口默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时:
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。覆盖选择其中一个接口的默认实现或者自己重写一个实现。
示例:
public interface IHello {
default void say(){
System.out.println("say hello");
}
String getName();
//Java8 中,接口中允许添加静态方法
public static void staticMethod(String str){
System.out.println(str);
}
}
新时间日期API
LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,因此是线程安全的,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。而SimpleDateFormat是可变对象,因此不是线程安全的,在使用时要注意每次使用时new创建一个新的SimpleDateFormat对象或者把SimpleDateFormat放在ThreadLocal上。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法。
Instant 时间戳,用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算
Duration:用于计算两个“时间”间隔
Period:用于计算两个“日期”间隔
Java8 中加入了对时区的支持,带时区的时间为分别为:
ZonedDate、ZonedTime、ZonedDateTime参考地址
代码示例:
public class LocalDateTest {
//LocalDateTime :本地日期时间 LocalDate LocalTime
@Test
public void test(){
//获取当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);//2018-12-15T01:40:03.375
//获取指定日期时间
LocalDateTime time = LocalDateTime.of(2018, 7, 10, 10, 10, 59);
System.out.println(time);//2018-07-10T10:10:59
//获取增加一天的日期时间
LocalDateTime nextDay = now.plusDays(1);
System.out.println(nextDay);//2018-12-16T01:41:41.071
//获取减少2个月的日期时间
LocalDateTime time1 = now.minusMonths(2);
System.out.println(time1);//2018-10-15T01:42:43.969
System.out.println(now.getMonth());//DECEMBER
System.out.println(now.getDayOfMonth());//15
System.out.println(now.getMonthValue());//12
System.out.println(now.getSecond());//45
}
//Instant 时间戳
@Test
public void test2(){
//获取当前时间戳,默认获取的是UTC时区的时间戳
Instant now = Instant.now();
System.out.println(now);//2020-12-14T17:58:20.991Z
OffsetDateTime dateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(dateTime);//2020-12-15T02:05:57.558+08:00
OffsetDateTime dateTime1 = now.atOffset(ZoneOffset.of("+8"));
System.out.println(dateTime1);//2020-12-15T02:05:57.558+08:00
//获取当前时间的时间戳
long epochMilli = now.toEpochMilli();
//旧的获取方式
long l = System.currentTimeMillis();
//新老获取时间戳的值是一样的,只是新的时间戳返回的时间是UTC时区的日期时间,但在用new Date(l)转化时或默认转化为
//当前日期时间。这个需要注意
System.out.println(epochMilli);//1607968605927
System.out.println(l);//1607968605927
System.out.println(new Date(l));//Tue Dec 15 01:59:01 CST 2020
}
//Duration:时间间隔
@Test
public void test3(){
Instant now = Instant.now();
Instant instant = Instant.ofEpochMilli(1607968605927L);
//Duration:用于计算两个“时间”间隔.注意间隔是用第二个参数减去第一个参数的间隔,因此当第一个参数是比较晚时得到的是负数
Duration duration = Duration.between(instant, now);
//获取间隔的毫秒值用to
System.out.println(duration.toMillis());//992143
//获取间隔的秒值用get
System.out.println(duration.getSeconds());//992
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime dateTime = LocalDateTime.of(2020, 12, 15, 0, 0, 0);
Duration between = Duration.between(localDateTime, dateTime);
System.out.println(between.getSeconds());//-8261
}
//Period:日期间隔
@Test
public void test5(){
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2020, 11, 11);
//Period:用于计算两个“日期”间隔 获取值不好用
Period period = Period.between(date, now);
System.out.println(period);//P1M4D:表示间隔1个月04天
System.out.println(period.getChronology());//ISO 年表
System.out.println(period.getYears());//0
System.out.println(period.getMonths());//1
System.out.println(period.getDays());//4
}
//TemporalAdjuster:时间校正器
@Test
public void test6(){
LocalDateTime now = LocalDateTime.now();
//调整日期为该月的第一天
LocalDateTime dateTime = now.withDayOfMonth(1);
System.out.println(dateTime);//2020-12-01T02:39:06.111
//调整日期为该月的第一天
LocalDateTime time = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(time);//2020-12-01T02:35:01.715
//调整日期为下一个星期天
LocalDateTime nextSunday = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(nextSunday);//2020-12-20T02:37:01.322
//自定义时间校正器 :获取下一个工作日
LocalDateTime nextWorkDate = now.with(temporal -> {
LocalDateTime dateTime1 = (LocalDateTime) temporal;
if (dateTime1.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return dateTime1.plusDays(3);
} else if (dateTime1.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return dateTime1.plusDays(2);
} else {
return dateTime1.plusDays(1);
}
});
System.out.println(nextWorkDate);//2020-12-16T02:44:45.370
}
//DateTimeFormatter :格式化时间/日期
@Test
public void test7(){
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd 时HH分mm秒ss");
LocalDateTime now = LocalDateTime.now();
//日期转指定格式的字符串
String str = formatter.format(now);
System.out.println(str);//2020-12-15
String str2 = formatter2.format(now);
System.out.println(str2);//2020-12-15 时02分54秒47
//注意2020-12-15这种类型的字符串没法转化为日期时间LocalDateTime,只能转化为日期LocalDate
// 会报错:java.time.format.DateTimeParseException: Text '2020-12-15' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2020-12-15 of type java.time.format.Parsed
// LocalDateTime dateTime = now.parse(str, formatter);
LocalDate date = LocalDate.parse(str, formatter);
System.out.println(date);//2020-12-15
//指定格式字符串转日期时间
LocalDateTime dateTime2 = LocalDateTime.parse(str2, formatter2);
System.out.println(dateTime2);//2020-12-15T03:01:43
}
//带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime
//Asia/Shanghai America/New_York
@Test
public void test9(){
//获取带时区的日期和时间 方式一
//默认时区
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println(now1);//2020-12-15T03:34:44.479+08:00[Asia/Shanghai]
//用指定时区获取当前时间
ZonedDateTime newyorkNow = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println(newyorkNow);//2020-12-14T14:36:21.999-05:00[America/New_York]
//获取带时区的日期和时间 方式二
//以这种方式创建的ZonedDateTime,它的日期和时间与LocalDateTime相同,但附加的时区不同,因此是两个不同的时刻
LocalDateTime now = LocalDateTime.now(ZoneId.of("America/New_York"));
System.out.println(now.atZone(ZoneId.systemDefault()));//2020-12-14T14:39:04.255+08:00[Asia/Shanghai]
LocalDateTime now2 = LocalDateTime.now(ZoneId.of("America/New_York"));
//获取到的区域时间是当地日期时间 +时区时间差
ZonedDateTime zonedDateTime = now2.atZone(ZoneId.of("America/New_York"));
System.out.println(zonedDateTime);//2020-12-14T14:19:41.923-05:00[America/New_York]
//时区转换
//通过withZoneSameInstant()将关联时区转换到另一个时区,转换后日期和时间都会相应调整。
//时区转换的时候,由于夏令时的存在,不同的日期转换的结果很可能是不同的。
//涉及到时区时,千万不要自己计算时差,否则难以正确处理夏令时。
//默认时区
ZonedDateTime now3 = ZonedDateTime.now();
ZonedDateTime newYorkDateTime1 = now3.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(now3);//2020-12-15T03:42:58.940+08:00[Asia/Shanghai]
System.out.println(newYorkDateTime1);//2020-12-14T14:42:58.940-05:00[America/New_York]
//纽约时区时间转换为本地时间
LocalDateTime localDateTime = newYorkDateTime1.toLocalDateTime();
System.out.println(localDateTime);//丢弃了时区信息
//练习:某航线从北京飞到纽约需要13小时20分钟,请根据北京起飞日期和时间计算到达纽约的当地日期和时间。
ZonedDateTime now4 = ZonedDateTime.now();
ZonedDateTime zonedDateTime1 = now4.plusHours(13).plusMinutes(20);
ZonedDateTime newyorkDateTime2 = zonedDateTime1.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(newyorkDateTime2);//2020-12-15T04:12:39.611-05:00[America/New_York]
System.out.println("before:"+now4);//before:2020-12-15T03:52:39.611+08:00[Asia/Shanghai]
System.out.println("after:"+zonedDateTime1);//after:2020-12-15T17:12:39.611+08:00[Asia/Shanghai]
}
@Test
public void test8(){
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.forEach(System.out::println);
}
}
重复注解和类型注解
示例:
//定义可重复注解的容器
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NameAnnotions {
NameAnnotion[] value();
}
//可重复注解必须加@Repeatable(NameAnnotions.class),NameAnnotions为其容器
@Repeatable(NameAnnotions.class)
//注意类型注解要求这两个目标注解都要标注才能使用,否则报错:ElementType.PARAMETER,ElementType.TYPE_PARAMETER
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NameAnnotion {
String value() default "";
}
public class AnnotionTest {
@Test
public void test() throws NoSuchMethodException {
Class<AnnotionTest> aClass = AnnotionTest.class;
Method work = aClass.getMethod("work");
NameAnnotion[] annotations = work.getAnnotationsByType(NameAnnotion.class);
for (NameAnnotion annotation : annotations) {
System.out.println(annotation.value());
}
}
//可重复注解
@NameAnnotion("996")
@NameAnnotion("955")
public void work(@NameAnnotion String str) {
}
}
其他新特性
HashMap的新特性——从数组加链表的存储方式转变为数组加(链表/红黑树)的方式。
HashMap在存储数据的时候会先计算出Key的hash值,再通过运算得到我们底层存储的数组的下标索引值。 当hash值一样时,会调用equal()方法比较内容是否相等,如果相等则替换原有的值,如果不相等,此时就产生了hash碰撞,数据会被以链表的形式存放起来。而当hash碰撞严重时,比如极端情况下,链表就会很长,导致查询或插入比较时效率低下,这时候我们就引入了采用红黑树来替换链表的形式进行存储。
链表转红黑树的条件是:当链表的长度大于8且HashMap的容量大于64时会触发由链表转红黑树逻辑。红黑树除了添加以外其他的效率都很高,因为链表添加的时候是添加在末尾比较快,而红黑树添加时要进行比较大小添加。
加载因子设置为0.75的原因,因为如果太小,HashMap就会不断扩容,浪费效率。太大了可能插入的值就是一直产生碰撞形成了链表,没有插入到数组的索引位置,导致一直没有扩容,效率低下。
ConcurrentHashMap的新特性——把锁分段技术改成CAS算法
ConcurrentHashMap在JDK1.8之前是采用锁分段技术,默认由16个分段,对于16把锁。因为分段的数量不好控制,如果分段过多就会浪费空间,因为很多段里面其实没有数据进入,而太小也不好,这样就导致效率太低,因为会导致太多操作竞争同一个段锁。
底层也采用数组加(链表/红黑树)的方式提高效率。
方法区实现从永久代实现变为元空间MetaSpace。
元空间使用的是物理内存。默认物理内存有多大,元空间就可以是多大,只受限于物理内存的大小,当然我们也可以指定MetaSpaceSize指定元空间大小,MaxMetaSpaceSize指定最大元空间大小。取代了永久代的PermGenSize和MaxPermGenSize.