1. 程式人生 > 其它 >Java16都快上線了,你該不會連Java8的特性都不會用吧?

Java16都快上線了,你該不會連Java8的特性都不會用吧?

技術標籤:《Java核心基礎》系列javaJava 8streamlambda面試

聽說微信搜尋《Java魚仔》會變更強哦!

本文收錄於JavaStarter ,裡面有我完整的Java系列文章,學習或面試都可以看看哦

(一)前言

2020年,Java16有了新的訊息,預計將在2021年的3月16日正式釋出。但是這一訊息對於百分之九十的國內程式設計師來說都只是一個新聞而已,因為國內的絕大部分公司依然使用著Java8。這款釋出於2014年的JDK版本深受各大公司的喜愛,最大的原因取決於它的穩定性。

即使如此,依然有一半以上的程式設計師對於Java8的特性不瞭解,於是我用一個週末的時間把JDK8的”新“特性肝了一遍,希望對大家有所幫助。

(二)Lambda表示式

Lambda是一個匿名函式,它允許你通過表示式來代替功能介面,使用lambda可以讓程式碼變得更加簡潔。簡單來講,lambda使用更簡潔的方式給介面新增實現方法。

Lambda只能在函式式介面中使用,函式式介面就是那些只有一個抽象方法的介面,可以用註解@FunctionalInterface修飾,如果一個介面中有多個方法,就不能再使用Lambda了。

我們常用的Runnable、Comparator都是函式式介面:

Comparator

Runnable

lambda的語法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements;
}

2.1 無引數、無返回值

lambda實現的是對功能介面程式碼的簡潔化,比如下面這一段:

Runnable runnable=new Runnable() {
    @Override
    public void run() {
        System.out.println("run");
    }
};

我們在建立一個Runnable物件的時候需要通過匿名內部類實現介面中定義的方法,而使用lambda表示式,上面的程式碼一行就可以搞定:

Runnable runnable2= () -> System.out.println("run"
);

2.2 有引數,無返回值

我們先寫一個這樣的介面:

public interface Student {
    public void getAge(int age);
}

在以前的程式碼中,我們需要首先寫一個類去繼承這個介面,或者是寫一個匿名內部類,如:

Student student=new Student() {
    @Override
    public void getAge(int age) {
        System.out.println(age);
    }
};

現在就變得簡單了:

Student student2=(age) -> { System.out.println(age); };

如果只有一個引數,小括號可以不寫:

Student student3=age -> { System.out.println(age); };

2.3 有引數,有返回值

寫一個介面:

public interface Student {
    public int getAge(int age);
}

直接寫lambda表示式了:

Student student5=age -> { return age; };
System.out.println(student5.getAge(1));

(三)內建函式式介面

在前面已經介紹了什麼是函式式介面,函式式介面就是那些只有一個抽象方法的介面,Java8中內建了四種函式式介面:

Consumer<T> : void accept(T t);
Supplier<T> : T get();
Function<T,R> : R apply(T t);
Predicate<T> : boolean test(T t)

這四種介面和我們平常寫的沒有什麼區別,可以在Java8中直接呼叫:

3.1 Consumer

消費型介面,提供了一個引數、無返回值的介面方法,就和消費一樣,花出去就沒有了。

public static void main(String[] args) {
    consume(100,money -> {
        System.out.println("消費了"+money+"元");
    });
}
public static void consume(double money, Consumer<Double> consumer){
    consumer.accept(money);
}

3.2 Supplier

供給型介面,提供了無引數,有返回值的介面方法,主要起到供給資料的作用,比如實現一個生成隨機數的功能:

public static void main(String[] args) {
    System.out.println(getRandomNum(()->(int)(Math.random()*100)));
}
public static int getRandomNum(Supplier<Integer> supplier){
    return supplier.get();
}

3.3 Function

函式型介面,提供帶引數和返回值的介面方法,可用來對資料進行處理後再返回,比如實現一個替換字串給功能:

public static void main(String[] args) {
   //去掉前後空格
   System.out.println(handlerString("\t\tJava魚仔\t\t",(str)->str.trim()));
}
public static String handlerString(String string, Function<String,String> function){
    return function.apply(string);
}

3.4 Predicate

斷言型介面,提供帶引數和返回值的介面方法,只不過返回值是boolean型別,可用於進行判斷,比如實現一個判斷某個字串是否是整數的需求

public static void main(String[] args) {
     Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
     if (isDigit("111",s -> {
         return pattern.matcher(s).matches();
     })){
         System.out.println("is Digit");
     }
}
public static String handlerString(String string, Function<String,String> function){
    return function.apply(string);
}

(四)Stream API

Java8中兩個最重大的改變,一個是Lambda表示式,另外一個就是Stream API

Stream是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。

使用Stream API對集合資料進行操作,就類似於使用SQL執行的資料庫查詢。也可以使用Stream API來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理資料的方式。

Stream的執行過程分為三步

