Khi nào nên sử dụng nguyên thủy vs lớp trong Java?


54

Tôi thấy rằng Java có Boolean (class) vs boolean (nguyên thủy). Tương tự như vậy, có một Integer (class) vs int (nguyên thủy). Cách thực hành tốt nhất khi sử dụng phiên bản nguyên thủy so với lớp học là gì? Về cơ bản tôi có nên luôn luôn sử dụng phiên bản lớp trừ khi tôi không có lý do cụ thể (hiệu suất?) Không? Cách phổ biến nhất, được chấp nhận để sử dụng mỗi là gì?



Đối với tôi, những lớp không phải là chúng, chúng là những cái hộp. Chúng chỉ ở đó để bạn có thể sử dụng nguyên thủy nơi các đối tượng được yêu cầu, tức là trong các bộ sưu tập. Bạn không thể thêm hai số nguyên (bạn có thể giả vờ nhưng thực sự java là tự động đấm bốc | mở hộp các giá trị cho bạn).
ném đá

Câu trả lời:


47

Trong mục 5, về Java hiệu quả, Joshua Bloch nói

Bài học rất rõ ràng: thích người nguyên thủy hơn người nguyên thủy đóng hộp, và coi chừng việc tự động vô ý .

Một cách sử dụng tốt cho các lớp là khi sử dụng chúng làm các kiểu chung (bao gồm các lớp Collection, chẳng hạn như danh sách và bản đồ) hoặc khi bạn muốn chuyển đổi chúng sang loại khác mà không cần truyền ẩn (ví dụ Integerlớp có các phương thức doubleValue()hoặc byteValue().

Chỉnh sửa: Lý do của Joshua Bloch là:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Chương trình này nhận được câu trả lời đúng, nhưng chậm hơn nhiều so với lỗi do lỗi đánh máy một ký tự. Biến sumđược khai báo Longthay cho a long, có nghĩa là chương trình xây dựng khoảng 2 ^ 31 Longtrường hợp không cần thiết (khoảng một cho mỗi lần long iđược thêm vào Long sum). Thay đổi khai báo tổng từ Longđể longgiảm thời gian chạy từ 43 giây xuống 6,8 giây trên máy của tôi.


2
Sẽ có ích nếu bạn liệt kê lý do của Bloch, thay vì chỉ trích dẫn kết luận của anh ấy!
vaughandroid

@Baqueta Tôi đã chỉnh sửa bài viết. Lý do là hiệu suất.
m3th0dman

Điều đó rõ ràng hơn một chút, cảm ơn. Tôi cảm thấy hợp lý trong việc đăng câu trả lời của riêng tôi bây giờ. :)
vaughandroid

Bạn có thể thấy kiến trúc lmax thú vị - "Cần thêm một chút thông minh để đi lên một cấp độ lớn khác. Có một số điều mà nhóm LMAX thấy hữu ích để đạt được điều đó. Một là viết các triển khai tùy chỉnh các bộ sưu tập java được thiết kế thân thiện với bộ nhớ cache và cẩn thận với rác. Một ví dụ về điều này là sử dụng java nguyên thủy làm khóa băm với một mảng được viết đặc biệt được hỗ trợ bằng cách thực hiện Bản đồ . "

Có vẻ như một cái gì đó JIT nên xử lý
deFreitas

28

Thực hành tiêu chuẩn là đi với người nguyên thủy, trừ khi bạn đang xử lý thuốc generic (đảm bảo bạn biết về việc tự động mở hộp & bỏ hộp !).

Có một số lý do tốt để tuân theo quy ước:

1. Bạn tránh những lỗi đơn giản:

Có một số trường hợp tinh tế, không trực quan thường bắt gặp người mới bắt đầu. Ngay cả các lập trình viên có kinh nghiệm cũng trượt lên và đôi khi mắc những lỗi này (hy vọng điều này sẽ được theo sau bằng cách chửi thề khi họ gỡ lỗi mã và tìm lỗi!).

