Java8 优势:速度快、代码更少(增加了新的语法 Lambda 表达式)、强大的 Stream API、便于并行、最大化减少空指针异常 Optional;
一、Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以取代大部分的匿名内部类,可以写出更简洁、更灵活的代码。尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到提升。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
【1】从匿名类到 Lambda 的转换:虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。
jdk8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。后续有专门的介绍。
//匿名类
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.printf("Hello World!");
}
};
/**
*1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
*2.简化参数小括号,如果只有一个参数则可以省略参数小括号
*3.简化方法体大括号,如果方法条只有一条语句,则可以胜率方法体大括号(如下案例)
*4.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号和rerun关键字:X x= a -> a+3;
*Lambda 表达式展示:
*/
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【2】原来使用匿名内部类作为参数传递到 Lambda 表达式:
//原来使用匿名内部类作为参数传递
TreeSet 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()));
【3】Lambda 表达式语法:Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” ,该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
■ 左侧:指定了 Lambda 表达式需要的所有参数;
■ 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能;
【语法格式一】:无参,无返回值,Lambda 体只需要一条语句;
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【语法格式二】:Lambda 需要一个参数;
Consumer<String> fun = (args) -> System.out.printf(args);
【语法格式三】:Lambda 只需要一个参数时,参数的小括号可以省略;
Consumer<String> fun = args -> System.out.printf(args);
【语法格式四】:Lambda 需要两个参数,并且有返回值;
BinaryOperator<Long> bo = (x,y)->{ System.out.printf("实现函数接口方法"); return x+y;};
【语法格式五】:当 Lambda 体只有一条语句时,return 与大括号可以省略;
BinaryOperator<Long> bo = (x,y) -> x+y;
【语法格式六】:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”:根据上下文环境推断参数类型;
BinaryOperator<Long> bo = (Long x,Long y)->{
System.out.printf("实现函数接口方法");
return x+y;
};
【4】遍历集合: 可以调用集合的 forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。Consumer 接口是 jdk 为我们提供的一个函数式接口。
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
//lambda表达式 方法引用
list.forEach(System.out::println);
list.forEach(element -> {
if (element % 2 == 0) {
System.out.println(element);
}
});
【5】删除集合:通过
removeIf(Predicate<? super E> filter) 方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
ArrayList<Item> items = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
items.removeIf(ele -> ele.getId() == 3);
【6】集合内元素的排序:若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
ArrayList<Item> list= new ArrayList<>();
Collections.addAll(list, 6,27,7,4,2);
list.sort((o1,o2) -> o1.getId() - o2.getId());
二、函数式接口
【1】只包含一个抽象方法的接口,称为函数式接口;
【2】你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
【3】我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,说明这个接口是一个函数式接口。
@FunctionalInterface
public interface MyLambda<T> {
public T getValue(T t);
}
public String toUpperString(MyLambda<String> mf, String str){
return mf.getValue(str);
}
//为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与 Lambda 表达式兼容的函数式接口的类型
String newStr = toUpperString((str)-> str.toUpperCase(),"abcde");
【4】Java 内置四大核心函数式接口 :
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t); |
Supplier<T> 供给型接口 | 无 | T | 返回类型为 T 对象,包含方法:T get(); |
Function<T, R> 函数型接口 | T | R | 对类型为 T 的对象应用操作,并返回结果。结果时 R 类型的对象。包含方法:R apply(T t) |
Predicate<T> 断定型接口 | T | boolean | 确定类型为 T 的对象是否满足某约束,并返回 Boolean 值,包含方法 Boolean test(T t) |
public class Java8Tester {
public static void main(String args[]){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Predicate<Integer> predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
}
public static void eval(List<Integer> list, Predicate<Integer> predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.println(n + " ");
}
}
}
}
三、方法引用与构造器引用
【1】方法引用:当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用中方法的参数列表保持一致!)方法引用:使用操作符 “::**” 将方法名和对象或类的名字分隔开来。如下三种主要使用情况:使用方法引用的时候需要保证引用方法的参数列表和返回值类型与我们当前所要实现的函数式接口方法的参数列表和返回值类型保持一致
①、对象::实例方法;②、类::静态方法;③、类::实例方法**;
(x) -> System.out.println(x);
//等同于
System.out::println
BinaryOperator<Double> bo = (x,y) -> Math.pow(x,y);
//等同于
BinaryOperator<Double> bo = Math::pow;
compare((x,y) -> x.equals(y),"abcd","abcd");
//等同于
compare(String::equals,"abc","abc");(x) -> System.out.println(x);//等同于System.out::printlnBinaryOperator<Double> bo = (x,y) -> Math.pow(x,y);//等同于BinaryOperator<Double> bo = Math::pow;compare((x,y) -> x.equals(y),"abcd","abcd");//等同于compare(String::equals,"abc","abc");
注意:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数(或无参数)时:
ClassName::methodName
【2】构造器引用: 格式: ClassName::new 与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!调用哪个构造器取决于函数式接口中的方法形参的定义。我们通过两种方式创建对象(无参和有参),具体如下:
//【案例一】无参构造器创建对象 使用Employee的无参构造器.
//使用 Supplier函数接口 因为 Supplier接口的抽象方法没有入参 可以参考二中的函数式编程.
Supplier<Employee> sup = Employee::new;
//【案例二】有参构造器创建对象 使用Employee的有参构造器.
//使用 Function函数接口 因为 Function接口的抽象方法有入参 可以参考二中的函数式编程.
Function<Integer,Employee> fun = (n) -> new MyClass(n);
//构造器应用改造后:
Function<Integer,Employee> fun = MyClass::new;
【3】数组引用: 同构造器引用,格式为 type[] :: new
Function<Integer,Integer[]> fun = (n) -> new Integer[n];
Function<Integer,Integer[]> fun = Integer[]::new;
四、Stream API
Java8 中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.)。Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
【1】Stream 到底是什么:**是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,流讲的是计算!
【2】注意:①、Stream 自己不会存储数据。②、Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。③、Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
【3】**Stream 的操作三个步骤:①、创建 Stream 一个数据源(如:集合、数组),获取一个流。 ②、中间操作:一个中间操作链,对数据源的数据进行处理。③、终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果:
default Stream<E> stream()//返回一个顺序流;
default Stream<E> parallelStream()//返回一个并行流;
//举例
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
numbers.stream().forEach(System.out::println);
});
【5】由数组创建流:Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:static <T> Stream<T> stream(T[] array):返回一个流;重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array) ;
public static LongStream stream(long[] array) ;
public static DoubleStream stream(double[] array);
//举例
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Arrays.stream(array).filter(i->i>20).count();
【6】由值创建流:可以使用静态方法 Stream.of(),通过显示值创建一个流。它可以接收任意数量的参数。
public static<T> Stream<T> of(T... values) : 返回一个流
//举例
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Stream.of(array).filter(i->i>20).count();
long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
【7】由函数创建流:创建无限流,可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); //迭代
public static<T> Stream<T> generate(Supplier<T> s); //生成
//举例 iterate
Stream<BigInteger> bigIntStream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN)).limit(10);
BigInteger[] bigIntArr = bigIntStream.toArray(BigInteger[]::new);
//输出:[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
System.out.println(Arrays.toString(bigIntArr));
//举例 generate
Stream<String> stream = Stream.generate(() -> "test").limit(10);
String[] strArr = stream.toArray(String[]::new);
//输出 [test, test, test, test, test, test, test, test, test, test]
System.out.println(Arrays.toString(strArr));
Stream 的中间操作:多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
【1】筛选与切片:
方法 | 描述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素。 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素。 |
limit(long maxSize) | 截断流,使其元素不超过给定数量。 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。 |
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
for(Integer i:numbers){
if(i>20){
count++;
}
}
//当使用 Stream 操作时 如下:
list.stream().filter(num -> num > 4).count();
//limit 使用:输出 [1, 2, 3, 4] 方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)
List<Integer> afterLimit = list .stream().limit(4).collect(Collectors.toList());
//skip(n) 使用:输出 [5, 6, 7, 8, 9] 与limit 正好相反,它会丢弃掉前面的n个元素。
List<Integer> afterSkip = list .stream().skip(4).collect(Collectors.toList());
//用limit和skip方法一起使用就可以实现日常的分页功能
List<Integer> pageList = myList.stream().skip(pageNumber*pageSize)
.limit(pageSize).collect(Collectors.toList());
【2】 映射:
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。 |
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//将Integer类型转换成String类型
List<String> afterString = integerList.stream().map(i->String.valueOf(i)).collect(Collectors.toList());
【3】 排序:上面介绍的流的转换方法都是无状态的。即从一个已经转换的流中取某个元素时,结果并不依赖于之前的元素。除此之外还有两个方法在转换流时是需要依赖于之前流中的元素的。一个是 distinct方法一个是 sorted方法。distinct方法会根据原始流中的元素返回一个具有相同顺序、去除了重复元素的流,这个操作显然是需要记住之前读取的元素。
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然排序排序。 |
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序。 |
//distinct
List<Integer> list = Arrays.asList(70, 25, 38, 64, 25, 46, 7, 18, 9);
List<Integer> distinctList = list.stream().distinct().collect(Collectors.toList());
//sorted
List<Integer> sortList = myTestList.stream()
.sorted(Integer::compareTo).collect(Collectors.toList());
Stream 的聚合操作:终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
【1】查找与匹配:
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
List<Integer> list = Arrays.asList(70, 125, 38, 64, 25, 46, 7, 18, 9);
//max 与 min 案例
Integer maxItem = list.stream().max(Integer::compareTo).get();
Integer minItem = list.stream().min(Integer::compareTo).get();
//findFirst:方法返回非空集合中的第一个值,它通常与filter方法结合起来使用。
Integer first = list.stream().filter(i->i>100).findFirst().get();
//findAny:可以在集合中只要找到任何一个所匹配的元素,就返回,此方法在对流并行执行时十分有效(任何片段中发现第一个匹配元素都会结束计算,串行流中和findFirst返回一样)。
Integer anyItem = list.parallelStream().filter(i->i>100).findAny().get();
//anyMatch:可以判定集合中是否还有匹配的元素。返回结果是一个boolean类型值。noneMatch 相反。
boolean isHas = list.parallelStream().anyMatch(i->i>100);
boolean noHas = hearList.parallelStream().noneMatch(i->i>100);
【2】 归约:
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
---|---|
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional<T> |
List<Integer> list = Arrays.asList(70, 125, 38, 64, 25, 46, 7, 18, 9);
//简化一下,对元素长度进行求和。
sum = list.stream().map(Objects::toString).mapToInt(String::length).sum();
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
【3】收集:当处理完流之后,通常是想查看一下结果,而不是将他们聚合为一个值。Collectorts 类为我们提供了常用的收集类的各个工厂方法。
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其它形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法。 |
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 使用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法 | 返回类型 | 作用 |
---|---|---|
toList | List<T> | 把流中元素收集到List |
List<Employee> emps= list.stream().collect(Collectors.toList()); | ||
toSet | Set<T> | 把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet()); | ||
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new)); | ||
counting | Long | 计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting()); | ||
summingInt | Integer | 对流中元素的整数属性求和 |
inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary)); | ||
averagingInt | Double | 计算流中元素Integer属性的平均值 |
doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary)); | ||
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。 |
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); | ||
joining | String | 连接流中每个字符串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); | ||
maxBy | Optional<T> | 根据比较器选择最大值 |
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); | ||
minBy | Optional<T> | 根据比较器选择最小值 |
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); | ||
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); | ||
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 |
inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); | ||
groupingBy | Map<K, List<T>> | 根据某属性值对流分组,属性为K,结果为V |
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus)); | ||
partitioningBy | Map<Boolean, List<T>> | 根据 true或 false进行分区 |
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
//例如前面的例子用的要将一个流收集到一个List中,只需要这样写就可以。
List<Integer> thereList = hereList.stream().collect(Collectors.toList());
//收集到Set中可以这样用
Set<Integer> thereSet = hereList.stream().collect(Collectors.toSet());
//将字流中的字符串连接并收集起来。
String resultString = stringList.stream().collect(Collectors.joining());
//在将流中的字符串连接并收集起来时,想在元素中介添加分隔符,传递个joining方法即可。
String resultString = stringList.stream().collect(Collectors.joining(","));
//总和、平均值,最大值,最小值
int sum = hereList.stream().collect(Collectors.summingInt(Integer::intValue));
Double ave = hereList.stream().collect(Collectors.averagingInt(Integer::intValue));
Integer max = hereList.stream().collect(Collectors.maxBy(Integer::compare)).get();
Integer min = hereList.stream().collect(Collectors.minBy(Integer::compare)).get();
【常见面试题】:将结果集收集到 Map;当我们希望将集合中的元素收集到 Map中时,可以使用 Collectors.toMap方法。这个方法有两个参数,用来生成 Map的 key和 value。例如将一个 Form 对象的 high作为键 width作为值:每个 toMap方法,都会有一个对应的 toConCurrentMap方法,用来生成一个并发Map。
//准备工作
List<Form> formList = Lists.newArrayList(
new Form (3,4,5),
new Form (6,5,3),
new Form (77,43,55);
)
//Form对象的 high作为键 width作为值
Map<Integer,Integer> hwMap = formList.stream().collect(Collectors.toMap(Form::getHigh, Form::getWidth));
//但是通常还是以具体元素作为值的情况多,可以使用Function.identity()来获取实际元素。
Map<Integer,Form> FormMap = formList.stream().collect(Collectors.toMap(Form::getHigh, Function.identity()));
//如果多个元素拥有相同的键,在收集结果时会抛出java.lang.IllegalStateException异常。
//可以使用第三个参数来解决,第三个参数用来确定当出现键冲突时,该如何处理结果,如果当出现键冲突时只保留一个并且是保留已经存在的值时,就是如下方式。
Map<Integer,Form> rMap = formList.stream().collect(Collectors.toMap(Form::getHigh, Function.identity(),(nowValue,newValue)->nowValue));
//如果想指定生成的 Map类型,则还需要第三个参数。
TreeMap<Integer,Form> FormTreeMap = FormList.stream().collect(Collectors.toMap(Form::getHigh,
Function.identity(),(nowValue,newValue)->newValue,TreeMap::new));
//******在一个集合中,对具有相同特性的值进行分组是一个很常见的功能,在Stream的API中也提供了相应的方法。
//根据 high 将 Form 进行分组
Map<Integer,List<Form>> groupMap = list .stream().collect(Collectors.groupingBy(Form::getHigh));
//当分类函数是一个返回布尔值的函数时,流元素会被分为两组列表:一组是返回true的元素集合,另一组是返回false的元素集合。这种情况适用 partitoningBy方法会比groupingBy更有效率。
Map<Boolean,List<Form>> partitionMap = list.stream().collect(Collectors.partitioningBy(form->form.getHigh()==22));
//例如我们将房间集合分为两组,一组是高度为22的房间,另一组是其他房间。
//mapping方法会将结果应用到另一个收集器上。如下取出分组中宽度最大的宽度。
Map<Integer, Optional<Integer>> collect = list.stream().collect(Collectors.groupingBy(Form::getHigh,
Collectors.mapping(Form::getWidth,Collectors.maxBy(Comparator.comparing(Integer::valueOf)))));
//*****groupingBy是支持多级分组的。例如第一级我们将按照高度分组,第二级按照宽度分组。
Map<Integer,Map<Integer,List<Form>>> multistageMap = list.stream().
collect(Collectors.groupingBy(Form::getHigh,Collectors.groupingBy(Form::getWidth)));
并行流与串行流:并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。但是并行流在使用的时候也是需要注意的。首先,必须是一个并行流,只要在终止方法执行时,流处于并行模式,那么所有的流操作就都会并行执行。
Stream.of(roomList).parallel();
parallel 方法可以将任意的串行流转换为一个并行流。其次要确保传递给并行流操作的函数是线程安全的。
//下面这个例子中的代码就是错误的,传递给并行流的操作并不是线程安全的。可以改为AtomicInteger的对象数组来作为计数器。
int[] words = new int[23];
Stream.of(roomList).parallel().forEach(s->{
if(s.size()<10){
words[s.size()]++;
}
});
我们使在处理集合数据量较大的时候才能体现出并行流的优势,并且目的是为了在保证线程安全的情况下,提升效率,利用多核 CPU的资源。使用 Stream 的 API时,在遍历或处理流的过程中当引用外部变量的时候会默认的将变量当成 fianl变量来处理。所以有些同学就会觉得在遍历的过程中取不出来集合的索引。其实可以换一种思想可以只遍历集合索引,然后在遍历中取值。
IntStream.range(0,roomList.size()).forEach(i->{ System.out.println(roomList.get(i));});
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
Fork/Join 框架与传统线程池的区别:采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。相对于一般的线程池实现,fork/join 框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因
无法继续运行,那么该线程会处于等待状态。而在 fork/join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
五、接口中的默认方法与静态方法
Java8 中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。例如:
interface MyFunc<T>{
T func(int a);
default String getName(){
return "Hello Java8!";
}
}
接口默认方法的 ”类优先” 原则:若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时。
【1】选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
【2】接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法
是否是默认方法),那么必须覆盖该方法来解决冲突 。
【接口默认方法的”类优先”原则】:
interface MyFunc{
default String getName(){
return "Hello Java8!";
}
}
interface Named(){
default String getName(){
return "Hello atguigu!";
}
}
class MyClass implements MyFunc,Named{
public String getName(){
return Named.super.getName();
}
}
【接口中的静态方法】:Java8 中,接口中允许添加静态方法;
interface Named{
public Integer myFunc();
default String getName(){
return "Hello atguigu!";
}
static void show(){
System.out.println("Hello Lambda!");
}
}
六、新时间日期 API
【1】LocalDate、LocalTime、LocalDateTime:的实例是不可变的对象,分别表示使用 ISO-8601 日期系统的日期、时间、日期和时间。它提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
方法 | 描述 | 示例 |
---|---|---|
now() | 静态方法,根据当前时间创建对象 | LocalDate localDate = LocalDate.now();LocalTime localTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now() |
of() | 静态方法,根据指定日期/时间创建对象 | LocalDate localDate = LocalDate.of(2016, 10, 26); LocalTime localTime = LocalTime.of(02, 22, 56); LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55); |
plusDays,plusWeeks, plusMonths, plusYears | 向当前LocalDate 对象添加几天、几周、几个月、几年 | |
minusDays, minusWeeks,minusMonths, minusYears | 从当前 LocalDate 对象减去几天、几周、几个月、几年 | |
plus, minus | 添加或减少一个 Duration 或 Period | |
withDayOfMonth, withDayOfYear, withMonth,withYear | 将月份天数、年份天数、月份、年份修改为指定的值并返 回新的LocalDate 对象 | |
getDayOfMonth | 获得月份天数(1-31) | |
getDayOfYear | getDayOfYear 获得年份天数(1-366) | |
getDayOfWeek | 获得星期几(返回一个 DayOfWeek 枚举值) | |
getMonth | 获得月份, 返回一个 Month 枚举值 | |
getMonthValue | 获得月份(1-12) | |
getYear | 获得年份 | |
until | 获得两个日期之间的 Period 对象,或者指定 ChronoUnits 的数字 | |
isBefore, isAfter | 比较两个 LocalDate | |
isLeapYear | 判断是否是闰年 |
【2】Instant 时间戳:**用于 “时间戳” 的运算。它是以 Unix 元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算;
【3】**Duration 和 Period:Duration:用于计算两个“时间”间隔;Period:用于计算两个“日期”间隔;
Instant instant_1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant instant_2 = Instant.now();
Duration duration = Duration.between(instant_1, instant_2);
System.out.println(duration.toMillis());
// 运行结果:1000
LocalTime localTime_1 = LocalTime.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LocalTime localTime_2 = LocalTime.now();
System.out.println(Duration.between(localTime_1, localTime_2).toMillis());
// 运行结果:1000
LocalDate localDate_1 = LocalDate.of(2018,9, 9);
LocalDate localDate_2 = LocalDate.now();
Period period = Period.between(localDate_1, localDate_2);
System.out.println(period.getYears()); // 运行结果:1
System.out.println(period.getMonths()); // 运行结果:1
System.out.println(period.getDays()); // 运行结果:18
【4】日期的操纵: TemporalAdjuster:时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。TemporalAdjusters:该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。例如获取下个周日:
LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));//获取下个周日
LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1); // 2019-10-27T14:19:56.884
// 获取这个第一天的日期
System.out.println(localDateTime1.with(TemporalAdjusters.firstDayOfMonth())); // 2019-10-01T14:22:58.574
// 获取下个周末的日期
System.out.println(localDateTime1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
【5】解析与格式化:java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
● 预定义的标准格式;
● 语言环境相关的格式;
● 自定义的格式;
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String strDate1 = localDateTime.format(dateTimeFormatter1);
System.out.println(strDate1);
// 运行结果:2019-10-27
// Date -> String
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String strDate2 = dateTimeFormatter2.format(localDateTime);
System.out.println(strDate2);
// 运行结果:2019-10-27 14:36:11
// String -> Date
LocalDateTime localDateTime1 = localDateTime.parse(strDate2, dateTimeFormatter2);
System.out.println(localDateTime1);
// 运行结果:2019-10-27T14:37:39
【6】时区的处理:Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime 其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式;例如 :Asia/Shanghai 等;
● ZoneId:该类中包含了所有的时区信息;
● getAvailableZoneIds():可以获取所有时区时区信息;
● of(id):用指定的时区信息获取 ZoneId 对象;
// 获取所有的时区
Set<String> set = ZoneId.getAvailableZoneIds();
// 通过时区构建LocalDateTime
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("America/El_Salvador"));
System.out.println(localDateTime1);
// 2019-10-27T00:46:21.268
// 以时区格式显示时间
LocalDateTime localDateTime2 = LocalDateTime.now();
ZonedDateTime zonedDateTime1 = localDateTime2.atZone(ZoneId.of("Africa/Nairobi"));
System.out.println(zonedDateTime1);
// 2019-10-27T14:46:21.273+03:00[Africa/Nairobi]
【7】与传统日期处理的转换:
类 | To 遗留类 | From 遗留类 |
---|---|---|
java.time.Instant java.util.Date | Date.from(instant) | date.toInstant() |
java.time.Instant java.sql.Timestamp | Timestamp.from(instant) | timestamp.toInstant() |
java.time.ZonedDateTime java.util.GregorianCalendar | GregorianCalendar.from(zonedDateTime) | cal.toZonedDateTime() |
java.time.LocalDate java.sql.Time | Date.valueOf(localDate) | date.toLocalDate() |
java.time.LocalTime java.sql.Time | Date.valueOf(localDate) | date.toLocalTime() |
java.time.LocalDateTime java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
java.time.ZoneId java.util.TimeZone | Timezone.getTimeZone(id) | timeZone.toZoneId() |
java.time.format.DateTimeFormatter java.text.DateFormat | formatter.toFormat() | 无 |
七、其他新特性
【1】Optional类:Optional<T> 类(java.util.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;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = new Integer(10);
// Optional.ofNullable - 允许传递为 null 参数
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
// Optional.isPresent - 判断值是否存在
System.out.println("第一个参数值存在: " + a.isPresent());
System.out.println("第二个参数值存在: " + b.isPresent());
// Optional.orElse - 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(new Integer(0));
//Optional.get - 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
}
//输出结果:
第一个参数值存在: false
第二个参数值存在: true
10
【2】重复注解与类型注解:Java8 对注解处理提供了两点改进,可重复的注解及可用于类型的注解。如下自定义可重复注解:使用 @Repeatable 元注解,参数为可重复注解的容器。
@Repeatable(MyAnnotations.class)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "java8";
}
/**
* 容器类
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepetitionAnnotations {
RepetitionAnnotation[] value();
}
【测试方法】
public class AnnotationTest {
@Test
public void t1() throws Exception {
Class<AnnotationTest> clazz = AnnotationTest.class;
Method method = clazz.getMethod("show");
// 获取方法上的注解
RepetitionAnnotation[] ras = method.getAnnotationsByType(RepetitionAnnotation.class);
for (RepetitionAnnotation repetitionAnnotation : ras) {
System.out.println(repetitionAnnotation.value());
}
}
@RepetitionAnnotation("Hello")
@RepetitionAnnotation("World")
public void show() {
}
}
【类型注解】
//就是向 @Target 添加一种类型 TYPE_PARAMETER
@Repeatable(RepetitionAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepetitionAnnotation {
String value() default "ling";
}
//使用
@RepetitionAnnotation("Hello")
@RepetitionAnnotation("World")
public void show(@RepetitionAnnotation String str) {}
文章来源:https://javajgs.com/archives/...
看到这里今天的分享就结束了,如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~
欢迎关注个人公众号 「JavaClub」,定期为你分享一些技术干货。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。