Java 面試知識點解析(四)——版本特性篇


  • 前言:

在遨游了一番 Java Web 的世界之后,發現了自己的一些缺失,所以就着一篇深度好文:知名互聯網公司校招 Java 開發崗面試知識點解析 ,來好好的對 Java 知識點進行復習和學習一番,大部分內容參照自這一篇文章,有一些自己補充的,也算是重新學習一下 Java 吧。

前序文章鏈接:

Java 面試知識點解析(一)——基礎知識篇

Java 面試知識點解析(二)——高並發編程篇

Java 面試知識點解析(三)——JVM篇


對於 Java 各個版本的特性,特別是 Java 8 的新知識點,我們都應該有所了解。
前排申明和好文推薦:閃爍之狐 » Java5新特性及使用 » Java6新特性及使用 » Java7新特性及使用 » Java8新特性及使用(一) » Java8新特性及使用(二)

(一)Java 5 相關知識點

參考文章:jdk 1.5新特性

1)增強型 for 循環:

答:增強 for 循環:foreach 語句,foreach 簡化了迭代器。

格式:// 增強for循環括號里寫兩個參數,第一個是聲明一個變量,第二個就是需要迭代的容器

for( 元素類型 變量名 : Collection集合 & 數組 ) {
	…
}

語法:
for ( type 變量名:集合變量名 ) { … }

注意事項:

  • 迭代變量必須在( )中定義!
  • 集合變量可以是數組或實現了Iterable接口的集合類。

高級for循環和傳統for循環的區別:

高級for循環在使用時,必須要明確被遍歷的目標。這個目標,可以是Collection集合或者數組,如果遍歷Collection集合,在遍歷過程中還需要對元素進行操作,比如刪除,需要使用迭代器。

如果遍歷數組,還需要對數組元素進行操作,建議用傳統for循環因為可以定義角標通過角標操作元素。如果只為遍歷獲取,可以簡化成高級for循環,它的出現為了簡化書寫。比起普通的for循環,高級for循環還有性能優勢,因為它對數組索引的邊界值只計算一次(摘自《Effective Java》第46條)。

高級for循環可以遍歷map集合嗎?

答:原則上map集合是無法使用增強for循環來迭代的,因為增強for循環只能針對實現了Iterable接口的集合進行迭代;Iterable是jdk5中新定義的接口,就一個方法iterator方法,只有實現了Iterable接口的類,才能保證一定有iterator方法,java有這樣的限定是因為增強for循環內部還是用迭代器實現的,而實際上,我們可以通過某種方式來使用增強for循環。

for(Object obj : map.entrySet()) {
	Map.Entry entry = (Entry) obj;  // obj 依次表示Entry
	System.out.println(entry.getKey() + "=" + entry.getValue());
}

總之,for-each 循環在簡潔性和預防 Bug 方面有着傳統 for 循環無法比擬的優勢,並且沒有性能損失。應該盡可能地使用 for-each 循環。遺憾的是,有三種常見的情況是無法使用 for-each 循環的:

  1. 過濾——如果需要遍歷集合,並刪除選定的元素,就需要使用顯式地迭代器,以便可以調用它的 remove 方法。

  2. 轉換——如果需要遍歷列表或者數組,並取代它部分或者全部的元素值(增刪、或對元素進行賦值),就需要列表迭代器或者數組索引,以便設定元素的值

  3. 平行迭代——如果需要並行地遍歷多個集合,就需要顯式地控制迭代器或者所因變量以便所有迭代器或者索引變量都可以得到同步前移

2)可變參數:

解析:什么意思呢?舉個例子:在 JDK 1.5 之前,當我們要為一個傳遞多個類型相同的參數時,我們有兩種方法解決,1.直接傳遞一個數組過去,2.有多少個參數就傳遞多少個參數。

例如:

public void printColor(String red,String green,String yellow){
}
// 或者
public void printColor(String[] colors){
}

這樣編寫方法參數雖然能夠實現我們想要的效果,但是,這樣是不是有點麻煩呢?再者,如果參數個數不確定,我們怎么辦呢?Java JDK1.5為我們提供的可變參數就能夠完美的解決這個問題

答:

可變參數(...):用到函數的參數上,當要操作的同一個類型元素個數不確定的時候,可是用這個方式,這個參數可以接受任意個數的同一類型的數據。

和以前接收數組不一樣的是:

以前定義數組類型,需要先創建一個數組對象,再將這個數組對象作為參數傳遞給函數。現在,直接將數組中的元素作為參數傳遞即可。底層其實是將這些元素進行數組的封裝,而這個封裝動作,是在底層完成的,被隱藏了。所以簡化了用戶的書寫,少了調用者定義數組的動作。

如果在參數列表中使用了可變參數,可變參數必須定義在參數列表結尾(也就是必須是最后一個參數,否則編譯會失敗。)。

如果要獲取多個int數的和呢?可以使用將多個int數封裝到數組中,直接對數組求和即可。

可變參數的特點:

  • ① 只能出現在參數列表的最后;
  • ② “...” 位於變量類型和變量名之間,前后有無空格都可以;
  • ③ 調用可變參數的方法時,編譯器為該可變參數隱含創建一個數組,在方法體中以數組的形式訪問可變參數。
Public int add(int  x, int... args){//也可以直接(int..args)就是說傳不傳都可以
		Int sum = x;
		For(int i = 0; i<=args.lengrth;i++){
				Sum+=args[i];
		}
		return sum;
}

實例:

public class VariableParameter {
      public static void main(String[] args) {
            System. out.println(add(1, 2));
            System. out.println(add(1, 2, 3));
      }
      
       public static int add(int x, int... args){
            int sum = x;
            for(int i = 0; i < args.length; i++){
                sum += args[i];
            }
            return sum;
      }
}

3)枚舉

解析:關鍵字 enum

答:

問題:對象的某個屬性的值不能是任意的,必須為固定的一組取值其中的某一個;

解決辦法:

1)在 setGrade 方法中做判斷,不符合格式要求就拋出異常;
2)直接限定用戶的選擇,通過自定義類模擬枚舉的方式來限定用戶的輸入,寫一個 Grade 類,私有構造函數,對外提供 5 個靜態的常量表示類的實例;
3)jdk5 中新定義了枚舉類型,專門用於解決此類問題;
4)枚舉就是一個特殊的java類,可以定義屬性、方法、構造函數、實現接口、繼承類;

為什么要有枚舉?

問題:要定義星期幾或性別的變量,該怎么定義?假設用1-7分別表示星期一到星期日,但有人可能會寫成int weekday = 0;或即使使用常量方式也無法阻止意外。

枚舉就是要讓某個類型的變量的取值只能為若干個固定值中的一個,否則,編譯器就會報錯。枚舉可以讓編譯器在編譯時就可以控制源程序中填寫的非法值,普通變量的方式在開發階段無法實現這一目標。

用普通類如何實現枚舉功能,定義一個Weekday的類來模擬枚舉功能。

1、私有的構造方法。
2、每個元素分別用一個公有的靜態成員變量表示。

可以有若干公有方法或抽象方法。采用抽象方法定義nextDay就將大量的if.else語句轉移成了一個個獨立的類

示例:定義一個Weekday的類來模擬枚舉功能。

public class WeekDay {
      
       private WeekDay(){}
      
       public static final WeekDay SUN = new WeekDay();
       public static final WeekDay MON = new WeekDay();
      
       public WeekDay nextDay(){
             if(this == SUN){
                   return MON ;
            } else{
                   return SUN ;
            }
      }
      
       public String toString(){
             return this == SUN? "SUN":"MON" ;
      }
}
public class EnumTest {

       public static void main(String[] args) {
            WeekDay day = WeekDay.MON;
            System. out.println(day.nextDay());
            //結果:SUN
      }
}

使用枚舉類實現

public class EnumTest {

       public static void main(String[] args) {
            WeekDay day = WeekDay.FRI;
            System.out.println(day);
             //結果:FRI
            System.out.println(day.name());
             //結果:FRI
            System.out.println(day.ordinal());
             //結果:5
            System.out.println(WeekDay. valueOf("SUN"));
             //結果:SUN
            System.out.println(WeekDay. values().length);
             //結果:7
      }
      
      public enum WeekDay{
             SUN,MON ,TUE,WED, THI,FRI ,SAT;
      }
}

總結: 枚舉是一種特殊的類,其中的每個元素都是該類的一個實例對象,例如可以調用WeekDay.SUN.getClass().getName 和 WeekDay.class.getName()。

注意: 最后一個枚舉元素后面可以加分號,也可以不加分號。

實現帶有構造方法的枚舉

  • 枚舉就相當於一個類,其中也可以定義構造方法、成員變量、普通方法和抽象方法。
  • 枚舉元素必須位於枚舉體中的最開始部分,枚舉元素列表的最后要有分號與其他成員分隔。把枚舉中的成員方法或變量等放在枚舉元素的前面,編譯器會報告錯誤。
  • 帶構造方法的枚舉:
    構造方法必須定義成私有的
    如果有多個構造方法,將根據枚舉元素創建時所帶的參數決定選擇哪個構造方法創建對象。
    枚舉元素 MON 和 MON() 的效果一樣,都是調用默認的構造方法。

示例:

public class EnumTest {

       public static void main(String[] args) {
            WeekDay day = WeekDay.FRI;
      }
      
      public enum WeekDay{
             SUN(1),MON (),TUE, WED,THI ,FRI,SAT;
            
             private WeekDay(){
                  System. out.println("first" );
            }
            
             private WeekDay(int value){
                  System. out.println("second" );
            }
             //結果:
             //second
             //first
             //first
             //first
             //first
             //first
             //first
      }
}

實現帶有抽象方法的枚舉

定義枚舉TrafficLamp,實現抽象的nextTrafficLamp方法:每個元素分別是由枚舉類的子類來生成的實例對象,這些子類采用類似內部類的方式進行定義。增加上表示時間的構造方法。

public class EnumTest {

	public static void main(String[] args) {
		TrafficLamp lamp = TrafficLamp.RED;
		System.out.println(lamp.nextLamp());
		//結果:GREEN
	}

	public enum TrafficLamp {
		RED(30) {
			public TrafficLamp nextLamp() {
				return GREEN;
			}
		}, GREEN(45) {
			public TrafficLamp nextLamp() {
				return YELLOW;
			}
		}, YELLOW(5) {
			public TrafficLamp nextLamp() {
				return RED;
			}
		};

		private int time;

		private TrafficLamp(int time) {
			this.time = time;
		}

		public abstract TrafficLamp nextLamp();
	}
}

注意:
1、枚舉只有一個成員時,就可以作為一種單例的實現方式。
2、查看生成的class文件,可以看到內部類對應的class文件。

4)自動拆裝箱

答:在 Java 中數據類型分為兩種:基本數據類型、引用數據類型(對象)

自動裝箱:把基本類型變成包裝器類型,本質是調用包裝器類型的valueOf()方法

注意:基本數據類型的數組與包裝器類型數組不能互換

在 java程序中所有的數據都需要當做對象來處理,針對8種基本數據類型提供了包裝類,如下:


int → Integer
byte → Byte
short → Short
long → Long
char → Character
double → Double
float → Float
boolean → Boolean


在 jdk 1.5 以前基本數據類型和包裝類之間需要相互轉換:

基本---引用 Integer x = new Integer(x);
引用---基本 int num = x.intValue();

1)Integer x = 1; x = x + 1; 經歷了什么過程?裝箱→拆箱→裝箱
2)為了優化,虛擬機為包裝類提供了緩沖池,Integer池的大小為 -128~127 一個字節的大小。String池:Java 為了優化字符串操作也提供了一個緩沖池;

→ 享元模式(Flyweight Pattern):享元模式的特點是,復用我們內存中已經存在的對象,降低系統創建對象實例。

自動裝箱:

Integer num1 = 12;

自動拆箱:

System.out.println(num1 + 12);

基本數據類型的對象緩存:

Integer num1 = 12;
Integer num2 = 12;
System.out.println(num1 == num2);//ture
Integer num3 = 129;
Integer num4 = 129;
System.out.println(num3 == num4);//false
Integer num5 = Integer.valueOf(12);
Integer num6 = Integer.valueOf(12);
System.out.println(num5 == num6);//true

示例:


public class AutoBox {
       public static void main(String[] args) {
             //裝箱
            Integer iObj = 3;
            
             //拆箱
            System. out.println(iObj + 12);
             //結果:15
            
            Integer i1 = 13;
            Integer i2 = 13;
            System. out.println(i1 == i2);
             //結果:true
            
            i1 = 137;
            i2 = 137;
            System. out.println(i1 == i2);
             //結果:false
      }
}

注意:
如果有很多很小的對象,並且他們有相同的東西,那就可以把他們作為一個對象。
如果還有很多不同的東西,那就可以作為外部的東西,作為參數傳入。
這就是享元設計模式(flyweight)。

例如示例中的Integer對象,在-128~127范圍內的Integer對象,用的頻率比較高,就會作為同一個對象,因此結果為true。超出這個范圍的就不是同一個對象,因此結果為false。

5)泛型 Generics

答:引用泛型之后,允許指定集合里元素的類型,免去了強制類型轉換,並且能在編譯時刻進行類型檢查的好處。Parameterized Type作為參數和返回值,Generic是vararg、annotation、enumeration、collection的基石。

泛型可以帶來如下的好處總結如下:

  1. 類型安全:拋棄List、Map,使用List、Map給它們添加元素或者使用Iterator遍歷時,編譯期就可以給你檢查出類型錯誤
  2. 方法參數和返回值加上了Type: 拋棄List、Map,使用List、Map
  3. 不需要類型轉換:List list = new ArrayList();
  4. 類型通配符“?”: 假設一個打印List中元素的方法printList,我們希望任何類型T的List都可以被打印

6)靜態導入

答:靜態導入:導入了類中的所有靜態成員,簡化靜態成員的書寫。
import語句可以導入一個類或某個包中的所有類
import static語句導入一個類中的某個靜態方法或所有靜態方法

import static java.util.Collections.*;  //導入了Collections類中的所有靜態成員

靜態導入可以導入靜態方法,這樣就不必寫類名而可以直接調用靜態方法了。

例子:

原來的:

public class Demo12 {
	public static void main(String[] args) {
		System.out.println(Math.max(12, 15));
         System. out.println(Math.abs(3-6));
	}
}

使用靜態導入的:

import static java.lang.Math.max ;
import static java.lang.Math.abs ;

public class Demo12 {
	public static void main(String[] args) {
		System.out.println(max(12, 15));
        System. out.println(abs(3-6));
	}
}

注意:

1、也可以通過import static java.lang.Math.*;導入Math類下所有的靜態方法。
2、如果將javac設置為了Java5以下,那么靜態導入等jdk1.5的特性都會報告錯誤。

7)新的線程模型和並發庫Thread Framework(重要)

答: 最主要的就是引入了 java.util.concurrent 包,這個都是需要重點掌握的。

HashMap 的替代者 ConcurrentHashMap 和 ArrayList 的替代者 CopyOnWriteArrayList 在大並發量讀取時采用 java.util.concurrent 包里的一些類會讓大家滿意 BlockingQueue、Callable、Executor、Semaphore

8)內省(Introspector)

答:是 Java 語言對 Bean 類屬性、事件的一種缺省處理方法。例如類 A 中有屬性 name , 那我們通過 getName,setName 來得到其值或者設置新的值。通過 getName/setName 來訪問name屬性,這就是默認的規則。Java 中提供了一套 API 用來訪問某個屬性的 getter /setter 方法,通過這些 API 可以使你不需要了解這個規則(但你最好還是要搞清楚),這些 API 存放於包 java.beans 中。

一般的做法是通過類 Introspector 來獲取某個對象的 BeanInfo 信息,然后通過 BeanInfo 來獲取屬性的描述器 (PropertyDescriptor),通過這個屬性描述器就可以獲取某個屬性對應的 getter/setter 方法,然后我們就可以通過反射機制來 調用這些方法。

擴展閱讀:java Introspector(內省) 的介紹

9)注解(Annotations)

答:

注解(Annotation)是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符,它是一種由JSR-175標准選擇用來描述元數據的一種工具。Java從Java5開始引入了注解。在注解出現之前,程序的元數據只是通過java注釋和javadoc,但是注解提供的功能要遠遠超過這些。注解不僅包含了元數據,它還可以作用於程序運行過程中、注解解釋器可以通過注解決定程序的執行順序。

比如,下面這段代碼:

@Override
public String toString() {  
    return "This is String.";
}

上面的代碼中,我重寫了toString()方法並使用了@Override注解。但是,即使我們不使用@Override注解標記代碼,程序也能夠正常執行。那么,該注解表示什么?這么寫有什么好處嗎?事實上,@Override告訴編譯器這個方法是一個重寫方法(描述方法的元數據),如果父類中不存在該方法,編譯器便會報錯,提示該方法沒有重寫父類中的方法。如果我不小心拼寫錯誤,例如將toString()寫成了toStrring(){double r},而且我也沒有使用@Override注解,那程序依然能編譯運行。但運行結果會和我期望的大不相同。現在我們了解了什么是注解,並且使用注解有助於閱讀程序。

為什么要引入注解?

使用注解之前(甚至在使用之后),XML被廣泛的應用於描述元數據。不知何時開始一些應用開發人員和架構師發現XML的維護越來越糟糕了。他們希望使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是松耦合的(在某些情況下甚至是完全分離的)代碼描述。如果你在Google中搜索“XML vs. annotations”,會看到許多關於這個問題的辯論。最有趣的是XML配置其實就是為了分離代碼和配置而引入的。上述兩種觀點可能會讓你很疑惑,兩者觀點似乎構成了一種循環,但各有利弊。下面我們通過一個例子來理解這兩者的區別。

假如你想為應用設置很多的常量或參數,這種情況下,XML是一個很好的選擇,因為它不會同特定的代碼相連。如果你想把某個方法聲明為服務,那么使用注解會更好一些,因為這種情況下需要注解和方法緊密耦合起來,開發人員也必須認識到這點。

另一個很重要的因素是注解定義了一種標准的描述元數據的方式。在這之前,開發人員通常使用他們自己的方式定義元數據。例如,使用標記接口,注釋,transient關鍵字等等。每個程序員按照自己的方式定義元數據,而不像注解這種標准的方式。

目前,許多框架將XML和Annotation兩種方式結合使用,平衡兩者之間的利弊。

參考文章(更多注解戳這里):Java注解的理解和應用

10)新增 ProcessBuilder 類

答:

