[英]Java object creation syntax efficiency?
這聽起來可能很基礎。 但是我是Java的新手。 到目前為止,我已經投入了最初的幾個小時的學習時間,但新對象聲明語法的冗余使我不斷困惑:
TypeName a = new TypeName();
特別是,
String s = new String("abc");
Character c = new Character("A");
為什么世界上有人要兩次鍵入關鍵字TypeName
(例如String
, Character
等)? 我了解以下方面的捷徑:
String s = "abc";
char c = "A";
但是這些是例外而不是規則。 那么任何人都可以啟發我嗎? 謝謝。
因為有時候您想做類似的事情:
// Explicitly force my object to be null
String s = null;
要么
// Cast a child class to its parent
MyParentClass mpc = new IneritedClassFromParent();
要么
// Store and access a concrete implementation using its interface
ISomeInterface isi = new ConcreteInterfaceImplementation();
換句話說,僅僅因為您聲明要存儲的類型並不總是意味着您希望使用該類的新實例對其進行初始化。
使用繼承時可能要使用子類的新實例,使用接口時可能要使用接口實現。
或者有時您可能想明確地強制某些內容最初為null,然后再填充。
使用此語法,您可以輕松創建X
類型的對象並將其分配給Y
類型的變量:
List<String> myList = new ArrayList<String>();
為什么在世界上有人想要兩次鍵入關鍵字TypeName(例如,字符串,字符等)?
因為您正在做兩件事:
兩種類型不一定相同,例如
Map m = new HashMap();
您可能已經習慣了像PHP這樣的“動態類型化”語言,其中變量沒有類型。 使用Java的靜態類型聲明所獲得的好處是,編譯器會捕獲很多編程錯誤(即使在鍵入時在現代IDE中也是如此)。 例如,如果您犯了一個簡單的錯誤:
m.siez();
編譯器會立即提醒您程序存在問題-它之所以能夠這樣做,僅是因為它知道聲明的類型Map
沒有方法siez()
。
一些現代的靜態類型化語言(例如C#和Scala)使用類型推斷為您提供“兩全其美”,您可以在其中省略類型聲明,並且編譯器會假定該聲明與您為其分配的對象的類型相同。 但是,此類語言始終允許顯式類型聲明,因為類型推斷並非總是可能或不合需要的(例如,在上面的示例中,該變量應該使用接口而不是具體的類)。
這根本不是多余的。 利用變量有兩個步驟:
聲明 :此步驟告訴VM變量的靜態足跡是多少。 例如: Object a;
僅在類Object
聲明的覆蓋區可見,而Integer b;
將在Integer
類和所有繼承的父類中聲明所有足跡,直到Object
可見。 這是針對靜態部分的。
實例化 :此步驟告訴VM變量的動態足跡是多少。 例如: List<String> c = new LinkedList<String>();
,然后c.put("foo");
即使List::put()
可見,也將使用LinkedList
的put()
方法的實現。 有時,您將需要這種聲明/實例化,但是將需要重寫以訪問靜態足跡不可見的非常特定的方法。 例如,讓我們考慮一個聲明為public void method1(Object obj)
,您知道 obj
實例實際上是一個Integer
,因此您可以通過將對象強制轉換為它來專門使用動態足跡: int value = ((Integer) obj).intValue();
現在,對於String a = "A";
部分。 為了簡化起見,Java提供了“原始”類的速記形式。 更具體地說,從Java 1.5開始,您可以執行以下操作:
Integer n1 = 1;
Integer n2 = new Integer(1); // same thing
int n3 = n2;
和所有的作品。 但是有什么區別呢? 考慮這段代碼:
String a = new String("A");
String b = new String("A");
String c = "A";
String d = "A";
System.out.println("a:" + a.hashCode() + " = b:" + b.hashCode() + " == " + (a == b));
System.out.println("b:" + b.hashCode() + " = c:" + c.hashCode() + " == " + (b == c));
System.out.println("c:" + c.hashCode() + " = d:" + d.hashCode() + " == " + (c == d));
將輸出
a:65 = b:65 == false
b:65 = c:65 == false
c:65 = d:65 == true
為什么? 因為JVM試圖盡可能多地重用內存,並且由於a
和b
正在創建String
新實例,所以它們沒有共享相同的內存空間。 但是, c
和d
使用常量字符串值(這是編譯器優化),因此指向完全相同的String
對象。
為何需要這樣做,這里有許多很好的答案。 您是對的,因為它通常看起來很多余。 Java經常(不公平地)受到批評,有點...冗長。 有一些快捷方式。 例如,對於字符串String s="Abc"
(實際上不是快捷方式,它有所不同,並且更好,因為您沒有顯式創建新對象)。 Java 7中針對泛型的聲明中的重復項也將有所減少。
這是因為沒有隱式方法可以分配復雜對象的值。
當你做int a = 3;
或double b = 2.5;
,您可以在右側隱式聲明類型。
在OOP中,必須使用構造函數,這就是為什么必須執行new TypeName()
。 這也使您能夠傳遞參數來設置對象。
另一個原因是使用接口時。 因此,您可以執行以下操作:
MyInterface blah = new InterfaceImplementation();
MyInterface bar = new AnotherInterfaceImplementation();
乃至:
ParentClass foo = new DerivedClass();
這是因為在使用接口時,您通常不想將變量類型設置為接口實現,而是將接口本身設置為。 否則,將無法指定要使用的實現。
另一個有用的東西是泛型:
List<SomeType> myList = new ArrayList<SomeType>();
Java 7會將其簡化為
List<SomeType> myList = new ArrayList<>();
這樣您就不必鍵入<SomeType>
兩次(這在Maps中尤其<SomeType>
)。
當您到達類的泛型(擴展名)時:
class a extends b
您將看到可以執行以下操作:
b=new a();
好吧,變量需要具有類型。 在創建對象的實例時,您需要確定對象的類型。 當然,這些不必相同。 例如,您可以將String設置為Object變量。 當然,您可以使用類似的方法使事情變得容易一些:
var s = new TypeName();
這就是在C#中完成的方式。 但是我想在Java中他們沒有看到這樣做的必要。
我同意Java在現代標准上相當冗長,但是它也很容易閱讀,並且沒有太多語法上的糖令您感到困惑。
有一些強類型語言支持“類型推斷”(例如Scala)。 Java根本不是其中之一(盡管Java 7中會有一些泛型參數的類型推斷)。 在這些語言中,盡管未聲明變量類型,但編譯器可以明確地推斷出它,並仍然檢測類型錯誤。 例如(非Java):
val str = "This is not a number!";
val x = str.intValue(); // Compiler error, because str is implicitly a String.
在Java中,在許多情況下,您會將右側的特定具體類型分配給左側的更常規的類型。 例如:
Set students = new TreeSet();
這是一種很好的樣式,因為您的代碼將不依賴於特定的實現。 如果需要切換實現(例如,需要從基於哈希的Set
更快的查找),則僅初始化程序的右側會更改。
因此,使用正確的抽象而不是具體類型聲明公共API尤為重要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.