JDK1.8新特性
1. Lambda表达式
1.1 lambda初识
- Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
- 示例:
package com.jdk.lambda;
public class Code01 {
public static void main(String[] args) {
// 开启一个新的线程,指定线程要执行的任务
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中执行的代码:" + Thread.currentThread().getName());//新线程中执行的代码:Thread-0
}
}).start();
System.out.println("主线程中的代码:" + Thread.currentThread().getName());//主线程中的代码:main
/**
* 代码分析:
* 1.Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
* 2.为了指定run方法体,不得不需要Runnable的实现类
* 3.为了省去定义一个Runnable的实现类,不得不使用匿名内部类
* 4.必须覆盖重新抽象的run方法,所有的方法名称,方法参数,方法返回值不得不重写一遍,而且不能出错
* 5.实际上,我们只在乎方法体中的代码
*/
new Thread(()->{
System.out.println("新线程lambda表达式。。。"+Thread.currentThread().getName());
}).start();//新线程lambda表达式。。。Thread-1
/**
* 1.lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
* 2.优点:简化了匿名内部类的使用,语法更加简单
* 3.匿名内部类语法冗余,体验lambda表达式后,发现lamdbda表达式就是简化匿名内部类的一种方式
*/
}
}
1.2 lambda语法规则
- lambda标准格式由以下三个部分组成
(参数类型 参数名) -> {
代码体;
}
- 格式说明:
(参数类型 参数名)
: 参数列表{ 代码体 }
: 方法体->
: 分割参数列表和方法体
- 示例1:无返回值
@FunctionalInterface//被该注解修饰的接口只能声明一个抽象方法
public interface UserService {
void show();
}
public class Code02 {
public static void goShow(UserService userService) {
userService.show();
}
public static void main(String[] args) {
//正常执行
goShow(new UserService() {
@Override
public void show() {
System.out.println("show方法执行了");
}//show方法执行了
});
//lambda表达式执行
goShow(() -> {
System.out.println("lambda show方法执行了");
});//lambda show方法执行了
goShow(() -> System.out.println("简化版lambda show方法执行了"));//简化版lambda show方法执行了
}
}
- 示例2:有返回值
// 以person类为例
public class Person {
private String name;
private Integer age;
private Double height;
public Person() {
}
public Person(String name, Integer age, Double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
public class Code03 {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("楚君归",33,175.5));
list.add(new Person("李悠然",53,185.5));
list.add(new Person("赵日天",93,195.5));
list.add(new Person("叶良辰",23,165.5));
// Collections.sort(list, new Comparator<Person>() {
// @Override
// public int compare(Person o1, Person o2) {
// return o1.getAge()-o2.getAge();
// }
// });
// list.forEach(System.out::println);
Collections.sort(list,(Person o1, Person o2)-> {
return o1.getAge()-o2.getAge();
});
list.forEach(System.out::println);
/**
* 执行结果:
* Person{name='叶良辰', age=23, height=165.5}
* Person{name='楚君归', age=33, height=175.5}
* Person{name='李悠然', age=53, height=185.5}
* Person{name='赵日天', age=93, height=195.5}
*/
}
}
1.3 lambda表达式原理
- 匿名内部类的本质就是在编译的时候生成一个
xxx$1.class
文件 - lambda表达式在程序运行的时候会形成一个类
- 在类中新增一个方法,这个方法体就是lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口重写抽象方法
- 在接口中重写方法会调用新生成的方法
1.4 lambda表达式简写规则
- 小括号内的参数类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字及语句分号
- 示例:
public interface StudentService {
String show(String name,Integer age);
}
public interface OrderService {
Integer show(String name);
}
public class Code06 {
public static void goStudent(StudentService studentService) {
studentService.show("林夕", 22);
}
public static void goOrder(OrderService orderService) {
orderService.show("心仪");
}
public static void main(String[] args) {
//标准写法
goStudent((String name, Integer age) -> {
return name + age + "666..." ;
});
goOrder((String name) -> {
System.out.println("goOrder invoked!order name :" + name);
return 666;
});//goOrder invoked!order name :心仪
//省略写法
goStudent((name,age)->name + age + "666...");
goOrder(name ->{System.out.println("goOrder invoked!order name :" + name);return 666;});//goOrder invoked!order name :心仪
// 如果goOrder没有sout语句,可以继续简写
goOrder(name -> 666);
}
}
1.5 lambda表达式的使用前提
- 方法的参数或者局部变量类型笔触为接口才能使用lambda表达式
- 接口中有且仅有一个抽象方法(@FunctionalInterface)
1.6 lambda和匿名内部类的对比
- 所需类型不一样
- 匿名内部类的类型可以是类,抽象类,接口
- lambda表达式需要的类型必须是接口
- 抽象方法的数量不一样
- 匿名内部类所需的接口中的抽象方法数量是任意的
- lambda表达式所需的接口中只能有一个抽象方法
- 实现原理不一样
- 匿名内部类实在编译后形成一个class
- lambda表达式是在程序运行的时候动态生成class
2.接口新增的方法
2.1 JDK8接口增强
- 在JDK8中针对接口做了增强,在此之前:
interface 接口名{
静态常量;
抽象方法;
}
- JDK8之后对接口做了增加,接口总可以有默认方法和静态方法
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}
2.2 默认方法
2.2.1 为什么要增加默认方法?
- 在JDK8之前,如果新增抽象方法,那么实现类都必须重写盖方法,不利于接口扩展
public class Demo01Imterface {
public static void main(String[] args) {
A ab = new B();
A ac = new C();
}
}
interface A{
void test1();
// 新增test2方法,classB和classC都必须重写该方法
void test2();
}
class B implements A{
@Override
public void test1() {}
@Override
public void test2() { }
}
class C implements A{
@Override
public void test1() {}
@Override
public void test2() {}
}
2.2.2 默认方法语法格式
- 接口中默认方法的语法格式:
interface 接口名{
修饰符 default 返回值类型 方法名{
方法体;
}
}
- 示例:
public class Demo01Imterface {
public static void main(String[] args) {
A ab = new B();
A ac = new C();
ab.test3();//class B 重写了默认方法!
ac.test3();//接口中的默认方法执行了。。。
}
}
interface A{
void test1();
// 新增test2方法,classB和classC都必须重写该方法
void test2();
// 接口中定义的默认方法
public default String test3(){
System.out.println("接口中的默认方法执行了。。。");
return "test3 invoked!";
}
}
class B implements A{
@Override
public void test1() {}
@Override
public void test2() {}
@Override
public String test3() {
System.out.println("class B 重写了默认方法!");
return "test3 invoked!";
}
}
class C implements A{
@Override
public void test1() {}
@Override
public void test2() {}
}
2.2.3 默认方法的使用
- 实现类直接调用接口的默认方法
- 实现类重写接口的默认方法
2.3 静态方法
- JDK8中为接口新增了静态方法,也是为了接口的扩展
2.3.1 静态方法语法规则
- 接口中静态方法的语法格式:
interface 接口名{
修饰符 static 返回值类型 方法名{
方法体;
}
}
- 示例:
public class Demo01Imterface {
public static void main(String[] args) {
A ab = new B();
A ac = new C();
ab.test3();//class B 重写了默认方法!
ac.test3();//接口中的默认方法执行了。。。
A.test4();//接口中的静态方法执行了。。。
}
}
interface A{
void test1();
// 新增test2方法,classB和classC都必须重写该方法
void test2();
// 接口中定义的默认方法
public default String test3(){
System.out.println("接口中的默认方法执行了。。。");
return "test3 invoked!";
}
// 接口中定义的静态方法
public static String test4(){
System.out.println("接口中的静态方法执行了。。。");
return "test4 invoked!";
}
}
class B implements A{
@Override
public void test1() {}
@Override
public void test2() { }
@Override
public String test3() {
System.out.println("class B 重写了默认方法!");
return "test3 invoked!";
}
}
class C implements A{
@Override
public void test1() { }
@Override
public void test2() {}
}
2.3.2 静态方法的使用
- 接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口接口类型实现
接口名.静态方法名()
2.4 默认方法和静态方法的区别
- 默认方法通过实例调用,静态方法通过接口名调用
- 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
- 静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用
3. 函数式接口
3.1 函数式接口的由来
- 使用lambda表达式的前提是需要又函数式接口,而lambda表达式使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型,因此,为了更加方便使用lambda表达式,JDK提供了大量常用的函数式接口。
public class Code07 {
public static void fun1(Operator operator) {
int[] arr = {1, 2, 3, 4, 56};
int sum = operator.getSum(arr);
System.out.println("sum = " + sum);
}
public static void main(String[] args) {
fun1((arr) -> {
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
});
//执行结果:sum = 66
}
}
interface Operator {
int getSum(int[] arr);
}
3.2 常用函数式接口
- JDK中帮我们提供的函数式接口,主要是在Java.util.function包中
3.2.1 Supplier
- 无参有返回值
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
* 获取一个结果。
* @return a result
*/
T get();
}
/**
* Supplier 函数式接口的使用
*/
public class SupplierTest {
private static void fun1(Supplier<Integer> supplier) {
Integer max = supplier.get();
System.out.println("max = " + max);
}
public static void main(String[] args) {
fun1(() -> {
int arr[] = {11, 22, 44, 33, 88, 66};
Arrays.sort(arr);
return arr[arr.length - 1];
});
// 执行结果:max = 88
}
}
3.2.2 Consumer
- 有参数无返回值,前面介绍的Supplier接口是用来生产数据的,而Consumer是用来消费数据的,使用时需要指定泛型
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
* 对给定的参数执行此操作。
* @param t the input argument
*/
void accept(T t);
- 默认方法:
andThen
- 如果一个方法的参数和返回值全是Consumer类型,消费一个数据时,连续做2次或以上操作,就可以用andThen方法实现组合
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
- 示例:
public class ConsumerTest {
public static void test(Consumer<String> consumer) {
consumer.accept("Hello World");
}
public static void test2(Consumer<String> c1, Consumer<String> c2) {
String str = "Hello Word";
c1.accept(str);//将执行小写
c2.accept(str);//将执行大写
}
public static void main(String[] args) {
test(msg -> {
System.out.println(msg + " 转化为小写:" + msg.toLowerCase());
});
// 执行结果--->Hello World 转化为小写:hello world
System.out.println("andThen方法示例:");
test2(msg1 -> {
System.out.println(msg1 + " 转化为小写:" + msg1.toLowerCase());
}, msg2 -> {
System.out.println(msg2 + " 转化为大写:" + msg2.toUpperCase());
});
//andThen方法示例:
//Hello Word 转化为小写:hello word
//Hello Word 转化为大写:HELLO WORD
}
}
3.2.3 Function
- 有参数有返回值的接口,Function接口是根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
//返回一个始终返回其输入参数的函数。
static <T> Function<T, T> identity() {
return t -> t;
}
- 默认方法:andThen,也是用来进行组合操作,先执行前置再执行后置
- 默认方法:compose,也是用来进行组合操作,作用顺序与andThen相反,先执行后置再执行前置
- 静态方法:identity,输入什么结果就返回什么
- 示例:
public class FunctionTest {
public static void test1(Function<String, Integer> function) {
Integer apply = function.apply("666");
System.out.println("apply的结果:" + apply);
}
public static void test2(Function<String, Integer> f1,Function<Integer, Integer> f2) {
// Integer apply1 = f1.apply("666");
// Integer apply2= f2.apply(apply1);
// 以上注释代码可用andThen替代,执行结果不变
Integer apply2= f1.andThen(f2).apply("666");
System.out.println("apply2的结果:" + apply2);
}
public static void test3(Function<String, Integer> f1,Function<Integer, Integer> f2) {
// compose替代andThen,f1、f2交换位置,执行结果不变
Integer apply2= f2.compose(f1).apply("666");
System.out.println("apply2的结果:" + apply2);
}
public static void main(String[] args) {
test1(msg -> {
return Integer.parseInt(msg);
});
//test1 ---> apply的结果:666
System.out.print("Function andThen方法测试:");
test2(msg1->{
return Integer.parseInt(msg1);
},msg2->{
return msg2*10;
});
// test2 --->Function andThen方法测试:apply2的结果:6660
System.out.print("Function compose方法测试:");
test3(msg1->{
return Integer.parseInt(msg1);
},msg2->{
return msg2*10;
});
// test3 --->Function compose方法测试:apply2的结果:6660
}
}
3.2.4 Predicate
- 有参数,返回值为Boolean的接口
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
* 给定参数计算此断定
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
/**
* Returns a composed predicate that represents a short-circuiting logical
* AND of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code false}, then the {@code other}
* predicate is not evaluated.
* 逻辑与
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
* 逻辑非
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* Returns a composed predicate that represents a short-circuiting logical
* OR of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code true}, then the {@code other}
* predicate is not evaluated.
* 逻辑或
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* Returns a predicate that tests if two arguments are equal according
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
- 在Predicate中的默认方法提供了逻辑关系的操作and or negate方法
public class PredicateTest {
private static void test1(Predicate<String> predicate, String msg) {
boolean b = predicate.test(msg);
System.out.println("b:" + b);
}
private static void test2(Predicate<String> p1, Predicate<String> p2) {
//逻辑与and方法的使用
//p1包含H且p2包含W
boolean b1 = p1.and(p2).test("Hello");
System.out.println("逻辑与and方法的使用,验证‘p1包含H且p2包含W’:" + b1);
//逻辑或or方法的使用
//p1包含H或者p2包含W
boolean b2 = p1.or(p2).test("Hello");
System.out.println("逻辑或or方法的使用,验证‘p1包含H或者p2包含W’:" + b2);
//逻辑非negate方法的使用
//p1不包含H
boolean b3 = p1.negate().test("Hello");
System.out.println("逻辑非negate方法的使用,验证‘p1不包含H’:" + b3);
}
public static void main(String[] args) {
test1(msg -> {
return msg.length() > 3;
},"HelloWorld");
//b:true
test2(msg1->{
return msg1.contains("H");
},msg2->{
return msg2.contains("W");
});
//逻辑与and方法的使用,验证‘p1包含H且p2包含W’:false
//逻辑或or方法的使用,验证‘p1包含H或者p2包含W’:true
//逻辑非negate方法的使用,验证‘p1不包含H’:false
}
}
4. 方法引用
4.1 为什么要用方法引用?
- lambda表达式冗余,或者已经有方法实现了需求
- 示例:
public class ConsumerTest2 {
public static void test(Consumer<int[]> consumer) {
int[] arr = {10, 20, 30, 40, 50};
consumer.accept(arr);
}
/**
* 求数组中所有元素的和
* @param arr
*/
public static void getTotal(int[] arr){
int sum = 0;
for (int i : arr) {
sum += i;
}
System.out.println("数组之和:" + sum);
}
public static void main(String[] args) {
test(arr -> {
int sum = 0;
for (int i : arr) {
sum += i;
}
System.out.println("数组之和:" + sum);
//数组之和:150
});
}
}
- 这种情况就可以使用方法引用
public class ConsumerTest2 {
public static void test(Consumer<int[]> consumer) {
int[] arr = {10, 20, 30, 40, 50};
consumer.accept(arr);
}
/**
* 求数组中所有元素的和
* @param arr
*/
public static void getTotal(int[] arr){
int sum = 0;
for (int i : arr) {
sum += i;
}
System.out.println("数组之和:" + sum);
}
public static void main(String[] args) {
// 使用方法引用
test(ConsumerTest2::getTotal);
// 执行结果 ---> 数组之和:150
}
}
4.2 方法引用语法格式
- 符号表示:
::
- 符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
- 应用场景:如果lambda表达式所要实现的方案已经有其他方法实现,那么就可以使用方法引用。
- 方法引用在JDK8中使用是相当灵活的,主要有以下几种方式:
instanceName::methodName
对象名 :: 方法名ClassName::StaticMethodName
类名::静态方法名ClassName::methodName
类名::方法名ClassName::new 类名::new 调用的构造器
TypeName[]::new String[]::new 调用数组的构造器
4.2.1 对象名 :: 方法名
- 这是常见的一种用法,如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法
- 注意事项:
- 被引用的方法:参数要和接口中的抽象方法的参数一样
- 当接口抽象方法有返回值时,被引用的方法也必须有返回值
- 示例:
public class FunctionRefTest1 {
public static void main(String[] args) {
Date now = new Date();
Supplier<Long> supplier1 = () -> {
return now.getTime();
};
System.out.println("supplier1:"+supplier1.get());
//简写
Supplier<Long> supplier2 = () -> now.getTime();
System.out.println("supplier2:"+supplier2.get());
//方法引用 ---> 对象名 :: 方法名
Supplier<Long> supplier3 = now::getTime;
System.out.println("supplier3:"+supplier3.get());
//执行结果:
//supplier1:1658806711987
//supplier2:1658806711987
//supplier3:1658806711987
}
}
4.2.2 类名::静态方法名
- 这也是比较常见的一种用法,如果一个类中已经存在了一个静态方法,则可以通过类名引用静态方法
- 示例:
public class FunctionRefTest2 {
public static void main(String[] args) {
Supplier<Long> supplier1 = () -> {
return System.currentTimeMillis();
};
System.out.println("supplier1:"+supplier1.get());
//简写
Supplier<Long> supplier2 = () -> System.currentTimeMillis();
System.out.println("supplier2:"+supplier2.get());
//方法引用 ---> 类名 :: 静态方法名
Supplier<Long> supplier3 = System::currentTimeMillis;
System.out.println("supplier3:"+supplier3.get());
//执行结果:
//supplier1:1658806971135
//supplier2:1658806971135
//supplier3:1658806971151
}
}
4.2.3 类名::引用实例方法
- java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际是拿第一个参数作为方法的调用者
- 示例:
public class FunctionRefTest3 {
public static void main(String[] args) {
Function<String, Integer> function1 = (s) -> {
return s.length();
};
System.out.println(function1.apply("hello"));
// 简写
Function<String, Integer> function2 = (s) -> s.length();
System.out.println(function2.apply("hello"));
// 通过方法引用来实现
Function<String, Integer> function3 = String::length;
System.out.println(function3.apply("hello"));// 5
BiFunction<String,Integer,String> biFunction = String::substring;
String msg = biFunction.apply("helloworld", 3);
System.out.println(msg);//loworld
}
}
4.2.4 类名::构造器
- 由于构造器的名称和类名完全一直,所以构造器引用使用
::new
的格式 - 示例:
public class FunctionRefTest4 {
public static void main(String[] args) {
//lambda表达式
Supplier<Person> supplier1 = ()->{return new Person();};
System.out.println(supplier1.get());
//简写
Supplier<Person> supplier2 = ()-> new Person("楚君归",22);
System.out.println(supplier2.get());
//方法引用 1
Supplier<Person> supplier3 = Person::new;
System.out.println(supplier3.get());
//方法引用 2
BiFunction<String,Integer,Person> function = Person::new;
System.out.println(function.apply("李悠然",66));
//Person{name='null', age=null}
//Person{name='楚君归', age=22}
//Person{name='null', age=null}
//Person{name='李悠然', age=66}
}
}
4.2.5 数组::构造器
- 数组构造示例:
public class FunctionRefTest5 {
public static void main(String[] args) {
//lambda表达式
Function<Integer,String[]> fun1 = (len)->{
return new String[len];
};
String[] arr1 = fun1.apply(3);
System.out.println("数组的长度是:"+arr1.length);//数组的长度是:3
//简写
Function<Integer,String[]> fun2 = (len)-> new String[len];
String[] arr2 = fun2.apply(3);
System.out.println("数组的长度是:"+arr2.length);//数组的长度是:3
//方法引用 1
Function<Integer,String[]> fun3 = String[]::new;
String[] arr3 = fun3.apply(6);
System.out.println("数组的长度是:"+arr3.length);//数组的长度是:6
}
}
4.3 方法引用总结
- 方法引用是对lambda表达式符合特定情况下的一种缩写方式,不过要注意方法引用只能引用已经存在的方法。
5. Stream API
5.1 集合处理数据的弊端
- 日常我们除了对集合的元素进行添加、删除、获取操作外,最典型的操作就是集合的遍历
- 示例:
public class StreamTest1 {
public static void main(String[] args) {
// 定义一个集合
List<String> list = Arrays.asList("张三", "张三丰", "成龙", "周星驰");
// 1.获取所有姓张的信息
List<String> list1 = new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")) {
list1.add(s);
}
}
// 2.获取姓张的且长度为3的信息
List<String> list2 = new ArrayList<>();
for (String s : list1) {
if (s.length() == 3) {
list2.add(s);
}
}
// 3.输出筛选后所有用户的信息
for (String s : list2) {
System.out.println(s);
}
//张三丰
}
}
- 上面的代码,针对于我们不同的需求总是一次次的循环,这时我们希望有更加高效的处理方式,所以JDK8引入Stream API来解决此类问题
public class StreamTest2 {
public static void main(String[] args) {
// 定义一个集合
List<String> list = Arrays.asList("张三", "张三丰", "成龙", "周星驰");
// 1.获取所有姓张的信息
// 2.获取姓张的且长度为3的信息
// 3.输出筛选后所有用户的信息
list.stream()
.filter(s->s.startsWith("张"))
.filter(s->s.length() == 3)
.forEach(s->System.out.println(s));
//简写
list.stream()
.filter(s->s.startsWith("张"))
.filter(s->s.length() == 3)
.forEach(System.out::println);
// 执行结果:张三丰
}
}
- 上面的Stream API代码的含义:获取流,过滤“张”,过滤长度,逐一打印,相较于以前的写法更加简洁直观
5.2 Stream流式思想
- Stream和IO流(InputStream/OutputStream)没有任何关系
- Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,它并不保存数据,而是对数据进行加工处理,Stream可以看做是流水线上的一个工序,在流水线上,通过多个工序让一个原材料加工成一个商品。
- Stream API能让我们快速完成许多复杂的操作,如筛选,切片,映射,查找,去重,统计,匹配和归约等操作。
5.3 Stream流获取方式
5.3.1 根据Collection获取
- 首先,Java.util.Collection接口加入了default方法,也就是说Collection接口下的所有实现都可以通过Stream方法来获取Stream流。
public class T {
public static void main(String[] args) {
List<System> list = new ArrayList<>();
list.stream();
Set<String> set = new HashSet<>();
set.stream();
Vector vector = new Vector();
vector.stream();
}
}
- 虽然Map接口并没有实现Collection接口,但是我们可以根据Map获取对于key value的集合
public class T {
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
Stream<String> stream1 = map.keySet().stream();
Stream<Object> stream2 = map.values().stream();
Stream<Map.Entry<String, Object>> stream3 = map.entrySet().stream();
}
}
5.3.2 通过Stream的of方法获取
- 在实际开发中我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of
- 示例:
public class StreamTest3 {
public static void main(String[] args) {
Stream<String> a1 = Stream.of("a1", "a2", "a3");
String[] arr1 = {"aa", "bb", "cc"};
Stream<String> arr11 = Stream.of(arr1);
Integer[] arr2 = {1, 2, 3, 4};
Stream<Integer> arr21 = Stream.of(arr2);
arr21.forEach(System.out::println);//1 2 3 4
//注意:基本数据类型的数组是不行的,会看做一个整体
int[] arr3 = {1,2,3,4};
Stream.of(arr3).forEach(System.out::println);//[I@6d311334
}
}
5.4 Stream的常用方法
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
- 终结方法:返回值类型不再是Stream类型的方法,不再支持链式调用
- 非终结方法:即函数拼接,返回值类型依然是Stream类型的方法,支持链式调用(除了终结方法外,其余方法均为非终结方法)
- Stream注意事项:
- Stream只能操作一次,且不可逆
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
5.4.1 forEach
- forEach用来遍历流中的数据的
void forEach(Consumer<? super T> action);
- 该方法接受一个Consumer接口,会将每一个流元素交给函数处理
public class StreamTest6ForEach {
public static void main(String[] args) {
Stream.of("aa","ab","ac").filter(s->s.contains("a"))
.forEach(System.out::println);
//执行结果:aa ab ac
}
}
5.4.2 count
- Stream流中的count方法用来统计其中的元素个数,该方法返回一个long值,代表元素个数。
long count();
- 示例:
public class StreamTest6Count {
public static void main(String[] args) {
long count = Stream.of("aa", "ab", "ac").count();
System.out.println(count);// 3
}
}
5.4.3 filter
- filter方法的作用是用来过滤数据的,返回符合条件的数据,通过filter方法将一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
- 该接口接收一个Predicate函数式接口参数作为筛选条件
- 示例:
public class StreamTest7Filter {
public static void main(String[] args) {
Stream.of("aa","ab","ac","bb","cc","dd")
.filter(s->s.contains("a"))
.forEach(System.out::println);//aa ab ac
}
}
5.4.4 limit
- limit方法可以对流进行截取处理,只取前几个数据
Stream<T> limit(long maxSize);
- 注意:maxSize>实际元素个数时,结果为实际元素,maxSize为负数时报错,maxSize为0时结果集为空
- 示例:
public class StreamTest8Limit {
public static void main(String[] args) {
Stream.of("aa","ab","ac","bb","cc","dd")
.limit(5)
.forEach(System.out::print);//aa ab ac bb cc
}
}
5.4.5 skip
- 如果希望跳过前面几个元素,就可以好似一个skip方法获取一个截取之后的新流。
Stream<T> skip(long n);
-
参数需要传入一个long类型的数,用法参考limit方法
-
示例:
public class StreamTest9Skip {
public static void main(String[] args) {
Stream.of("aa","ab","ac","bb","cc","dd")
.skip(3)
.forEach(System.out::print);//bb cc dd
}
}
5.4.6 map
- 如果我们需要将流中的元素映射到另一个流中,可以使用map方法
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
- 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据
- 示例:
public class StreamTest10Map {
public static void main(String[] args) {
Stream.of("1","2","3","4","5","6","7")
.map(msg->Integer.parseInt(msg))
.forEach(System.out::print);//1234567
Stream.of("1","2","3","4","5","6","7")
.map(Integer::parseInt)
.forEach(System.out::print);//1234567
}
}
5.4.7 sorted
- 如果需要排序,可以使用sorted方法
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
- 源码中有2个方法,默认是自然顺序排序,也可以重写Compartor方法自定义排序
- 示例:
public class StreamTest11Sorted {
public static void main(String[] args) {
Stream.of("7","2","8","0","9","6","1")
.map(Integer::parseInt)
.sorted()
.forEach(System.out::print);//0126789
Stream.of("7","2","8","0","9","6","1")
.map(Integer::parseInt)
.sorted((o1,o2)->o2-o1)
.forEach(System.out::print);//9876210
}
}
5.4.8 distinct
- 该方法用于去除重复元素
Stream<T> distinct();
- 注意:该方法对于基本数据类型是可以直接去重的,对于引用数据类型需要重写equals和hashCode方法才可以移除重复数据。
- 示例:
public class StreamTest12Distinct {
public static void main(String[] args) {
Stream.of("2","2","8","9","9","6","1")
.map(Integer::parseInt)
.distinct()
.forEach(System.out::print);//28961
Stream.of(
new Person("楚君归",22)
,new Person("李心怡",18)
,new Person("楚君归",22)
).distinct()
.forEach(System.out::println);
//执行结果:
//Person{name='楚君归', age=22}
//Person{name='李心怡', age=18}
}
}
5.4.9 match
- 该方法是一个终结方法,用于判断元素是否匹配指定的条件
// 元素是否有任意一个满足条件
boolean anyMatch(Predicate<? super T> predicate);
// 元素是否都满足条件
boolean allMatch(Predicate<? super T> predicate);
// 元素是否都不满足条件
boolean noneMatch(Predicate<? super T> predicate);
- 示例:
public class StreamTest13Match {
public static void main(String[] args) {
boolean b1 = Stream.of("1", "3", "9", "7", "5", "6")
.map(Integer::parseInt)
.anyMatch(s -> s > 4);
System.out.println(b1);// true
boolean b2 = Stream.of("1", "3", "9", "7", "5", "6")
.map(Integer::parseInt)
.allMatch(s -> s > 4);
System.out.println(b2);// false
boolean b3 = Stream.of("1", "3", "9", "7", "5", "6")
.map(Integer::parseInt)
.noneMatch(s -> s > 4);
System.out.println(b3);//false
}
}
5.4.10 find
- 该方法用于查找某些数据
// 查询第一个
Optional<T> findFirst();
// 查询全部
Optional<T> findAny();
public class StreamTest13Find {
public static void main(String[] args) {
Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7").findFirst();
System.out.println(first.get());
Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7").findAny();
System.out.println(any.get());
}
}
5.4.11 max和min
- 该方法用于获取最大值或者最小值
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
- 示例:
public class StreamTest14MaxMin {
public static void main(String[] args) {
Optional<Integer> max = Stream.of("1", "2", "3", "4", "5", "6", "7")
.map(Integer::parseInt)
.max((o1, o2) -> o1 - o2);
System.out.println(max.get());//7
Optional<Integer> min = Stream.of("1", "2", "3", "4", "5", "6", "7")
.map(Integer::parseInt)
.min((o1, o2) -> o1 - o2);
System.out.println(min.get());//1
}
}
5.4.12 reduce
- 该方法可以将所有数据归纳得到一个数据
T reduce(T identity, BinaryOperator<T> accumulator);
- 示例:
public class StreamTest15Reduce {
public static void main(String[] args) {
Integer sum = Stream.of(4, 5, 3, 9)
//identity默认值
//第一次会将默认值赋值给x
//y就是每次从数据中获取的元素
//后续每次会将上一次的操作结果赋值给x
.reduce(0, (x, y) -> x + y);
System.out.println(sum);//求和:21
Integer max = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> x > y ? x : y);
System.out.println(max);//求最大:9
}
}
5.4.13 map和reduce组合
- 实际开发中,map和reduce经常组合使用
public class StreamTest16Reduce {
public static void main(String[] args) {
// 求年龄和
Integer sumAge = Stream.of(
new Person("楚君归", 22)
, new Person("李心怡", 18)
, new Person("楚君归", 22)
, new Person("林兮", 23)
, new Person("楚君归", 22)
).map(Person::getAge)//实现数据类型的转换
.reduce(0, Integer::sum);
System.out.println(sumAge);//107
// 求最大年龄
Integer maxAge = Stream.of(
new Person("楚君归", 22)
, new Person("李心怡", 18)
, new Person("楚君归", 22)
, new Person("林兮", 23)
, new Person("楚君归", 22)
).map(Person::getAge)//实现数据类型的转换,符合reduce数据的要求
.reduce(0, (x, y) -> x > y ? x : y);//reduce实现数据处理
System.out.println(maxAge);//23
// 统计字符a出现的次数
Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
.map(ch -> "a".equals(ch) ? 1 : 0)
.reduce(0, Integer::sum);
System.out.println(count);//3
}
}
5.4.14 mapToInt
- 该方法可以将Stream中的引用数据类型转换为对应基本类型,减少自动拆箱和装箱以提升运行效率。
public class StreamTest17MapToInt {
public static void main(String[] args) {
//Integer占用内存比int多很多,,在Stream流中会自动装箱和拆箱操作
Integer[] arr = {1, 2, 3, 5, 6, 8};
Stream.of(arr)
.filter(i -> i > 0)
.forEach(System.out::println);
//为了提高程序的效率,可以先将流中的Integer转换为int数据再操作
IntStream intStream = Stream.of(arr)
.mapToInt(Integer::intValue);
intStream.filter(i -> i > 3)
.forEach(System.out::print);//568
}
}
5.4.15 concat
- 该方法用于将两个Stream流合成一个Stream流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
- 示例:
public class StreamTest18Concat {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("a","b","c");
Stream<String> stream2 = Stream.of("x","y");
Stream.concat(stream1,stream2).forEach(System.out::print);//abcxy
}
}
5.4.16 综合练习
- 学了这么久,简单做个小练习吧!
public class StreamTest19 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("迪丽热巴","宋远桥","苏星河","老子","庄子","孙子","洪七 公");
List<String> list2 = Arrays.asList("古力娜扎","张无忌","张三丰","赵丽颖 ","张二狗","张天爱","张三");
// - 第一个队伍只保留姓名长度为3的成员
// - 第一个队伍筛选后只要前三个人
Stream<String> stream1 = list1.stream()
.filter(s -> s.length() == 3)
.limit(3);
// - 第二个队伍只要姓张的成员第二个队伍赛选之后不要前两个人
Stream<String> stream2 = list2.stream()
.filter(s -> s.startsWith("张"))
.skip(2);
// - 将两个队伍合并成一个队伍
// - 根据姓名创建Person对象
// - 打印整个队伍的Person信息
Stream.concat(stream1,stream2)
//.map(n->new Person(n))
.map(Person::new)
.forEach(System.out::println);
//执行结果:
// Person{name='宋远桥', age=null}
//Person{name='苏星河', age=null}
//Person{name='张二狗', age=null}
//Person{name='张天爱', age=null}
//Person{name='张三', age=null}
}
}
5.5 Stream结果收集
5.5.1 结果收集到集合中
@Test
public void test01() {
//收集到list
List<String> list = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toList());
System.out.println(list);//[aa, bb, cc, aa]
//收集到set
Set<String> set = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toSet());
System.out.println(set);//[aa, bb, cc]
// 如果需要获取的类型为具体的实现,比如:ArrayList hashSet
ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayList);//[aa, bb, cc, aa]
HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);//[aa, bb, cc]
}
5.5.1 结果收集到数组中
@Test
public void test02() {
//收集到数组
Object[] objects = Stream.of("aa", "bb", "cc", "aa")
.toArray();
System.out.println(Arrays.toString(objects));//[aa, bb, cc, aa]
String[] strings = Stream.of("aa", "bb", "cc", "aa")
.toArray(String[]::new);
System.out.println(Arrays.toString(strings));//[aa, bb, cc, aa]
}
5.5.2 Stream聚合计算
- 使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,如最大、最小,求和,求平均,统计数量。
@Test
public void test03() {
Optional<Person> maxAge = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
System.out.println("最大年龄:" + maxAge.get());//最大年龄:Person{name='李四', age=22}
Integer sumAge = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.summingInt(s -> s.getAge()));
System.out.println("年龄之和:" + sumAge);//年龄之和:87
Double avgAge = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.averagingInt(Person::getAge));
System.out.println("平均年龄:" + avgAge);//平均年龄:17.4
Long countAge = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).filter(s -> s.getAge() > 18)
.collect(Collectors.counting());
System.out.println("大于18人数:" + countAge);//大于18人数:2
}
5.5.3 Stream分组计算
@Test
public void test04() {
Map<String, List<Person>> map1 = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.groupingBy(Person::getName));
map1.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
//根据姓名对数据进行分组
// k=李四 v=[Person{name='李四', age=22}]
//k=张三 v=[Person{name='张三', age=18}, Person{name='张三', age=13}, Person{name='张三', age=19}]
//k=王五 v=[Person{name='王五', age=15}]
Map<String, List<Person>> map2 = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));
map2.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
//根据年龄分组,18及以上为成年
//k=未成年 v=[Person{name='张三', age=13}, Person{name='王五', age=15}]
//k=成年 v=[Person{name='张三', age=18}, Person{name='李四', age=22}, Person{name='张三', age=19}]
Map<String, Map<String, List<Person>>> map3 = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.groupingBy(
Person::getName
, Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")
));
map3.forEach((K, v) -> {
System.out.println(K);
v.forEach((k1, v1) -> {
System.out.println("\t" + k1 + "===" + v1);
});
});
//李四
// 成年===[Person{name='李四', age=22}]
//张三
// 未成年===[Person{name='张三', age=13}]
// 成年===[Person{name='张三', age=18}, Person{name='张三', age=19}]
//王五
// 未成年===[Person{name='王五', age=15}]
}
5.5.4 对流中的数据做分区操作
- Collections.partitioningBy会根据值是否为true,把集合中的数据分为两个列表,一个true列表,一个false列表
@Test
public void test06(){
Map<Boolean, List<Person>> map = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
map.forEach((k,v)-> System.out.println(k+"\t"+v));
//false [Person{name='张三', age=18}, Person{name='张三', age=13}, Person{name='王五', age=15}]
//true [Person{name='李四', age=22}, Person{name='张三', age=19}]
}
5.5.5 对流中的数据做拼接
- Collections.joining会根据指定的连接符,将所有的数据连接到一起
@Test
public void test07(){
String s1 = Stream.of(
new Person("张三", 18)
, new Person("李四", 22)
, new Person("张三", 13)
, new Person("王五", 15)
, new Person("张三", 19)
).map(Person::getName)
// joining(分隔符,前缀,后缀)
.collect(Collectors.joining("_","--","==="));
System.out.println(s1);
//--张三_李四_张三_王五_张三===
}
5.6 并行的Stream流
5.6.1 串行的Stream流
- 我们前面学习的Stream都是串行的,如下:
@Test
public void test08(){
Stream.of(5,6,4,8,69,12)
.filter(s->{
System.out.println(Thread.currentThread()+""+s);
return s>3;
}).count();
//Thread[main,5,main]5
//Thread[main,5,main]6
//Thread[main,5,main]4
//Thread[main,5,main]8
//Thread[main,5,main]69
//Thread[main,5,main]12
}
5.6.2 并行的Stream流
- parallelStream其实就是一个并行执行的流,它通过ForkJoinPool,可以提高多线程任务的速度。
- 并行流获取方式:
- 通过List接口直接获取并行流
- 将已有的串行流转换为并行流
@Test
public void test09(){
List<Integer> list = new ArrayList<>();
// 通过list接口直接获取并行流
Stream<Integer> integerStream = list.parallelStream();
// 将已有的串行流转换为并行流
Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
}
@Test
public void test10() {
Stream.of(1, 2, 3, 9, 6, 8, 4, 5)
.parallel()
.filter(s -> {
System.out.println(Thread.currentThread() + " s=" + s);
return s > 2;
}).count();
//Thread[main,5,main] s=8
//Thread[ForkJoinPool.commonPool-worker-5,5,main] s=9
//Thread[ForkJoinPool.commonPool-worker-1,5,main] s=3
//Thread[ForkJoinPool.commonPool-worker-2,5,main] s=5
//Thread[ForkJoinPool.commonPool-worker-6,5,main] s=1
//Thread[ForkJoinPool.commonPool-worker-7,5,main] s=6
//Thread[ForkJoinPool.commonPool-worker-3,5,main] s=2
//Thread[ForkJoinPool.commonPool-worker-4,5,main] s=4
}
5.6.2 串行流和并行流对比
- 我们通过for循环,串行Stream,并行Stream来对20亿个数字求和,然后查看消耗时间
public class Test02 {
private static long times = 2000000000;
private long start;
private long end;
@Before
public void befor() {
start = System.currentTimeMillis();
}
@After
public void end() {
end = System.currentTimeMillis();
System.out.println("消耗时间:" + (end - start));
}
@Test
public void test01() {
System.out.print("普通for循环");
long res = 0;
for (int i = 0; i < times; i++) {
res += 1;
}
//普通for循环消耗时间:494
}
@Test
public void test02() {
System.out.print("串行流serialStream");
LongStream.rangeClosed(0, times)
.reduce(0, Long::sum);
//串行流serialStream消耗时间:592
}
@Test
public void test03() {
System.out.print("并行流parallelStream");
LongStream.rangeClosed(0, times)
.parallel()
.reduce(0, Long::sum);
//并行流parallelStream消耗时间:222
}
}
5.6.3 并行流线程安全问题
- 在多线程的处理下,肯定会出现数据安全问题。如下:
@Test
public void test01(){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());//1000
List<Integer> listNew = new ArrayList<>();
//使用并行流向集合中添加数据
list.parallelStream()
.forEach(s->listNew.add(s));
System.out.println(listNew.size());
//994或者直接抛异常
//java.lang.ArrayIndexOutOfBoundsException
}
- 解决方案1:
- 使用synchronized 同步代码块保证线程安全
@Test
public void test02(){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());//1000
List<Integer> listNew = new ArrayList<>();
Object obj = new Object();
//使用并行流向集合中添加数据
list.parallelStream()
//.forEach(s->listNew.add(s));
.forEach(s->{
synchronized (obj){
listNew.add(s);
}
});
System.out.println(listNew.size());
//1000
}
- 解决方案2:
- 将线程不安全的容器,包装成线程安全的容器
@Test
public void test05() {
List list = new ArrayList();
List synchronizedList = Collections.synchronizedList(list);
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> synchronizedList.add(i));
System.out.println(synchronizedList.size());
}
- 解决方案3:
- 使用同步容器
@Test
public void test03() {
Vector v = new Vector();
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> v.add(i));
System.out.println(v.size());
}
- 解决方案4:
- 通过Stream中的toArray或者collect方法操作
@Test
public void test07() {
int[] ints = IntStream.rangeClosed(1, 1000)
.parallel()
.toArray();
System.out.println(ints.length);
}
@Test
public void test06() {
List<Integer> list = IntStream.rangeClosed(1, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(list.size());
}
6.Optional类
6.1 Optional类初识
- 用于解决空指针问题
- 以前对null的处理:
@Test
public void tes08() {
String userName = null;
if (userName != null) {
System.out.println("字符串长度为:" + userName.length());
} else {
System.out.println("字符串为空!");
}
}
- Optional是一个没有子类的工具类,是一个可以为null的容器对象,它的主要作用是为了避免null检查,防止NullpointerException。
6.2 Optional类用法
- Optional对象创建
@Test
public void test09() {
//通过of方法,of方法不支持null的
Optional<String> op1 = Optional.of("张三");
//Optional<String> op2 = Optional.of(null);
//通过ofNullable方法,支持null
Optional<String> op3 = Optional.ofNullable("张三");
Optional<String> op4 = Optional.ofNullable(null);
//通过empty方法创建一个空的Optional对象
Optional<Object> op5 = Optional.empty();
}
- 常用方法
- 通过
get()
方法直接获取值,如果值为null报异常java.util.NoSuchElementException: No value present
,通常get()和isPresent方法一块使用 isPresent()
:判断是否包含值,包含返回true
,否则返回false
orElse(T t)
:如果调用对象包含值,返回该值,否则返回torElseGet(Supplier s)
:如果调用对象包含值,就返回该值,否则返回lambda表达式的返回值
- 通过
@Test
public void test10() {
Optional<String> op1 = Optional.ofNullable("张三");
Optional<String> op2 = Optional.ofNullable(null);
//获取Optional中的值
System.out.println(op1.get());
//System.out.println(op2.get());
if (op1.isPresent()) {
System.out.println(op1.get());
}
if (op2.isPresent()) {
System.out.println(op2.get());
} else {
System.out.println("op2是一个空的Optional对象");
}
System.out.println(op1.orElse("李四"));//张三
System.out.println(op2.orElse("王五"));//王五
System.out.println(op2.orElseGet(() -> {
return "666";
}));//666
}
- 一个案例:
@Test
public void test14() {
Person p = new Person("zhangsan",18);
System.out.println(getName(p));
Optional<Person> p2 = Optional.of(p);
String name1 = getNameForOptional(p2);
System.out.println(name1);
//ZHANGSAN
//ZHANGSAN
}
/**
* 通过Optional<Person>将name转换为大写并返回
* @param op
* @return
*/
public String getNameForOptional(Optional<Person> op){
if(op.isPresent()){
String msg = op
.map(p -> p.getName())
.map(p -> p.toUpperCase())
.orElse(null);
return msg;
}
return null;
}
/**
* 将name转换为大写并返回
* @param person
* @return
*/
public String getName(Person person) {
if (person != null) {
String name = person.getName();
if (name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}
}
7.时间和日期
- 参照《日期相关常用类》: link
8.其他特性
- 重复注解和类型注解:注解还没整明白,先不更,就这!