ProcessBuilder 類是 Java5 在 java.lang 包中新添加的一個新類,此類用於創建操作系統進程,它提供一種啟動和管理進程(也就是應用程序)的方法。在此之前,都是由 Process 類處來實現進程的控制管理。每個 ProcessBuilder 實例管理一個進程屬性集。它的 start() 方法利用這些屬性創建一個新的 Process 實例。start() 方法可以從同一實例重復調用,以利用相同的或相關的屬性創建新的子進程。

ProcessBuilder 是一個 final 類,有兩個帶參數的構造方法,你可以通過構造方法來直接創建 ProcessBuilder 的對象。而 Process 是一個抽象類,一般都通過 Runtime.exec()ProcessBuilder.start() 來間接創建其實例。ProcessBuilder 為進程提供了更多的控制,例如,可以設置當前工作目錄,還可以改變環境參數。而 Process 類的功能相對來說簡單的多。ProcessBuilder 類不是同步的。如果多個線程同時訪問一個 ProcessBuilder,而其中至少一個線程從結構上修改了其中一個屬性,它必須保持外部同步。

若要使用 ProcessBuilder 創建一個進程,只需要創建 ProcessBuilder 的一個實例,指定該進程的名稱和所需參數。要執行此程序,調用該實例上的 start() 即可。下面是一個執行打開 Windows 記事本的例子。注意它將要編輯的文件名指定為一個參數。

class PBDemo {

    public static void main(String args[]) {
        try {
            ProcessBuilder proc = new ProcessBuilder("notepad.exe", "testfile");
            proc.start();
        } catch (Exception e) {
            System.out.println("Error executing notepad.");
        }
    }

}

參考文章:Java5新特性及使用

11)新增Formatter格式化器(Formatter)

Formatter 類是Java5中新增的 printf-style 格式化字符串的解釋器,它提供對布局和對齊的支持,提供了對數字,字符串和日期/時間數據的常用格式以及特定於語言環境的輸出。常見的 Java 類型,如 bytejava.math.BigDecimaljava.util.Calendar 都支持。 通過 java.util.Formattable 接口提供了針對任意用戶類型的有限格式定制。

更詳細的介紹見這里。主要使用方法的代碼示例如下:

import java.io.BufferedReader;  
import java.io.FileReader;  
import java.text.MessageFormat;  
import java.text.SimpleDateFormat;  
import java.util.*;

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;

/**
 * 格式化測試使用的示例類.
 *
 * @author blinkfox on 2017-11-28.
 */
public class FormatTester {

    private static final Logger log = LoggerFactory.getLogger(FormatTester.class);

    /**
     * 格式化.
     */
    private static void formatter() {
        StringBuilder sb = new StringBuilder();
        Formatter formatter = new Formatter(sb, Locale.US);

        // 可重新排序輸出.
        formatter.format("%n%4$2s %3$2s %2$2s %1$2s %n", "a", "b", "c", "d"); // -> " d  c  b  a"
        formatter.format(Locale.FRANCE, "e = %+10.4f", Math.E); // -> "e =    +2,7183"
        formatter.format("%nAmount gained or lost since last statement: $ %(,.2f", 6217.58);
        // -> "Amount gained or lost since last statement: $ 6,217.58"

        log.info("打印出格式化后的字符串:{}", formatter);
        formatter.close();
    }

    /**
     * printf打印.
     */
    private static void printf() {
        String filename = "testfile";
        try (FileReader fileReader = new FileReader(filename)) {
            BufferedReader reader = new BufferedReader(fileReader);
            String line;
            int i = 1;
            while ((line = reader.readLine()) != null) {
                System.out.printf("Line %d: %s%n", i++, line);
            }
        } catch (Exception e) {
            System.err.printf("Unable to open file named '%s': %s", filename, e.getMessage());
        }
    }

    /**
     * stringFormat使用.
     */
    private static void stringFormat() {
        // 格式化日期.
        Calendar c = new GregorianCalendar(1995, Calendar.MAY, 23);
        String s = String.format("Duke's Birthday: %1$tm %1$te,%1$tY", c);
        // -> s == "Duke's Birthday: May 23, 1995"
        log.info(s);
    }

    /**
     * 格式化消息.
     */
    private static void messageFormat() {
        String msg = "歡迎光臨,當前({0})等待的業務受理的顧客有{1}位,請排號辦理業務!";
        MessageFormat mf = new MessageFormat(msg);
        String fmsg = mf.format(new Object[]{new Date(), 35});
        log.info(fmsg);
    }

    /**
     * 格式化日期.
     */
    private static void dateFormat() {
        String str = "2010-1-10 17:39:21";
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        try {
            log.info("格式化后的日期:{}", format.format(format.parse(str)));
        } catch (Exception e) {
            log.error("日期格式化出錯!", e);
        }
    }

    public static void main(String[] args) {
        formatter();
        stringFormat();
        messageFormat();
        dateFormat();
        printf();
    }

}

參考文章:Java5新特性及使用

12)新增 Scanner 類(Scanner)

java.util.Scanner 是 Java5 的新特征,主要功能是簡化文本掃描,但這個類最實用的地方還是在獲取控制台輸入。

(1).Scanner概述

可以從字符串(Readable)、輸入流、文件、Channel等來直接構造Scanner對象,有了Scanner了,就可以逐段(根據正則分隔式)來掃描整個文本,並對掃描后的結果做想要的處理。

Scanner 默認使用空格作為分割符來分隔文本,但允許你使用 useDelimiter(Pattern pattern)useDelimiter(String pattern) 方法來指定新的分隔符。

主要API如下:

  • delimiter(): 返回此 Scanner 當前正在用於匹配分隔符的 Pattern
  • hasNext(): 判斷掃描器中當前掃描位置后是否還存在下一段。
  • hasNextLine(): 如果在此掃描器的輸入中存在另一行,則返回true。
  • next(): 查找並返回來自此掃描器的下一個完整標記。
  • nextLine(): 此掃描器執行當前行,並返回跳過的輸入信息。

(2).掃描控制台輸入

當通過 new Scanner(System.in) 創建了一個 Scanner 實例時,控制台會一直等待輸入,直到敲回車鍵結束,把所輸入的內容傳給 Scanner,作為掃描對象。如果要獲取輸入的內容,則只需要調用 ScannernextLine() 方法即可。

/**
* 掃描控制台輸入.
*
* @author blinkfox 2017-11-28
*/
public class ScannerTest {

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.println("請輸入字符串:");
        while (true) {
            String line = s.nextLine();
            if (line.equals("exit")) break;
            System.out.println(">>>" + line);
        }
    }

}

(3).其它示例

該示例中會從 myNumbers 文件中讀取長整型 long 的數據。

Scanner sc = new Scanner(new File("myNumbers"));  
while (sc.hasNextLong()) {  
    long aLong = sc.nextLong();
}

以下示例可以使用除空格之外的分隔符來從一個字符串中讀取幾個條目:

String input = "1 fish 2 fish red fish blue fish";  
Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");  
System.out.println(s.nextInt());  
System.out.println(s.nextInt());  
System.out.println(s.next());  
System.out.println(s.next());  
s.close();  

將輸出:

1  
2  
red  
blue  

參考文章:Java5新特性及使用

13)StringBuilder

StringBuilder 也是 Java5 中新增的類,主要用來代替 + 號和 StringBuffer 來更加高效的拼接字符串。StringBufferStringBuilder 都是繼承於 AbstractStringBuilder,主要的區別就是 StringBuffer 的函數上都有 synchronized 關鍵字,保證線程安全。

關於 StringBuilder 的使用這里就不再詳細介紹了,網上文章也有很多。總之,對於動態字符串的拼接推薦使用 StringBuilder。靜態字符串的拼接直接使用 + 號或者字符串的 concat(String str) 方法,甚至也使用 StringBuilder 亦可。

參考文章:Java5新特性及使用


(二)Java 6 相關知識點

關於 JDK 1.6 的新特性,了解一下就可以了...如果有興趣深入研究的童鞋,右轉這里:Java6新特性及使用

1)Desktop 類和 SystemTray 類:

答:

在JDK6中 ,AWT新增加了兩個類:Desktop 和 SystemTray 。

前者可以用來打開系統默認瀏覽器瀏覽指定的 URL,打開系統默認郵件客戶端給指定的郵箱發郵件,用默認應用程序打開或編輯文件(比如,用記事本打開以txt為后綴名的文件),用系統默認的打印機打印文檔;

后者可以用來在系統托盤區創建一個托盤程序.

2)使用 JAXB2 來實現對象與 XML 之間的映射

答:

JAXB是Java Architecture for XML Binding的縮寫,可以將一個Java對象轉變成為XML格式,反之亦然。

我們把對象與關系數據庫之間的映射稱為ORM, 其實也可以把對象與XML之間的映射稱為OXM(Object XML Mapping). 原來JAXB是Java EE的一部分,在JDK6中,SUN將其放到了Java SE中,這也是SUN的一貫做法。

JDK6中自帶的這個JAXB版本是2.0, 比起1.0(JSR 31)來,JAXB2(JSR 222)用JDK5的新特性Annotation來標識要作綁定的類和屬性等,這就極大簡化了開發的工作量。

實際上,在Java EE 5.0中,EJB和Web Services也通過Annotation來簡化開發工作。另外,JAXB2在底層是用StAX(JSR 173)來處理XML文檔。除了JAXB之外,我們還可以通過XMLBeans和Castor等來實現同樣的功能。

3)理解StAX

答:

