2010-08-31 58 views
7

Điều này nghe có vẻ thực sự cơ bản. Nhưng tôi là người mới sử dụng Java. Với vài giờ đầu tiên của học tập tôi đã đưa vào cho đến nay, tôi liên tục lúng túng bởi sự dư thừa trong cú pháp của một bản tuyên bố đối tượng mới:Hiệu quả cú pháp tạo đối tượng Java?

TypeName a = new TypeName(); 

Đặc biệt,

String s = new String("abc"); 
Character c = new Character("A"); 

Tại sao trên thế giới có ai đó muốn nhập từ khóa TypeName (ví dụ: String, Character, v.v ...) hai lần? Tôi hiểu rằng có một số nội dung ngắn:

String s = "abc"; 
char c = "A"; 

Nhưng đây là những ngoại lệ thay vì quy tắc. Vì vậy, bất kỳ ai có thể khai sáng cho tôi xin vui lòng? Cám ơn.

+2

Một câu hỏi dọc theo dòng "tại sao các nhà thiết kế ngôn ngữ thực hiện theo cách đó" có thể được yêu cầu tốt hơn về các nhà thiết kế ngôn ngữ chứ không phải chúng tôi. –

+0

Tôi hỏi ở đây bởi vì mặc dù tôi không biết câu trả lời, tôi chắc chắn nó sẽ đun sôi xuống một sự thuận tiện lập trình (do đó tốt nhất được trả lời bởi các lập trình viên) hơn là triết lý thiết kế mà không ảnh hưởng trực tiếp, phải không? – Zhang18

Trả lời

4

Điều này hoàn toàn không cần thiết. Có hai bước để sử dụng một biến:

  1. khai: bước này cho VM gì sẽ là dấu chân tĩnh của biến. Ví dụ: Object a; sẽ chỉ có dấu chân được khai báo trong lớp Object hiển thị, trong khi Integer b; sẽ có tất cả dấu chân được khai báo trong lớp Integer và tất cả các lớp cha được thừa kế, có thể hiển thị lên đến Object. Đây là phần tĩnh.

  2. instanciation: bước này cho VM biết dấu chân động của biến là gì. Ví dụ: List<String> c = new LinkedList<String>();, sau đó c.put("foo"); sẽ sử dụng phương pháp của phương pháp put(), ngay cả khi hiển thị là List::put(). Đôi khi, bạn sẽ yêu cầu loại khai báo/instanciation, nhưng sẽ cần phải ghi đè để truy cập một phương pháp rất cụ thể không nhìn thấy được với dấu chân tĩnh. Ví dụ, chúng ta hãy xem xét một phương pháp khai báo là public void method1(Object obj) và bạn biết rằng dụ obj thực sự là một Integer, do đó bạn sẽ đặc biệt sử dụng dấu chân động bằng cách đúc các đối tượng vào nó: int value = ((Integer) obj).intValue();

Bây giờ , như đối với phần String a = "A";. Java đã viết tắt viết tắt của các lớp "nguyên thủy" có sẵn để đơn giản. Cụ thể hơn, kể từ Java 1.5, bạn có thể làm:

Integer n1 = 1; 
Integer n2 = new Integer(1); // same thing 
int n3 = n2; 

Và tất cả các công trình. Nhưng sự khác biệt là gì? Hãy xem xét đoạn mã này:

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)); 

chí đầu ra

a:65 = b:65 == false 
b:65 = c:65 == false 
c:65 = d:65 == true 

Tại sao? Vì JVM đang cố gắng sử dụng lại bộ nhớ nhiều nhất có thể và vì ab đang tạo các phiên bản mới của String, chúng không chia sẻ cùng một không gian bộ nhớ. Tuy nhiên, cd đang sử dụng hằng số giá trị chuỗi (đây là tối ưu hóa trình biên dịch) và do đó đang trỏ đến chính xác đối tượng String.

5

Với cú pháp này bạn có thể dễ dàng tạo ra một đối tượng kiểu X và gán nó vào một biến kiểu Y:

List<String> myList = new ArrayList<String>(); 
19

Bởi vì đôi khi bạn muốn làm một cái gì đó như:

