java8的新特性
本文主要针对个人在工作中常用的java8新特性进行总结,希望共同进步,共同分享!
1、函数式接口@FunctionalInterface
简单来说,函数式接口是只包含一个方法的接口,像这样的接口可以被隐式转换为成为函数式接口。为保证方法数量不多不少,java8提供了一个专用注解@FunctionalInterface标记接口是函数接口,这样,当接口中声明的抽象方法多于或少于一个时就会报错。
java8通用的几类函数接口
类名 | 方法类型 | 说明 |
---|---|---|
Consumer | void accept(T t); | 接受单个输入参数,无返回值 |
Function<T, R> | R apply(T t); | 将此函数应用于给定参数 |
Predicate | boolean test(T t); | 对给你定参数进行断言 |
Supplier | T get(); | 生产对象 |
Runnable | public abstract void run(); |
说明:后面讲解的lambda表达式答题基于以上四种接口类型进行扩展
2、lambda表达式
只要听说过java8的新特性,大家都会想到lambda表达式吧,最主观的感受是,这玩意儿怎么还用到了箭头符号(->)来表示呢?那它到底是什么呢?要怎么使用呢?让我们一起看看
2.1、lambda表达式是什么
- 1)lambda表达式本质上是一个匿名方法。
- 2)Lambda允许把函数作为一个方法的参数(函数作为参数传递进 方法中)或者把代码看成数据。使用 Lambda 表达式可以使代码变 的更加简洁紧凑。
- 3)在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。
2.2、lambda表达式语法
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
2.2、示例
public class TestLambda {
public static void runThreadUseLambda() {
//Runnable是一个函数接口,只包含了有个无参数的,返回void的run方法;
//所以lambda表达式左边没有参数,右边也没有return,只是单纯的打印一句话
new Thread(() ->System.out.println("lambda实现的线程")).start();
}
public static void runThreadUseInnerClass() {
//这种方式就不多讲了,以前旧版本比较常见的做法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("内部类实现的线程");
}
}).start();
}
public static void main(String[] args) {
TestLambda.runThreadUseLambda();
TestLambda.runThreadUseInnerClass();
}
}
2、接口的默认方法和静态方法
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。
为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。
然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
2.1、默认方法
1)语法格式(以Functioin的默认函数为例):
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
2)多个默认方法
一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:
- 创建自己的默认方法,来覆盖重写接口的默认方法
- 使用 super 来调用指定接口的默认方法
2.2、静态方法
Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:
3、新增方法引用格式
①方法引用通过方法的名字来指向一个方法。
②方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
③方法引用使用一对冒号 :: 。
3.1、示例:
package com.runoob.main;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
class Car {
//Supplier是jdk1.8的接口,这里和lamda一起使用了
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
构造器引用
:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
静态方法引用
:它的语法是Class::static_method,实例如下:
cars.forEach( Car::collide );
特定类的任意对象的方法引用
:它的语法是Class::method实例如下:
cars.forEach( Car::repair );
特定对象的方法引用
:它的语法是instance::method实例如下:
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
4、新增Stream类
4.1、什么是Stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素
是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源:
流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。聚合操作:
类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
4.2、Stream两个基础的特征
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
4.3、使用流的基本操作
- 创建Stream
- 转换Stream,每次转换原有Stream对象不改变,返回一个新的 Stream对象(可以有多次转换)
- 对Stream进行聚合操作,获取想要的结果
4.3.1、 流的生成方法
1.Collection接口的stream()或parallelStream()方法
2.静态的Stream.of()、Stream.empty()方法
3.Arrays.stream(array, from, to)
4.静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
5.静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
6.Pattern接口的splitAsStream(input)方法
7.静态的Files.lines(path)、Files.lines(path, charSet)方法
8.静态的Stream.concat()方法将两个流连接起来
4.3.2、 流的Intermediate方法(中间操作)
1.filter(Predicate) :将结果为false的元素过滤掉
2.map(fun) :转换元素的值,可以用方法引元或者lambda表达式
3.flatMap(fun) :若元素是流,将流摊平为正常元素,再进行元素转换
4.limit(n) :保留前n个元素
5.skip(n) :跳过前n个元素
6.distinct() :剔除重复元素
7.sorted() :将Comparable元素的流排序
8.sorted(Comparator) :将流元素按Comparator排序
9.peek(fun) :流不变,但会把每个元素传入fun执行,可以用作调试
4.3.3、 流的Terminal方法(终结操作)
(1)约简操作
1.reduce(fun) :从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数
2.reduce(a, fun) :a为幺元值,作为累积器的起点
3.reduce(a, fun1, fun2) :与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并
(2)收集操作
1.iterator():
2.forEach(fun):
3.forEachOrdered(fun) :可以应用在并行流上以保持元素顺序
4.toArray():
5.toArray(T[] :: new) :返回正确的元素类型
6.collect(Collector):
7.collect(fun1, fun2, fun3) :fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来
(3)查找与收集操作
1.max(Comparator):返回流中最大值
2.min(Comparator):返回流中最小值
3.count():返回流中元素个数
4.findFirst() :返回第一个元素
5.findAny() :返回任意元素
6.anyMatch(Predicate) :任意元素匹配时返回true
7.allMatch(Predicate) :所有元素匹配时返回true
8.noneMatch(Predicate) :没有元素匹配时返回true
4.4、示例
- 集合的List转化为Map(一)
public class TestLambda {
public static void main(String args[]) {
Student student1 = Student.create(Student::new);
student1.setId("11");
student1.setName("haha");
Student student2 = new Student("22", "test");
Student student3 = new Student("11", "test333");
List<Student> students = Arrays.asList(student1, student2,student3);
Map<String, List<Student>> classes = students.stream().collect(Collectors.toMap(Student::getId,
p->{
List<Student> list = new ArrayList<>();
list.add(p);
return list; },
(List<Student> s1, List<Student> s2)->{
s1.addAll(s2);
return s1;}
));
System.out.println("classes==" + classes);
}
}
- 集合的List转化为Map(二)-
推荐
public static void main(String args[]) {
Student student1 = Student.create(Student::new);
student1.setId("11");
student1.setName("haha");
Student student2 = new Student("22", "test");
Student student3 = new Student("11", "test333");
List<Student> students = Arrays.asList(student1, student2,student3);
//使用groupBy进行List转map更简洁
Map<String, List<Student>> classes1 =students.stream().collect(Collectors.groupingBy(Student::getId));
System.out.println("classes1==" + classes1);
}
公共代码:
class Student{
private String id;
private String name;
public Student(){}
public static Student create(final Supplier<Student> supplier) {
return supplier.get();
}
public Student(String id, String name){
this.id=id;
this.name=name;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
class classNo {
private List<Student> students;
private String className;
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
5、引入Optional
Optional类实际上是个容器,它可以保存类型T的值,或者仅仅保存null。Optional 类的引入很好的解决空指针异常。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
TIP:尽量避免在程序中直接调用Optional对象的get()和isPresent()方法,避免使用Optional类型声明实体类的属性。
- optional常用的方法
(1)Optional.of(T t) : 创建一个 Optional 实例
(2)Optional.empty() : 创建一个空的 Optional 实例
(3)Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
(4)isPresent() : 判断是否包含值 orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
(5)orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
(6)map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
(7)flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
6、新的日期API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
1. 在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
- 1)非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 2)设计很差 −Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
- 3)时区处理麻烦 −日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
2. Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
- Local(本地) − 简化了日期时间的处理,没有时区的问题。
- Zoned(时区) − 通过制定的时区处理日期时间。
6.1、本地化时间API
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testLocalDateTime();
}
public void testLocalDateTime(){
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
}
}
6.2、本地化时间API
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testZonedDateTime();
}
public void testZonedDateTime(){
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
}
}