StAX(JSR 173)是JDK6.0中除了DOM和SAX之外的又一種處理XML文檔的API。

StAX 的來歷 :在JAXP1.3(JSR 206)有兩種處理XML文檔的方法:DOM(Document Object Model)和SAX(Simple API for XML).

由 於JDK6.0中的JAXB2(JSR 222)和JAX-WS 2.0(JSR 224)都會用到StAX,所以Sun決定把StAX加入到JAXP家族當中來,並將JAXP的版本升級到1.4(JAXP1.4是JAXP1.3的維護版本). JDK6里面JAXP的版本就是1.4. 。

StAX是The Streaming API for XML的縮寫,一種利用拉模式解析(pull-parsing)XML文檔的API.StAX通過提供一種基於事件迭代器(Iterator)的API讓 程序員去控制xml文檔解析過程,程序遍歷這個事件迭代器去處理每一個解析事件,解析事件可以看做是程序拉出來的,也就是程序促使解析器產生一個解析事件,然后處理該事件,之后又促使解析器產生下一個解析事件,如此循環直到碰到文檔結束符;

SAX也是基於事件處理xml文檔,但卻是用推模式解析,解析器解析完整個xml文檔后,才產生解析事件,然后推給程序去處理這些事件;DOM 采用的方式是將整個xml文檔映射到一顆內存樹,這樣就可以很容易地得到父節點和子結點以及兄弟節點的數據,但如果文檔很大,將會嚴重影響性能。

4)使用Compiler API

答:

現在我們可以用JDK6 的Compiler API(JSR 199)去動態編譯Java源文件,Compiler API結合反射功能就可以實現動態的產生Java代碼並編譯執行這些代碼,有點動態語言的特征。

這個特性對於某些需要用到動態編譯的應用程序相當有用,比如JSP Web Server,當我們手動修改JSP后,是不希望需要重啟Web Server才可以看到效果的,這時候我們就可以用Compiler API來實現動態編譯JSP文件,當然,現在的JSP Web Server也是支持JSP熱部署的,現在的JSP Web Server通過在運行期間通過Runtime.exec或ProcessBuilder來調用javac來編譯代碼,這種方式需要我們產生另一個進程去 做編譯工作,不夠優雅而且容易使代碼依賴與特定的操作系統;Compiler API通過一套易用的標准的API提供了更加豐富的方式去做動態編譯,而且是跨平台的。

5)輕量級Http Server API

答:

JDK6 提供了一個簡單的Http Server API,據此我們可以構建自己的嵌入式Http Server,它支持Http和Https協議,提供了HTTP1.1的部分實現,沒有被實現的那部分可以通過擴展已有的Http Server API來實現,程序員必須自己實現HttpHandler接口,HttpServer會調用HttpHandler實現類的回調方法來處理客戶端請求,在 這里,我們把一個Http請求和它的響應稱為一個交換,包裝成HttpExchange類,HttpServer負責將HttpExchange傳給 HttpHandler實現類的回調方法.

6)插入式注解處理API(Pluggable Annotation Processing API)

答:

插入式注解處理API(JSR 269)提供一套標准API來處理Annotations(JSR 175)

實 際上JSR 269不僅僅用來處理Annotation,我覺得更強大的功能是它建立了Java 語言本身的一個模型,它把method, package, constructor, type, variable, enum, annotation等Java語言元素映射為Types和Elements(兩者有什么區別?), 從而將Java語言的語義映射成為對象, 我們可以在javax.lang.model包下面可以看到這些類. 所以我們可以利用JSR 269提供的API來構建一個功能豐富的元編程(metaprogramming)環境.

JSR 269用Annotation Processor在編譯期間而不是運行期間處理Annotation, Annotation Processor相當於編譯器的一個插件,所以稱為插入式注解處理.如果Annotation Processor處理Annotation時(執行process方法)產生了新的Java代碼,編譯器會再調用一次Annotation Processor,如果第二次處理還有新代碼產生,就會接着調用Annotation Processor,直到沒有新代碼產生為止.每執行一次process()方法被稱為一個"round",這樣整個Annotation processing過程可以看作是一個round的序列.

JSR 269主要被設計成為針對Tools或者容器的API. 舉個例子,我們想建立一套基於Annotation的單元測試框架(如TestNG),在測試類里面用Annotation來標識測試期間需要執行的測試方法。

7)用Console開發控制台程序

JDK6 中提供了java.io.Console 類專用來訪問基於字符的控制台設備. 你的程序如果要與Windows下的cmd或者Linux下的Terminal交互,就可以用Console類代勞. 但我們不總是能得到可用的Console, 一個JVM是否有可用的Console依賴於底層平台和JVM如何被調用. 如果JVM是在交互式命令行(比如Windows的cmd)中啟動的,並且輸入輸出沒有重定向到另外的地方,那么就可以得到一個可用的Console實例.

8)對腳本語言的支持

如: ruby, groovy, javascript.

9)Common annotations

Common annotations 原本是Java EE 5.0(JSR 244)規范的一部分,現在SUN把它的一部分放到了Java SE 6.0中.隨着Annotation元數據功能(JSR 175)加入到Java SE 5.0里面,很多Java 技術(比如EJB,Web Services)都會用Annotation部分代替XML文件來配置運行參數(或者說是支持聲明式編程,如EJB的聲明式事務), 如果這些技術為通用目的都單獨定義了自己的Annotations,顯然有點重復建設, 所以,為其他相關的Java技術定義一套公共的Annotation是有價值的,可以避免重復建設的同時,也保證Java SE和Java EE 各種技術的一致性。

10)Java DB(Derby)

從 JDK6 開始,JDK 目錄中新增了一個名為 db 的目錄。這便是 Java 6 的新成員:Java DB。這是一個純 Java 實現、開源的數據庫管理系統(DBMS),源於 Apache 軟件基金會(ASF)名下的項目 Derby。它只有 2MB 大小,對比動輒上 G 的數據庫來說可謂袖珍。但這並不妨礙 Derby 功能齊備,支持幾乎大部分的數據庫應用所需要的特性。JDK6.0里面帶的這個Derby的版本是10.2.1.7,支持存儲過程和觸發器;有兩種運行模式,一種是作為嵌入式數據庫,另一種是作為網絡數據庫。前者的數據庫服務器和客戶端都在同一個JVM里面運行,后者允許數據庫服務器端和客戶端不在同一個JVM里面,而且允許這兩者在不同的物理機器上。值得注意的是JDK6里面的這個Derby支持JDK6的新特性 JDBC 4.0 規范(JSR 221)。

11)JDBC 4.0

在 Java SE 6 所提供的諸多新特性和改進中,值得一提的是為 Java 程序提供數據庫訪問機制的 JDBC 版本升級到了 4.0, 這個以 JSR-221 為代號的版本,提供了更加便利的代碼編寫機制及柔性,並且支持更多的數據類型。JDBC4.0 主要有以下改進和新特性。

  • 自動加載 java.sql.Driver,而不需要再調用 class.forName
  • 添加了 java.sql.RowId 數據類型用來可以訪問 sql rowid
  • 添加了 National Character Set 的支持;
  • 增強了 BLOBCLOB 的支持功能;
  • SQL/XMLXML 支持;
  • Wrapper Pattern
  • SQLException 增強;
  • ConnectionStatement 接口增強;
  • New Scalar Funtions
  • JDBC API changes

(三)JAVA 7 相關知識點

之前已經寫過一篇詳細介紹 Java 7 特性的文章了,這里就直接黏了:Java 7新特性

1)Diamond Operator

類型判斷是一個人特殊的煩惱,入下面的代碼:

Map<String,List<String>> anagrams = new HashMap<String,List<String>>();

通過類型推斷后變成:

Map<String,List<String>> anagrams = new HashMap<>();

注:這個<>被叫做diamond(鑽石)運算符,Java 7后這個運算符從引用的聲明中推斷類型。

2)在switch語句中使用字符串

switch語句可以使用原始類型或枚舉類型。Java引入了另一種類型,我們可以在switch語句中使用:字符串類型。

說我們有一個根據其地位來處理貿易的要求。直到現在,我們使用if-其他語句來完成這個任務。

private voidprocessTrade(Trade t){

            String status = t.getStatus();

            if(status.equalsIgnoreCase(NEW)) {

                  newTrade(t);

            } else if(status.equalsIgnoreCase(EXECUTE)) {

                  executeTrade(t);

            } else if(status.equalsIgnoreCase(PENDING)) {

                  pendingTrade(t);

            }

}

這種處理字符串的方法是粗糙的。在Java中,我們可以使用增強的switch語句來改進程序,該語句以String類型作為參數。

public voidprocessTrade(Trade t) {
    String status = t.getStatus();
    switch(status) {
        caseNEW:
            newTrade(t);
            break;
        caseEXECUTE:
            executeTrade(t);
            break;
        casePENDING:
            pendingTrade(t);
            break;
         default:
            break;
    }
}

在上面的程序中,狀態字段總是通過使用 String.equals() 與案例標簽來進行比較。

3)自動資源管理

Java中有一些資源需要手動關閉,例如Connections,Files,Input/OutStreams等。通常我們使用 try-finally 來關閉資源:

public voidoldTry() {

	try{

		fos= newFileOutputStream("movies.txt");

		dos= newDataOutputStream(fos);

		dos.writeUTF("Java 7 Block Buster");

	} catch(IOException e) {

		e.printStackTrace();

	} finally{

		try{

			fos.close();

			dos.close();

		} catch(IOException e) {

			// log the exception

		}

	}

}