1、建立stream

2、操作stream(查詢、篩選、過濾等等)

3、執行stream

stream的操作屬於惰性求值,即所有的操作是在執行stream時一次性執行。

4.1 建立stream

建立流的方式有四種,這裡直接把建立方式的註釋寫進程式碼裡

public static void main(String[] args) {
    //建立Stream
    //1、通過Collection系列集合提供的stream()或parallelStream()
    List<String> list=new ArrayList<>();
    Stream<String> stream = list.stream();

    //2、通過Arrays中的靜態方法stream()獲取
    Integer[] integers=new Integer[10];
    Stream<Integer> stream1 = Arrays.stream(integers);

    //3、通過Stream類中的靜態方法of()
    Stream<Integer> stream2 = Stream.of(integers);

    //4、迭代建立
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
}

4.2 操作stream

stream有多種操作集合的方法,下面一一進行講解:

為了更方便的演示,新建一個實體類User:

public class User {
    private String name;
    private int age;
    private String sex;
    //這裡省略構造方法、get、set、toString方法
}

filter過濾–通過某種條件過濾元素:

filter通過Predicate介面過濾元素,下面這段程式碼將年齡超過30歲的過濾出來

public static void main(String[] args) {
    List<User> list= Arrays.asList(
            new User("javayz",23,"male"),
            new User("張三",25,"male"),
            new User("李四",30,"female"),
            new User("王五",33,"female")
            new User("王五",33,"female")
    );
    //過濾出年齡大於30歲的人
    Stream<User> userStream = list.stream().filter((e) -> e.getAge() >= 30);
   userStream.forEach((e)-> System.out.println(e));
}

limit截斷–使元素不超過給定的數量

//對取出來的資料只展示一條
Stream<User> userStream = list.stream()
        .filter((e) -> e.getAge() >= 30)
        .limit(1);
userStream.forEach((e)-> System.out.println(e));

skip–跳過集合中的前n個數

可以和limit聯合使用取出指定的範圍的數

Stream<User> userStream = list.stream().skip(2);

distinct–去重,通過元素的hashcode()和equals()去除重複元素

去重是根據hashcode()和equals()方法判斷重複元素的,因此實體類了需要重寫hashcode()和equals()方法

Stream<User> userStream = list.stream().distinct();

map–通過Function介面對stream中的每個資料處理後返回新的資料

比如我想把所有的英文改為大寫,就可以這樣

List<String> list1=Arrays.asList("aa","bb","cc");
Stream<String> stringStream = list1.stream().map((str) -> str.toUpperCase());
stringStream.forEach((e)-> System.out.println(e));

flatmap–通過Function介面把stream的每一個值都換成另外個stream,最後再把所有stream連線成一個stream

這個方法的使用可能會比較難懂,通過一個例子,把包含三個字串的集合中的每個字串都分成一個個字元提取出來。比如把一個集合{“aa”,“bb”,“cc”}變成{‘a’,‘a’,‘b’,‘b’,‘c’,‘c’},使用map就需要這樣:

List<String> list1=Arrays.asList("aa","bb","cc");
Stream<Stream<Character>> streamStream = list1.stream().map((str) -> {
    List<Character> characters = new ArrayList<>();
    for (Character ch : str.toCharArray()) {
        characters.add(ch);
    }
    return characters.stream();
});
streamStream.forEach((stream)->{
    stream.forEach((character -> System.out.println(character)));
});

它第一個stream的返回值是stream中套一個字元流,輸出的時候也需要先遍歷最外層的stream再遍歷內層的stream,比較麻煩,於是就可以使用flatmap,他會把多個stream合併為一個。

Stream<Character> characterStream = list1.stream().flatMap((str) -> {
    List<Character> characters = new ArrayList<>();
    for (Character ch : str.toCharArray()) {
        characters.add(ch);
    }
    return characters.stream();
});
characterStream.forEach((e)-> System.out.println(e));

sorted–自然排序,按照Comparable方式排序

Stream<String> stringStream = list.stream().sorted();

sorted(Comparator comparator)–定製排序,自己寫一個Comparator 實現排序

List<User> list= Arrays.asList(
        new User("javayz",23,"male"),
        new User("張三",25,"male"),
        new User("李四",30,"female"),
        new User("王五",33,"female")
);

Stream<User> sorted = list.stream().sorted((e1, e2) -> {
    return e1.getAge() == e2.getAge() ? 0 : e1.getAge() > e2.getAge() ? 1 : -1;
});
sorted.forEach((e)-> System.out.println(e));

4.3 執行stream

如果只是操作stream,流的資料是不會變化的,接下來介紹執行stream的一系列方法,首先把接下來會用的資料建立進來:

List<User> list= Arrays.asList(
        new User("javayz",23,"male"),
        new User("張三",25,"male"),
        new User("李四",30,"female"),
        new User("王五",33,"female")
);

allMatch–檢查是否匹配所有元素