Lỗi commmon nhất là sử dụng a == bthay vì a.equals(b). Mọi người thường làm a == bvới các nguyên thủy để dễ dàng thực hiện khi bạn đang sử dụng trình bao bọc Object.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Dễ đọc:

Hãy xem xét hai ví dụ sau đây. Hầu hết mọi người sẽ nói thứ hai là dễ đọc hơn.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Hiệu suất:

Thực tế là nó chậm hơn để sử dụng những gói Object cho nguyên thủy hơn là chỉ sử dụng nguyên thủy. Bạn đang thêm chi phí khởi tạo đối tượng, gọi phương thức, v.v. vào những thứ bạn sử dụng ở mọi nơi .

Câu nói "... nói khoảng 97% thời gian của Knuth: tối ưu hóa sớm là gốc rễ của mọi tội lỗi" không thực sự được áp dụng ở đây. Ông đã nói về việc tối ưu hóa làm cho mã (hoặc hệ thống) trở nên phức tạp hơn - nếu bạn đồng ý với điểm # 2, đây là một tối ưu hóa giúp mã ít phức tạp hơn!

4. Đó là quy ước:

Nếu bạn thực hiện các lựa chọn phong cách khác nhau cho 99% các lập trình viên Java khác ngoài kia, có 2 nhược điểm:

  • Bạn sẽ thấy mã của người khác khó đọc hơn. 99% các ví dụ / hướng dẫn / vv ngoài kia sẽ sử dụng nguyên thủy. Bất cứ khi nào bạn đọc, bạn sẽ có thêm chi phí nhận thức về suy nghĩ về việc nó sẽ trông như thế nào theo phong cách mà bạn đã từng sử dụng.
  • Người khác sẽ thấy mã của bạn khó đọc hơn. Bất cứ khi nào bạn đặt câu hỏi trên Stack Overflow, bạn sẽ phải chọn lọc các câu trả lời / nhận xét hỏi "tại sao bạn không sử dụng nguyên thủy?". Nếu bạn không tin tôi, chỉ cần nhìn vào các trận chiến mà mọi người có đối với những thứ như vị trí khung, thậm chí không ảnh hưởng đến mã được tạo!

Thông thường tôi sẽ liệt kê một số điểm phản đối, nhưng thực lòng tôi không thể nghĩ ra bất kỳ lý do chính đáng nào để không đi theo quy ước ở đây!


2
Đừng cố gắng so sánh mọi người với các đối tượng ==. Đối tượng nên được so sánh với equals().
Tulains Córdova

2
@ user61852 Tôi không gợi ý rằng đó là một việc cần làm, nhưng là một lỗi phổ biến được thực hiện! Tôi có nên làm cho rõ ràng hơn?
vaughandroid

Có, bạn không đề cập đến việc các đối tượng sẽ được so sánh với equals()... bạn cung cấp cho họ một cách giải quyết để so sánh các đối tượng với ==kết quả mong đợi.
Tulains Córdova

Điểm tốt. Đã thay đổi nó.
vaughandroid

Tôi đã thêm vào equals()đoạn mã thứ hai và thay đổi phiếu bầu của mình.
Tulains Córdova

12

Thông thường tôi đi với người nguyên thủy. Tuy nhiên, một đặc thù của việc sử dụng các lớp như IntegerBooleanlà khả năng gán nullcho các biến đó. Tất nhiên, điều này có nghĩa là bạn phải nullkiểm tra mọi lúc, nhưng vẫn tốt hơn để có được NullPulumException hơn là có lỗi logic do sử dụng một số inthoặc booleanbiến chưa được khởi tạo chính xác.

Tất nhiên, vì Java 8, bạn có thể (và có lẽ nên) tiến thêm một bước và thay vì ví dụ: Integerbạn có thể sử dụng Optional<Integer>cho các biến có thể có hoặc không có giá trị.

