【Java 8 新特性】最全Java 8新特性 详解
Java 8 是 Java 语言开发的一个主要版本,oracle公司于2014年3月发布,是自Java 5 以来最具革命性的版本,Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性
一、注解
重复注解
在Java 8中使用 @Repeatable 注解定义重复注解
在某些情况下,我们希望将相同的注释应用于声明或类型使用,从 Java SE 8 版本开始,重复注释能够做到这一点。
如:正在编写代码以使用计时器服务,该服务使您能够在给定时间或按特定时间表运行方法,类似于 UNIX cron服务。现在您想设置一个计时器来在每月的最后一天和每个星期五晚上 11:00运行方法doPeriodicCleanup@Schedule 。要设置计时器运行,请创建一个注释并将其两次应用于doPeriodicCleanup方法。第一次使用指定该月的最后一天,第二次指定周五晚上 11 点
如以下代码示例所示:
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }
类型注解
允许定义枚举时使用@Target(ElementType.TYPE_USE)修饰,这种注解被称为Type Annotation(类型注解)
Java 8为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE
两个枚举值
●ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中
●ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中
@Target({ ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation { }
二、Stream API
Stream API 把真正的函数式编程风格引入到Java中
- Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
- 操作步骤:
①创建 Stream:一个数据源(如:集合、数组),获取一个流
②中间操作:一个中间操作链,对数据源的数据进行处理
③终止操作:一旦执行终止操作,就执行中间操作链并产生结果,之后不会再被使用
①创建 Stream
- 创建 Stream方式一:通过集合
提供了两个获取流方法:
//返回一个顺序流
default Stream<E> stream()
//返回一个并行流
default Stream<E> parallelStream()
- 创建 Stream方式二:通过数组
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)
- 创建 Stream方式三:通过Stream的of()
调用Stream类静态方法of()
, 通过显示值创建一个流
//返回一个流
public static<T> Stream<T> of(T... values)
- 创建 Stream方式四:创建无限流
使用静态方法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)
创建无限流举例:
public void test4() {
// 迭代
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);
// 生成
Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);
}
②中间操作
多个中间操作可以连接起来形成一个流水线
- 筛选与切片
filter(Predicate p)
接收 Lambda , 从流中排除某些元素
distinct()
筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)
截断流,使其元素不超过给定数量
skip(long n)
跳过元素,返回一个扔掉了前 n 个元素的流 - 映射
- 排序
sorted()
产生一个新流,其中按自然顺序排序
sorted(Comparator com)
产生一个新流,其中按比较器顺序排序
③终止操作
终端操作会从流的流水线生成结果
- 匹配与查找
- 归约
- 收集
collect(Collector c)
将流转换为其他形式,接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
?Collectors
三、函数式接口
Functional
只包含一个抽象方法的接口,称为函数式接口
- 可以在一个接口上使用 @FunctionalInterface 注解,可以检查它是否是一个函数式接口
- 在
java.util.function
包下定义了Java 8 的丰富的函数式接口 - Java不但可以支持OOP,还可以支持OOF(面向函数编程)
- 在Java8中,Lambda表达式就是一个函数式接口的实例,这就是Lambda表达式和函数式接口的关系;也是说只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示
- 举例
?自定义函数式接口
@FunctionalInterface . public interface MyNumber{
public double getValue();
}
//函数式接口中使用泛型:
@FunctionalInterface
public interface MyFun<T>{
public T getValue(T t);
}
作为参数传递 Lambda 表达式
为了将 Lambda 表达式作为参数传递,接收Lambda表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型
四、Optional类
最大化减少空指针异常,解决空指针异常
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在
- 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接口实现提供的异常
五、引用
?方法引用
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
- 方法引用就是Lambda表达式,也就是函数式接口的一个实例
- 要求:
实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致 - 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来
- 三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名 - 举例
?构造器引用
- 格式:
ClassName::new
- 要求:构造器参数列表与接口中抽象方法的参数列表一致且方法的返回值即为构造器对应类的对象
- 举例
?数组引用
格式: type[] :: new
六、接口的默认方法
扩展方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字
interface marla {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
marla marla = new marla() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
marla.calculate(100); // 100.0
marla.sqrt(16); // 4.0
marla接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用
七、新的时间日期API
请看传送门 >>> 【07章Java常用类】盘点 Java常用类?
八、Lambda表达式
新的语法,代码更少
Lambda 是匿名函数,Lambda 表达式可以理解为是一段可传递的代码,将代码像数据一样进行传递
- 语法
引入一种新的语法元素和操作符 “->” ,该操作符被称为 Lambda 操作符或箭头操作符
将 Lambda 分为两个部分:
左侧:指定了 Lambda 表达式需要的参数列表
右侧:指定了 Lambda 体,是抽象方法的实现逻辑 - 语法格式
语法格式一:无参,无返回值
Runnable r = () -> { System.out.println("I am Lambda!");};
语法格式二:Lambda 需要一个参数,但是没有返回值
Consumer<String> con = (String str) -> { System.out.println(str);};
语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con = (str) -> { System.out.println(str);};
语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
Consumer<String> con = str -> { System.out.println(str);};
语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> com = (x,y) -> { System.out.println("实现函数式接口方法!");
Return Integer,compare(x,y);
};
语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
Comparator<Integer> com = (x,y) -> Interger.compare(x,y);
- 代码举例:
Runnable r1 = new Runnable(){
@override
public void run(){
System.out.println("Hello Lambda!");
}
}
匿名类到 Lambda 后
Runnable r = () -> { System.out.println("Hello Lambda!");};
九、类型推断
Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖上下文环境,是由编译器推断出来的,这就是所谓的 “类型推断”
十、新编译工具
对开发者最有用的工具
Nashorn引擎:jjs
jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行
举例:
一个 un.js 文件
function f() {
return 1;
};
print( f() + 3 );
可以在命令行中执行这个命令:jjs un.js,控制台输出结果是:4
官方文档传送门 >>> jjs官方文档?
类依赖分析器:jdeps
jdeps可以展示包层级和类层级的Java类依赖关系,以.class文件、目录或者jar文件为输入,然后把依赖关系输出到控制台
官方文档传送门 >>> jdeps官方文档?
十一、JDK 更新
并发
基于新增的lambda表达式和steam特性,Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操
Arrays
IO/NIO的改进
- 在传统的IO输入输出中,如果我们从流中去读数据,而数据源中没有数据时,程序就会阻塞该线程,而NIO是非阻塞式IO,NIO采用了内存映射的方式具体通过Channel对象来体现,Channel通道是模拟的传统IO,可以通过它读取和写入数据。它不同之处在于提供了map()方法,可以直接将一块数据映射到内存
标准访问:
内存映射:
- NIO也是面向缓冲的
Buffer是一个数组容器,发送到Channel的对象都必须先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中
Buffer与Channel之间的交互 - NIO中使用单个线程来处理多个Channel,一个线程可以管理多个网络连接。具体的实现是Selector
Selector选择器的工作原理
:首先向Selector中注册Channel,然后Selector就可以检测到一个或多个NIO通道,并且能够了解每个通道是否已经做好准备进行读写
?总结:这Java 8新特性很多,学起来也学不太明白,写写笔记到处请教学习