Java11都出來了還要學Java8新特性嗎?

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

Java8全部的新特性

Lambda表達式、 函數式接口、接口默認方法、接口靜態方法、擴展註解、重複註解、Optional、Stream、時間/日期API、方法引用、 參數名字保留在字節碼中、並行數組、CompletableFuturejava

Lambda表達式

在JDK8以前,一個方法能接受的參數都是變量,例如:object.method(Object o) 那麼,若是須要傳入一個動做呢?好比回調。 那麼你可能會想到匿名內部類。 例如: 匿名內部類是須要依賴接口的,因此須要先定義個接口apache

@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表達式的接口maven

Lambda容許把函數做爲一個方法的參數,一個lambda由用逗號分隔的參數列表、–>符號、函數體三部分表示。對於上面的表達式ide

  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能夠起到校驗的做用。 下面的接口只有一個抽象方法能編譯正確:this

@FunctionalInterface
public interface TestFunctionalInterface {
    void test1();
}

下面的接口有多個抽象方法會編譯錯誤:線程

@FunctionalInterface
public interface TestFunctionalInterface {
    void test1();
    void test2();
}

在JDK7中其實就已經有一些函數式接口了,好比RunnableCallableFileFilter等等。 在JDK8中也增長了不少函數式接口,好比java.util.function包。 好比這四個經常使用的接口:

接口 描述
Supplier<T> 無參數,返回一個結果
Function<T,R> 接受一個輸入參數,返回一個結果
Consumer<T> 接受一個輸入參數,無返回結果
Predicate<T> 接受一個輸入參數,返回一個布爾值結果。

那麼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中的特性確定會一直在後續的版本中保留的,至於這篇文章的這些新特性咱們估計用的比較少,因此特已此篇來進行一個普及,但願都有所收貨。

相關文章
相關標籤/搜索