Ngoài ra, nó giới thiệu khả năng sử dụng nullđể gán cho các biến đó một giá trị " không xác định " hoặc " ký tự đại diện ". Điều này có thể có ích trong một số tình huống, ví dụ, trong Ternary Logic . Hoặc bạn có thể muốn kiểm tra xem một đối tượng nào đó có khớp với một số mẫu không; trong trường hợp này, bạn có thể sử dụng nullcho các biến đó trong mẫu có thể có bất kỳ giá trị nào trong đối tượng.


2
(Không phải là downvote của tôi, nhưng ...) Java đã phát hiện các biến chưa được khởi tạo và sẽ không cho phép bạn đọc một biến cho đến khi mọi đường dẫn mã dẫn đến việc sử dụng nó đều được gán một giá trị. Vì vậy, bạn không nhận được nhiều từ khả năng gán nulllàm mặc định. Ngược lại: tốt hơn hết là bạn không nên "khởi tạo" biến. Đặt bất kỳ giá trị mặc định nào, thậm chí null, tắt trình biên dịch ... nhưng cũng giữ cho nó không phát hiện ra sự thiếu gán hữu ích dọc theo tất cả các đường dẫn mã. Vì vậy, một lỗi trình biên dịch có thể đã mắc phải, trượt qua thời gian chạy.
cHao

@cHao Nhưng nếu không có giá trị mặc định hợp lý để khởi tạo biến với? Bạn có thể đặt nó thành 0.0, hoặc -1, hoặc Integer.MAX_VALUE, Falsenhưng cuối cùng bạn không biết đó là giá trị mặc định hay giá trị thực được gán cho biến đó. Trong trường hợp điều này là quan trọng, có một nullgiá trị có thể rõ ràng hơn.
tobias_k

Nó không rõ ràng hơn, nó dễ dàng hơn để nói với trình biên dịch không cảnh báo bạn về logic không rõ ràng cho đến khi một lỗi đã được lan truyền. : P Trong trường hợp không có mặc định hợp lý, không khởi tạo biến. Chỉ đặt nó một khi bạn có một giá trị hợp lý để đặt ở đó. Điều này cho phép Java dừng bạn tại thời gian biên dịch nếu giá trị của bạn có thể không được đặt chính xác.
cHao

@cHao Ý tôi là, có thể có trường hợp bạn không thể khởi tạo biến và bạn phải xử lý nó khi chạy. Trong các trường hợp như vậy, một "mặc định" như "null", rõ ràng có thể được xác định là mặc định hoặc "chưa được khởi tạo", có thể tốt hơn bất kỳ khởi tạo thời gian biên dịch nào cũng có thể là một giá trị hợp lệ.
tobias_k

Bạn có một trường hợp như vậy trong tâm trí? Các trường hợp mà tôi có thể nghĩ là ở ranh giới giao diện (ví dụ: dưới dạng tham số hoặc kiểu trả về) ... nhưng ngay cả ở đó, chúng sẽ tốt hơn nhiều nếu được bao bọc. (Khỏa thân nullđi kèm với một loạt các vấn đề, bao gồm cả hoang tưởng null.) Trong một hàm, một biến thực sự có thể không được khởi tạo tại điểm sử dụng thường biểu thị các trường hợp không được phát hiện. (Phân tích phân công xác định là đơn giản, vì vậy có thể có dương tính giả. Nhưng bạn thường có thể giải quyết chúng bằng cách đơn giản hóa logic, vì vậy.)
cHao

2

Theo cách nói của giáo dân:

Bạn sử dụng các hàm bao khi bạn cần thêm các thứ vào bộ sưu tập.

Bộ sưu tập không thể giữ nguyên thủy.


0

Java có tính năng tự động hộp như m3th0dman đã chỉ ra. Hãy suy nghĩ về mức thấp nhất có thể và bạn sẽ thấy rằng hộp số tự động (vào hoặc ra) một giá trị nguyên thủy sẽ ngụ ý chu kỳ đồng hồ được chi tiêu trong một số tác vụ bạn không cần nếu bạn làm việc với các loại dữ liệu gốc xung quanh ứng dụng của mình.

Theo nguyên tắc chung, bạn nên cố gắng sử dụng các loại dữ liệu gốc bất cứ khi nào có thể.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.