博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java11都出来了还要学Java8新特性吗?
阅读量:5891 次
发布时间:2019-06-19

本文共 10048 字,大约阅读时间需要 33 分钟。

  • 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由用逗号分隔的参数列表、–>符号、函数体三部分表示。对于上面的表达式

  1. **(Person person)为Lambda表达式的入参,{System.out.println("去注册...");}**为函数体
  2. 重点是这个表达式是没有名字的。 我们知道,当我们实现一个接口的时候,肯定要实现接口里面的方法,那么现在一个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中其实就已经有一些函数式接口了,比如RunnableCallableFileFilter等等。 在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
1.8
1.8
复制代码

输出结果则变为:

p1p22复制代码

CompletableFuture

当我们Javer说异步调用时,我们自然会想到Future,比如:

public class FutureDemo {    /**     * 异步进行一个计算     * @param args     */    public static void main(String[] args) {        ExecutorService executor = Executors.newCachedThreadPool();        Future
result = 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中的特性肯定会一直在后续的版本中保留的,至于这篇文章的这些新特性我们估计用的比较少,所以特已此篇来进行一个普及,希望都有所收货。

转载于:https://juejin.im/post/5c2c63ddf265da615d72c10e

你可能感兴趣的文章
C语言博客作业03--函数
查看>>
不用任何第三方,写一个RTMP直播推流器
查看>>
常见负载均衡的优点和缺点对比(Nginx、HAProxy、LVS)
查看>>
selenium设置chrome浏览器保持登录方式两种options和cookie
查看>>
冲刺NO.9
查看>>
tar 命令详解 / xz 命令
查看>>
FineUI开源版(ASP.Net)开发实践-目录
查看>>
web.xml 中CharacterEncodingFilter类的学习
查看>>
axis2 对webservice进行接口的调用
查看>>
JSP下载txt 和 Excel两种文件
查看>>
列表的特点
查看>>
PHP使用字符串名称调用类的方法
查看>>
Go语言学习笔记一: Hello World
查看>>
三点运算符使用方法
查看>>
推荐两款简单好用的图片放大jquery插件
查看>>
react获取当前页面的url参数
查看>>
Fibonacci Series in C++ without Recursion, using recursion
查看>>
Bubble sort C++
查看>>
asp语言中if判断语句的求助
查看>>
Collection接口 map
查看>>