Tại sao lớp String được khai báo cuối cùng trong Java?


141

Từ khi tôi biết rằng lớp java.lang.Stringđược khai báo là cuối cùng trong Java, tôi đã tự hỏi tại sao lại như vậy. Tôi đã không tìm thấy bất kỳ câu trả lời nào sau đó, nhưng bài đăng này: Làm thế nào để tạo một bản sao của lớp String trong Java? nhắc nhở tôi về truy vấn của tôi.

Chắc chắn, String cung cấp tất cả các chức năng tôi từng cần và tôi chưa bao giờ nghĩ đến bất kỳ hoạt động nào sẽ yêu cầu mở rộng Chuỗi lớp, nhưng bạn sẽ không bao giờ biết ai đó có thể cần gì!

Vì vậy, có ai biết ý định của các nhà thiết kế là gì khi họ quyết định làm cho nó cuối cùng?


Cảm ơn tất cả các câu trả lời của bạn, đặc biệt là TrueWill, Bruno Reis và Thilo! Tôi ước mình có thể chọn nhiều hơn một câu trả lời là câu trả lời hay nhất, nhưng thật không may ...!
Alex Ntousias

1
Ngoài ra, hãy xem xét sự phổ biến của các dự án "Ôi tôi chỉ cần một vài phương thức hữu dụng hơn trên các chuỗi" sẽ xuất hiện - tất cả đều không thể sử dụng các chuỗi khác vì chúng là một lớp khác nhau.
Thorbjørn Ravn Andersen

Cảm ơn vì trả lời này rất hữu ích. Bây giờ chúng ta có hai sự thật. Chuỗi là một lớp Cuối cùng & không thay đổi vì nó không thể thay đổi nhưng có thể được chuyển đến một đối tượng khác. nhưng những gì về: - Chuỗi a = new String ("test1"); sau đó, s = "test2"; Nếu String là đối tượng lớp Final thì làm sao sửa đổi được? Làm thế nào tôi có thể sử dụng đối tượng cuối cùng sửa đổi. Xin vui lòng cho tôi nếu tôi hỏi sai bất cứ điều gì.
Suresh Sharma

Bạn có thể xem bài viết tốt này .
Aniket Thakur

4
Một điều chúng tôi rất may tránh được trong Java là "mọi người đều có lớp con Chuỗi riêng của họ với rất nhiều phương thức bổ sung và không có phương thức nào trong số này tương thích với nhau".
Thorbjørn Ravn Andersen

Câu trả lời:


88

Nó rất hữu ích để có các chuỗi được thực hiện như các đối tượng bất biến . Bạn nên đọc về sự bất biến để hiểu thêm về nó.

Một lợi thế của các đối tượng bất biến

Bạn có thể chia sẻ các bản sao bằng cách trỏ chúng vào một thể hiện duy nhất.

(từ đây ).

Nếu Chuỗi không phải là cuối cùng, bạn có thể tạo một lớp con và có hai chuỗi trông giống nhau khi "được xem là Chuỗi", nhưng điều đó thực sự khác nhau.


70
Trừ khi có một mối liên hệ giữa các lớp cuối cùng và các đối tượng bất biến mà tôi không nhìn thấy, tôi không thấy câu trả lời của bạn liên quan đến câu hỏi như thế nào.
sepp2k

9
Bởi vì nếu nó không phải là cuối cùng, bạn có thể truyền StringChild cho một phương thức nào đó dưới dạng tham số String và nó có thể thay đổi được (vì trạng thái lớp con thay đổi).
helios

4
Ồ Downvote? Bạn không hiểu làm thế nào phân lớp liên quan đến bất biến? Tôi đánh giá cao một lời giải thích về những gì vấn đề.
Bruno Reis

7
@Bruno, re: downvotes: Tôi không downvote bạn, nhưng bạn có thể thêm một câu về cách ngăn chặn các lớp con thực thi tính bất biến. Ngay bây giờ, nó là một loại câu trả lời một nửa.
Thilo

12
@BrunoReis - tìm thấy một bài viết hay mà bạn có thể liên kết đến đó có một cuộc phỏng vấn với James Gosling (người tạo ra Java), nơi anh ấy nói ngắn gọn về chủ đề này ở đây . Đây là một đoạn thú vị: "Một trong những điều buộc String trở nên bất biến là bảo mật. Bạn có một phương thức mở tệp. Bạn chuyển một Chuỗi cho nó. Và sau đó, nó thực hiện tất cả các loại kiểm tra xác thực trước khi thực hiện HĐH gọi. Nếu bạn quản lý để làm một cái gì đó có hiệu quả đột biến Chuỗi, sau khi kiểm tra bảo mật và trước khi gọi hệ điều hành, thì hãy bùng nổ, bạn đang ở ... "
Anurag