然而,在Java 7中引入了另一個很酷的特性,可以自動管理資源。它的操作也很簡單,我們所要做的就是在 try 塊中申明資源如下:

try(resources_to_be_cleant){

   // your code

}

以上方法與舊的 try-finally 能最終寫成下面的代碼:

public voidnewTry() {

	try(FileOutputStream fos = newFileOutputStream("movies.txt");

		DataOutputStream dos = newDataOutputStream(fos)) {

		dos.writeUTF("Java 7 Block Buster");

	} catch(IOException e) {

		// log the exception

	}

}

上面的代碼也代表了這個特性的另一個方面:處理多個資源。FileOutputStreamDataOutputStream 在try語句中一個接一個地含在語句中,每一個都用分號(;)分隔符分隔開。我們不必手動取消或關閉流,因為當空間存在try塊時,它們將自動關閉。

在后台,應該自動關閉的資源必須試驗 java.lang.AutoCloseable 接口。

任何實現 AutoCloseable 接口的資源都可以作為自動資源管理的候選。AutoCloseablejava.io.Closeable 接口的父類,JVM會在程序退出try塊后調用一個方法 close()

4)帶下划線的數字文本

數字文字絕對是對眼睛的一種考驗。我相信,如果你給了一個數字,比如說,十個零,你就會像我一樣數零。如果不計算從右到左的位置,識別一個文字的話,就很容易出錯,而且很麻煩。Not anymore。Java在識別位置時引入了下划線。例如,您可以聲明1000,如下所示:

int thousand =  1_000;

或1000000(一百萬)如下:

int million  =  1_000_000

請注意,這個版本中也引入了二進制文字-例如“0b1”-因此開發人員不必再將它們轉換為十六進制。

5)改進的異常處理

在異常處理區域有幾處改進。Java引入了多個catch功能,以使用單個抓到塊捕獲多個異常類型。

假設您有一個方法,它拋出三個異常。在當前狀態下,您將分別處理它們,如下所示:

public voidoldMultiCatch() {

	try{

		methodThatThrowsThreeExceptions();

	} catch(ExceptionOne e) {

		// log and deal with ExceptionOne

	} catch(ExceptionTwo e) {

		// log and deal with ExceptionTwo

	} catch(ExceptionThree e) {

		// log and deal with ExceptionThree

	}

}

在一個catch塊中逐個捕獲一個連續的異常,看起來很混亂。我還看到了捕獲十幾個異常的代碼。這是非常低效和容易出錯的。Java為解決這只丑小鴨帶來了新的語言變化。請參閱下面的方法oldMultiCatch方法的改進版本:

public voidnewMultiCatch() {

	try{

		methodThatThrowsThreeExceptions();

	} catch(ExceptionOne | ExceptionTwo | ExceptionThree e) {

		// log and deal with all Exceptions

	}

}

多個異常通過使用 “|” 操作符在一個catch塊中捕獲。這樣,您不必編寫數十個異常捕獲。但是,如果您有許多屬於不同類型的異常,那么您也可以使用“多個catch塊”塊。下面的代碼片段說明了這一點:

public voidnewMultiMultiCatch() {

	try{

		methodThatThrowsThreeExceptions();

	} catch(ExceptionOne e) {

		// log and deal with ExceptionOne
		
	} catch(ExceptionTwo | ExceptionThree e) {

		// log and deal with ExceptionTwo and ExceptionThree

	}
	
}

在上面的例子中,在和ExceptionThree屬於不同的層次結構,因此您希望以不同的方式處理它們,但使用一個抓到塊。

6)New file system API(NIO 2.0)

那些使用Java的人可能還記得框架引起的頭痛。在操作系統或多文件系統之間無縫地工作從來都不是一件容易的事情.。有些方法,例如刪除或重命名,在大多數情況下都是出乎意料的。使用符號鏈接是另一個問題。實質上API需要大修。

為了解決上述問題,Java引入了一個新的API,並在許多情況下引入了新的api。

在NIO2.0提出了許多增強功能。在處理多個文件系統時,它還引入了新的類來簡化開發人員的生活。

Working With Path(使用路徑):

新的 java.nio.file 由包和接口組成例如:Path,Paths,FileSystem,FileSystems等等。

路徑只是對文件路徑的簡單引用。它與java.io.File等價(並具有更多的特性)。下面的代碼段顯示了如何獲取對“臨時”文件夾的路徑引用:

public voidpathInfo() {

	Path path= Paths.get("c:\Temp\temp");

	System.out.println("Number of Nodes:"+ path.getNameCount());

	System.out.println("File Name:"+ path.getFileName());

	System.out.println("File Root:"+ path.getRoot());

	System.out.println("File Parent:"+ path.getParent());

}

最終控制台的輸出將是:

Number of Nodes:2

File Name:temp.txt

File Root:c:

File Parent:c:Temp

刪除文件或目錄就像在文件中調用delete方法(注意復數)一樣簡單。在類公開兩個刪除方法,一個拋出NoSuchFileException,另一個不拋。

下面的delete方法調用拋出NoSuchFileException,因此您必須處理它:

Files.delete(path);

Where as Files.deleteIfExists(path) does not throw exception (as expected) if the file/directory does not exist.

使用 Files.deteleIfExists(path) 則不會拋出異常。

您可以使用其他實用程序方法,例如Files.copy(.)和Files.move(.)來有效地對文件系統執行操作。類似地,使用 createSymbolicLink(..) 方法使用代碼創建符號鏈接。

文件更改通知:

JDK 7中最好的改善算是File change notifications(文件更改通知)了。這是一個長期等待的特性,它最終被刻在NIO 2.0中。WatchService API 允許您在對主題(目錄或文件)進行更改時接收通知事件。

具體的創建步驟就不給了,總之它的功能就跟它的名字一般,當文件發生更改的時候,能及時作出反饋。

7)Fork and Join(Fork/Join框架)

在一個 Java 程序中有效地使用並行內核一直是一個挑戰。很少有國內開發的框架將工作分配到多個核心,然后加入它們來返回結果集。Java已經將這個特性作為Fork/Join框架結合了起來。

基本上,在把手頭的任務變成了小任務,直到小任務簡單到可以不進一步分手的情況下解決。這就像一個分而治之的算法.。在這個框架中需要注意的一個重要概念是,理想情況下,沒有工作線程是空閑的。他們實現了一個 work-stealing 算法,在空閑的工人“偷”工作從那些工人誰是忙。

支持Fork-Join機制的核心類是 ForkJoinPool和ForkJoinTask。

什么是Fork/Join框架:

Java7提供的一個用於並行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。

Fork/Join的運行流程圖如下:

工作竊取算法:

工作竊取(work-stealing)算法是指某個線程從其他隊列里竊取任務來執行。工作竊取的運行流程圖如下:

工作竊取算法的優點是充分利用線程進行並行計算,並減少了線程間的競爭,其缺點是在某些情況下還是存在競爭,比如雙端隊列里只有一個任務時。並且消耗了更多的系統資源,比如創建多個線程和多個雙端隊列。

Fork/Join框架使用示例:

讓我們通過一個簡單的需求來使用下 Fork/Join 框架,需求是:計算1 + 2 + 3 + 4的結果。

使用 Fork/Join 框架首先要考慮到的是如何分割任務,如果我們希望每個子任務最多執行兩個數的相加,那么我們設置分割的閾值是2,由於是4個數字相加,所以 Fork/Join 框架會把這個任務 fork 成兩個子任務,子任務一負責計算1 + 2,子任務二負責計算3 + 4,然后再 join 兩個子任務的結果。

因為是有結果的任務,所以必須繼承 RecursiveTask ,實現代碼如下:

import java.util.concurrent.ExecutionException;  
import java.util.concurrent.ForkJoinPool;  
import java.util.concurrent.Future;  
import java.util.concurrent.RecursiveTask;

/**
 * CountTask.
 *
 * @author blinkfox on 2018-01-03.
 */
public class CountTask extends RecursiveTask<Integer> {

    /** 閾值. */
    public static final int THRESHOLD = 2;

    /** 計算的開始值. */
    private int start;

    /** 計算的結束值. */
    private int end;

