Java-Web学习-Java基础-Java 8 新特性(局部)

Java 8新特性

我在本篇中仅介绍作为初学者所需要知道的Java8的新特性,而对新的编译工具、引擎等作忽略处理,此外还有些功能上新特性没写进来。

默认方法

默认方法引进了新的关键字default,其能够将被修饰的方法声明为默认方法。默认方法是用在接口中的,用于规范一个方法在没有被具体类实现的情况下的行为。子类可以:

  • Not Override:直接获得默认实现
  • Override:类似普通覆写规则
  • Override and redeclared abstract:重新声明为抽象,则恢复原抽象方法的编程规则

默认方法出现的背景是,一旦一个具有多个子类的接口需要修改(实际上为添加)方法,所有的实现类都需要进行修改,这种过高的耦合性为实际工作带来了繁琐性。默认方法的引入使得我们只需要修改接口本身就能直接为所有没有实现该方法的子类修改行为。

默认方法也能被继承,则引出一个问题:接口的多继承性带来的默认方法的歧义性。当出现该问题,编译器将报错而阻止程序运行,我们使用父类super的方式来手动指定需要的接口默认方法:

interface InterA{
	default void fun(){}
}
interface InterB{
	default void fun(){}
}
class Class implement InterA, InterB{
	public void main(){
		InterA.super.fun(); // invoke InterA.fun()
		InterB.super.fun(); // invoke InterB.fun()
	}
}

Lambda表达式

Lambbda表达式,又被称为闭包,是Java 8 中最为重要的新特性,其使得函数声明本身能够作为一个参数传递进入方法中,使得代码更为紧凑。在Java 8之前,需要将方法包装到Callable接口中,然后通过传递Callable对象实现函数的传递,或者使用内部匿名方法类。当然,其最主要的目的是为了替换之前广泛使用的内部匿名类。

Java有一颗函数式编程的心,但是只能是尽可能去往上面凑,Lambda表达式就是一个伟大的尝试,将函数提升到和对象同等的层次上来。

Lambda表达式的语法格式主要有两种:

(parameters) -> expression // 用于计算表达式
(parameters) -> {statement;} // 用于执行某些操作

其有四个可选的格式改动:

  1. 参数类型不一定要声明,编译器能够自动识别
  2. 当传入参数为一个时,无需使用圆括号
  3. 若statement只有一句,无需使用大括号
  4. 若statement只有一句,则该条语句的返回值自动作为Lambda表达式的返回值;否则需要用return显式指定返回值。

Lambda拥有很多奇怪的特性需要牢记:

  • 理论上,Lambda除了参数列表传入的参数外,只能引用外层标记为final的局部变量
  • 而实际上,Lambda还可以引用外界任意的局部变量,但是要求其不能在Lambda表达式之后的语句所修改,这可以看做该外层普通局部变量在被Lambda表达式引用后具备了final的性质
  • Lambda表达式中的参数列表以及内部声明的局部变量,不能与外层局部变量同名

此时我们应当继续深入,Lambda表达式究竟是什么?我们知道,Java中万物皆对象,那么Lambda表达式是什么对象呢?

实际上,Lambda表达式是一个“函数式接口”类型的对象,这是Java 8中引入的新概念,指一个只有一个显式声明的抽象方法的接口,则该类型自动被认为是一个“函数式接口”,其使用@FunctionalInterface进行标注。注意,由于“函数式接口”实际上也是Object的一员,所以在计算其显式声明的抽象方法的数量时,忽略覆写Object类的方法

“函数式接口”包括Runnable、Comparator等多种类型,你可以通过一个Lambda表达式直接创建转型为一个“函数式接口”对象,这种转型方式很大程度上解决了替代内部匿名类的问题:

Runnable r = (Runnable)()->{System.print(1);};

当然,如果你自己写了一个满足“函数式接口”的类型,也可以像这样进行转型。但是注意,Lambda表达式本身并不会被视作一个Object对象,这意味着下面这种写法是意义不明的:

Object o = (a,b)-> {a+b;};

上文讲到,Lambda表达式表示一个符合“函数式接口”规则的接口的唯一方法的匿名描述符,而方法引用(Method referrence)就是Java 8 在这个概念上的进一步拓展,其目的在于使用描述符来唯一对应一个函数或方法

