Java11都出来了还要学Java8新特性吗?

  • Java8新特性你知道多少?
  • Java8新特性你用过多少?
  • Java8新特性你理解多少?

Java8所有的新特性

Lambda表达式、 函数式接口、接口默认方法、接口静态方法、扩展注解、重复注解、Optional、Stream、时间/日期API、方法引用、 参数名字保留在字节码中、并行数组、CompletableFuture

Lambda表达式

在JDK8之前,一个方法能接受的参数都是变量,例如:object.method(Object o) 那么,如果需要传入一个动作呢?比如回调。 那么你可能会想到匿名内部类。 例如: 匿名内部类是需要依赖接口的,所以需要先定义个接口

@FunctionalInterface
public 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可以起到校验的作用。 下面的接口只有一个抽象方法能编译正确:

@FunctionalInterface
public interface TestFunctionalInterface {
    void test1();
}
复制代码

下面的接口有多个抽象方法会编译错误:

@FunctionalInterface
public 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|12
4|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());

    }
}
复制代码

输出:

arg0
arg1
2
复制代码

从结果可以看出输出的参数个数正确,但是名字不正确!需要在编译时增加**–parameters**参数后再运行。 在Maven中增加:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>
复制代码

输出结果则变为:

p1
p2
2
复制代码

CompletableFuture

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

public class FutureDemo {

    /**
     * 异步进行一个计算
     * @param args
     */
    public static void main(String[] args) {

        ExecutorService executor = Executors.newCachedThreadPool();
        Future<Integer> result = executor.submit(new Callable<Integer>() {
            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打印4950
result:4950
事情都做完了...
复制代码

只需要简单的使用thenApplyAsync就可以实现了。 当然CompletableFuture还有很多其他的特性,我们下次单独开个专题来讲解。

Java8的特性还有Stream和Optional,这两个也是用的特别多的,相信很多同学早有耳闻,并且已经对这两个的特性有所了解,所以本片博客就不进行讲解了,有机会再单独讲解,可讲的内容还是非常之多的 对于Java8,新增的特性还是非常之多的,就是目前Java11已经出了,但是Java8中的特性肯定会一直在后续的版本中保留的,至于这篇文章的这些新特性我们估计用的比较少,所以特已此篇来进行一个普及,希望都有所收货。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值