Là một chuỗi Java thực sự bất biến?


399

Chúng ta đều biết rằng StringJava là bất biến, nhưng hãy kiểm tra đoạn mã sau:

String s1 = "Hello World";  
String s2 = "Hello World";  
String s3 = s1.substring(6);  
System.out.println(s1); // Hello World  
System.out.println(s2); // Hello World  
System.out.println(s3); // World  

Field field = String.class.getDeclaredField("value");  
field.setAccessible(true);  
char[] value = (char[])field.get(s1);  
value[6] = 'J';  
value[7] = 'a';  
value[8] = 'v';  
value[9] = 'a';  
value[10] = '!';  

System.out.println(s1); // Hello Java!  
System.out.println(s2); // Hello Java!  
System.out.println(s3); // World  

Tại sao chương trình này hoạt động như thế này? Và tại sao giá trị của s1s2thay đổi, nhưng không s3?


394
Bạn có thể làm tất cả các loại thủ đoạn ngu ngốc với sự phản ánh. Nhưng về cơ bản, bạn đang phá vỡ nhãn dán "khoảng trống bảo hành nếu bị xóa" trên lớp ngay khi bạn thực hiện.
cHao

16
@DarshanPatel sử dụng SecurityManager để tắt phản xạ
Sean Patrick Floyd

39
Nếu bạn thực sự muốn gây rối với những thứ bạn có thể làm cho nó (Integer)1+(Integer)2=42bằng cách gây rối với hộp thư tự động được lưu trữ; (Disgruntled-Bomb-Java-Edition) ( thed Dailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx )
Richard Tingle

15
Bạn có thể thấy thích thú với câu trả lời này Tôi đã viết gần 5 năm trước stackoverflow.com/a/1232332/27423 - đó là về danh sách bất biến trong C # nhưng về cơ bản là giống nhau: làm cách nào tôi có thể ngăn người dùng sửa đổi dữ liệu của mình? Và câu trả lời là, bạn không thể; phản ánh làm cho nó rất dễ dàng. Một ngôn ngữ chính không gặp phải vấn đề này là JavaScript, vì nó không có hệ thống phản chiếu có thể truy cập các biến cục bộ trong một bao đóng, vì vậy private thực sự có nghĩa là riêng tư (mặc dù không có từ khóa nào cho nó!)
Daniel Earwicker

49
Có ai đọc câu hỏi đến cùng không ?? Câu hỏi là, hãy để tôi nhắc lại: "Tại sao chương trình này hoạt động như thế này? Tại sao giá trị của s1 và s2 thay đổi và không thay đổi cho s3?" Câu hỏi KHÔNG phải là tại sao s1 và s2 thay đổi! Câu hỏi là: TẠI SAO s3 không thay đổi?
Roland Pihlakas

Câu trả lời:


403

String là bất biến * nhưng điều này chỉ có nghĩa là bạn không thể thay đổi nó bằng API công khai.

Những gì bạn đang làm ở đây là phá vỡ API thông thường, sử dụng sự phản chiếu. Tương tự, bạn có thể thay đổi các giá trị của enum, thay đổi bảng tra cứu được sử dụng trong hộp số tự động Integer, v.v.

Bây giờ, lý do s1s2giá trị thay đổi, là cả hai đều đề cập đến cùng một chuỗi nội bộ. Trình biên dịch thực hiện điều này (như được đề cập bởi các câu trả lời khác).

Lý do s3nào không thực sự là một chút ngạc nhiên đối với tôi, vì tôi nghĩ rằng nó sẽ chia sẻ các valuemảng ( nó đã làm trong phiên bản trước của Java , trước khi Java 7u6). Tuy nhiên, nhìn vào mã nguồn của String, chúng ta có thể thấy rằng valuemảng ký tự cho một chuỗi con thực sự được sao chép (sử dụng Arrays.copyOfRange(..)). Đây là lý do tại sao nó đi không thay đổi.

Bạn có thể cài đặt một SecurityManager, để tránh mã độc để làm những việc như vậy. Nhưng hãy nhớ rằng một số thư viện phụ thuộc vào việc sử dụng các loại thủ thuật phản chiếu này (điển hình là các công cụ ORM, thư viện AOP, v.v.).