60

Đây là một bài viết hay nêu ra hai lý do đã được đề cập trong các câu trả lời trên:

  1. Bảo mật : hệ thống có thể phân phát các bit nhạy cảm của thông tin chỉ đọc mà không lo chúng bị thay đổi
  2. Hiệu suất : dữ liệu bất biến rất hữu ích trong việc làm cho mọi thứ an toàn theo luồng.

Và đây có lẽ là bình luận chi tiết nhất trong bài viết đó. Nó phải làm với nhóm chuỗi trong Java và các vấn đề bảo mật. Đó là về cách quyết định những gì đi vào nhóm chuỗi. Giả sử cả hai chuỗi bằng nhau nếu chuỗi ký tự của chúng giống nhau, thì chúng ta có một điều kiện cuộc đua về việc ai đến đó trước và cùng với đó là các vấn đề bảo mật. Nếu không, nhóm chuỗi sẽ chứa các chuỗi dư thừa do đó làm mất lợi thế của việc có nó ở vị trí đầu tiên. Chỉ cần đọc nó ra cho chính mình, sẽ ya?


Chuỗi mở rộng sẽ chơi tàn phá với bằng và thực tập. JavaDoc nói bằng:

So sánh chuỗi này với đối tượng được chỉ định. Kết quả là đúng khi và chỉ khi đối số không phải là null và là đối tượng Chuỗi đại diện cho cùng một chuỗi ký tự với đối tượng này.

Giả sử java.lang.Stringkhông phải là cuối cùng, a SafeStringcó thể bằng a String, và ngược lại; bởi vì họ đại diện cho cùng một chuỗi các ký tự.

Điều gì sẽ xảy ra nếu bạn áp dụng interncho một SafeString- sẽ SafeStringđi vào nhóm chuỗi của JVM? Và ClassLoadertất cả các đối tượng mà các SafeStringtham chiếu được tổ chức sau đó sẽ bị khóa tại chỗ trong suốt vòng đời của JVM. Bạn sẽ nhận được một điều kiện cuộc đua về việc ai có thể là người đầu tiên thực hiện một chuỗi các ký tự - có thể bạn SafeStringsẽ giành chiến thắng, có thể là một Stringhoặc có thể được SafeStringtải bởi một trình nạp lớp khác (do đó là một lớp khác).

Nếu bạn giành chiến thắng trong cuộc đua vào bể bơi, đây sẽ là một người độc thân thực sự và mọi người có thể truy cập vào toàn bộ môi trường của bạn (hộp cát) thông qua sự phản chiếu và secretKey.intern().getClass().getClassLoader().

Hoặc JVM có thể chặn lỗ này bằng cách đảm bảo rằng chỉ các đối tượng Chuỗi cụ thể (và không có lớp con) nào được thêm vào nhóm.

Nếu bằng được thực hiện sao cho SafeString! = StringThì SafeString.intern! = String.intern, Và SafeStringsẽ phải được thêm vào nhóm. Hồ bơi sau đó sẽ trở thành một bể <Class, String>thay vì <String>và tất cả những gì bạn cần để vào bể sẽ là một trình nạp lớp mới.


2
Tất nhiên lý do hiệu năng là sai lầm: nếu String là một giao diện tôi sẽ có thể cung cấp một triển khai thực hiện tốt hơn trong ứng dụng của mình.
Stephan Eggermont

28

Lý do hoàn toàn quan trọng nhất mà String là bất biến hoặc cuối cùng là nó được sử dụng bởi cơ chế tải lớp, và do đó có các khía cạnh bảo mật sâu sắc và cơ bản.

Nếu Chuỗi có thể thay đổi hoặc không phải là cuối cùng, yêu cầu tải "java.io.Writer" có thể đã được thay đổi để tải "mil.vogoon.DiskErasingWriter"

tham khảo: Tại sao String là bất biến trong Java


15

String là một lớp rất cốt lõi trong Java, nhiều thứ dựa vào nó hoạt động theo một cách nhất định, ví dụ như là bất biến.

Làm cho lớp finalngăn chặn các lớp con có thể phá vỡ các giả định này.