静态方法:
(a,b)->Interger.compare(a,b) 等价于 Integer::compare
实例方法:
persons.forEach(person->person.eat()) 等价于 persons.forEach(Person::eat())
构造器:
strings.map(s->new Integer(s)) 等价于 strings.map(Integer::new)

可以看出,方法引用的实质是:利用类名::方法名的描述符方式,来唯一指定一个方法,而我们可以通过这个描述符去找到这个函数的具体声明,接着就知道它的参数、实现声明和返回值,由此就能用一个Lambda表达式来描述它。这就是方法引用和Lambda函数之间互相映射的关系了。

流操作 Stream API 与 批处理

集合类是Java标准库的重要组成部分,而以往面向集合类的批处理操作是不完备的。以往我们需要对集合类进行操作,比如说遍历,是需要自己来写遍历逻辑的,比如说用迭代器不停next(),这也就是说Java自身对集合类本身的“操作”是不完备的。Java 8 中引入批处理的概念,就是希望从Java本身来对操作逻辑进行完善,并希望藉此提高效率,比如forEach()方法。

另外,在批处理的基础上,Java 8还引入了Stream流,其关键特点在于一个流在经过其定义的一系列操作后仍然返回另一个流,使得不同操作得以在一条语句中实现,直观且简洁。

流除了提高了代码的整洁性以外,还极大地提高了效率,这是由Java 8新定义的lazy-eager规则所决定的。lazy和eager分别是Java针对不同流操作进行的分类,前者通常是面向单个元素的操作(map/filter/distinct),而后者通常是涉及到整个流本身的操作(forEach/reduce/collect),前者在遇到后者之前不会执行。

以下面这条语句为例:

List<Integer> I = Lists.newArrayList{1,22,333,4444,55555,666666};
List<String> = I.stream().map(e->e.toString())
						.filter(e->e.size()<6)
						.collect(Coollectors.toList());

其中map和filter是lazy的,而collect是eager的,这意味着,直到一个元素在遇到collect时才会被执行map和filter操作,这使得整条语句虽然看起来进行了三个操作,但是每个元素会被单独经历map和filter过程,而没有通过filter的元素将无法到达collect,这就像极了管道运输。

新的Date time API

JAva 8 之前的日期时间API存在诸多问题:非线程安全、同时存在两个类表示日期且拥有同样名称、时区处理麻烦。

新的Java Date time API被定义在java.time下,涵盖了所有日期有关的操作,同时分为Local和Zoned两种,分别处理本地时间和指定时区的问题。新的Java 日期/时间类都是不可变对象,解决了线程安全问题。

Java 8针对日期操作还提供了TemporalAdjuster类来静态提供复杂的日期操作函数,其具体使用中需要搭配Date.with()方法使用:

LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); // 返回date1之后的时间最近的星期天(包括自己)

DateTime类还定义了用于格式化操作,可以进行字符串和日期对象的相互转化:

String strDate = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 指定特定形式进行转换

同时还定义了三个类Instant(时间戳)、During(时间段)、Period(日期段)三个类。Instant可以表示某个特定时间点(支持精确到纳秒),During则表示两个Instant之间的时间差(同样支持纳秒),Period则面向Date进行时间差的计算(只支持到日)。

Optional类

Optional类是为了应对臭名昭著的NullPointerException(欧耶~)。在应对NPE时,程序员需要大量的null值检测,来避免将一个null值当做了有价值的对象进行了处理。

Optional类就是Java 8引入的、描述一个可能存在也可能不存在的对象的抽象,其是一个泛型类。

Optional<String> op = Optional.ofNullable(HashMap.get(1));if(Optional.isEmpty()){    throw new Exception("empty");}

你可能会问,我使用Optional.isEmpty()判空和使用 == null判空有什么区别吗?首先Optional使用函数的方式来处理判空,符合了函数化编程的思想,其次Optional不仅只是判空,而是封装了更多的处理逻辑和操作在Optional的接口内。

在此我抛砖引玉:若是需要学习具体的Optinal类使用方法,在我看过的文章中Java 8 Optional 良心指南,建议收藏写的最为深入浅出的,感觉再怎么写都无法超越了。

Optional和流操作密不可分,其能够支持非常优美的判空逻辑,而且在效率上不输传统方法,可以参考Java8 Optional优雅空值判断中的样例,其中虽然样例少,但是非常凸显了流操作和判空逻辑结合的思想。

posted @ 2021-05-19 09:44  neumy  阅读(92)  评论(0编辑  收藏  举报