*) Ban đầu tôi đã viết rằng Stringnó không thực sự bất biến, chỉ là "bất biến hiệu quả". Điều này có thể gây hiểu nhầm trong việc triển khai hiện tại String, trong đó valuemảng thực sự được đánh dấu private final. Tuy nhiên, điều đáng chú ý là không có cách nào để khai báo một mảng trong Java là bất biến, do đó, cần chú ý không để lộ ra bên ngoài lớp của nó, ngay cả với các công cụ sửa đổi truy cập thích hợp.


Vì chủ đề này dường như rất phổ biến, đây là một số gợi ý đọc thêm: Cuộc nói chuyện về sự phản chiếu điên rồ của Heinz Kabutz từ JavaZone 2009, bao gồm rất nhiều vấn đề trong OP, cùng với sự phản ánh khác ... cũng ... điên rồ.

Nó bao gồm tại sao điều này đôi khi hữu ích. Và tại sao, hầu hết thời gian, bạn nên tránh nó. :-)


7
Trên thực tế, Stringthực tập là một phần của JLS ( "một chuỗi ký tự luôn luôn đề cập đến cùng một thể hiện của Chuỗi lớp" ). Nhưng tôi đồng ý, nó không phải là thông lệ tốt để tính vào các chi tiết thực hiện của Stringlớp.
haraldK

3
Có lẽ lý do tại sao các substringbản sao thay vì sử dụng một "phần" của mảng hiện có, là nếu tôi có một chuỗi lớn svà lấy ra một chuỗi con nhỏ được gọi ttừ nó, và sau đó tôi đã từ bỏ snhưng giữ lại t, thì mảng lớn sẽ được giữ nguyên (không thu gom rác). Vì vậy, có lẽ sẽ tự nhiên hơn khi mỗi giá trị chuỗi có mảng liên kết riêng?
Jeppe Stig Nielsen

10
Chia sẻ mảng giữa một chuỗi và chuỗi con của nó cũng ngụ ý rằng mọi String trường hợp phải mang các biến để ghi nhớ phần bù vào mảng và độ dài đã đề cập. Đó là một chi phí không thể bỏ qua với tổng số chuỗi và tỷ lệ điển hình giữa các chuỗi thông thường và chuỗi con trong một ứng dụng. Vì họ phải được đánh giá cho mọi hoạt động chuỗi, điều đó có nghĩa là làm chậm mọi hoạt động chuỗi chỉ vì lợi ích của chỉ một hoạt động, một chuỗi con giá rẻ.
Holger

2
@Holger - Đúng, hiểu biết của tôi là trường bù đã bị loại bỏ trong các JVM gần đây. Và ngay cả khi nó có mặt, nó vẫn không được sử dụng thường xuyên.
Hot Licks

2
@supercat: không quan trọng bạn có mã gốc hay không, có các cách triển khai khác nhau cho các chuỗi và chuỗi con trong cùng một JVM hoặc có các byte[]chuỗi cho chuỗi ASCII và char[]đối với các hoạt động khác phải kiểm tra loại chuỗi nào trước đó điều hành. Điều này cản trở việc nội tuyến mã vào các phương thức bằng cách sử dụng các chuỗi là bước đầu tiên của tối ưu hóa hơn nữa bằng cách sử dụng thông tin ngữ cảnh của người gọi. Đây là một tác động lớn.
Holger

93

Trong Java, nếu hai biến nguyên thủy chuỗi được khởi tạo cho cùng một chữ, thì nó gán cùng một tham chiếu cho cả hai biến:

String Test1="Hello World";
String Test2="Hello World";
System.out.println(test1==test2); // true

khởi tạo

Đó là lý do so sánh trả về đúng. Chuỗi thứ ba được tạo bằng cách sử dụng substring()tạo chuỗi mới thay vì trỏ đến cùng.

chuỗi phụ

Khi bạn truy cập một chuỗi bằng phản xạ, bạn sẽ nhận được con trỏ thực tế:

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