// Explicitly force my object to be null 
String s = null; 

hoặc

// Cast a child class to its parent 
MyParentClass mpc = new IneritedClassFromParent(); 

hoặc

// Store and access a concrete implementation using its interface 
ISomeInterface isi = new ConcreteInterfaceImplementation(); 

Nói cách khác, chỉ vì bạn tuyên bố loại lưu trữ không phải lúc nào cũng có nghĩa là bạn muốn nó được khởi tạo bằng phiên bản mới của lớp đó.

Bạn có thể muốn sử dụng phiên bản mới của lớp con khi sử dụng thừa kế hoặc Triển khai giao diện khi sử dụng Giao diện.

Hoặc đôi khi bạn có thể muốn buộc một cách rõ ràng một cái gì đó để được null ban đầu và điền vào nó sau này.

+0

Tuy nhiên, điều này không giải thích tại sao bạn cần phải đề cập đến loại hai lần trong các ví dụ mà OP đã đưa ra. Loại suy luận đã tồn tại gần 100 năm, tức là dài hơn các ngôn ngữ lập trình và * chắc chắn * dài hơn Java, vì vậy tôi nghĩ câu hỏi tại sao Java thiếu một tính năng cơ bản chuẩn từ những năm 80 vẫn còn mở. –

+0

Tôi đồng ý w/Jorg W Mittag. Đặc biệt, tôi có một câu hỏi tiếp theo cho các ví dụ bạn đã trình bày ở đây. Tại sao không ai đó có thể có 'InheritedClassFromParent mpc = new InheritedClassFromParent()'? Lợi ích của việc khai báo biến trong lớp cha là gì? – Zhang18

+1

@ Zhang18 - Đó là vì lý do tương tự bạn sẽ sử dụng giao diện. Nếu phụ huynh xác định các thành viên bạn cần, tốt hơn là sử dụng loại chung hơn. Sau đó bạn được tự do thay đổi loại cơ bản mà không phá vỡ mã phụ thuộc. Ví dụ, bạn có thể thay đổi 'MyParentClass mpc = new InheritedClassFromParent();' thành 'MyParentClass mpc = SomeOtherChild();' mà không phá vỡ mã sử dụng mpc trong khi sử dụng các loại cụ thể ... bạn có thể gây ra các thay đổi. –

0

Đó là vì không có ẩn ngụ cách để gán giá trị của một đối tượng phức tạp.

Khi bạn làm int a = 3; hoặc double b = 2.5;, bạn có thể nói rõ loại ở bên phải.

Trong OOP, bạn phải sử dụng hàm tạo nên đó là lý do bạn phải làm new TypeName(). Điều này cũng cung cấp cho bạn khả năng truyền các tham số để thiết lập đối tượng.

Một lý do khác là khi bạn sử dụng giao diện. Vì vậy, bạn có thể làm:

MyInterface blah = new InterfaceImplementation(); 
MyInterface bar = new AnotherInterfaceImplementation(); 

và thậm chí:

ParentClass foo = new DerivedClass(); 

Điều này là do khi làm việc với giao diện bạn thường không muốn đặt kiểu biến là giao diện thực hiện, nhưng giao diện chinh no. Nếu không, sẽ không có cách nào để xác định việc triển khai thực hiện.

Một điều hữu ích là Generics:

List<SomeType> myList = new ArrayList<SomeType>(); 

Java 7 sẽ đơn giản hóa này để

List<SomeType> myList = new ArrayList<>(); 

để bạn không cần phải gõ <SomeType> hai lần (điều này đặc biệt trở nên đau đớn trong Maps).

0

Khi nào bạn sẽ đến được với các thuốc generic class (phần mở rộng):

class a extends b 

Bạn sẽ thấy rằng bạn có thể làm một cái gì đó như thế này:

b=new a(); 
0

Vâng, một biến cần phải có một loại . Và khi bạn đang tạo ra một thể hiện của một đối tượng, bạn cần phải nói loại đó là gì. Và tất nhiên đây không phải là như nhau. Ví dụ, bạn có thể đặt một String thành một biến Object. Tất nhiên bạn có thể có một cái gì đó như thế này để làm cho mọi thứ dễ dàng hơn một chút:

