1、Java 8 新特性简介
主要内容
- Lambda表达式
- 函数式接口
- 方法引用与构造器引用
- Stream API
- 接口中的默认方法与静态方法
- 新时间日期API
- 其他新特性
特点
- 速度更快。对于底层的数据结构做了更新与改动,垃圾回收机制内存结构做了一定的改变。
- 代码更少(增加了新的语法Lambda表达式)
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常 Optional
其中最为核心的就是lambda表达式与Stream API
底层数据结构的更改 1、对于顶层数据结构最核心的体现,HashMap。 原来的HashMap数据存储的结构 数组 + 链表 HashMap底层实际上是一个16位的数组,数组的值为entry。存储对象时先通过hash算法计算出对象所对应的数组索引值,然后在通过equals方法比较该索引下是否已有相同的对象,有则覆盖原来的对象。没有则放在原有对象的前面,与原来的对象形成一个链表,这种情况我们称之为碰撞。碰撞会使我们存储的效率变低,我们需要重写hashCode与equals方法来尽量避免这种碰撞。当HashMap中元素存储的数量过大,会降低我们的存储的效率,HashMap为其提供加载因子。当元素到达现有Hash表的75%的时候,即会对其进行扩容。原来的元素就会进行重新运算,存储到扩容后的空间内,这样就减小了元素碰撞的几率。
Java 8以后HashMap数据存储的结构 数组 + 链表 + 红黑树 Java8以后在原来的存储结构下增加了红黑树的结构,我们知道在元素发生碰撞,即多个元素被存储到一个索引下时会时会形成链表。当链表元素的数量到8且整个HashMap存储的元素数量的大于64时,会将原来的链表转换为红黑树。红树。在添加时会将元素进行比较,并将较大的元素添加到元素的左边。这种结构增强了除添加以外的其它所有操作的效率。且在Hash表扩容时,原来的元素不在不需要经过复杂的排序。只需要找原来Hash表的总长度,加上它当前所在的索引位置。
2、Lambda表达式
Lambda是一个匿名函数,我们可以将Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到提升。 先看看以下示例来感受一下Java8的Lambda表达式与其他新特性的独特之处吧
public class TestLambda {
@Test
public void t1() {
Comparator<Integer> comparator = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
}
@Test
public void t2() {
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
}
List<Emp> empList = asList(
new Emp("张三", 18, 100.00),
new Emp("李四", 20, 200.00),
new Emp("王五", 24, 300.00),
new Emp("赵六", 14, 400.00)
);
public List<Emp> filterEmployee(List<Emp> list,MyPredicate<Emp> mp){
List<Emp> emps = new ArrayList<Emp>();
for (Emp emp : empList) {
if(mp.test(emp)){
emps.add(emp);
}
}
return emps;
}
@Test
public void t3() {
System.out.println("实现方案一:接口实现类");
List<Emp> emps = filterEmployee(empList,new FilterEmployeeByAge());
for (Emp emp : emps) {
System.out.println(emp);
}
System.out.println("实现方案二:匿名内部类");
List<Emp> emps1 = filterEmployee(empList, new MyPredicate<Emp>() {
@Override
public boolean test(Emp emp) {
return emp.getAge() >= 18;
}
});
for (Emp emp : emps1) {
System.out.println(emp);
}
System.out.println("实现方案三:lambda表达式");
List<Emp> emps2 = filterEmployee(empList, (e) -> e.getAge() >= 18);
emps2.forEach(System.out::println);
System.out.println("实现方案四:Stream API");
empList.stream()
.filter((e)->e.getAge()>=19)
.limit(2)
.forEach(System.out::println);
empList.stream()
.map(Emp::getName)
.forEach(System.out::println);
}
}
public interface MyPredicate<T> {
boolean test(T t);
}
public class FilterEmployeeByAge implements MyPredicate<Emp>{
@Override
public boolean test(Emp emp) {
return emp.getAge() >= 18;
}
}
以上示例是不是让你感受到Lambda表达式与Stream API的强大,想要继续向下了解,接下来讲解的是Lambda表达式的相关知识。
Lambda表达式的基础语法:Java8中引入了一个新的操作符"->"该操作符称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式分为两部分: 左侧:Lambda表达式的参数列表。 右侧:Lambda表达式中所需执行的功能,即Lambda体
() -> System.out.println("Hello Lambda");
(x) -> System.out.println(x);
x -> System.out.println(x);
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
Comparator<Integer> com = (x, y) -> return Integer.compare(x,y);
(Integer x,Integer y) -> Integer.compare(x,y);
Lambda函数式接口 Lambda表达式需要函数式接口的支持 函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。根据规范需要使用@FuncatinalInterface修饰函数式接口,该接口可以用于检查接口是否是函数式接口。
Java8 内置的四大核心函数式接口
Consumer<T>:消费型接口
void accept(T t);
Supplier<T>:供给型接口
T get();
Function<T>:函数型接口
R apply(T t);
Predicate<T>:断言型接口
boolean test(T t);
其他接口
方法引用与构造器引用 1、方法引用:若Lambda体中的内容已经有别的方法实现了,我们可以使用"方法引用"引用已有的方法。
Consumer<String> sup = System.out::println;
Comparator<Integer> sup2 = Integer::compare;
BiPredicate<String,String> b = String::equals;
方法引用的规则: (1)Lambda体中调用方法的参数列表与返回值类要与函数式接口中抽象方法的函数列表和返回值类型保持一致。 (2)若Lambda参数列表中的第一参数是实例方法的调用者,而第二参数是实例方法的参数时,可以使用ClassName::method。
2、构造器引用
Supplier<Emp> sup = Emp::new;
Function<Integer,String[]> fun = String[]::new;
注意:使用构造器引用时构造器参数列表要符合函数式接口抽象方法的参数列表保持一致。
3、Stream API
了解Stream Java8中两个最为重要的改变一个是Lambda表达式,一个就是Stream API(java.util.stream.*)了。 Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似与使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。 Stream流到底是什么,其实它是一种数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合是元素,流的作用就是对元素进行计算。
Stream流使用时注意事项: (1)Stream自己不会存储元素。 (2)Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。 (3)Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream使用步骤:创建(获取流) >>> 中间操作(处理流) >>> 终止操作(执行流) 创建Stream流 1.通过Collection 系列集合提供的stream()或parallelStream()
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream();
2.通过Arrays 中的静态方法stream()获取数组流
Emp[] emps = new Emp[10];
Stream<Emp> stream2 = Arrays.stream(emps);
3.通过Stream类中的静态方法 of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
4.创建无限流
Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);
stream4.limit(10).forEach(System.out::println);
Stream.generate(()->Math.random())
.forEach(System.out::println);
Stream的中间操作 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作是不会执行任何的处理的。而在终止操作时一次性处理全部,称为“惰性求值”。
筛选与切面操作
方法 | 描述 |
---|
filter(Predicate p) | 提供一个断言型接口实现,获取返回值为ture的元素 | distiinct() | 筛选,通过流所生成元素的hashCode()与equals()方法去除重复元素 | limit(long maxSize) | 截断流,使其元素不超过给定数量。 | skip(long n) | 跳过元素,返回一个扔掉的前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补 |
排序
方法 | 描述 |
---|
sorted() | 产生一个新流,其中按自然顺序排序 | sorted(Comparator comp | 产生一个新流,其中按比较器顺序排序 |
示例:
public void t2() {
int[] arr = {4,3,5,6};
Arrays.stream(arr)
.sorted()
.forEach(System.out::println);
List<Emp> empList = Arrays.asList(
new Emp("张三", 18, 100.00),
new Emp("李四", 20, 200.00),
new Emp("王五", 24, 500.00),
new Emp("王五", 24, 600.00),
new Emp("赵六", 14, 400.00)
);
Stream<Emp> sorted = empList.stream()
.sorted((e1, e2) -> {
if (e1.getAge().equals(e2.getAge())) {
return e1.getSalary().compareTo(e2.getSalary());
}
return e1.getAge().compareTo(e2.getAge());
});
sorted.forEach(System.out::println);
}
映射
方法 | 描述 |
---|
map(Funcation f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 | mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的DoubleStream。 | mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的IntStream。 | mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的LongStream。 | flatMap(Function f) | 接收一个函数作为参数,将流中的每一个值都转换为另一个流,并将所有流连接成一个流。 |
Stream的终止操作
查找与匹配
方法 | 描述 |
---|
allMatch | 检查是否匹配所有元素 | anyMatch | 检查是否至少匹配一个元素 | noneMatch | 检查是否没有匹配的元素 | findFirst | 返回第一个元素 | findAny | 返回当前流中的任意元素 | count | 返回流中元素的总个数 | max | 返回流中最大值 | min | 返回六中最小值 | forEach(Consumer c) | 内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代。相反,Stream API使用内部迭代——它帮你把迭代做了) |
示例:
@Test
public void t3() {
List<Emp> empList = Arrays.asList(
new Emp("张三", 18, 100.00,Status.FREE),
new Emp("李四", 20, 200.00,Status.BUSY),
new Emp("王五", 24, 500.00,Status.VOCATION),
new Emp("王五", 24, 600.00,Status.FREE),
new Emp("赵六", 14, 400.00,Status.BUSY)
);
boolean b = empList.stream()
.noneMatch(e -> e.getStatus().equals(Status.VOCATION));
System.out.println(b);
}
归纳(汇总)
方法 | 描述 |
---|
reduce(T iden,BinaryOperator b) | 可将流中的元素反复结合,得到一个值,返回T | reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回Optional<T> |
备注:map与reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜素而出名。
示例:
@Test
public void t4() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
Integer sum = list.stream()
.reduce(0, Integer::sum);
System.out.println(sum);
List<Emp> empList = Arrays.asList(
new Emp("张三", 18, 100.00,Status.FREE),
new Emp("李四", 20, 200.00,Status.BUSY),
new Emp("王五", 24, 500.00,Status.VOCATION),
new Emp("王五", 24, 600.00,Status.FREE),
new Emp("赵六", 14, 400.00,Status.BUSY)
);
Optional<Double> op = empList.stream()
.map(Emp::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
}
收集
方法 | 描述 |
---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用类提供了很多静态方法,可以方便的创建常见收集器实例,具体方法如下表。
示例:
List<Emp> empList = Arrays.asList(
new Emp("张三", 18, 100.00,Status.FREE),
new Emp("李四", 36, 200.00,Status.BUSY),
new Emp("王五", 24, 500.00,Status.VOCATION),
new Emp("王五", 24, 600.00,Status.FREE),
new Emp("赵六", 14, 400.00,Status.BUSY)
);
@Test
public void t5() {
List<String> list1 = empList.stream()
.map(Emp::getName)
.collect(Collectors.toList());
System.out.println(list1);
HashSet<String> list2 = empList.stream()
.map(Emp::getName)
.collect(Collectors.toCollection(HashSet::new));
System.out.println(list2);
Double avg = empList.stream()
.collect(Collectors.averagingDouble(Emp::getSalary));
Map<Status, List<Emp>> map = empList.stream()
.collect(Collectors.groupingBy(Emp::getStatus));
System.out.println(map.get(Status.BUSY));
Map<Status, Map<String, List<Emp>>> statusMapMap = empList.stream()
.collect(Collectors.groupingBy(Emp::getStatus, Collectors.groupingBy(
e -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() <= 50) {
return "中年";
}
return "老年";
}
)));
Map<String, List<Emp>> stringListMap = statusMapMap.get(Status.BUSY);
List<Emp> qn = stringListMap.get("青年");
List<Emp> zn = stringListMap.get("中年");
System.out.println("请年:" + qn);
System.out.println("中年:" + zn);
Map<Boolean, List<Emp>> partitionBySalary = empList.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 300));
System.out.println("条件为真的:" + partitionBySalary.get(true));;
System.out.println("条件为假的:" + partitionBySalary.get(false));;
DoubleSummaryStatistics doubleSummaryStatistics = empList.stream()
.collect(Collectors.summarizingDouble(Emp::getSalary));
System.out.println(doubleSummaryStatistics.getMax());
System.out.println(doubleSummaryStatistics.getCount());
System.out.println(doubleSummaryStatistics.getAverage());
String str = empList.stream()
.map(Emp::getName)
.collect(Collectors.joining(","));
System.out.println(str);
}
|