Vì vậy, thay đổi này sẽ thay đổi chuỗi giữ một con trỏ tới nó, nhưng như s3được tạo bằng một chuỗi mới do substring()nó sẽ không thay đổi.

thay đổi


Điều này chỉ hoạt động cho chữ và là một tối ưu hóa thời gian biên dịch.
SpacePrez

2
@ Zaphod42 Không đúng. Bạn cũng có thể gọi internthủ công trên Chuỗi không theo nghĩa đen và gặt hái những lợi ích.
Chris Hayes

Lưu ý, mặc dù: bạn muốn sử dụng internthận trọng. Thực tập tất cả mọi thứ không mang lại cho bạn nhiều, và có thể là nguồn gốc của một số khoảnh khắc đau đầu khi bạn thêm sự phản chiếu vào hỗn hợp.
cHao

Test1Test1không phù hợp với test1==test2và không tuân theo các quy ước đặt tên java.
c0der

50

Bạn đang sử dụng sự phản chiếu để phá vỡ tính bất biến của Chuỗi - đó là một dạng "tấn công".

Có rất nhiều ví dụ bạn có thể tạo như thế này (ví dụ: bạn thậm chí có thể khởi tạo một Voidđối tượng nữa), nhưng điều đó không có nghĩa là String không "bất biến".

Có những trường hợp sử dụng trong đó loại mã này có thể được sử dụng để lợi thế của bạn và là "mã hóa tốt", chẳng hạn như xóa mật khẩu khỏi bộ nhớ tại thời điểm sớm nhất có thể (trước GC) .

Tùy thuộc vào người quản lý bảo mật, bạn có thể không thực thi được mã của mình.


30

Bạn đang sử dụng sự phản chiếu để truy cập vào "chi tiết triển khai" của đối tượng chuỗi. Tính không thay đổi là tính năng của giao diện công cộng của một đối tượng.


24

Công cụ sửa đổi khả năng hiển thị và cuối cùng (tức là tính không thay đổi) không phải là phép đo đối với mã độc hại trong Java; chúng chỉ là các công cụ để bảo vệ chống lại các lỗi và để làm cho mã dễ bảo trì hơn (một trong những điểm bán hàng lớn của hệ thống). Đó là lý do tại sao bạn có thể truy cập các chi tiết triển khai nội bộ như mảng char sao lưu cho Strings thông qua sự phản chiếu.

Hiệu ứng thứ hai bạn thấy là tất cả đều Stringthay đổi trong khi có vẻ như bạn chỉ thay đổi s1. Đó là một thuộc tính nhất định của các chuỗi ký tự Java mà chúng được tự động thực hiện, tức là được lưu trữ. Hai chuỗi ký tự có cùng giá trị sẽ thực sự là cùng một đối tượng. Khi bạn tạo một Chuỗi với newnó sẽ không được tự động thực hiện và bạn sẽ không thấy hiệu ứng này.

#substringcho đến gần đây (Java 7u6) hoạt động theo cách tương tự, điều này sẽ giải thích hành vi trong phiên bản gốc của câu hỏi của bạn. Nó không tạo ra một mảng char sao lưu mới mà sử dụng lại một mảng từ Chuỗi gốc; nó vừa tạo một đối tượng String mới sử dụng offset và độ dài để chỉ hiển thị một phần của mảng đó. Điều này thường hoạt động như Chuỗi là bất biến - trừ khi bạn phá vỡ điều đó. Thuộc tính này #substringcũng có nghĩa là toàn bộ Chuỗi ban đầu không thể là rác được thu thập khi một chuỗi con ngắn hơn được tạo từ nó vẫn tồn tại.

Đối với Java hiện tại và phiên bản hiện tại của câu hỏi của bạn, không có hành vi lạ nào #substring.


2
Trên thực tế, các công cụ sửa đổi khả năng hiển thị được (hoặc ít nhất là) nhằm mục đích bảo vệ chống lại mã độc - tuy nhiên, bạn cần đặt SecurityManager (System.setSecurityManager ()) để kích hoạt bảo vệ. Làm thế nào an toàn này thực sự là một câu hỏi khác ...
sleske

2
Xứng đáng là một upvote vì bạn nhấn mạnh rằng các sửa đổi truy cập không nhằm mục đích 'bảo vệ' mã. Điều này dường như bị hiểu lầm rộng rãi trong cả Java và .NET. Mặc dù bình luận trước đó không mâu thuẫn với điều đó; Tôi không biết nhiều về Java, nhưng trong .NET thì điều này hoàn toàn đúng. Trong cả hai ngôn ngữ, người dùng không nên cho rằng điều này làm cho mã của họ không bị hack.
Tom W

Không thể vi phạm hợp đồng finalthậm chí thông qua phản ánh. Ngoài ra, như đã đề cập trong một câu trả lời khác, vì Java 7u6, #substringkhông chia sẻ mảng.
ntoskrnl

Trên thực tế, hành vi của finalđã thay đổi theo thời gian ...: -O Theo bài nói chuyện "Reflection Madness" của Heinz tôi đã đăng trong chủ đề khác, finalcó nghĩa là cuối cùng trong JDK 1.1, 1.3 và 1.4, nhưng có thể được sửa đổi bằng phản xạ bằng cách sử dụng 1.2 luôn và trong 1,5 và 6 trong hầu hết các trường hợp ...
haraldK

1
finalcác trường có thể được thay đổi thông qua nativemã được thực hiện bởi khung Nối tiếp khi đọc các trường của một thể hiện được tuần tự hóa cũng như System.setOut(…)điều chỉnh System.outbiến cuối cùng . Cái sau là tính năng thú vị nhất vì sự phản chiếu với ghi đè truy cập không thể thay đổi static finalcác trường.
Holger

11

Chuỗi bất biến là từ quan điểm giao diện. Bạn đang sử dụng sự phản chiếu để bỏ qua giao diện và trực tiếp sửa đổi các phần bên trong của các thể hiện Chuỗi.

s1s2cả hai đều được thay đổi vì cả hai đều được gán cho cùng một thể hiện Chuỗi "thực". Bạn có thể tìm hiểu thêm một chút về phần đó từ bài viết này về bình đẳng chuỗi và thực tập. Bạn có thể ngạc nhiên khi biết rằng trong mã mẫu của bạn, s1 == s2trả về true!


10

Phiên bản Java nào bạn đang sử dụng? Từ Java 1.7.0_06, Oracle đã thay đổi biểu diễn bên trong của Chuỗi, đặc biệt là chuỗi con.

Trích dẫn từ Đại diện chuỗi nội bộ của Java Tunes Java :

Trong mô hình mới, các trường bù và đếm chuỗi đã bị xóa, do đó các chuỗi con không còn chia sẻ giá trị char [] bên dưới.

Với sự thay đổi này, nó có thể xảy ra mà không có sự phản ánh (???).


2
Nếu OP đang sử dụng JRE Sun / Oracle cũ hơn, câu lệnh cuối cùng sẽ in "Java!" (như anh vô tình đăng). Điều này chỉ ảnh hưởng đến việc chia sẻ mảng giá trị giữa các chuỗi và chuỗi phụ. Bạn vẫn không thể thay đổi giá trị mà không có thủ thuật, như phản xạ.
haraldK

7

Thực sự có hai câu hỏi ở đây:

  1. Là chuỗi thực sự bất biến?
  2. Tại sao s3 không thay đổi?

Đến điểm 1: Ngoại trừ ROM, không có bộ nhớ bất biến trong máy tính của bạn. Ngày nay, ngay cả ROM đôi khi cũng có thể ghi. Luôn có một số mã ở đâu đó (cho dù đó là kernel hoặc mã riêng vượt qua môi trường được quản lý của bạn) có thể ghi vào địa chỉ bộ nhớ của bạn. Vì vậy, trong "thực tế", không có họ không hoàn toàn bất biến.

Đến điểm 2: Điều này là do chuỗi con có thể đang phân bổ một thể hiện chuỗi mới, có khả năng sao chép mảng. Có thể triển khai chuỗi con theo cách mà nó sẽ không làm một bản sao, nhưng điều đó không có nghĩa là nó làm. Có sự đánh đổi liên quan.

Ví dụ, có nên giữ một tham chiếu để reallyLargeString.substring(reallyLargeString.length - 2)khiến cho một lượng lớn bộ nhớ được giữ nguyên hoặc chỉ một vài byte?

Điều đó phụ thuộc vào cách thực hiện chuỗi con. Một bản sao sâu sẽ giữ ít bộ nhớ sống hơn, nhưng nó sẽ chạy chậm hơn một chút. Một bản sao nông sẽ giữ nhiều bộ nhớ hơn, nhưng nó sẽ nhanh hơn. Sử dụng một bản sao sâu cũng có thể làm giảm phân mảnh heap, vì đối tượng chuỗi và bộ đệm của nó có thể được phân bổ trong một khối, trái ngược với 2 phân bổ heap riêng biệt.

Trong mọi trường hợp, có vẻ như JVM của bạn đã chọn sử dụng các bản sao sâu cho các cuộc gọi chuỗi con.


3
ROM thật cũng bất biến như một bản in ảnh được bọc trong nhựa. Mẫu được đặt vĩnh viễn khi wafer (hoặc in) được phát triển hóa học. Các bộ nhớ có thể thay đổi bằng điện, bao gồm cả chip RAM , có thể hoạt động như ROM "thật" nếu các tín hiệu điều khiển cần thiết để ghi nó không thể được cấp điện mà không cần thêm các kết nối điện vào mạch trong đó nó được cài đặt. Thực tế không có gì lạ khi các thiết bị nhúng bao gồm RAM được đặt tại nhà máy và được duy trì bằng pin dự phòng và có nội dung cần được nhà máy tải lại nếu battey thất bại.
supercat

3
@supercat: Tuy nhiên, máy tính của bạn không phải là một trong những hệ thống nhúng đó. :) ROM có dây cứng thực sự không phổ biến trong PC trong một hoặc hai thập kỷ; EEPROM của mọi thứ và flash những ngày này. Về cơ bản mọi địa chỉ hiển thị của người dùng đề cập đến bộ nhớ, đều đề cập đến bộ nhớ có khả năng ghi.
cHao

@cHao: Nhiều chip flash cho phép các phần được bảo vệ chống ghi theo kiểu mà nếu có thể hoàn tác, sẽ yêu cầu áp dụng các điện áp khác nhau so với yêu cầu cho hoạt động bình thường (mà bo mạch chủ sẽ không được trang bị). Tôi mong đợi bo mạch chủ sử dụng tính năng đó. Hơn nữa, tôi không chắc chắn về các máy tính ngày nay, nhưng trong lịch sử, một số máy tính có vùng RAM được bảo vệ chống ghi trong giai đoạn khởi động và chỉ có thể không được bảo vệ bởi thiết lập lại (điều này sẽ buộc thực thi bắt đầu từ ROM).
supercat

2
@supercat Tôi nghĩ rằng bạn đang thiếu điểm chính của chủ đề, đó là các chuỗi, được lưu trữ trong RAM, sẽ không bao giờ thực sự bất biến.
Scott Wisniewski

5

Để thêm vào câu trả lời của @ haraldK - đây là một hack bảo mật có thể dẫn đến một tác động nghiêm trọng trong ứng dụng.

Điều đầu tiên là sửa đổi một chuỗi không đổi được lưu trữ trong Chuỗi nhóm. Khi chuỗi được khai báo là a String s = "Hello World";, nó sẽ được đặt vào một nhóm đối tượng đặc biệt để tái sử dụng tiềm năng. Vấn đề là trình biên dịch sẽ đặt tham chiếu đến phiên bản đã sửa đổi vào thời gian biên dịch và một khi người dùng sửa đổi chuỗi được lưu trữ trong nhóm này khi chạy, tất cả các tham chiếu trong mã sẽ trỏ đến phiên bản đã sửa đổi. Điều này sẽ dẫn đến một lỗi sau:

System.out.println("Hello World"); 

Sẽ in:

Hello Java!

Có một vấn đề khác mà tôi gặp phải khi tôi thực hiện một tính toán nặng nề đối với các chuỗi rủi ro như vậy. Có một lỗi xảy ra trong khoảng 1 trên 1000000 lần trong quá trình tính toán khiến kết quả không được xác định. Tôi đã có thể tìm ra vấn đề bằng cách tắt JIT - Tôi luôn nhận được kết quả tương tự với JIT bị tắt. Tôi đoán là lý do là vụ hack bảo mật String này đã phá vỡ một số hợp đồng tối ưu hóa JIT.


Nó có thể là một vấn đề an toàn luồng đã được che dấu bởi thời gian thực hiện chậm hơn và ít đồng thời hơn mà không có JIT.
Ted Pennings

@TedPennings Từ mô tả của tôi có thể, tôi chỉ không muốn đi sâu vào chi tiết. Tôi thực sự đã dành như một vài ngày cố gắng bản địa hóa nó. Đó là một thuật toán đơn luồng tính toán khoảng cách giữa hai văn bản được viết bằng hai ngôn ngữ khác nhau. Tôi đã tìm thấy hai cách khắc phục có thể cho sự cố - một là tắt JIT và cách thứ hai là thêm no-op theo nghĩa đen String.format("")vào một trong các vòng lặp bên trong. Có một cơ hội cho nó là một vấn đề thất bại khác, nhưng tôi tin đó là JIT, bởi vì vấn đề này không bao giờ được sao chép lại sau khi thêm no-op này.
Andrey Chaschev

Tôi đã làm điều này với một phiên bản đầu tiên của JDK ~ 7u9, vì vậy nó có thể là nó.
Andrey Chaschev

1
@Andrey Chaschev: xông Tôi tìm thấy hai cách khắc phục có thể xảy ra đối với vấn đề, cường độ khắc phục thứ ba, không hack vào Stringbên trong, không đi vào tâm trí của bạn?
Holger

1
@Ted Pennings: các vấn đề an toàn luồng và các vấn đề JIT thường giống nhau. JIT được phép tạo mã dựa trên finalđảm bảo an toàn của luồng trường đảm bảo phá vỡ khi sửa đổi dữ liệu sau khi xây dựng đối tượng. Vì vậy, bạn có thể xem nó như một vấn đề JIT hoặc một vấn đề MT như bạn muốn. Vấn đề thực sự là hack vào Stringvà sửa đổi dữ liệu dự kiến ​​là bất biến.
Holger

5

Theo khái niệm gộp chung, tất cả các biến String chứa cùng một giá trị sẽ trỏ đến cùng một địa chỉ bộ nhớ. Do đó, s1 và s2, cả hai đều chứa cùng một giá trị của Hello Hello World, sẽ hướng về cùng một vị trí bộ nhớ (giả sử là M1).

Mặt khác, s3 chứa Thế giới vụng, do đó, nó sẽ chỉ đến một cấp phát bộ nhớ khác (giả sử M2).

Vì vậy, bây giờ điều đang xảy ra là giá trị của S1 đang được thay đổi (bằng cách sử dụng giá trị char []). Vì vậy, giá trị tại vị trí bộ nhớ M1 được chỉ cả bởi s1 và s2 đã được thay đổi.

Do đó, vị trí bộ nhớ M1 đã được sửa đổi, điều này gây ra thay đổi giá trị của s1 và s2.

Nhưng giá trị của vị trí M2 vẫn không thay đổi, do đó s3 chứa cùng giá trị ban đầu.


5

Lý do s3 không thực sự thay đổi là vì trong Java khi bạn thực hiện một chuỗi con, mảng ký tự giá trị cho một chuỗi con được sao chép bên trong (sử dụng Arrays.copyOfRange ()).

s1 và s2 giống nhau bởi vì trong Java cả hai đều đề cập đến cùng một chuỗi được liên kết. Đó là do thiết kế trong Java.


2
Làm thế nào mà câu trả lời này thêm bất cứ điều gì vào câu trả lời trước bạn?
Xám

Cũng lưu ý rằng đây là một hành vi khá mới và không được đảm bảo bởi bất kỳ thông số kỹ thuật nào.
Paŭlo Ebermann

Việc thực hiện String.substring(int, int)thay đổi với Java 7u6. Trước khi 7u6, JVM sẽ chỉ giữ một con trỏ với bản gốc Stringchar[]cùng với một chỉ số và thời gian. Sau 7u6, nó sao chép chuỗi con thành một chuỗi mới StringCó những ưu và nhược điểm.
Eric Jablow

2

Chuỗi là bất biến, nhưng thông qua sự phản chiếu, bạn được phép thay đổi lớp Chuỗi. Bạn vừa định nghĩa lại lớp String là có thể thay đổi trong thời gian thực. Bạn có thể xác định lại các phương thức là công khai hoặc riêng tư hoặc tĩnh nếu bạn muốn.


2
Nếu bạn thay đổi mức độ hiển thị của các trường / phương thức thì không hữu ích vì tại thời điểm biên dịch chúng là riêng tư
Bohemian

1
Bạn có thể thay đổi khả năng truy cập trên các phương thức nhưng bạn không thể thay đổi trạng thái công khai / riêng tư của họ và bạn không thể làm cho chúng ở trạng thái tĩnh.
Xám

1

[Tuyên bố miễn trừ trách nhiệm đây là một kiểu trả lời có chủ ý vì tôi cảm thấy câu trả lời "không làm điều này ở nhà trẻ em" được bảo đảm]

Tội lỗi là dòng field.setAccessible(true);nói vi phạm api công cộng bằng cách cho phép truy cập vào một lĩnh vực riêng tư. Đó là một lỗ hổng bảo mật khổng lồ có thể bị khóa bằng cách cấu hình một trình quản lý bảo mật.

Hiện tượng trong câu hỏi là các chi tiết triển khai mà bạn sẽ không bao giờ thấy khi không sử dụng dòng mã nguy hiểm đó để vi phạm các công cụ sửa đổi truy cập thông qua sự phản chiếu. Rõ ràng hai chuỗi bất biến (thông thường) có thể chia sẻ cùng một mảng char. Việc một chuỗi con có chia sẻ cùng một mảng hay không phụ thuộc vào việc nó có thể hay không và liệu nhà phát triển có nghĩ sẽ chia sẻ nó hay không. Thông thường đây là những chi tiết triển khai vô hình mà bạn không cần phải biết trừ khi bạn bắn công cụ sửa đổi truy cập qua đầu với dòng mã đó.

Đơn giản là không nên dựa vào những chi tiết như vậy mà không thể trải nghiệm mà không vi phạm các công cụ sửa đổi truy cập bằng cách sử dụng sự phản chiếu. Chủ sở hữu của lớp đó chỉ hỗ trợ API công cộng thông thường và được tự do thực hiện các thay đổi triển khai trong tương lai.

Đã nói tất cả rằng dòng mã thực sự rất hữu ích khi bạn có một khẩu súng giữ đầu bạn buộc bạn phải làm những việc nguy hiểm như vậy. Sử dụng cửa sau đó thường là mùi mã mà bạn cần nâng cấp lên mã thư viện tốt hơn, nơi bạn không phải phạm tội. Một cách sử dụng phổ biến khác của dòng mã nguy hiểm đó là viết "khung voodoo" (orm, container container, ...). Nhiều người tôn giáo về các khuôn khổ như vậy (cả cho và chống lại họ) vì vậy tôi sẽ tránh mời một cuộc chiến nảy lửa bằng cách không nói gì khác ngoài đại đa số các lập trình viên không phải đến đó.


1

Các chuỗi được tạo trong vùng cố định của bộ nhớ heap JVM. Vì vậy, có, nó thực sự bất biến và không thể thay đổi sau khi được tạo ra. Bởi vì trong JVM, có ba loại bộ nhớ heap: 1. Thế hệ trẻ 2. Thế hệ cũ 3. Thế hệ vĩnh viễn.

Khi bất kỳ đối tượng nào được tạo, nó sẽ đi vào vùng heap thế hệ trẻ và vùng PermGen dành riêng cho nhóm gộp.

Dưới đây là chi tiết hơn bạn có thể đi và lấy thêm thông tin từ: Cách Bộ sưu tập rác hoạt động trong Java .


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.