雖然這個問題的核心已經被問過很多次了,但是有一個問題還沒有被問到(或者說我還沒有找到)。
在 Java 中,沒有辦法擁有一個參考型別本身的泛型。你可能會說 "如果你最終嘗試了,那就是你的設計有缺陷",但我反駁了。 為什么?因為java在他們自己的設計中需要它。
Object.getClass()的代碼
/**.
* ...
*
* <p><b>實際的結果型別是{@code Class<? extends |X|>}。
* 其中{@code |X|}是擦除的靜態型別的
*運算式,{@code getClass}被呼叫。
* 例如,在這個代碼片段中不需要進行轉換:</p>
*
* <p>
* {@code Number n = 0; }<br>
* {@code Class<? extends Number> c = n.getClass(); }<br>
* </p>
* . . .
*/
public final native Class<? > getClass()。
所以要回傳Class<? extends SELF>顯然需要SELF。這是有道理的,如果你輸入instanceOfCar.getClass(),你希望得到Class<? extends Car>
(編輯:這些問題假定不保存鑄造不是一個選項)
Class<? extends Color> c = BLACK.getClass();
竟然可以編譯?
class SuperClass{
private SELF getThis(){
return this;
}
}
即使它看起來不像,我也知道這樣做的后果(比如使用SELF作為引數,只能在? extends SELF
的情況下作業。
uj5u.com熱心網友回復:
他們為什么不用泛型引入一個允許自我參考的功能?
他們為什么不用泛型引入一個允許自我參考的功能?
因為這比乍看之下更難。例如,假設你指定 "名稱This
指的是this
的型別",而有人寫了如下內容:
class Number {
public abstract int compareTo (This other)。
}
class Integer extends Number {
final int 值。
public Integer(intvalue) {
this.value = value。
}
@Override
public int compareTo(this other) {
return value - other.value。
}
}
public static void main(String[] args) {
Number n1 = new Integer(42)。
Number n2 = new Double(Math.PI)。
n1.compareTo(n2)。
這應該被編譯嗎?可能不會,因為類Integer
提供的compareTo
實作只適用于Integers
,而不是Number
的一些其他子型別。
問題是,我們的規范是模糊的。當我們說"This
是this
的型別 "時,我們是指this
的宣告型別(即this
出現在其源代碼中的類)還是指在運行時用于創建this
物件的子類?
如果我們選擇宣告的型別,this
在子類中的含義將與它的超類不同。這將是非常令人困惑的。例如:
class Super {
這個委托人。
}
class Sub extends Super {
void foo() {
delegate.foo(); //error: delegate是 "This "型別的,它沒有 "foo "方法。
}
}
如果我們選擇運行時型別,This
型別就不為呼叫者所知:
Number x = new Integer(42)。
Number y = new Integer(43);
x.compareTo(y); // error: the method compareTo takes an argument of unknown type, but was provided a Number.
也就是說,我們不能呼叫任何需要This
的方法。我們甚至不能做像這樣簡單的事情:
class Super {
這個資料。
}
void temporarilyRemoveDataFrom(Super s) {
Super d = s.data。
s.data = null;
處理(s)。
s.data = d; //error: type Super is not assignable to an unknown subtype of Super。
}
正如你所看到的,引入對自參考型別的支持引發了參考任意型別的型別的所有問題。特別是,我們既需要型別的差異性,也需要一種方法來捕獲未知型別的值。
因此,自參考型別并不比泛型明顯簡單。相比之下,如果我們有了泛型,那么構建自指型別是很簡單的:
自指型別的構建是很簡單的。
class SelfAware< T extends SelfAware<T> > {
抽象 T getThis()。
}
class Sub extends SelfAware< Sub> {
子getThis() {
return this;
}
}
SelfAware<Sub> x = new Sub() 。
x = x.getThis(); //編譯得很好。
此外,還可以提出一個理由,即自參考型別往往被過度約束。要求程式員明確地定義型別變數和它們的邊界,可以促使他們思考哪些邊界是合適的,避免意外的過度約束。例如,java.lang.Integer
并沒有實作Comparable<Integer>
,而是實作了更普遍(也更有用)的Comparable<Number>
。
總結來說,可子類化的自指型別并不比正常的泛型更容易使用,也不會使語言更具表現力,而且會誘使程式員過度限制型別引數,并增加語言及其工具的復雜性,但卻沒有明顯的好處。
說了這么多,讓我們回到getClass()
的奇特案例:
開發者是如何做到不需要對
getClass()
的回傳值進行鑄造的?
通過在 Java 語言規范中為該方法引入特殊處理,該規范寫道:
。值得注意的是,即使支持自指型別,這個方法也需要特殊處理,因為它與運行時型別系統的互動使呼叫者暴露在型別清除中。
getClass
的方法呼叫運算式的型別是Class<? extends |T|>
,其中T
是為getClass
搜索的類或介面(§15.12.1),|T|
表示擦除T
(§4.6)。
uj5u.com熱心網友回復:
為什么不呢
?你必須要問java 1.5的設計者,他們不會在堆疊溢位中閑逛。據我所知,他們從未被問過,或者至少從未回答過這個問題。
但讓我們做一個有根據的猜測
。有很多原因。最主要的是,因為泛型已經足夠復雜了。實事求是地說,因為你很少需要它,所以,雖然,是的,如果foo.getClass()
最終成為Class<? extends WhateverFoosTypeIs>
型別的運算式就好了,但這并不重要。如果你經常遇到這種情況,那么那就是你做錯的地方。你不應該使用Class
,幾乎任何時候都不應該(如果你過度使用它,你可能需要寫工廠來代替),especially如果你希望.getClass()
的型別是Class<? extends ThatThing>
。
Java不會引入關鍵詞。至少,不是輕而易舉的。他們曾經做過一次。誠然,這是史詩級的白癡行為。引入可以想象到的最糟糕的關鍵詞:java 1.4引入了assert
,這意味著整個生態系統中核心庫之外最受歡迎的方法(junit,這簡直是最受歡迎的庫,而assert
很可能是它最常用的方法)不再被編譯,也不能被使用。
因此,雖然我認為正確的回應是。引入關鍵字是次優的,但如果你必須這樣做,好吧,但不要把一個常見的方法名稱作為關鍵字引入--openjdk團隊實際上從中學到的是 "在任何情況下,永遠永遠不要引入關鍵字。- 甚至var
也被引入為背景關系敏感的關鍵字(你可以在網上搜索一下;基本上,如果你使用var
作為型別名或欄位名或其他什么,你的代碼在java10 中仍然有效,編譯器試圖找出你是否打算將'var'作為java10的特征或只是作為一個識別符號)。
所以,鑒于新的關鍵字是正確的,你有什么建議?SELF
不起作用 - 這已經是一個有效的識別符號了。#
?Java不是Perl,也不希望把自己變成卡通式的臟話。這并不重要,不值得花一個寶貴的符號在上面。這就剩下'this'了,所以你可以做一些類似于public class Foo<S extends this>
{}的事情,這將意味著Foo并沒有一個真正的typevar,但是S
可以在里面用來表示'我自己的型別',并且this
在任何時候都是S
的型別,并且S必然是extends Foo
,這可能是對java值得的一個補充。
但是,這將是一個有點復雜和難以理解的問題(你會無意識到class Foo<F extends this>
是什么意思嗎?我敢打賭,大多數java程式員將不得不去查找它,而這是一個有點奇怪的需求,這不是一個好主意)。) 你要權衡利弊(歸根結底是你需要自我打字的頻率),例如學習曲線和所有工具堆疊的負擔,以及 "燒毀 "一個功能和永久地延長已經相當大的java規范)。與其假設java lang的設計者是一群沒有考慮到這一點的白癡,不如讓我們給他們以懷疑的好處,假設他們確實意識到這是有用的,列出了一個利弊清單,并決定這不值得做。
我真的想要這個
。你可以破解它。各種各樣的類都能做到這一點,特別是包括列舉:
class Foo< F extends Foo<F> > {
public F returnSelf() {
return (F) this; /MARK
}
}
class Bar extends Foo< Bar> {
}
Bar b = new Bar() 。
Bar c = b.returnSelf(); // worked
上面的代碼是有效的,并且得到了一個 "自我參考 "的泛型。這確實意味著你在型別中引入了一個實際的typevar,這可能是你不想要的(例如,對于.getClass()
方法來說,這不是一個可行的解決方案--將j.l.Object
改為class Object<S extends Object<S>>
會給大家帶來很大的負擔,完全不值得)。但它確實有效,而且這種 "模式 "在某種程度上是很常見的。
注意,在標有/MARK
的那一行,你會得到一個編譯器警告,你必須用@SuppressWarnings
來抑制這個警告,因為這個警告是針對一個不會發生的情況而發出的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/318596.html
標籤: