- Java8新特性你知道多少?
- Java8新特性你用过多少?
- Java8新特性你理解多少?
Java8所有的新特性
Lambda表达式、 函数式接口、接口默认方法、接口静态方法、扩展注解、重复注解、Optional、Stream、时间/日期API、方法引用、 参数名字保留在字节码中、并行数组、CompletableFuture
Lambda表达式
在JDK8之前,一个方法能接受的参数都是变量,例如:object.method(Object o)
那么,如果需要传入一个动作呢?比如回调。 那么你可能会想到匿名内部类。 例如: 匿名内部类是需要依赖接口的,所以需要先定义个接口
@FunctionalInterfacepublic interface PersonCallback { void callback(Person person);}复制代码
Person类:
public class Person { private int id; private String name; public Person(int id, String name) { this.id = id; this.name = name; } // 创建一个Person后,进行回调 public static void create(Integer id, String name, PersonCallback personCallback) { Person person = new Person(id, name); personCallback.callback(person); }}复制代码
public static void main(String[] args) { Person.create(1, "周瑜", new PersonCallback() { public void callback(Person person) { System.out.println("去注册..."); } }); Person.create(2, "老师", new PersonCallback() { public void callback(Person person) { System.out.println("去登陆..."); } }); }复制代码
上面的PersonCallback其实就是一种动作,但是我们真正关心的只有callback方法里的内容而已,我们用Lambda表示,可以将上面的代码就可以优化成:
Person.create(1, "周瑜", (Person person) -> {System.out.println("去注册...");})复制代码
有没有发现特别简单了...,但是我们发现Person.create这个方法其实接收的对象依然是PersonCallback这个接口,但是现在传的是一个Lambda表达式,那么难道Lambda表达式也实现了这个接口?。问题也放这,我们先看一下Lambda表达式的接口
Lambda允许把函数作为一个方法的参数,一个lambda由用逗号分隔的参数列表、–>符号、函数体三部分表示。对于上面的表达式
- **(Person person)为Lambda表达式的入参,{System.out.println("去注册...");}**为函数体
- 重点是这个表达式是没有名字的。 我们知道,当我们实现一个接口的时候,肯定要实现接口里面的方法,那么现在一个Lambda表达式应该也要遵循这一个基本准则,那么一个Lambda表达式它实现了接口里的什么方法呢? 答案是:一个Lambda表达式实现了接口里的有且仅有的唯一一个抽象方法。那么对于这种接口就叫做函数式接口。 Lambda表达式其实完成了实现接口并且实现接口里的方法这一功能,也可以认为Lambda表达式代表一种动作,我们可以直接把这种特殊的动作进行传递。
当然,对于上面的Lambda表达式你可以简化:
Person.create(1, "周瑜", person -> System.out.println("去注册..."));复制代码
这归功于Java8的类型推导机制。因为现在接口里只有一个方法,那么现在这个Lambda表达式肯定是对应实现了这个方法,既然是唯一的对应关系,那么入参肯定是Person类,所以可以简写,并且方法体只有唯一的一条语句,所以也可以简写,以达到表达式简洁的效果。
函数式接口
函数式接口是新增的一种接口定义。 用**@FunctionalInterface修饰的接口叫做函数式接口**,或者,函数式接口就是一个只具有一个抽象方法的普通接口,@FunctionalInterface可以起到校验的作用。 下面的接口只有一个抽象方法能编译正确:
@FunctionalInterfacepublic interface TestFunctionalInterface { void test1();}复制代码
下面的接口有多个抽象方法会编译错误:
@FunctionalInterfacepublic interface TestFunctionalInterface { void test1(); void test2();}复制代码
在JDK7中其实就已经有一些函数式接口了,比如Runnable、Callable、FileFilter等等。 在JDK8中也增加了很多函数式接口,比如java.util.function包。 比如这四个常用的接口:
接口 | 描述 |
---|---|
Supplier | 无参数,返回一个结果 |
Function<T,R> | 接受一个输入参数,返回一个结果 |
Consumer | 接受一个输入参数,无返回结果 |
Predicate | 接受一个输入参数,返回一个布尔值结果。 |
那么Java8中给我们加了这么多函数式接口有什么作用?
上文我们分析到,一个Lambda表达式其实也可以理解为一个函数式接口的实现者,但是作为表达式,它的写法其实是多种多样的,比如
- () -> {return 0;},没有传入参数,有返回值
- (int i) -> {return 0;},传入一个参数,有返回值
- (int i) -> {System.out.println(i)},传入一个int类型的参数,但是没有返回值
- (int i, int j) -> {System.out.println(i)},传入两个int类型的参数,但是没有返回值
- (int i, int j) -> {return i+j;},传入两个int类型的参数,返回一个int值
- (int i, int j) -> {return i>j;},传入两个int类型的参数,返回一个boolean值 等等,还有许多许多种情况。那么这每种表达式的写法其实都应该是某个函数式接口的实现类,需要特定函数式接口进行对应,比如上面的四种情况就分别对应
Supplier<T>
,Function<T,R>
,Consumer<T>
,BiConsumer<T, U>
,BiFunction<T, U, R>
,BiPredicate<T, U>
。
答案已经明显了,Java8中提供给我们这么多函数式接口就是为了让我们写Lambda表达式更加方便,当然遇到特殊情况,你还是需要定义你自己的函数式接口然后才能写对应的Lambda表达式。
总的来说,如果没有函数式接口,就不能写Lambda表达式.
接口的默认方法与静态方法
在JDK7中,如果想对接口Collection新增一个方法,那么你需要修改它所有的实现类源码(这是非常恐怖的),在那么Java8之前是怎么设计来解决这个问题的呢,用的是抽象类,比如: 现在有一个接口PersonInterface接口,里面有1个抽象方法:
public interface PersonInterface { void getName();}复制代码
有三个实现类:
public class YellowPerson implements PersonInterface { @Override public void getName() { System.out.println("yellow"); }}public class WhitePerson implements PersonInterface { @Override public void getName() { System.out.println("white"); }}public class BlackPerson implements PersonInterface { @Override public void getName() { System.out.println("black"); }}复制代码
现在我需要在PersonInterface接口中新增一个方法,那么势必它的三个实现类都需要做相应改动才能编译通过,这里我就不进行演示了,那么我们在最开始设计的时候,其实可以增加一个抽象类PersonAbstract,三个实现类改为继承这个抽象类,按照这种设计方法,对PersonInterface接口中新增一个方法是,其实只需要改动PersonAbstract类去实现新增的方法就好了,其他实现类不需要改动了:
public interface PersonInterface { void getName(); void walk();}public abstract class PersonAbstract implements PersonInterface { @Override public void walk() { System.out.println("walk"); }}public class BlackPerson extends PersonAbstract { @Override public void getName() { System.out.println("black"); }}public class WhitePerson extends PersonAbstract { @Override public void getName() { System.out.println("white"); }}public class YellowPerson extends PersonAbstract { @Override public void getName() { System.out.println("yellow"); }}复制代码
那么在Java8中支持直接在接口中添加已经实现了的方法,一种是Default方法(默认方法),一种Static方法(静态方法)。
接口的默认方法
在接口中用default修饰的方法称为默认方法。 接口中的默认方法一定要有默认实现(方法体),接口实现者可以继承它,也可以覆盖它。
default void testDefault(){ System.out.println("default"); };复制代码
接口的静态方法
在接口中用static修饰的方法称为静态方法。
static void testStatic(){ System.out.println("static"); };复制代码
调用方式:
TestInterface.testStatic();复制代码
因为有了默认方法和静态方法,所以你不用去修改它的实现类了,可以进行直接调用。
重复注解
假设,现在有一个服务我们需要定时运行,就像Linux中的cron一样,假设我们需要它在每周三的12点运行一次,那我们可能会定义一个注解,有两个代表时间的属性。
public @interface Schedule { int dayOfWeek() default 1; // 周几 int hour() default 0; // 几点}复制代码
所以我们可以给对应的服务方法上使用该注解,代表运行的时间:
public class ScheduleService { // 每周3的12点运行 @Schedule(dayOfWeek = 3, hour = 12) public void start() { // 执行服务 }}复制代码
那么如果我们需要这个服务在每周四的13点也需要运行一下,如果是JDK8之前,那么...尴尬了!你不能像下面的代码,会编译错误
public class ScheduleService { // jdk中两个相同的注解会编译报错 @Schedule(dayOfWeek = 3, hour = 12) @Schedule(dayOfWeek = 4, hour = 13) public void start() { // 执行服务 }}复制代码
那么如果是JDK8,你可以改一下注解的代码,在自定义注解上加上@Repeatable元注解,并且指定重复注解的存储注解(其实就是需要需要数组来存储重复注解),这样就可以解决上面的编译报错问题。
@Repeatable(value = Schedule.Schedules.class)public @interface Schedule { int dayOfWeek() default 1; int hour() default 0; @interface Schedules { Schedule[] value(); }}复制代码
同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型。 添加main方法:
public static void main(String[] args) { try { Method method = ScheduleService.class.getMethod("start"); for (Annotation annotation : method.getAnnotations()) { System.out.println(annotation); } for (Schedule s : method.getAnnotationsByType(Schedule.class)) { System.out.println(s.dayOfWeek() + "|" + s.hour()); } } catch (NoSuchMethodException e) { e.printStackTrace(); } }复制代码
输出:
@repeatannotation.Schedule$Schedules(value=[@repeatannotation.Schedule(hour=12, dayOfWeek=3), @repeatannotation.Schedule(hour=13, dayOfWeek=4)])3|124|13复制代码
获取方法参数的名字
在Java8之前,我们如果想获取方法参数的名字是非常困难的,需要使用ASM、javassist等技术来实现,现在,在Java8中则可以直接在Method对象中就可以获取了。
public class ParameterNames { public void test(String p1, String p2) { } public static void main(String[] args) throws Exception { Method method = ParameterNames.class.getMethod("test", String.class, String.class); for (Parameter parameter : method.getParameters()) { System.out.println(parameter.getName()); } System.out.println(method.getParameterCount()); }}复制代码
输出:
arg0arg12复制代码
从结果可以看出输出的参数个数正确,但是名字不正确!需要在编译时增加**–parameters**参数后再运行。 在Maven中增加:
复制代码 org.apache.maven.plugins maven-compiler-plugin 3.1 -parameters
输出结果则变为:
p1p22复制代码
CompletableFuture
当我们Javer说异步调用时,我们自然会想到Future,比如:
public class FutureDemo { /** * 异步进行一个计算 * @param args */ public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); Futureresult = executor.submit(new Callable () { public Integer call() throws Exception { int sum=0; System.out.println("正在计算..."); for (int i=0; i<100; i++) { sum = sum + i; } Thread.sleep(TimeUnit.SECONDS.toSeconds(3)); System.out.println("算完了!"); return sum; } }); System.out.println("做其他事情..."); try { System.out.println("result:" + result.get()); } catch (Exception e) { e.printStackTrace(); } System.out.println("事情都做完了..."); executor.shutdown(); }}复制代码
那么现在如果想实现异步计算完成之后,立马能拿到这个结果继续异步做其他事情呢?这个问题就是一个线程依赖另外一个线程,这个时候Future就不方便,我们来看一下CompletableFuture的实现:
public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture result = CompletableFuture.supplyAsync(() -> { int sum=0; System.out.println("正在计算..."); for (int i=0; i<100; i++) { sum = sum + i; } try { Thread.sleep(TimeUnit.SECONDS.toSeconds(3)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"算完了!"); return sum; }, executor).thenApplyAsync(sum -> { System.out.println(Thread.currentThread().getName()+"打印"+sum); return sum; }, executor); System.out.println("做其他事情..."); try { System.out.println("result:" + result.get()); } catch (Exception e) { e.printStackTrace(); } System.out.println("事情都做完了..."); executor.shutdown(); }复制代码
结果:
正在计算...做其他事情...pool-1-thread-1算完了!pool-1-thread-2打印4950result:4950事情都做完了...复制代码
只需要简单的使用thenApplyAsync就可以实现了。 当然CompletableFuture还有很多其他的特性,我们下次单独开个专题来讲解。
Java8的特性还有Stream和Optional,这两个也是用的特别多的,相信很多同学早有耳闻,并且已经对这两个的特性有所了解,所以本片博客就不进行讲解了,有机会再单独讲解,可讲的内容还是非常之多的 对于Java8,新增的特性还是非常之多的,就是目前Java11已经出了,但是Java8中的特性肯定会一直在后续的版本中保留的,至于这篇文章的这些新特性我们估计用的比较少,所以特已此篇来进行一个普及,希望都有所收货。