    /**
     * 構造方法.
     *
     * @param start 計算的開始值
     * @param end 計算的結束值
     */
    public CountTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    /**
     * 執行計算的方法.
     *
     * @return int型結果
     */
    @Override
    protected Integer compute() {
        int sum = 0;

        // 如果任務足夠小就計算任務.
        if ((end - start) <= THRESHOLD) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果任務大於閾值,就分裂成兩個子任務來計算.
            int middle = (start + end) / 2;
            CountTask leftTask = new CountTask(start, middle);
            CountTask rightTask = new CountTask(middle + 1, end);

            // 等待子任務執行完,並得到結果,再合並執行結果.
            leftTask.fork();
            rightTask.fork();
            sum = leftTask.join() + rightTask.join();
        }
        return sum;
    }

    /**
     * main方法.
     *
     * @param args 數組參數
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool fkPool = new ForkJoinPool();
        CountTask task = new CountTask(1, 4);
        Future<Integer> result = fkPool.submit(task);
        System.out.println("result:" + result.get());
    }

}

參考文章:Java7新特性及使用
這里是Java 7的新特性一覽表:http://www.oschina.net/news/20119/new-features-of-java-7


(四)Java 8 相關知識點

關於 Java 8 中新知識點,面試官會讓你說說 Java 8 你了解多少,下面分享一下我收集的 Java 8 新增的知識點的內容,前排申明引用自:Java8新特性及使用

1)接口默認方法和靜態方法

Java 8用默認方法與靜態方法這兩個新概念來擴展接口的聲明。與傳統的接口又有些不一樣,它允許在已有的接口中添加新方法,而同時又保持了與舊版本代碼的兼容性。

1.接口默認方法

默認方法與抽象方法不同之處在於抽象方法必須要求實現,但是默認方法則沒有這個要求。相反,每個接口都必須提供一個所謂的默認實現,這樣所有的接口實現者將會默認繼承它(如果有必要的話,可以覆蓋這個默認實現)。讓我們看看下面的例子:

private interface Defaulable {  
    // Interfaces now allow default methods, the implementer may or
    // may not implement (override) them.
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {  
}

private static class OverridableImpl implements Defaulable {  
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

Defaulable 接口用關鍵字 default 聲明了一個默認方法 notRequired()Defaulable 接口的實現者之一 DefaultableImpl 實現了這個接口,並且讓默認方法保持原樣。Defaulable 接口的另一個實現者 OverridableImpl 用自己的方法覆蓋了默認方法。

1.1 多重繼承的沖突說明:

由於同一個方法可以從不同的接口引入,自然而然的會有沖突的現象,規則如下:

  • 一個聲明在類里面的方法優先於任何默認方法
  • 優先選取最具體的實現
public interface A {

    default void hello() {
        System.out.println("Hello A");
    }

}
public interface B extends A {

    default void hello() {
        System.out.println("Hello B");
    }

}
public class C implements A, B {

    public static void main(String[] args) {
        new C().hello(); // 輸出 Hello B
    }

}

1.2 優缺點:

  • 優點: 可以在不破壞代碼的前提下擴展原有庫的功能。它通過一個很優雅的方式使得接口變得更智能,同時還避免了代碼冗余,並且擴展類庫。
  • 缺點: 使得接口作為協議,類作為具體實現的界限開始變得有點模糊。

1.3 接口默認方法不能重載Object類的任何方法:

接口不能提供對Object類的任何方法的默認實現。簡單地講,每一個java類都是Object的子類,也都繼承了它類中的 equals()/hashCode()/toString() 方法,那么在類的接口上包含這些默認方法是沒有意義的,它們也從來不會被編譯。

在 JVM 中,默認方法的實現是非常高效的,並且通過字節碼指令為方法調用提供了支持。默認方法允許繼續使用現有的Java接口,而同時能夠保障正常的編譯過程。這方面好的例子是大量的方法被添加到 java.util.Collection 接口中去:stream()parallelStream()forEach()removeIf() 等。盡管默認方法非常強大,但是在使用默認方法時我們需要小心注意一個地方:在聲明一個默認方法前,請仔細思考是不是真的有必要使用默認方法。

2.接口靜態方法

Java 8 帶來的另一個有趣的特性是接口可以聲明(並且可以提供實現)靜態方法。在接口中定義靜態方法,使用 static 關鍵字,例如:

public interface StaticInterface {

    static void method() {
        System.out.println("這是Java8接口中的靜態方法!");
    }

}

下面的一小段代碼是上面靜態方法的使用。

public class Main {

    public static void main(String[] args) {
        StaticInterface.method(); // 輸出 這是Java8接口中的靜態方法!
    }

}

Java 支持一個實現類可以實現多個接口,如果多個接口中存在同樣的 static 方法會怎么樣呢?如果有兩個接口中的靜態方法一模一樣,並且一個實現類同時實現了這兩個接口,此時並不會產生錯誤,因為Java8中只能通過接口類調用接口中的靜態方法,所以對編譯器來說是可以區分的。

2)Lambda 表達式

Lambda 表達式(也稱為閉包)是整個Java 8發行版中最受期待的在Java語言層面上的改變,Lambda允許把函數作為一個方法的參數(即:行為參數化,函數作為參數傳遞進方法中)。

一個 Lambda 可以由用逗號分隔的參數列表、–> 符號與函數體三部分表示。

首先看看在老版本的Java中是如何排列字符串的:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");  
Collections.sort(names, new Comparator<String>() {

    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }

});

只需要給靜態方法 Collections.sort 傳入一個List對象以及一個比較器來按指定順序排列。通常做法都是創建一個匿名的比較器對象然后將其傳遞給sort方法。 在Java 8 中你就沒必要使用這種傳統的匿名對象的方式了,Java 8提供了更簡潔的語法,lambda表達式:

Collections.sort(names, (String a, String b) -> {  
    return b.compareTo(a);
});

看到了吧,代碼變得更短且更具有可讀性,但是實際上還可以寫得更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));  

對於函數體只有一行代碼的,你可以去掉大括號{}以及return關鍵字,但是你還可以寫得更短點:

Collections.sort(names, (a, b) -> b.compareTo(a));  

Java編譯器可以自動推導出參數類型,所以你可以不用再寫一次類型。

更多 Lambda 表達式的示例在這里:Java8 lambda表達式10個示例

3)函數式接口

Lambda 表達式是如何在 Java 的類型系統中表示的呢?每一個Lambda表達式都對應一個類型,通常是接口類型。而函數式接口是指僅僅只包含一個抽象方法的接口,每一個該類型的Lambda表達式都會被匹配到這個抽象方法。因為默認方法不算抽象方法,所以你也可以給你的函數式接口添加默認方法。

我們可以將Lambda表達式當作任意只包含一個抽象方法的接口類型,確保你的接口一定達到這個要求,你只需要給你的接口添加 @FunctionalInterface 注解,編譯器如果發現你標注了這個注解的接口有多於一個抽象方法的時候會報錯的。

示例如下:

@FunctionalInterface
interface Converter<F, T> {  
    T convert(F from);
}

Converter<String, Integer> converter = (from) -> Integer.valueOf(from);  
Integer converted = converter.convert("123");  
System.out.println(converted); // 123  

注意: 如果 @FunctionalInterface 如果沒有指定,上面的代碼也是對的。
更多參考: Java 8——Lambda表達式Java8新特性及使用

4)方法引用

1.概述:

在學習了Lambda表達式之后,我們通常使用Lambda表達式來創建匿名方法。然而,有時候我們僅僅是調用了一個已存在的方法。如下:

Arrays.sort(strArray, (s1, s2) -> s1.compareToIgnoreCase(s2));  

在Java8中,我們可以直接通過方法引用來簡寫Lambda表達式中已經存在的方法。

Arrays.sort(strArray, String::compareToIgnoreCase);  

這種特性就叫做方法引用(Method Reference)。

方法引用是用來直接訪問類或者實例的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由兼容的函數式接口構成的目標類型上下文。計算時,方法引用會創建函數式接口的一個實例。當Lambda表達式中只是執行一個方法調用時,不用Lambda表達式,直接通過方法引用的形式可讀性更高一些。方法引用是一種更簡潔易懂的Lambda表達式。

注意: 方法引用是一個Lambda表達式,其中方法引用的操作符是雙冒號::。

2.分類:

方法引用的標准形式是:類名::方法名。(注意:只需要寫方法名,不需要寫括號)

有以下四種形式的方法引用:

  • 引用靜態方法: ContainingClass::staticMethodName
  • 引用某個對象的實例方法: containingObject::instanceMethodName
  • 引用某個類型的任意對象的實例方法:ContainingType::methodName
  • 引用構造方法: ClassName::new

3.示例:

使用示例如下:

public class Person {

    String name;

    LocalDate birthday;

    public Person(String name, LocalDate birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }

    @Override
    public String toString() {
        return this.name;
    }
}
public class MethodReferenceTest {

    @Test
    public static void main() {
        Person[] pArr = new Person[] {
            new Person("003", LocalDate.of(2016,9,1)),
            new Person("001", LocalDate.of(2016,2,1)),
            new Person("002", LocalDate.of(2016,3,1)),
            new Person("004", LocalDate.of(2016,12,1))
        };

        // 使用匿名類
        Arrays.sort(pArr, new Comparator<Person>() {
            @Override
            public int compare(Person a, Person b) {
                return a.getBirthday().compareTo(b.getBirthday());
            }
        });

        //使用lambda表達式
        Arrays.sort(pArr, (Person a, Person b) -> {
            return a.getBirthday().compareTo(b.getBirthday());
        });

        //使用方法引用,引用的是類的靜態方法
        Arrays.sort(pArr, Person::compareByAge);
    }

}

5)Steam

Java8添加的 Stream API(java.util.stream) 把真正的函數式編程風格引入到Java中。這是目前為止對Java類庫最好的補充,因為 Stream API 可以極大提供Java程序員的生產力,讓程序員寫出高效率、干凈、簡潔的代碼。使用 Steam 寫出來的代碼真的能讓人興奮,這里鏈出之前的一篇文章:Java 8——函數式數據處理(流)

流可以是無限的、有狀態的,可以是順序的,也可以是並行的。在使用流的時候,你首先需要從一些來源中獲取一個流,執行一個或者多個中間操作,然后執行一個最終操作。中間操作包括filtermapflatMappeeldistinctsortedlimitsubstream。終止操作包括 forEachtoArrayreducecollectminmaxcountanyMatchallMatchnoneMatchfindFirstfindAnyjava.util.stream.Collectors 是一個非常有用的實用類。該類實現了很多歸約操作,例如將流轉換成集合和聚合元素。

1.一些重要方法說明:

  • stream: 返回數據流,集合作為其源
  • parallelStream: 返回並行數據流, 集合作為其源
  • filter: 方法用於過濾出滿足條件的元素
  • map: 方法用於映射每個元素對應的結果
  • forEach: 方法遍歷該流中的每個元素
  • limit: 方法用於減少流的大小
  • sorted: 方法用來對流中的元素進行排序
  • anyMatch: 是否存在任意一個元素滿足條件(返回布爾值)
  • allMatch: 是否所有元素都滿足條件(返回布爾值)
  • noneMatch: 是否所有元素都不滿足條件(返回布爾值)
  • collect: 方法是終端操作,這是通常出現在管道傳輸操作結束標記流的結束

2.一些使用示例:

2.1 Filter 過濾:

stringCollection  
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

2.2 Sort 排序:

stringCollection  
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

2.3 Map 映射:

stringCollection  
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

2.4 Match 匹配:

boolean anyStartsWithA = stringCollection  
        .stream()
        .anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA);      // true

boolean allStartsWithA = stringCollection  
        .stream()
        .allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ = stringCollection  
        .stream()
        .noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ);      // true  

2.5 Count 計數:

long startsWithB = stringCollection  
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();
System.out.println(startsWithB);    // 3  

2.6 Reduce 規約:

Optional<String> reduced = stringCollection  
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println); 

6)Optional

到目前為止,臭名昭著的空指針異常是導致Java應用程序失敗的最常見原因。以前,為了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染,它鼓勵程序員寫更干凈的代碼。受到Google Guava的啟發,Optional類已經成為Java 8類庫的一部分。

Optional實際上是個容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。

我們下面用兩個小例子來演示如何使用Optional類:一個允許為空值,一個不允許為空值。

Optional<String> fullName = Optional.ofNullable(null);  
System.out.println("Full Name is set? " + fullName.isPresent());  
System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]"));  
System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));  

如果Optional類的實例為非空值的話,isPresent()返回true,否從返回false。為了防止Optional為空值,orElseGet()方法通過回調函數來產生一個默認值。map()函數對當前Optional的值進行轉化,然后返回一個新的Optional實例。orElse()方法和orElseGet()方法類似,但是orElse接受一個默認值而不是一個回調函數。下面是這個程序的輸出:

Full Name is set? false  
Full Name: [none]  
Hey Stranger!  

讓我們來看看另一個例子:

Optional<String> firstName = Optional.of("Tom");  
System.out.println("First Name is set? " + firstName.isPresent());  
System.out.println("First Name: " + firstName.orElseGet(() -> "[none]"));  
System.out.println(firstName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));  
System.out.println();  

下面是程序的輸出:

First Name is set? true  
First Name: Tom  
Hey Tom! 

7)Date/Time API

Java 8 在包java.time下包含了一組全新的時間日期API。新的日期API和開源的Joda-Time庫差不多,但又不完全一樣,下面的例子展示了這組新API里最重要的一些部分:

1.Clock 時鍾:

Clock類提供了訪問當前日期和時間的方法,Clock是時區敏感的,可以用來取代System.currentTimeMillis()來獲取當前的微秒數。某一個特定的時間點也可以使用Instant類來表示,Instant類也可以用來創建老的java.util.Date對象。代碼如下:

Clock clock = Clock.systemDefaultZone();  
long millis = clock.millis();  
Instant instant = clock.instant();  
Date legacyDate = Date.from(instant);   // legacy java.util.Date  

2.Timezones 時區:

在新API中時區使用ZoneId來表示。時區可以很方便的使用靜態方法of來獲取到。時區定義了到UTS時間的時間差,在Instant時間點對象到本地日期對象之間轉換的時候是極其重要的。代碼如下:

System.out.println(ZoneId.getAvailableZoneIds());  
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");  
ZoneId zone2 = ZoneId.of("Brazil/East");  
System.out.println(zone1.getRules());  
System.out.println(zone2.getRules());  
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]

3.LocalTime 本地時間:

LocalTime定義了一個沒有時區信息的時間,例如 晚上10點,或者 17:30:15。下面的例子使用前面代碼創建的時區創建了兩個本地時間。之后比較時間並以小時和分鍾為單位計算兩個時間的時間差。代碼如下:

LocalTime now1 = LocalTime.now(zone1);  
LocalTime now2 = LocalTime.now(zone2);  
System.out.println(now1.isBefore(now2));  // false  
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);  
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);  
System.out.println(hoursBetween);       // -3  
System.out.println(minutesBetween);     // -239  

LocalTime提供了多種工廠方法來簡化對象的創建,包括解析時間字符串。代碼如下:

LocalTime late = LocalTime.of(23, 59, 59);  
System.out.println(late);       // 23:59:59  
DateTimeFormatter germanFormatter = DateTimeFormatter  
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);  
System.out.println(leetTime);   // 13:37  

4.LocalDate 本地日期:

LocalDate表示了一個確切的日期,比如2014-03-11。該對象值是不可變的,用起來和LocalTime基本一致。下面的例子展示了如何給Date對象加減天/月/年。另外要注意的是這些對象是不可變的,操作返回的總是一個新實例。代碼如下:

LocalDate today = LocalDate.now();  
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);  
LocalDate yesterday = tomorrow.minusDays(2);  
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);  
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();

System.out.println(dayOfWeek);    // FRIDAY  

從字符串解析一個LocalDate類型和解析LocalTime一樣簡單。代碼如下:

DateTimeFormatter germanFormatter = DateTimeFormatter  
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);  
System.out.println(xmas);   // 2014-12-24  

5.LocalDateTime 本地日期時間:

LocalDateTime同時表示了時間和日期,相當於前兩節內容合並到一個對象上了。LocalDateTimeLocalTime還有LocalDate一樣,都是不可變的。LocalDateTime提供了一些能訪問具體字段的方法。代碼如下:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);  
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();  
System.out.println(dayOfWeek);      // WEDNESDAY  
Month month = sylvester.getMonth();  
System.out.println(month);          // DECEMBER  
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);  
System.out.println(minuteOfDay);    // 1439  

只要附加上時區信息,就可以將其轉換為一個時間點Instant對象,Instant時間點對象可以很容易的轉換為老式的java.util.Date。代碼如下:

Instant instant = sylvester  
        .atZone(ZoneId.systemDefault())
        .toInstant();
Date legacyDate = Date.from(instant);  
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014  

格式化LocalDateTime和格式化時間和日期一樣的,除了使用預定義好的格式外,我們也可以自己定義格式。代碼如下:

DateTimeFormatter formatter =  
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);  
String string = formatter.format(parsed);  
System.out.println(string);     // Nov 03, 2014 - 07:13  

java.text.NumberFormat不一樣的是新版的DateTimeFormatter是不可變的,所以它是線程安全的。

關於Java8中日期API更多的使用示例可以參考Java 8中關於日期和時間API的20個使用示例

8)重復注解

自從Java 5引入了注解機制,這一特性就變得非常流行並且廣為使用。然而,使用注解的一個限制是相同的注解在同一位置只能聲明一次,不能聲明多次。Java 8打破了這條規則,引入了重復注解機制,這樣相同的注解可以在同一地方聲明多次。

重復注解機制本身必須用@Repeatable注解。事實上,這並不是語言層面上的改變,更多的是編譯器的技巧,底層的原理保持不變。讓我們看一個快速入門的例子:

import java.lang.annotation.ElementType;  
import java.lang.annotation.Repeatable;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;

public class RepeatingAnnotations {

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Filters {
        Filter[] value();
    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Filters.class)
    public @interface Filter {
        String value();
    };

    @Filter("filter1")
    @Filter("filter2")
    public interface Filterable {
    }

    public static void main(String[] args) {
        for(Filter filter: Filterable.class.getAnnotationsByType(Filter.class)) {
            System.out.println(filter.value());
        }
    }

}

正如我們看到的,這里有個使用@Repeatable(Filters.class)注解的注解類FilterFilters僅僅是Filter注解的數組,但Java編譯器並不想讓程序員意識到Filters的存在。這樣,接口Filterable就擁有了兩次Filter(並沒有提到Filter)注解。

同時,反射相關的API提供了新的函數getAnnotationsByType()來返回重復注解的類型(請注意Filterable.class.getAnnotation(Filters.class)經編譯器處理后將會返回Filters的實例)。

9)擴展注解的支持

Java 8擴展了注解的上下文。現在幾乎可以為任何東西添加注解:局部變量、泛型類、父類與接口的實現,就連方法的異常也能添加注解。下面演示幾個例子:

import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
import java.util.ArrayList;  
import java.util.Collection;

public class Annotations {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings("unused")
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }

}

10)Base 64

在Java 8中,Base64編碼已經成為Java類庫的標准。它的使用十分簡單,下面讓我們看一個例子:

import java.nio.charset.StandardCharsets;  
import java.util.Base64;

public class Base64s {

    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
        System.out.println(encoded);

        final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
        System.out.println(decoded);
    }

}

程序在控制台上輸出了編碼后的字符與解碼后的字符:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==  
Base64 finally in Java 8!  

Base64類同時還提供了對URL、MIME友好的編碼器與解碼器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。

11)JavaFX

JavaFX是一個強大的圖形和多媒體處理工具包集合,它允許開發者來設計、創建、測試、調試和部署富客戶端程序,並且和Java一樣跨平台。從Java8開始,JavaFx已經內置到了JDK中。關於JavaFx更詳細的文檔可參考JavaFX中文文檔

12)HashMap的底層實現有變化

Java8中,HashMap內部實現又引入了紅黑樹(數組+鏈表+紅黑樹),使得HashMap的總體性能相較於Java7有比較明顯的提升。

13)JVM內存管理方面,由元空間代替了永久代。

區別:

  1. 元空間並不在虛擬機中,而是使用本地內存
  2. 默認情況下,元空間的大小僅受本地內存限制
  3. 也可以通過-XX:MetaspaceSize指定元空間大小

(五)Java 9 相關知識點

引用自文章:Java 9 中的 9 個新特性Java 9 新特性概述——IBM【譯】使用示例帶你提前了解 Java 9 中的新特性

1)Java 9 PEPK(JShell)

Oracle 公司(Java Library 開發者)新引進一個代表 Java Shell 的稱之為 “jshell” 或者 REPL(Read Evaluate Print Loop)的新工具。該工具可以被用來執行和測試任何 Java 中的結構,如 class,interface,enum,object,statements 等。使用非常簡單。

JDK 9 EA(Early Access)下載地址:https://jdk9.java.net/download/

G:\>jshell
|  Welcome to JShell -- Version 9-ea
|  For an introduction type: /help intro
jshell> int a = 10
a ==> 10
jshell> System.out.println("a value = " + a )
a value = 10

2)集合工廠方法

通常,您希望在代碼中創建一個集合(例如,List 或 Set ),並直接用一些元素填充它。 實例化集合,幾個 “add” 調用,使得代碼重復。 Java 9,添加了幾種集合工廠方法:

Set<Integer> ints = Set.of(1, 2, 3);
List<String> strings = List.of("first", "second");

除了更短和更好閱讀之外,這些方法也可以避免您選擇特定的集合實現。 事實上,從工廠方法返回已放入數個元素的集合實現是高度優化的。這是可能的,因為它們是不可變的:在創建后,繼續添加元素到這些集合會導致 “UnsupportedOperationException” 。

3)接口中的私有方法

在 Java 8 中,我們可以在接口中使用默認或者靜態方法提供一些實現方式,但是不能創建私有方法。

為了避免冗余代碼和提高重用性,Oracle 公司准備在 Java SE 9 接口中引入私有方法。也就是說從 Java SE 9 開始,我們也能夠在接口類中使用 ‘private’ 關鍵字寫私有化方法和私有化靜態方法。

接口中的私有方法與 class 類中的私有方法在寫法上並無差異,如:

public interface Card{
  private Long createCardID(){
    // Method implementation goes here.
  }
  private static void displayCardDetails(){
    // Method implementation goes here.
  }
}

4)Java 平台級模塊系統

這里只給出解決的問題,僅限了解....

Java 9 的定義功能是一套全新的模塊系統。當代碼庫越來越大,創建復雜,盤根錯節的“意大利面條式代碼”的幾率呈指數級的增長。這時候就得面對兩個基礎的問題: 很難真正地對代碼進行封裝, 而系統並沒有對不同部分(也就是 JAR 文件)之間的依賴關系有個明確的概念。每一個公共類都可以被類路徑之下任何其它的公共類所訪問到, 這樣就會導致無意中使用了並不想被公開訪問的 API。此外,類路徑本身也存在問題: 你怎么知曉所有需要的 JAR 都已經有了, 或者是不是會有重復的項呢? 模塊系統把這倆個問題都給解決了。

5)進程 API

Java SE 9 迎來一些 Process API 的改進,通過添加一些新的類和方法來優化系統級進程的管控。

Process API 中的兩個新接口:

  • java.lang.ProcessHandle
  • java.lang.ProcessHandle.Info

Process API 示例

ProcessHandle currentProcess = ProcessHandle.current();
System.out.println("Current Process Id: = " + currentProcess.getPid());

6)Try With Resources Improvement

我們知道,Java SE 7 引入了一個新的異常處理結構:Try-With-Resources,來自動管理資源。這個新的聲明結構主要目的是實現“Automatic Better Resource Management”(“自動資源管理”)。

Java SE 9 將對這個聲明作出一些改進來避免一些冗長寫法,同時提高可讀性。

Java SE 7 示例

void testARM_Before_Java9() throws IOException {
	BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
	try (BufferedReader reader2 = reader1) {
		System.out.println(reader2.readLine());
	}
}

Java SE 9 示例

void testARM_Java9() throws IOException {
	BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
	try (reader1) {
		System.out.println(reader1.readLine());
	}
}

7)CompletableFuture API Improvements

在 Java SE 9 中,Oracle 公司將改進 CompletableFuture API 來解決一些 Java SE 8 中出現的問題。這些被添加的 API 將用來支持一些延時和超時操作,實用方法和更好的子類化。

Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);

這里的 delayedExecutor() 是靜態實用方法,用來返回一個在指定延時時間提交任務到默認執行器的新 Executor 對象。

8)反應式流 ( Reactive Streams )

反應式編程的思想最近得到了廣泛的流行。 在 Java 平台上有流行的反應式 庫 RxJava 和 R eactor。反應式流規范的出發點是提供一個帶非阻塞負壓( non-blocking backpressure ) 的異步流處理規范。反應式流規范的核心接口已經添加到了 Java9 中的 java.util.concurrent.Flow 類中。

Flow 中包含了 Flow.Publisher、Flow.Subscriber、Flow.Subscription 和 F low.Processor 等 4 個核心接口。Java 9 還提供了 SubmissionPublisher 作為 Flow.Publisher 的一個實現。RxJava 2 和 Reactor 都可以很方便的 與 Flow 類的核心接口進行互操作。

9)改進的 Stream API

長期以來,Stream API 都是 Java 標准庫最好的改進之一。通過這套 API 可以在集合上建立用於轉換的申明管道。在 Java 9 中它會變得更好。Stream 接口中添加了 4 個新的方法:dropWhile, takeWhile, ofNullable。還有個 iterate 方法的新重載方法,可以讓你提供一個 Predicate (判斷條件)來指定什么時候結束迭代:

IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

第二個參數是一個 Lambda,它會在當前 IntStream 中的元素到達 100 的時候返回 true。因此這個簡單的示例是向控制台打印 1 到 99。

除了對 Stream 本身的擴展,Optional 和 Stream 之間的結合也得到了改進。現在可以通過 Optional 的新方法 stram 將一個 Optional 對象轉換為一個(可能是空的) Stream 對象:

Stream<Integer> s = Optional.of(1).stream();

在組合復雜的 Stream 管道時,將 Optional 轉換為 Stream 非常有用。

10)HTTP/2

Java 9 中有新的方式來處理 HTTP 調用。這個遲到的特性用於代替老舊的 HttpURLConnection API,並提供對 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所謂的孵化器模塊交付。也就是說,這套 API 不能保證 100% 完成。不過你可以在 Java 9 中開始使用這套 API:

HttpClient client = HttpClient.newHttpClient();
 
HttpRequest req =
   HttpRequest.newBuilder(URI.create("http://www.google.com"))
              .header("User-Agent","Java")
              .GET()
              .build();
 
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());

HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());

除了這個簡單的請求/響應模型之外,HttpClient 還提供了新的 API 來處理 HTTP/2 的特性,比如流和服務端推送。

11)Optional Class Improvements

在 Java SE 9 中,Oracle 公司添加了一些新的實用方法到 java.util.Optional 類里面。這里我將使用一些簡單的示例來描述其中的一個:stream 方法。

如果一個值出現在給定 Optional 對象中,stream() 方法可以返回包含該值的一個順序 Stream 對象。否則,將返回一個空 Stream。

stream() 方法已經被添加,並用來在 Optional 對象中使用,如:

Stream<Optional> emp = getEmployee(id)
Stream empStream = emp.flatMap(Optional::stream)

這里的 Optional.stream() 方法被用來轉化 Employee 可選流對象 到 Employee 流中,如此我們便可以在后續代碼中使用這個結果。

12)多版本兼容 JAR

我們最后要來着重介紹的這個特性對於庫的維護者而言是個特別好的消息。當一個新版本的 Java 出現的時候,你的庫用戶要花費數年時間才會切換到這個新的版本。這就意味着庫得去向后兼容你想要支持的最老的 Java 版本 (許多情況下就是 Java 6 或者 7)。這實際上意味着未來的很長一段時間,你都不能在庫中運用 Java 9 所提供的新特性。幸運的是,多版本兼容 JAR 功能能讓你創建僅在特定版本的 Java 環境中運行庫程序時選擇使用的 class 版本:

multirelease.jar
├── META-INF
│   └── versions
│       └── 9
│           └── multirelease
│               └── Helper.class
├── multirelease
    ├── Helper.class
    └── Main.class

在上述場景中, multirelease.jar 可以在 Java 9 中使用, 不過 Helper 這個類使用的不是頂層的 multirelease.Helper 這個 class, 而是處在“META-INF/versions/9”下面的這個。這是特別為 Java 9 准備的 class 版本,可以運用 Java 9 所提供的特性和庫。同時,在早期的 Java 諸版本中使用這個 JAR 也是能運行的,因為較老版本的 Java 只會看到頂層的這個 Helper 類。


歡迎轉載,轉載請注明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關注公眾微信號:wmyskxz_javaweb
分享自己的Java Web學習之路以及各種Java學習資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM