原文連接:https://dzone.com/articles/var-work-in-progressjava
做者:Anghel Leonardgit
譯者:沈歌編程
Java局部變量類型推斷(LVTI),簡稱var
類型(標識符var
不是一個關鍵字,是一個預留類型名),Java 10中經過JEP 286: Local-Variable Type Inference 添加進來。做爲100%編譯特徵,它不會影響字節碼,運行時或者性能。在編譯時,編譯器會檢查賦值語句右側代碼,從而推斷出具體類型。它查看聲明的右側,若是這是一個初始化語句,它會用那個類型取代var
。另外,它很是有助於減小冗餘代碼和樣板代碼。它還只在旨在編寫代碼時所涉及的儀式。例如,使用var evenAndOdd=...
代替Map<Boolean, List<Integer>> evenAndOdd...
很是方便。根據用例,它有一個代碼可讀性的權衡,會在下面第一條中提到。數組
此外,這裏有26條細則,覆蓋了var
類型的用例,包括它的限制。安全
一般咱們在起全局變量名的時候會注意這一點,可是選擇局部變量名的時候不太注意。尤爲是當方法很短,方法名和實現都不錯的時候,咱們趨向於簡化咱們的變量名。可是當咱們使用var
替代顯式類型的時候,具體的類型是經過編譯器推斷出來的。因此,對於人來講閱讀或者理解代碼很是困難。在這一點上var
削弱了代碼可讀性。這種事情之因此會發生,是由於大多數狀況下,咱們會把變量類型當成是第一信息,而把變量名當成第二信息。可是使用var
的時候,偏偏相反。ide
即便到這裏,一些朋友仍然堅持局部變量名短點好。咱們看一下:性能
// HAVING public boolean callDocumentationTask() { DocumentationTool dtl = ToolProvider.getSystemDocumentationTool(); DocumentationTask dtt = dtl.getTask(...); return dtt.call(); }
咱們換成var
時,避免:fetch
// AVOID public boolean callDocumentationTask() { var dtl = ToolProvider.getSystemDocumentationTool(); var dtt = dtl.getTask(...); return dtt.call(); }
更好:優化
// PREFER public boolean callDocumentationTask() { var documentationTool = ToolProvider.getSystemDocumentationTool(); var documentationTask = documentationTool.getTask(...); return documentationTask.call(); }
避免:.net
// AVOID public List<Product> fetchProducts(long userId) { var u = userRepository.findById(userId); var p = u.getCart(); return p; }
更好:
// PREFER public List<Product> fetchProducts(long userId) { var user = userRepository.findById(userId); var productList = user.getCart(); return productList; }
爭取爲局部變量起有意義的名字並不意味着要掉入過分命名的坑,避免在短方法中使用單一類型的數據流:
// AVOID var byteArrayOutputStream = new ByteArrayOutputStream();
用以下代碼代替更加清晰:
// PREFER var outputStream = new ByteArrayOutputStream(); // or var outputStreamOfFoo = new ByteArrayOutputStream();
另外,你知道嗎,Java內部使用了一個類名字叫:InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
額。。。命名這個類型的變量是個挑戰。
若是在基本數據類型中不使用有效的數據類型標誌,咱們可能會發現預期的類型和推測出的類型不一致。這是因爲var
的隱式類型轉換致使的。
例如,下面兩行代碼的表現是符合預期的,首先,咱們聲明一個boolean
和一個char
使用顯式類型:
boolean flag = true; // 這是一個boolean類型 char a = 'a'; // 這是一個char類型
如今,咱們使用var
代替顯式基本類型:
var flag = true; // 被推斷爲boolean類型 var a = 'a'; // 被推斷爲char類型
到目前爲止,一切都很完美。接下來,咱們看一下相同邏輯下的int
, long
, double
和 float
:
int intNumber = 20; // 這是int類型 long longNumber = 20; // 這是long類型 float floatNumber = 20; // 這是float類型, 20.0 double doubleNumber = 20; // 這是double類型, 20.0
以上代碼是很常見並且清晰的,如今咱們使用var
:
避免:
// AVOID var intNumber = 20; // 推斷爲int var longNumber = 20; // 推斷爲int var floatNumber = 20; // 推斷爲int var doubleNumber = 20; // 推斷爲int
四個變量都被推斷成了int
。爲了修正這個行爲,咱們須要依賴Java中的數據類型標誌。
更好實現:
// PREFER var intNumber = 20; // 推斷爲int var longNumber = 20L; // 推斷爲long var floatNumber = 20F; // 推斷爲float, 20.0 var doubleNumber = 20D; // 推斷爲double, 20.0
可是若是咱們使用小數聲明一個數字,會發生什麼呢?當你認爲你的數字是一個float
的時候,避免這樣作:
// 避免,若是這是一個float var floatNumber = 20.5; // 推斷爲double
你應該用對應的數據類型標誌來避免這樣的問題:
// 更好, 若是這是一個float var floatNumber = 20.5F; // 推斷爲float
在某些狀況下,Var和隱式類型轉換能夠維持可維護性。例如,假設咱們的代碼包含兩個方法:第一個方法接收一個包含不一樣條目的購物卡,比較市場中不一樣的價格,計算出最好的價格,並彙總返回float
類型的總價。另外一個方法簡單的把這個float
價格從卡中扣除。
首先,咱們看一下計算最好價格的方法:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
而後,咱們看一下扣款的方法:
public boolean debitCard(float amount, ...) { ... }
如今,咱們把這兩個方法彙總,提供一個服務方法。顧客選擇要買的商品,計算最優價格,而後扣款:
// AVOID public void purchaseCart(long customerId) { ... float price = computeBestPrice(...); debitCard(price, ...); }
一段時間後,公司想要去除價格中的小數部分做爲打折策略,使用int
代替了float
, 咱們須要修改代碼。
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
問題在於咱們使用了顯示類型float
,這樣的更改不能被兼容。代碼會報編譯時錯誤。可是若是咱們預判到這種狀況,使用var
代替float
, 咱們的代碼會由於隱式類型轉換而變得沒有兼容性問題。
// PREFER public void purchaseCart(long customerId) { ... var price = computeBestPrice(...); debitCard(price, ...); }
一些Java基礎數據類型不支持數據類型標誌。例如byte
和short
。使用顯式基礎數據類型時沒有任何問題。使用var
代替的時候:
// 這樣更好,而不是使用var byte byteNumber = 45; // 這是byte類型 short shortNumber = 4533; // 這是short類型
爲何在這種狀況下顯式類型比var
好呢?咱們切換到var
.注意示例中都會被推斷爲int
, 而不是咱們預期的類型。
避免使用如下代碼:
// AVOID var byteNumber = 45; // 推斷爲int var shortNumber = 4533; // 推斷爲int
這裏沒有基礎數據類型幫助咱們,所以咱們須要依賴顯示強制類型轉換。從我的角度來說,我會避免這麼用,由於沒啥好處,可是能夠這麼用。
若是你真的想用var
,這麼用:
// 若是你真的想用var,這麼寫 var byteNumber = (byte) 45; // 推斷爲byte var shortNumber = (short) 4533; // 推斷爲short
使用var
有助於提供更加簡練的代碼。例如, 在使用構造方法時(這是使用局部變量的常見示例),咱們能夠簡單地避免重複類名的必要性,從而消除冗餘。
避免:
// AVOID MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...);
更好:
// PREFER var inputStream = new MemoryCacheImageInputStream(...);
在下面的結構中,var
也是一個簡化代碼而不丟失信息的好方法。
避免:
// AVOID JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fm = compiler.getStandardFileManager(...);
更好:
// PREFER var compiler = ToolProvider.getSystemJavaCompiler(); var fileManager = compiler.getStandardFileManager(...);
爲何這樣基於var
的例子咱們感受比較舒服呢?由於須要的信息已經在變量名中了。可是若是使用var
加上變量名,仍是會丟失信息,那麼最好避免使用var
。
避免:
// AVOID public File fetchCartContent() { return new File(...); } // As a human, is hard to infer the "cart" type without // inspecting the fetchCartContent() method var cart = fetchCartContent();
使用如下代碼代替:
// PREFER public File fetchCartContent() { return new File(...); } File cart = fetchCartContent();
思考一個基於java.nio.channels.Selector
的例子。這個類有一個靜態方法叫作open()
,返回一個新的Selector
實例而且執行open動做。可是Selector.open()
很容易被認爲返回一個boolean
標識打開當前選擇器是否成功,或者返回void
。使用var
致使丟失信息會引起這樣的困擾。
var
類型是編譯時安全的。這意味着若是咱們試圖實現一個錯的賦值,會致使編譯時報錯。例如,如下代碼編譯不會經過。
// 編譯通不過 var items = 10; items = "10 items"; // 不兼容類型: String不能轉爲int
如下代碼編譯會經過
var items = 10; items = 20;
這個代碼也會編譯經過:
var items = "10"; items = "10 items" ;
因此,一旦編譯器已經推斷出了var
對應的類型,咱們只能賦值對應類型的值給它。
在Java中,咱們使用「面向接口編程」的技術。
例如,咱們建立一個ArrayList
的實例,以下(綁定代碼到抽象):
List<String> products = new ArrayList<>();
咱們避免這樣的事情(綁定代碼到實現):
ArrayList<String> products = new ArrayList<>();
因此,經過第一個例子建立一個ArrayList
實例更好,可是咱們也須要聲明一個List
類型的變量。由於List
是一個接口,咱們能夠很容易的切換到List
的其餘實現類,而無需額外的修改。
這就是「面向接口編程」,可是var
不能這麼用。這意味着當咱們使用var
時,推斷出的類型是實現類的類型。例如,下面這行代碼,推測出的類型是ArrayList<String>
:
var productList = new ArrayList<String>(); // 推斷爲ArrayList<String>
如下幾個論點支持這一行爲:
var
是局部變量,大多數狀況下,「面向接口編程」在方法參數和返回類型的時候更有用。若是不存在推斷類型所需的信息,則與菱形運算符組合的var
類型可能致使意外推斷類型。
在Java 7以前的Coin項目中,咱們寫了這樣的代碼:
//顯式指定泛型類的實例化參數類型 List<String> products = new ArrayList<String>();
從Java 7開始,咱們有了菱形運算符,它可以推斷泛型類實例化參數類型:
// inferring generic class's instantiation parameter type List<String> products = new ArrayList<>();
那麼,如下代碼推斷出什麼類型呢?
首先應該避免這麼用:
// AVOID var productList = new ArrayList<>(); // 推斷爲ArrayList<Object>
推斷出的類型是Object的ArrayList。之因此會這樣是由於沒有找到可以推測到預期類型爲String的信息,這會致使返回一個最普遍可用類型,Object。
因此爲了不這樣的情形,咱們必須提供可以推斷到預測類型的信息。這個能夠直接給也能夠間接給。
更好的實現(直接):
// PREFER var productList = new ArrayList<String>(); // 推斷爲ArrayList<String>
更好的實現(間接):
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack); // 推斷爲ArrayList<String>
更好的實現(間接):
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2); // 推斷爲List<Product> // 不要這麼幹 var listofProduct = List.of(); // 推斷爲List<Object> listofProduct.add(p1); listofProduct.add(p2);
咱們都知道Java中如何聲明一個數組:
int[] numbers = new int[5]; // 或者,這樣寫不太好 int numbers[] = new int[5];
那麼怎麼用var呢?左邊不須要使用括號。
避免這麼寫(編譯不經過):
// 編譯通不過 var[] numbers = new int[5]; // 或者 var numbers[] = new int[5];
應該這麼用:
// PREFER var numbers = new int[5]; // 推斷爲int數組 numbers[0] = 2; // 對 numbers[0] = 2.2; // 錯 numbers[0] = "2"; // 錯
另外,這麼用也不能編譯,這是由於右邊沒有本身的類型。
// 顯式類型表現符合預期 int[] numbers = {1, 2, 3}; // 編譯通不過 var numbers = {1, 2, 3}; var numbers[] = {1, 2, 3}; var[] numbers = {1, 2, 3};
若是你是複合聲明的粉絲,你必定要知道var
不支持這種聲明。下面的代碼不能編譯:
// 編譯通不過 // error: 'var' 不容許複合聲明 var hello = "hello", bye = "bye", welcome = "welcome";
用下面的代碼代替:
// PREFER String hello = "hello", bye = "bye", welcome = "welcome";
或者這麼用:
// PREFER var hello = "hello"; var bye = "bye"; var welcome = "welcome";
局部變量應該保持小做用域,我肯定你在var
出現以前就聽過這個,這樣能夠加強代碼可讀性,也方便更快的修復bug。
例如咱們定義一個java棧:
避免:
// AVOID ... var stack = new Stack<String>(); stack.push("George"); stack.push("Tyllen"); stack.push("Martin"); stack.push("Kelly"); ... // 50行不用stack的代碼 // George, Tyllen, Martin, Kelly stack.forEach(...); ...
注意咱們調用forEach
方法,該方法繼承自java.util.Vector
.這個方法將以Vector的方式遍歷棧。如今咱們準備切換Stack
到ArrayDeque
,切換以後forEach()
方法將變成ArrayDeque
的,將以stack(LIFO)的方式遍歷stack。
// AVOID ... var stack = new ArrayDeque<String>(); stack.push("George"); stack.push("Tyllen"); stack.push("Martin"); stack.push("Kelly"); ... // 50行不用stack的代碼 // Kelly, Martin, Tyllen, George stack.forEach(...); ...
這不是咱們想要的,咱們很難看出引入了一個錯誤,由於包含forEach()
部分的代碼不在研發完成修改的代碼附近。爲了快速修復這個錯誤,並避免上下滾動來了解發生了什麼,最好縮小stack變量的做用域範圍。
最好這麼寫:
// PREFER ... var stack = new Stack<String>(); stack.push("George"); stack.push("Tyllen"); stack.push("Martin"); stack.push("Kelly"); ... // George, Tyllen, Martin, Kelly stack.forEach(...); ... // 50行不用stack的代碼
如今,當開發人員從Stack
切換到ArrayQueue
的時候,他們可以很快的注意到bug,並修復它。
咱們能夠在三元運算符的右側使用不一樣類型的操做數。
使用具體類型的時候,如下代碼沒法編譯:
// 編譯通不過 List code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); // or Set code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
雖然咱們能夠這麼寫:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
這樣也編譯不過:
// 編譯通不過: int code = intOrString ? 12112 : "12112"; String code = intOrString ? 12112 : "12112";
可是咱們能夠這麼寫:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
在這種狀況下,使用var
更好:
// PREFER // inferred as Collection<Integer> var code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); // inferred as Serializable var code = intOrString ? 12112 : "12112";
千萬不要從這些例子中得出var類型是在運行時作類型推斷的,它不是!!!
固然,咱們使用相同的類型做爲操做數時var
是支持的。
// 推斷爲float var code = oneOrTwoDigits ? 1211.2f : 1211.25f;
咱們能很是簡單的在for循環中用var
類型取代具體類型。這是兩個例子。
var替換int:
// 顯式類型 for (int i = 0; i < 5; i++) { ... } // 使用 var for (var i = 0; i < 5; i++) { // i 推斷爲 int類型 ... }
var替換Order:
List<Order> orderList = ...; // 顯式類型 for (Order order : orderList) { ... } // 使用 var for (var order : orderList) { // order 推斷成Order類型 ... }
將Java10中的var與Java 8中的Stream結合起來很是簡單。
你須要使用var取代顯式類型Stream:
例1:
// 顯式類型 Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5); numbers.filter(t -> t % 2 == 0).forEach(System.out::println); // 使用 var var numbers = Stream.of(1, 2, 3, 4, 5); // 推斷爲 Stream<Integer> numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
例2:
// 顯式類型 Stream<String> paths = Files.lines(Path.of("...")); List<File> files = paths.map(p -> new File(p)).collect(toList()); // 使用 var var paths = Files.lines(Path.of("...")); // 推斷爲 Stream<String> var files = paths.map(p -> new File(p)).collect(toList()); // 推斷爲 List<File>
var
類型可用於聲明局部變量,可用於分解表達式嵌套/長鏈.
大的或者嵌套的表達看起來使人印象深入,一般它們被認爲是聰明的代碼。有時候咱們會故意這麼寫,有時候咱們從一個小表達式開始寫,慢慢愈來愈大。爲了提升代碼可讀性,建議用局部變量來破壞大型/嵌套表達式。但有時候,添加這些局部變量是咱們想要避免的體力活。以下:
避免:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5); // AVOID int result = intList.stream() .collect(Collectors.partitioningBy(i -> i % 2 == 0)) .values() .stream() .max(Comparator.comparing(List::size)) .orElse(Collections.emptyList()) .stream() .mapToInt(Integer::intValue) .sum();
更好:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5); // PREFER Map<Boolean, List<Integer>> evenAndOdd = intList.stream() .collect(Collectors.partitioningBy(i -> i % 2 == 0)); Optional<List<Integer>> evenOrOdd = evenAndOdd.values() .stream() .max(Comparator.comparing(List::size)); int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList()) .stream() .mapToInt(Integer::intValue) .sum();
第二段代碼可讀性更強,更簡潔,可是第一段代碼也是對的。咱們的思惟會適應這樣的大表達式而且更喜歡它們而不是局部變量。然而,使用var類型對於使用局部變量的方式來講是一個優化,由於它節省了獲取顯式類型的時間。
更好
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5); // PREFER var evenAndOdd = intList.stream() .collect(Collectors.partitioningBy(i -> i % 2 == 0)); var evenOrOdd = evenAndOdd.values() .stream() .max(Comparator.comparing(List::size)); var sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList()) .stream() .mapToInt(Integer::intValue) .sum();
試着寫下面的兩段代碼,編譯通不過。
使用var做爲方法返回類型:
// 編譯通不過 public var countItems(Order order, long timestamp) { ... }
使用var做爲方法參數類型:
// 編譯通不過 public int countItems(var order, var timestamp) { ... }
var
類型的局部變量能夠用來傳入到方法參數,也能夠用來存放方法返回值。下面這兩段代碼可以編譯並且運行。
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...; // Order實例 var timestamp = ...; // long類型的 timestamp var itemsNr = countItems(order, timestamp); // 推斷爲int類型 ... }
它也適用於泛型。下面的代碼片斷也是對的。
public <A, B> B contains(A container, B tocontain) { ... } var order = ...; // Order實例 var product = ...; // Product實例 var resultProduct = contains(order, product); // inferred as Product type
避免:
public interface Weighter { int getWeight(Product product); } // AVOID Weighter weighter = new Weighter() { @Override public int getWeight(Product product) { ... } }; Product product = ...; // Product實例 int weight = weighter.getWeight(product);
更好的代碼:
public interface Weighter { int getWeight(Product product); } // PREFER var weighter = new Weighter() { @Override public int getWeight(Product product) { ... } }; var product = ...; // Product實例 var weight = weighter.getWeight(product);
從Java SE 8開始,局部類能夠訪問封閉塊內final或者effectively final的參數。變量初始化後再也不改變的參數爲effectively final。
因此,var類型的變量能夠是effectively final的。咱們能夠從如下代碼中看到。
避免:
public interface Weighter { int getWeight(Product product); } // AVOID int ratio = 5; // 這是effectively final Weighter weighter = new Weighter() { @Override public int getWeight(Product product) { return ratio * ...; } }; ratio = 3; // 這行賦值語句會報錯
更好:
public interface Weighter { int getWeight(Product product); } // PREFER var ratio = 5; // 這是effectively final var weighter = new Weighter() { @Override public int getWeight(Product product) { return ratio * ...; } }; ratio = 3; // 這行賦值語句會報錯
默認狀況下,var類型的局部變量能夠被從新賦值(除非它是effectively final的)。可是咱們能夠聲明它爲final類型,以下:
避免:
// AVOID // IT DOESN'T COMPILE public void discount(int price) { final int limit = 2000; final int discount = 5; if (price > limit) { discount++; // 這行會報錯 } }
更好:
// PREFER // IT DOESN'T COMPILE public void discount(int price) { final var limit = 2000; final var discount = 5; if (price > limit) { discount++; // 這行會報錯 } }
當對應的類型推斷不出來時不能使用var類型。因此,lambda表達式和方法引用初始化不被容許。這是var類型限制的一部分。
下面的代碼沒法編譯:
// 編譯不經過 // lambda表達式須要顯式目標類型 var f = x -> x + 1; // 方法引用須要顯式目標類型 var exception = IllegalArgumentException::new;
用如下代碼代替:
// PREFER Function<Integer, Integer> f = x -> x + 1; Supplier<IllegalArgumentException> exception = IllegalArgumentException::new;
可是在lambda的內容中,Java 11容許咱們去使用var做爲lambda參數。例如,如下代碼在Java 11中能夠很好的工做(詳見JEP 323(lambda參數中的局部變量))
// Java 11 (var x, var y) -> x + y // or (@Nonnull var x, @Nonnull var y) -> x + y
此外,也不容許缺乏初始化程序。這是var類型的另外一個限制。
如下代碼不會編譯經過(賦值null):
// 編譯通不過 var message = null; // 類型錯誤: 變量初始化爲'null'
這個代碼也不會編譯經過(缺乏初始化):
// IT DOESN'T COMPILE var message; // 使用var不能不作初始化 ... message = "hello";
更好:
// PREFER String message = null; // or String message; ... message = "hello";
var類型能夠用來作局部變量,可是不能用來作對象的域/全局變量。
這個限制會致使這裏的編譯錯誤:
// 編譯通不過 public class Product { private var price; // 'var' 不被容許 private var name; // 'var' 不被容許 ... }
用如下代碼代替:
// PREFER public class Product { private int price; private String name; ... }
可是它被容許在try-with-resources中。
當代碼拋出異常時,咱們必須經過顯式類型catch它,由於var類型不被容許。這個限制會致使如下代碼的編譯時錯誤:
// 編譯通不過 try { TimeUnit.NANOSECONDS.sleep(5000); } catch (var ex) { ... }
用這個取代:
// PREFER try { TimeUnit.NANOSECONDS.sleep(5000); } catch (InterruptedException ex) { ... }
另外一方面,var類型能夠用在try-with-resource中,例如:
// 顯式類型 try (PrintWriter writer = new PrintWriter(new File("welcome.txt"))) { writer.println("Welcome message"); }
能夠用var重寫:
// 使用 var try (var writer = new PrintWriter(new File("welcome.txt"))) { writer.println("Welcome message"); }
假定咱們有下面的代碼:
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
這種狀況下,使用var的運行結果是符合預期的,咱們能夠用var替換T,以下:
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
咱們看一下另外一個var可以成功使用的例子,以下:
public <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5"); // 錯誤:類型不兼容,string不能轉爲T ... }
能夠用var取代List
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>(); // DON'T DO THIS, DON'T FORGET THE, T var numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5"); // // 錯誤:類型不兼容,string不能轉爲T ... }
這麼作是安全的:
// 顯式類型 Class<?> clazz = Integer.class; // 使用var var clazz = Integer.class;
可是,不要由於代碼中有錯誤,而var可讓它們魔法般的消失,就使用var取代Foo 。看下一個例子,不是很是明顯,可是我想讓它指出核心。考慮一下當你編寫這一段代碼的過程,也許,你嘗試定義一個String的ArrayList,並最終定義成了Collection 。
// 顯式類型 Collection<?> stuff = new ArrayList<>(); stuff.add("hello"); // 編譯錯誤 stuff.add("world"); // 編譯錯誤 // 使用var,錯誤會消失,可是我不肯定你是你想要的結果 var stuff = new ArrayList<>(); strings.add("hello"); // 錯誤消失 strings.add("world"); // 錯誤消失
咱們知道能夠這麼寫:
// 顯式類型 Class<? extends Number> intNumber = Integer.class; Class<? super FilterReader> fileReader = Reader.class;
並且若是咱們錯誤賦值了錯誤的類型,接收到一個編譯時錯誤,這就是咱們想要的:
// 編譯通不過 // 錯誤: Class<Reader> 不能轉換到 Class<? extends Number> Class<? extends Number> intNumber = Reader.class; // 錯誤: Class<Integer> 不能轉化到Class<? super FilterReader> Class<? super FilterReader> fileReader = Integer.class;
可是若是咱們使用var:
// using var var intNumber = Integer.class; var fileReader = Reader.class;
而後咱們能夠爲這些變量賦值任何類,所以咱們的邊界/約束消失了,這並非咱們想要的。
// 編譯經過 var intNumber = Reader.class; var fileReader = Integer.class;