//判斷所有的元素的sex是否都是male,這裡返回false
boolean male = list.stream().allMatch((e) -> e.getSex().equals("male"));
System.out.println(male);

anyMatch–檢查是否至少匹配一個元素

boolean male = list.stream().anyMatch((e) -> e.getSex().equals("male"));
System.out.println(male);//這裡返回true

noneMatch–檢查是不是沒有元素能夠匹配指定的規則

boolean male = list.stream().noneMatch((e) -> e.getSex().equals("male"));
System.out.println(male);//這裡返回false

findFirst–返回第一個元素

Optional<User> first = list.stream().findFirst();
System.out.println(first.get());

findAny–返回當前流中的任意一個元素

Optional<User> first = list.stream().findAny();
System.out.println(first.get());

count–返回當前流中的元素總個數

long count = list.stream().count();
System.out.println(count);

max–返回當前流中的最大值

返回最大值和最小值依舊需要實現comparator介面方法

Optional<User> max = list.stream().max((e1, e2) -> {
    return Integer.compare(e1.getAge(), e2.getAge());
});
System.out.println(max.get());

min–返回當前流中的最小值

Optional<User> max = list.stream().min((e1, e2) -> {
    return Integer.compare(e1.getAge(), e2.getAge());
});
System.out.println(max.get());

reduce–規約,將流中的元素反覆結合起來,得到一個值

List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9);
//從第0個元素開始,對list求和
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);

collection–收集,將流中的資料接收成其他的形式

比如我們把上面User資料的名字收集到List陣列中:

List<String> collect = list.stream().map((e) -> e.getName()).collect(Collectors.toList());
System.out.println(collect);

Collectors提供了大量的轉化為其他方式的實現,這裡不做過多介紹。

(五)介面中的預設方法和靜態方法

在1.8之前,介面中的方法只能宣告無法實現,在Java8中,介面中可以新增預設方法和靜態方法了。

首先介紹預設方法,通過default修飾的方法可以在介面中增加實現。

public interface MyInterface {
    default void defaultMethod(){
        System.out.println("hello");
    }
}

另外是介面中的靜態方法:

public interface MyInterface {
    public static void staticMethod(){
        System.out.println("hello");
    }
}

(六)新時間日期API

JAVA8中增加了新的時間日期API,通過程式碼來混個眼熟:

6.1 LocalDate、LocalTime、LocalDateTime

@Test
public void test1(){
    //獲取當前時間
    LocalDateTime localDateTime=LocalDateTime.now();
    System.out.println(localDateTime);
    //增加兩年
    System.out.println(localDateTime.plusYears(2));
    //減少兩年
    System.out.println(localDateTime.minusYears(2));
    //獲取年、月、日、小時、分鐘、秒
    System.out.println(localDateTime.getYear());
    System.out.println(localDateTime.getMonthValue());
    System.out.println(localDateTime.getDayOfMonth());
    System.out.println(localDateTime.getHour());
    System.out.println(localDateTime.getMinute());
    System.out.println(localDateTime.getSecond());
}

6.2 Instant :時間戳

@Test
public void test2(){
    Instant instant=Instant.now();
    //獲取當前時間戳
    System.out.println(instant.toEpochMilli());
}

6.3 Duration :計算兩個時間間隔

@Test
public void test3(){
    Instant instant1=Instant.now();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Instant instant2=Instant.now();
    Duration between = Duration.between(instant1, instant2);
    //計算出兩個日期相差的毫秒、分鐘、小時等資料
    System.out.println(between.toMillis());
    System.out.println(between.toMinutes());
    System.out.println(between.toHours());
}

6.4 Period:計算兩個日期間隔

@Test
public void test4(){
    LocalDate localDate1=LocalDate.of(2020,11,1);
    LocalDate localDate2=LocalDate.now();
    Period between = Period.between(localDate1, localDate2);
    //計算相差幾年幾個月零幾天
    System.out.println(between.getYears());
    System.out.println(between.getMonths());
    System.out.println(between.getDays());
}

6.5 TemporalAdjuster:時間矯正器

TemporalAdjuster用於對時間進行操作,我們可以通過TemporalAdjusters這個類呼叫大量對時間操作的方法,比如下個週日等等。

@Test
public void test5(){
    LocalDateTime localDateTime=LocalDateTime.now();
    LocalDateTime with = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println(with);
}

6.6 格式化時間

時間格式化類在JAVA8之前用的最多的就是SimpleDateFormat,現在多了一個叫做DateTimeFormatter的格式化類:

@Test
public void test6(){
    DateTimeFormatter dtf= DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDateTime localDateTime=LocalDateTime.now();
    System.out.println(localDateTime.format(dtf));
}

(七)總結

總的來講,Java8的新東西還是有很多的,雖然現在很多程式設計師都不習慣使用新的語法,但對這些新語法也不要抗拒,畢竟現在最新的Java版本已經到16了呢!