- What’s New in JDK 8
- JDK8允许内部类使用的外部变量可以不用声明其为
final
,但实际上还是final
类型。
一、改进的类型推断
泛型的最大优点是提供了程序的类型安全同时可以向后兼容,但尴尬的是每次定义时都要写明泛型的类型,这样在显示上有些冗长,最主要是很多程序员不熟悉泛型,因此很多时候不能够给出正确的类型参数,现在通过编译器自动推断泛型的参数类型,能够减少这样的情况,并提高代码可读性。
Java 8 里面泛型的目标类型推断主要有 2 个方面:
- 支持通过方法上下文推断泛型目标类型
- 支持在方法调用链路当中,泛型类型推断传递到最后一个方法
Java SE 7在创建泛型实例时的类型推断是有限制的;只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。下面的代码在 JDK1.7下无法通过:
List<String> list = new ArrayList<>();
list.add("A");
list.addAll(Arrays.asList());
但这段代码在 JDK 1.8 中可以正常执行,若要在JDK 1.7中要想执行则必须修正如下:
List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.<String>asList());
addAll
的目标类型是Collection<?ExtendedString>
,Arrays.asList
返回一个List<T>
实例。在本例中,JavaSE 8 编译器可以推断类型变量T
的值是字符串。编译器从目标类型Collection<?ExtensionString>
推断这一点。
官网的例子:
class List<E> {
static <Z> List<Z> nil() { ... };
static <Z> List<Z> cons(Z head, List<Z> tail) { ... };
E head() { ... }
}
在调用上面方法的时候可以这样写:
//通过方法赋值的目标参数来自动推断泛型的类型
List<String> l = List.nil();
//而不是显示的指定类型
//List<String> l = List.<String>nil();
//通过前面方法参数类型推断泛型的类型
List.cons(42, List.nil());
//而不是显示的指定类型
//List.cons(42, List.<Integer>nil());
二、Lambda表达式
三、接口中的默认方法静态方法
默认方法
-
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用
default
关键字即可,这个特征又叫做扩展方法。该非抽象的方法实现可以在子类中直接调用,子类只需要实现所有的抽象方法即可。
public interface Formula {
public double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
测试方法:
public class Ceshi {
public static void main(String[] args) {
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100));// 100.0
System.out.println(formula.sqrt(16));// 4.0
}
}
注意:由于 Java 的接口是多继承的 , 就可能存在一个类实现多个接口 , 如果多个接口中的多个默认方法不冲突(方法签名不冲突) , 则互不影响 , 若存在方法签名一样的默认方法 , 则要求子类必须显式实现并重写默认方法 。
静态方法
静态方法比较简单,使用static
修饰方法即可。
接口中的默认方法和静态方法的区别在于:
- 静态方法只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用;
default
方法,只能通过接口实现类的对象来调用。
不错的博文可查看:《Java 8 新特性:接口的静态方法和默认方法》
四、Stream
详见:
注意:这部分还有一个重要的地方那就是并行处理,后期补充,详细的可以阅读《Java 8 实战_高清中文版》
五、重复注解和类型注解
六、方法参数反射
方法参数反射【Method Parameter Reflection】【待补充】
七、Optional
Java 开发中最常见的 bug 就是空值异常【NullPointerException】。在Java 8之前,Google Guava引入了Optionals 类来解决 NullPointerException,从而避免源码被各种 null 检查污染,以便开发者写出更加整洁的代码。Java 8也将 Optional 加入了官方库。
null 并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题,原因是当这个变量被传递到系统中的另一个部分后,你将无法获知这个 null 变量最初的赋值到底是什么类型。
Optionals
类是一个封装类,当其封装的类的引用变量存在时,Optional
类知识对类简单封装,而引用变量不存在时,缺失的值会被建模成一个空的Optional
对象,有方法Optional.empty()
返回。
我们在声明变量时使用的是Optional<Car>
类型,而不是 Car
类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。与此相反,使用Car
这样的类型,可能将变量赋值为null
,这意味着你需要独立面对这些,你只能依赖你对业务模型的理解,判断一个null
是否属于该变量的有效范畴。
强调:引入Optional
类的意图并非要消除每一个null
引用。与此相反,它的目标是帮助我们更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个Optional
的值。这种强制会让你更积极地将变量从Optional
中解包出来,直面缺失的变量值
注意:不推荐使用基础类型的Optional
对象(OptionalInt
、OptionalLong
和OptionalDouble
),对于Stream
对象包含了大量元素,出于性能的考量,使用基础类型是不错的选择,但对Optional
对象而言,这个理由就不成立了,因为Optional
对象最多包含一个值。而且基础类型的Optional
不支持map
、flatMap
以及filter
方法。
创建Optional对象
Optional
类为我们提供了三种静态方法来创建Optional
对象:
empty()
返回一个空的Optional
实例。of(T value)
返回具有指定的当前非空值的Optional
。ofNullable(T value)
如果指定值为非空则返回一个Optional
,否则则返回一个空的Optional
。
使用map从Optional对象中提取和转换中值
//JDK源码
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper); // 检查参数是否为空
if (!isPresent()) // 如果不存在则返回一个空的Optional实例
return empty();
else {
//如果存在则进行转换并将结果封装到Optional实例中
return Optional.ofNullable(mapper.apply(value));
}
}
相比之下,Optional
的map
方法 和 Stream
中的map
方法相差无几。流中的map
操作会将提供的函数应用于流的每个元素。我们可以把Optional
对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional
包含一个值,那函数就将该值作为参数传递给map
,对该值进行转换;如果Optional
为空,就什么也不做。
注意:Optional
的map
方法将其中包含的元素进行转换,然后再包装到Optional
中。
// 省略get和set方法
public class Person {
private Optional<Car> car;
}
public class Car {
private Optional<Insurance> insurance;
}
public class Insurance {
private String name;
}
// 然后调用如下
Optional<Person> optPerson = Optional.of(person);
optPerson.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName);
/*
* 没有使用Optional之前我们的代码可书写为:
* person.getCar().getInsurance().getName();
*/
然而上面的调用在编译器就无法通过,原因map(Person::getCar)
返回的是Optional<Optional<Car>>
类型的对象。因此,他对getInsurance
的调用是非法的。明白了原因了,那么我们就可以想方法解决:
optPerson.map(Person::getCar).get()
.map(Car::getInsurance).get()
.map(Insurance::getName);
在流中有一个流的扁平化操作,同样Optional
也提供了类似的方法。
使用flatMap链接Optional对象
使用流时,flatMap
方法接受一个函数作为参数,这个函数的返回值是另一个流。这个方法应用到流中的每一个元素,最终形成一个新的流的流。但是flagMap
会用流的内容替换每个新生成的流。换句话说,由方法生成的各个流会被合并或者扁平化为一个单一的流。
上面的图中,传递给流的flatMap
方法会将每个正方形转换为另一个流中的两个三角形。那么map
操作的结果就包含有三个新的流,每一个流包含两个三角形,但flatMap
方法会将这种两层的流合并为一个包含留个三角形的单一流。类似地,传递给optional
的flatMap
方法的函数会将原始包含正方形的optional
对象转换为包含三角形的optional
对象。如果将该方法传递个map
方法,结果会是一个Optional
对象,而这个Optional
对象中包含了三角形;但flatMap
方法会将这种两层的Optional
对象转换为包含三角形的单一Optional
对象。
// JDK源码
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
其实我们查看源码不难看出Map
方法是对其封装的元素进行转换然后再将结果封装到Optional
中,而flatMap
方法是对其封装的元素进行转换然后直接返回而已!
因此我们可以将上面的代码优化如下:
optPerson.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("UnKnown");
使用filter剔除特定的值
// JDK源码
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
其实逻辑不难理解,如果为空则直接返回,否则判断封装的元素是否匹配谓词,如果匹配返回否则返回一个空的Optional
。
使用Optional封装null和异常
现存的Java API几乎都是返回一个null来表示需要的值得缺失,而我们可能希望返回一个Optional对象。我们无法修改方法的签名,但我们很容易用Optional
对这些方法的返回值进行封装。
我们希望安全地对潜在为null的对象进行转换,将其替换为Optional对象时,可采取如下方式:
Optional<Object> value = Optional.ofNullable(map.get("key"));
Java API中无法返回某个值的时候,除了返回null,还可能抛出一个异常。这时我们可以用空的Optional对象对其进行建模,如下:
public Optional<Integer> stringToInt(String s){
try {
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
八、新的日期和时间的API
Java 8 引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题,java.util.Date 和后来的 java.util.Calendar 一直没有解决这个问题(甚至令开发者更加迷茫)。
因为上面这些原因,诞生了第三方库Joda-Time,可以替代 Java 的时间管理API。Java 8 中新的时间和日期管理API深受 Joda-Time 影响,并吸收了很多 Joda-Time 的精华。新的 java.time
包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。
《暂时不打算写,后期会追加一个详细的博文》—-》》》》》》》》》
重要的类:
- LocalTime:提供简单的时间,并不包含年月日信息。
- LocalDate:提供简单的日期,并不包含当天的时间信息。
- LocalDateTime:LocalTime和LocalDate的合体,同时表明日期和时间。
- Instant:以Unix元年时间开始所经历的秒数进行计算。
- 时间间隔
- Duration:以秒和纳秒为单位
- Period:以年月日为时间单位
- TemporalAdjuster接口:日期调整
- TemporalAdjusters类的为我们提供了大量预定义的TemporalAdjuster。
重要知识点:
java.time.temporal
接口定义了如何读取和操纵为时间建模的对象【temporal object】的值,如日期、时间、偏移量或它们的某种组合。- LocalDateTime和Instant视为不同的目的而设计的,前者是为了便于人阅读使用,后者是为了便于及其处理。
九、紧凑API配置文件
JDK 8新增的此项功能将API库的子集组织成所谓的紧凑配置文件。他们分别叫做compact1、compact2 和 compact3。每个配置文件包含库的一个子集。此外,compact2包含整个compact1,compact3包含整个compact2。因此,每个配置文件以前一个配置文件为基础。紧凑配置文件的优势在于,用不到完整库的应用程序不需要下载整个库。使用紧凑配置文件减小了库的大小,从而让一些Java应用程序能够在无法支持完整Java API的设备上运行。使用紧凑配置文件还降低了加载程序所需的事件。Java API文档说明了每个API元素所属的配置文件。
编译程序时,使用-profile选项可以确定程序是否使用了紧凑配置文件中的定义的API。具体形式如下所示:
javac -profile profileName programName
其中,profileName指定了配置文件,必须是compact1、compact2 或 compact3。
参考资料: