了解Java8新特性,看这个就够了!
并行流与串行流
并行流:就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java8中将并行进行了优化,使我们可以很容易的对数据进行并行操作。Stream API可以声明性的通过parallel()与sequential()在并行与顺序流之间进行切换。
Lambda表达式
Lambda是一个匿名函数,可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
从匿名类到Lambda的转换案例1:
@Test
public void testLambda02() {
// 常用方式
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("This is 【Just For Joy】.");
}
};
runnable.run();
/************Lambda表达式*************/
Runnable runnable1 = () -> {
System.out.println("This is Lambda Expression.");
};
runnable1.run();
}
案例2:
@Test
public void testLambda04() {
//原来使用匿名内部类作为参数传递
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
/************Lambda表达式*************/
TreeSet<String> ts2 = new TreeSet<>((o1, o2) -> Integer.compare(o1.length(), o2.length()));
}
Lambada表达式语法:
Lambda表达式:在Java8语言中引入的一种新的语法元素和操作符。这个操作符为“->”,该操作符被称为Lambda操作符或者箭头操作符。它将Lambda分为两个部分:
-
左侧:指定了Lambda表达式需要的参数列表;
-
右侧:指定了Lambda体,是抽象方法的实现逻辑,也即Lambda表达式要执行的功能。
格式一:无参,无返回值
Runnable runnable1 = () -> {
System.out.println("This is Lambda Expression.");
};
格式二:Lambda需要一个参数,但是没有返回值
Consumer<String> consumer1 = (String s) -> {
System.out.println(s);
};
格式三:数据类型可以省略,因为可由编译器推断器推断得出,成为“类型推断“
Consumer<String> consumer1 = (s) -> {
System.out.println(s);
};
格式四:Lambda若只需要一个参数时,参数的小括号可以省略
Consumer<String> consumer1 = s -> {
System.out.println(s);
};
格式五:Lambda需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> com = (x,y) -> {
System.out.println("实现函数式接口方法!");
return Integer.compare(x,y);
};
格式六:当Lambda体只有一条语句时,return与大括号若有,都可以省略
Comparator<Integer> comparator1 = (o1, o2) -> Integer.compare(o1, o2);
类型推断:上述Lambda表达式中的参数类型都是由编译器推断得出的。Lambda表达式无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
函数式(Functional)接口
- 只包含一个抽象方法的接口,成为函数式接口。
- 可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
- 可以在一个接口上使用@FunctionalInterface注解,这样做可以检查他是否是一个函数式接口。同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
- 在java.util.function包下定义了Java8的丰富的函数式接口
Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着Python、Scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)。
在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言当中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,他们必须依附于一类特别的对象类型——函数式接口。
简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数是接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以使用Lambda表达式来表示。
所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
自定义函数式接口:
@FunctionalInterface
public interface MyInterface {
/**
* 求和
*
* @param a
* @param b
* @return
*/
Integer add(Integer a, Integer b);
}
函数式接口中使用泛型:
public interface MyInterface02<T> {
/**
* 使用泛型
*
* @param t
* @return
*/
T getValue(T t);
}
作为参数传递Lambda表达式:
public String toUpperString(MyInterface02<String> mf, String str) {
return mf.getValue(str);
}
@Test
public void testLambda05() {
String str = toUpperString(s -> s.toUpperCase(), "lixiaochangx");
System.out.println(str);
}
Java内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get() |
Function<T,R>函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate断言型接口 | T | Boolean | 确定类型为T的对象是否满足某约束,并返回Boolean值,包含方法:boolean test(T t) |
其他接口:
方法引用与构造器引用
方法引用(Method References)
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
- 当方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法参数列表和返回值类型保持一致!
- 格式:使用操作符“::”将类(或对象)与方法名分割开来。
如以下三种情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
Consumer<String> con = x -> System.out.println(x);
Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1, o2);
BiPredicate<String, String> predicate = (s1, s2) -> s1.equals(s2);
// 等同于
Consumer<String> con1 = System.out::println;
Comparator<Integer> comparator1 = Integer::compareTo;
BiPredicate<String,String> predicate1 = String::equals;
注意:当函数式接口方法的第一个参数时需要引用方法的调用者,并且第二个参数时需要引用方法的参数(或无参数)时:ClassName::methodName。
构造器引用
格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表与接口中抽象方法的参数列表一致!切方法的返回值即为构造器对应类的对象。
/**
* 构造器引用
* Supplier中的T get()
* Employee的空参构造器:Employee()
*/
@Test
public void test01() {
Supplier<Employee> employeeSupplier = new Supplier<Employee>() {
@Override
public Employee get() {
return new Employee();
}
};
System.out.println("***************************");
Supplier<Employee> supplier = () -> new Employee();
System.out.println("supplier:" + supplier.get());
System.out.println("***************************");
Supplier<Employee> supplier1 = Employee::new;
System.out.println("supplier:" + supplier1.get());
}
Stream API
-
Java8中有两大最为重要的改变。第一个是Lambda表达式;另一个则是Stream API。
-
StreamAPI(java.util.stream)把真正的函数式编程风格引入到java中。这是目前为止对java类库最好的补充。因为StreamAPI可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
-
Stream是Java8中处理集合的关键抽象概念,他可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简言之,Stream API提供了一一种高效且易于使用的处理数据的方式。
-
实际开发中,项目中多数数据源都来自MySQL,Oracle等。但现在数据源可以更多了,有MongoDB,Redis等,而这些NoSQL的数据就需要Java层面去处理。
-
Stream和Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,Stream讲的是计算!
注意:
- Stream自己不会存储元素;
- Stream不会改变源对象。相反,它们会返回一个持有结果的新Stream;
- Stream操作是延迟执行的。这意味着它们会等到需要结果的时候才执行。
Stream的创建步骤:
- 创建Stream
一个数据源(如集合、数组),获取一个流。 - 中间操作
一个中间操作链,对数据源的数据进行处理。 - 终止操作
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用。
创建Stream方式一:通过集合
java8中的Collection接口被扩展,提供两个获取流的方法:
default Stream stream():返回一个顺序流
default Stream parallel():返回一个并行流
/**
* 创建Stream方式一:通过集合
*/
@Test
public void test01() {
List<Employee> list = Employee.getEmployees();
//default Stream<E> stream() 返回一个顺序流
Stream<Employee> stream = list.stream();
//default Stream<E> parallelStream 返回一个并行流
Stream<Employee> parallelStream = list.parallelStream();
}
创建Stream方式二:通过数组
Java8中的Arrays的静态方法stream()可以获取数组流:
static Stream stream(T[] array):返回一个流
重载形式,能够处理对应基本类型的数组:
- public static IntStream stream(int[] array)
- public static LongStream stream(int[] array)
- public static DoubleStream stream(int[] array)
/**
* 创建Stream方式二:通过数组
*/
@Test
public void test02() {
int[] arr = {1, 2, 3, 4, 5};
// 调用Arrays类的static <T> Stream<T> stream(T[] array):返回一个流
IntStream stream = Arrays.stream(arr);
Employee jerry = new Employee(1001, "Jerry");
Employee tom = new Employee(1001, "Tom");
Employee[] employees = {jerry, tom};
Stream<Employee> stream1 = Arrays.stream(employees);
}
创建Stream方式三:通过Stream的of()
可以调用Stream类静态方法of(),通过显示值创建一个流。它可以接收任意数量的参数。
static Stream of(T… values):返回一个流
@Test
public void test03() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
创建Stream方式四:创建无限流
可以使用静态方法Stream.iterate()和Stream.generate(),创建无限流。
- 迭代
public static Stream iterate(final T seed, final UnaryOperator f) - 生成
public static Stream generate(Supplier s)
@Test
public void test04() {
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
// 遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上出发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,成为“惰性求值”。
筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的hashCode和equals去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补 |
@Test
public void test01() {
List<Employee> list = Employee.getEmployees();
// filter(Predicate p)——接受 Lambda,从流中排除某些元素
Stream<Employee> stream = list.stream();
// 练习:查询员工表中薪资大于7000的员工信息
stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
System.out.println();
// limit(n)——截断流,使其元素不超过给定数量
list.stream().limit(3).forEach(System.out::println);
System.out.println();
// 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
list.stream().skip(3).forEach(System.out::println);
System.out.println();
// distinct() ——筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素
list.add(new Employee(1010, "刘强东", 40, 9999));
list.add(new Employee(1010, "刘强东", 40, 9999));
list.add(new Employee(1010, "刘强东", 40, 9999));
list.add(new Employee(1010, "刘强东", 40, 9999));
list.add(new Employee(1010, "刘强东", 40, 9999));
// System.out.println(list);
list.stream().distinct().forEach(System.out::println);
}
映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。 |
@Test
public void test02() {
// map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> list = Arrays.asList("aa", "bb", "cc");
list.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
// 练习1 获取员工姓名长度大于3的员工的姓名
List<Employee> employees = Employee.getEmployees();
Stream<String> nameStream = employees.stream().map(Employee::getName);
nameStream.filter(name -> name.length() > 3).forEach(System.out::println);
System.out.println();
// 练习2
Stream<Stream<Character>> streamStream = list.stream().map(StreamApi02Test::fromStringToStream);
streamStream.forEach(s -> s.forEach(System.out::println));
System.out.println();
// flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Character> characterStream = list.stream().flatMap(StreamApi02Test::fromStringToStream);
characterStream.forEach(System.out::println);
}
排序
方法 | 描述 |
---|---|
sorted() | 产生一个流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个流,其中按比较器顺序排序 |
@Test
public void test04() {
// sorted ——自然排序
List<Integer> list = Arrays.asList(12, 83, 54, 67, 0, -90, 7);
list.stream().sorted().forEach(System.out::println);
// 抛异常,原因:Employee没有实现Comparable接口
List<Employee> employees = Employee.getEmployees();
employees.stream().sorted().forEach(System.out::println);
// sorted(Comparator com)——定制排序
/*List<Employee> employees = Employee.getEmployees();
employees.stream().sorted( (e1,e2) -> {
int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println);*/
}
Stream的终止操作
匹配与查找
方法 | 描述 |
---|---|
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用Collection接口需要用户去做迭代,成为外部迭代。相反,Stream API使用内部迭代——他帮你把迭代做了) |
@Test
public void test01() {
List<Employee> employees = Employee.getEmployees();
// allMatch(Predicate p)——检查是否匹配所有元素
// 练习 是否所有员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(employee -> employee.getAge() > 18);
System.out.println(allMatch);
// anyMatch(Predicate p)——检查是否至少匹配一个元素。
// 练习:是否存在员工的工资大于 10000
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(anyMatch);
// noneMatch(Predicate p)——检查是否没有匹配的元素。
// 练习:是否存在员工姓“马”
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("马"));
System.out.println(noneMatch);
// findFirst——返回第一个元素
Optional<Employee> employee = employees.stream().findFirst();
System.out.println(employee);
// findAny——返回当前流中的任意元素
Optional<Employee> employee1 = employees.parallelStream().findAny();
System.out.println(employee1);
}
@Test
public void test2() {
List<Employee> employees = Employee.getEmployees();
// count——返回流中元素的总个数
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
// max(Comparator c)——返回流中最大值
// 练习:返回最高的工资:
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> maxSalary = salaryStream.max(Double::compare);
System.out.println(maxSalary);
// min(Comparator c)——返回流中最小值
// 练习:返回最低工资的员工
Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(employee);
System.out.println();
// forEach(Consumer c)——内部迭代
employees.stream().forEach(System.out::println);
System.out.println();
// 使用集合的遍历操作
employees.forEach(System.out::println);
}
归约
方法 | 描述 |
---|---|
reduce(T iden,BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回T |
reduce(BinaryOoperator b) | 可以将流中元素反复结合起来,得到一个值,返回Optional |
备注:map和reduce的连接通常成为map-reduce模式,因Google用它来进行网络搜索而出名。
@Test
public void test3() {
// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
// 练习1:计算1-10的自然数的和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
// reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
// 练习2:计算公司所有员工工资的总和
// Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
List<Employee> employees = Employee.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
// Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
Optional<Double> sumMoney = salaryStream.reduce((d1, d2) -> d1 + d2);
System.out.println(sumMoney);
}
收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream元素做汇总的方法 |
@Test
public void test4() {
// collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
// 练习1:查找工资大于6000的员工,结果返回为一个List或Set
List<Employee> employees = Employee.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out::println);
System.out.println();
Set<Employee> employeeSet = employeeList.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
employeeList.forEach(System.out::println);
}
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
另外,Collectors实现类提供了很多静态方法,可以方便的创建常见收集器实例,具体方法如下。
Optional类
到目前为止,臭名远昭的空指针异常时导致java应用程序失败的最常见的原因。以前,为了解决空指针异常,Google公司著名的Guava向某引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,他鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java8类库的一部分。
Optional类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
Optional类的javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent方法会返回true,调用get方法会返回该对象。
Optional提供很多有用的方法,这样我们就不用显示进行空值检测。
创建Optional类对象的方法
- Optional.of(T t):创建一个Optional实例,t必须非空
- Optional.empty():创建一个空的Optional实例
- Optional.ofNullable(T t):t可以为null
判断Optional容器中是否包含对象:
- boolean isPresent():判断是否包含对象。
- void ifPresent(Consumer<? super T> consumer):如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
获取Optional容器的对象:
- T get():如果调用对象包含值,返回该值,否则抛异常。
- T orElse(T other):如果有值则将其返回,否则返回指定的other对象。
- T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
- T orElseThrow(Supplier<? extends X exceptionSupplier):如果有值,则将其返回,否则抛出由Supplier接口实现提供的异常。
public class OptionalTest {
/**
* Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
* Optional.empty() : 创建一个空的 Optional 实例
* Optional.ofNullable(T t):t可以为null
*/
@Test
public void test01() {
Girl girl = new Girl();
// girl = null;
//of(T t):保证t是非空的
Optional<Girl> optionalGirl = Optional.of(girl);
}
@Test
public void test02() {
Girl girl = new Girl();
// girl = null;
//ofNullable(T t):t可以为null
Optional<Girl> optionalGirl = Optional.ofNullable(girl);
System.out.println(optionalGirl);
//orElse(T t1):如果单前的Optional内部封装的t是非空的,则返回内部的t.
//如果内部的t是空的,则返回orElse()方法中的参数t1.
Girl girl1 = optionalGirl.orElse(new Girl("赵丽颖"));
System.out.println(girl1);
}
public String getGirlName(Boy boy) {
return boy.getGirl().getName();
}
@Test
public void test3() {
Boy boy = new Boy();
boy = null;
String girlName = getGirlName(boy);
System.out.println(girlName);
}
/**
* 优化以后的getGirlName():
*/
public String getGirlName1(Boy boy) {
if (boy != null) {
Girl girl = boy.getGirl();
if (girl != null) {
return girl.getName();
}
}
return null;
}
@Test
public void test4() {
Boy boy = new Boy();
boy = null;
String girlName = getGirlName1(boy);
System.out.println(girlName);
}
/**
* 使用Optional类的getGirlName():
*
* @param boy
* @return
*/
public String getGirlName2(Boy boy) {
Optional<Boy> boyOptional = Optional.ofNullable(boy);
//此时的boy1一定非空
Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));
Girl girl = boy1.getGirl();
Optional<Girl> girlOptional = Optional.ofNullable(girl);
//girl1一定非空
Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));
return girl1.getName();
}
@Test
public void test5() {
Boy boy = null;
boy = new Boy();
boy = new Boy(new Girl("刘德华"));
String girlName = getGirlName2(boy);
System.out.println(girlName);
}
}