var s = new TypeName(); 

Đó là cách nó đã được thực hiện trong C#. Nhưng tôi cho rằng trong Java họ đã không thấy cần thiết cho điều đó.
Tôi đồng ý rằng Java khá chi tiết bởi các tiêu chuẩn hiện đại, nhưng nó cũng khá dễ đọc và không có nhiều cú pháp để gây nhầm lẫn cho bạn.

1

Nhiều câu trả lời hay ở đây là tại sao cần thiết. Bạn đúng ở chỗ nó thường có vẻ dư thừa. java thường được (không công bằng) chỉ trích như một chút, er ... tiết. Có một vài phím tắt. ví dụ. cho các chuỗi String s="Abc" (không thực sự là lối tắt, có một chút khác biệt và tốt hơn là bạn không tạo đối tượng mới rõ ràng). Cũng sẽ có một số giảm sự trùng lặp trong các khai báo trong java 7 cho Generics.

0

Có các ngôn ngữ được nhập mạnh mẽ hỗ trợ "suy luận kiểu" (ví dụ như Scala). Java đơn giản không phải là một trong số chúng (mặc dù sẽ có một số suy luận kiểu của các đối số chung trong Java 7). Trong các ngôn ngữ này, mặc dù loại biến không được khai báo, trình biên dịch có thể suy luận rõ ràng nó, và vẫn phát hiện lỗi kiểu. Ví dụ: (không phải Java):

val str = "This is not a number!"; 
val x = str.intValue(); // Compiler error, because str is implicitly a String. 

Trong Java, sẽ có nhiều trường hợp bạn chỉ định loại bê tông cụ thể ở bên phải loại chung hơn ở bên trái. Ví dụ:

Set students = new TreeSet(); 

Đây là một phong cách tốt, vì mã của bạn sẽ không phụ thuộc vào việc triển khai cụ thể. Nếu bạn cần chuyển đổi triển khai (ví dụ, bạn cần tra cứu nhanh hơn từ một băm dựa trên Set), chỉ phía bên tay phải của các thay đổi khởi tạo.

Vì lý do này, điều đặc biệt quan trọng là khai báo các API công khai bằng cách sử dụng các trừu tượng chính xác, chứ không phải là các loại cụ thể.

5

Tại sao trên thế giới ai đó muốn nhập từ khóa TypeName (ví dụ: String, Ký tự, v.v ...) hai lần?

Bởi vì bạn đang làm hai điều:

  • Khai báo một biến của một loại nhất định
  • Tạo một đối tượng của một loại nhất định

Hai loại này không nhất thiết phải là giống nhau, ví dụ

Map m = new HashMap(); 

Có thể bạn đã quen với các ngôn ngữ "được nhập động" như PHP nơi các biến không có loại. Lợi thế bạn nhận được với các khai báo kiểu tĩnh của Java là rất nhiều lỗi lập trình được trình biên dịch bắt (ngay cả trong một IDE hiện đại, trong khi bạn đang gõ). Ví dụ, nếu bạn thực hiện một sai lầm rất đơn giản:

m.siez(); 

Trình biên dịch sẽ ngay lập tức cảnh báo bạn về một thực tế rằng có cái gì đó sai với chương trình của bạn - và nó có thể làm điều đó chỉ vì nó biết rằng tuyên bố loại Map làm không có phương thức siez(). Một số ngôn ngữ được gõ tĩnh hiện đại như C# và Scala sử dụng type inference để cung cấp cho bạn "tốt nhất của cả hai thế giới", nơi bạn có thể bỏ qua khai báo kiểu và trình biên dịch sẽ giả định rằng nó giống như kiểu đối tượng bạn đang gán cho nó. Tuy nhiên, các ngôn ngữ như vậy luôn cho phép khai báo kiểu rõ ràng bởi vì suy luận kiểu không phải lúc nào cũng có thể hoặc desirables (chẳng hạn như trong ví dụ ở trên mà biến được cho là sử dụng giao diện chứ không phải lớp bê tông).

Các vấn đề liên quan