Lưu ý rằng, ngay cả bây giờ, nếu bạn sử dụng sự phản chiếu, bạn có thể phá vỡ Chuỗi (thay đổi giá trị hoặc mã băm của chúng). Phản ánh có thể được dừng lại với một người quản lý an ninh. Nếu Stringkhông final, mọi người đều có thể làm được.

Các lớp khác không được khai báo finalcho phép bạn xác định các lớp con bị hỏng đôi chút ( Liství dụ, bạn có thể có thêm vị trí sai), nhưng ít nhất JVM không phụ thuộc vào các lớp đó cho các hoạt động cốt lõi của nó.


6
finaltrên một lớp học không đảm bảo tính bất biến. Nó chỉ đảm bảo rằng các bất biến của một lớp (một trong số đó có thể là bất biến) không thể được thay đổi bởi một lớp con.
Kevin Brock

1
@ Kevin: vâng. Cuối cùng trên một lớp đảm bảo rằng không có lớp con. Không có gì để làm với sự bất biến.
Thilo

4
Làm cho một trận chung kết lớp không, của chính nó, làm cho nó vô tận. Nhưng làm cho một lớp cuối cùng bất biến đảm bảo rằng không ai tạo ra một lớp con phá vỡ tính bất biến. Có lẽ những người đưa ra quan điểm về sự bất biến là không rõ ràng chính xác những gì họ có nghĩa, nhưng tuyên bố của họ là chính xác khi hiểu trong bối cảnh.
Jay

Đôi khi tôi đọc câu trả lời này và tôi nghĩ đó là câu trả lời okey, sau đó tôi đọc mã băm & bằng với 'Java hiệu quả' và nhận ra đó là một câu trả lời rất hay. Bất cứ ai cần giải thích, tôi khuyên bạn nên ieam 8 & iteam 9 của cùng một cuốn sách.
Abhishek Singh

6

Như Bruno nói đó là về sự bất biến. Đó không chỉ là về Chuỗi mà còn về bất kỳ trình bao bọc nào, chẳng hạn như Double, Integer, Character, v.v. Có nhiều lý do cho việc này:

  • Chủ đề an toàn
  • Bảo vệ
  • Heap được quản lý bởi chính Java (khác với heap thông thường là Rác được thu thập theo cách khác nhau)
  • Quản lý bộ nhớ

Về cơ bản nó để bạn, như một lập trình viên, có thể chắc chắn rằng chuỗi của bạn sẽ không bao giờ bị thay đổi. Nó cũng vậy, nếu bạn biết nó hoạt động như thế nào, có thể cải thiện khả năng quản lý bộ nhớ. Cố gắng tạo hai chuỗi giống nhau lần lượt, ví dụ "xin chào". Bạn sẽ nhận thấy, nếu bạn gỡ lỗi, rằng chúng có ID giống hệt nhau, điều đó có nghĩa là chúng chính xác là các đối tượng CÙNG. Điều này là do thực tế là Java cho phép bạn làm điều đó. Điều này sẽ không thể có được nếu các chuỗi có thể cắt được. Họ có thể có cùng tôi, v.v., vì họ sẽ không bao giờ thay đổi. Vì vậy, nếu bạn từng quyết định tạo chuỗi 1.000.000 "xin chào" những gì bạn thực sự làm là tạo 1.000.000 con trỏ để "xin chào". Cũng như việc liên kết bất kỳ hàm nào trên chuỗi hoặc bất kỳ hàm bao nào vì lý do đó, sẽ dẫn đến việc tạo một đối tượng khác (nhìn lại ID đối tượng - nó sẽ thay đổi).

Cuối cùng về mặt quảng cáo trong Java không nhất thiết có nghĩa là đối tượng không thể thay đổi (ví dụ khác với C ++). Điều đó có nghĩa là địa chỉ mà điểm nó không thể thay đổi, nhưng bạn vẫn có thể thay đổi thuộc tính và / hoặc thuộc tính của nó. Vì vậy, hiểu được sự khác biệt giữa bất biến và cuối cùng trong một số trường hợp có thể thực sự quan trọng.

HTH

Người giới thiệu:


1
Tôi không tin rằng Chuỗi đi đến một đống khác hoặc sử dụng quản lý bộ nhớ khác. Chúng chắc chắn là rác thu gom.
Thilo

2
Ngoài ra, từ khóa cuối cùng trên một lớp hoàn toàn khác với từ khóa cuối cùng cho một trường.
Thilo

1
Được rồi, trên JVM của Sun, các chuỗi có tập tin () ed có thể đi vào perm-gen, không phải là một phần của heap. Nhưng điều đó chắc chắn không xảy ra đối với tất cả các Chuỗi hoặc tất cả JVM.
Thilo

2
Không phải tất cả các Chuỗi đều đi đến khu vực đó, chỉ là các Chuỗi đã được thực tập. Thực tập là tự động cho chuỗi ký tự. (@Thilo, gõ khi bạn gửi nhận xét của mình).
Kevin Brock

Cảm ơn vì trả lời này rất hữu ích. Bây giờ chúng ta có hai sự thật. Chuỗi là một lớp Cuối cùng & không thay đổi vì nó không thể thay đổi nhưng có thể được chuyển đến một đối tượng khác. nhưng những gì về: - Chuỗi a = new String ("test1"); sau đó, s = "test2"; Nếu String là đối tượng lớp Final thì làm sao sửa đổi được? Làm thế nào tôi có thể sử dụng đối tượng cuối cùng sửa đổi. Xin vui lòng cho tôi nếu tôi hỏi sai bất cứ điều gì.
Suresh Sharma

2

Nó có thể đã được đơn giản hóa việc thực hiện. Nếu bạn thiết kế một lớp sẽ được kế thừa bởi người dùng của lớp, thì bạn có một bộ trường hợp sử dụng hoàn toàn mới để xem xét vào thiết kế của bạn. Điều gì xảy ra nếu họ làm điều này hoặc điều đó với trường dự đoán X? Cuối cùng, họ có thể tập trung vào việc làm cho giao diện công cộng hoạt động chính xác và đảm bảo chắc chắn.


3
+1 cho "thiết kế để thừa kế là khó". BTW, điều đó được giải thích rất hay trong "Java hiệu quả" của Bloch.
sleske

2

Với rất nhiều điểm tốt đã được đề cập, tôi muốn thêm một điểm nữa - vì lý do Why String là bất biến trong Java là cho phép String lưu trữ mã băm của nó , là Chuỗi bất biến trong Java lưu trữ mã băm của nó và không tính toán mọi thời gian chúng ta gọi phương thức mã băm của Chuỗi , điều này làm cho nó rất nhanh là khóa băm được sử dụng trong hàm băm trong Java.

Nói tóm lại vì String là bất biến, không ai có thể thay đổi nội dung của nó một khi được tạo để đảm bảo hashCode của String giống nhau trên nhiều lệnh gọi.

Nếu bạn thấy Stringlớp đã được khai báo là

/** Cache the hash code for the string */
private int hash; // Default to 0

hashcode()chức năng như sau -

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Nếu nó đã là máy tính, chỉ cần trả về giá trị.


2

Để đảm bảo chúng tôi không nhận được một triển khai tốt hơn. Tất nhiên nó phải là một giao diện.

[sửa] Ah, nhận được nhiều phiếu giảm giá. Câu trả lời là hoàn toàn nghiêm túc. Tôi đã phải lập trình theo cách của mình xung quanh việc triển khai Chuỗi ngu ngốc nhiều lần, dẫn đến giảm hiệu suất và năng suất nghiêm trọng


2

Ngoài các lý do rõ ràng được đề xuất trong các câu trả lời khác, người ta còn nghĩ đến việc tạo lớp cuối cùng cho lớp String cũng có thể liên quan đến chi phí hiệu năng của các phương thức ảo. Ghi nhớ Chuỗi là một lớp nặng, làm cho cuối cùng này, có nghĩa là không có triển khai phụ chắc chắn, có nghĩa là không có chi phí gọi điện thoại gián tiếp bao giờ. Tất nhiên bây giờ chúng tôi có những thứ như gọi ảo và những thứ khác, luôn luôn thực hiện các loại tối ưu hóa này cho bạn.


2

Ngoài các lý do được đề cập trong các câu trả lời khác (bảo mật, bất biến, hiệu suất), cần lưu ý rằng Stringcó hỗ trợ ngôn ngữ đặc biệt. Bạn có thể viết Stringchữ và có hỗ trợ cho +nhà điều hành. Cho phép lập trình viên vào lớp con String, sẽ khuyến khích các hack như:

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.

1

Chà, tôi có một số suy nghĩ khác Tôi không chắc mình có đúng hay không nhưng trong Chuỗi Java là đối tượng duy nhất có thể được coi là kiểu dữ liệu nguyên thủy, ý tôi là chúng ta có thể tạo một đối tượng Chuỗi như String name = "java " . Bây giờ giống như các kiểu dữ liệu nguyên thủy khác được sao chép theo giá trị không được sao chép bởi Chuỗi tham chiếu dự kiến ​​sẽ có hành vi tương tự vì vậy đó là lý do tại sao Chuỗi là cuối cùng. Đó là những gì tôi nghĩ nó. Hãy bỏ qua nếu nó hoàn toàn phi logic.


1
Theo tôi, String không bao giờ hành xử như một kiểu nguyên thủy. Một chuỗi ký tự như "java" thực sự là một đối tượng của lớp String (bạn có thể sử dụng toán tử dấu chấm trên nó, ngay sau trích dẫn kết thúc). Vì vậy, việc gán một chữ cho một biến chuỗi chỉ là gán các tham chiếu đối tượng như bình thường. Điều khác biệt là lớp String có hỗ trợ mức ngôn ngữ được tích hợp trong trình biên dịch ... biến mọi thứ trong dấu ngoặc kép thành các đối tượng String và toán tử + như đã đề cập ở trên.
Georgie

1

Tính hữu hạn của chuỗi cũng bảo vệ chúng như một tiêu chuẩn. Trong C ++, bạn có thể tạo các lớp con của chuỗi, vì vậy mọi cửa hàng lập trình có thể có phiên bản chuỗi riêng. Điều này sẽ dẫn đến việc thiếu một tiêu chuẩn mạnh mẽ.


1

Giả sử bạn có một Employeelớp có phương thức greet. Khi greetphương thức được gọi nó chỉ đơn giản là in Hello everyone!. Vì vậy, đó là hành vi dự kiến của greetphương pháp

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Bây giờ, hãy để GrumpyEmployeelớp con Employeegreetphương thức ghi đè như dưới đây.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Bây giờ trong đoạn mã dưới đây có một cái nhìn về sayHellophương pháp. Nó lấy Employeeví dụ như một tham số và gọi phương thức chào với hy vọng rằng nó sẽ nói Hello everyone!Nhưng những gì chúng ta nhận được là Get lost!. Sự thay đổi hành vi này là doEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Tình huống này có thể tránh được nếu Employeelớp học được thực hiện final. Bây giờ tùy theo trí tưởng tượng của bạn, lượng hỗn loạn mà một lập trình viên táo bạo có thể gây ra nếu StringClass không được tuyên bố là final.


0

JVM có biết cái gì là bất biến không? Trả lời là Không, Nhóm hằng số chứa tất cả các trường bất biến nhưng tất cả các trường / đối tượng bất biến không chỉ được lưu trữ trong nhóm không đổi. Chỉ có chúng tôi thực hiện nó theo cách mà nó đạt được sự bất biến và các tính năng của nó. CustomString có thể được triển khai mà không làm cho nó trở thành cuối cùng bằng cách sử dụng MarkerInterface, nó sẽ cung cấp hành vi đặc biệt java cho nhóm của nó, tính năng vẫn đang được chờ đợi!


0

Hầu hết các câu trả lời có liên quan đến tính bất biến - tại sao một đối tượng của kiểu Chuỗi không thể được cập nhật tại chỗ. Có rất nhiều cuộc thảo luận tốt ở đây và cộng đồng Java sẽ làm tốt việc chấp nhận sự bất biến làm hiệu trưởng. (Không nín thở.)

Tuy nhiên, câu hỏi của OP là về lý do tại sao nó là cuối cùng - tại sao nó không thể được mở rộng. Một số người ở đây đã thực hiện điều này, nhưng tôi sẽ đồng ý với OP rằng có một khoảng cách thực sự ở đây. Ngôn ngữ khác cho phép nhà phát triển tạo các loại danh nghĩa mới cho một loại. Ví dụ: trong Haskell tôi có thể tạo các loại mới sau đây giống hệt nhau trong thời gian chạy dưới dạng văn bản, nhưng cung cấp an toàn liên kết tại thời gian biên dịch.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Vì vậy, tôi sẽ đưa ra gợi ý sau đây như là một sự cải tiến cho ngôn ngữ Java:

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(Hoặc có lẽ chúng ta có thể bắt đầu xóa bỏ Java)


-1

Nếu bạn tạo một chuỗi một lần Nó sẽ xem xét điều đó, nó là một đối tượng nếu bạn muốn sửa đổi điều đó, điều đó là không thể, nó sẽ tạo ra đối tượng mới.


Hãy làm rõ câu trả lời của bạn.
Marko Popovic
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.