Làm thế nào để lớp String ghi đè toán tử +?


129

Tại sao trong Java bạn có thể thêm Chuỗi bằng toán tử +, khi Chuỗi là một lớp? bên trongString.java mã tôi không tìm thấy bất kỳ triển khai cho toán tử này. Liệu khái niệm này có vi phạm định hướng đối tượng?


21
+Toán tử Plus ( ) là tính năng ngôn ngữ của Java.
adatapost

18
Đó là tất cả phép thuật của nhà soạn nhạc. Bạn không thể làm quá tải toán tử trong Java.
Sư tử

18
Tôi nghĩ điều kỳ lạ là String được triển khai như một thư viện bên ngoài ngôn ngữ (trong java.lang.String), nhưng nó có hỗ trợ cụ thể bên trong ngôn ngữ. Điều đó không đúng.
13ren


@ 13ren bạn sai rồi. Chuỗi nằm ngoài lớp java.lang, viết tắt của ngôn ngữ - ngôn ngữ java !!! Tất cả các lớp trong gói này là đặc biệt. Lưu ý rằng bạn không cần nhập java.lang để sử dụng chúng.

Câu trả lời:


156

Hãy xem các biểu thức đơn giản sau trong Java

int x=15;
String temp="x = "+x;

Trình biên dịch chuyển đổi "x = "+x;thành một StringBuilderbên trong và sử dụng .append(int)để "thêm" số nguyên vào chuỗi.

5.1.11. Chuyển đổi chuỗi

Bất kỳ loại nào cũng có thể được chuyển đổi thành loại Chuỗi bằng cách chuyển đổi chuỗi.

Giá trị x của kiểu nguyên thủy T trước tiên được chuyển đổi thành giá trị tham chiếu như thể bằng cách đưa nó làm đối số cho biểu thức tạo cá thể lớp thích hợp (§15.9):

  • Nếu T là boolean, thì sử dụng Boolean (x) mới.
  • Nếu T là char, thì sử dụng Ký tự mới (x).
  • Nếu T là byte, short hoặc int, thì sử dụng Integer (x) mới.
  • Nếu T dài thì sử dụng Long (x) mới.
  • Nếu T là float, sau đó sử dụng Float mới (x).
  • Nếu T là gấp đôi, sau đó sử dụng Double (x) mới.

Giá trị tham chiếu này sau đó được chuyển đổi thành kiểu Chuỗi bằng chuyển đổi chuỗi.

Bây giờ chỉ cần xem xét các giá trị tham chiếu:

  • Nếu tham chiếu là null, nó được chuyển đổi thành chuỗi "null" (bốn ký tự ASCII n, u, l, l).
  • Mặt khác, việc chuyển đổi được thực hiện như thể bằng một lời gọi của phương thức toString của đối tượng được tham chiếu không có đối số; nhưng nếu kết quả của việc gọi phương thức toString là null, thì chuỗi "null" được sử dụng thay thế.

Phương thức toString được xác định bởi Đối tượng lớp nguyên thủy (§4.3.2). Nhiều lớp ghi đè lên nó, đáng chú ý là Boolean, Ký tự, Số nguyên, Dài, Nổi, Đôi và Chuỗi.

Xem §5.4 để biết chi tiết về bối cảnh chuyển đổi chuỗi.

15,18.1.

Tối ưu hóa chuỗi kết nối: Một triển khai có thể chọn thực hiện chuyển đổi và nối chuỗi trong một bước để tránh tạo và sau đó loại bỏ một đối tượng Chuỗi trung gian. Để tăng hiệu năng nối chuỗi lặp lại, trình biên dịch Java có thể sử dụng lớp StringBuffer hoặc một kỹ thuật tương tự để giảm số lượng các đối tượng Chuỗi trung gian được tạo bằng cách đánh giá biểu thức.

Đối với các kiểu nguyên thủy, việc triển khai cũng có thể tối ưu hóa việc tạo đối tượng trình bao bọc bằng cách chuyển đổi trực tiếp từ kiểu nguyên thủy sang chuỗi.

Phiên bản tối ưu hóa sẽ không thực sự thực hiện chuyển đổi Chuỗi được gói đầy đủ trước tiên.

Đây là một minh họa tốt về một phiên bản tối ưu hóa được sử dụng bởi trình biên dịch, mặc dù không có sự chuyển đổi của một nguyên thủy, nơi bạn có thể thấy trình biên dịch thay đổi mọi thứ thành StringBuilder trong nền:

http://caprazzi.net/posts/java-bytecode-opes-concatenation-and-opesbuilder/


Mã java này:

public static void main(String[] args) {
    String cip = "cip";
    String ciop = "ciop";
    String plus = cip + ciop;
    String build = new StringBuilder(cip).append(ciop).toString();
}

Tạo điều này - xem cách hai kiểu ghép nối dẫn đến mã byte rất giống nhau:

 L0
    LINENUMBER 23 L0
    LDC "cip"
    ASTORE 1
   L1
    LINENUMBER 24 L1
    LDC "ciop"
    ASTORE 2

   // cip + ciop

   L2
    LINENUMBER 25 L2

    NEW java/lang/StringBuilder
    DUP
    ALOAD 1
    INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;

    ASTORE 3

    // new StringBuilder(cip).append(ciop).toString()

   L3
    LINENUMBER 26 L3

    NEW java/lang/StringBuilder
    DUP
    ALOAD 1
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;

    ASTORE 4
   L4
    LINENUMBER 27 L4
    RETURN

Nhìn vào ví dụ trên và cách tạo mã byte dựa trên mã nguồn trong ví dụ đã cho, bạn sẽ có thể nhận thấy rằng trình biên dịch đã chuyển đổi nội bộ câu lệnh sau

cip+ciop; 

vào

new StringBuilder(cip).append(ciop).toString();

Nói cách khác, toán tử +trong nối chuỗi có hiệu quả là một tốc ký cho StringBuilderthành ngữ dài dòng hơn .


3
Cảm ơn rất nhiều, tôi không quen với mã byte jvm nhưng đã tạo mã cho String plus = cip + ciop; và String build = new StringBuilder (cip) .append (ciop) .toString (); là tương tự nhau. và câu hỏi của tôi là hoạt động này có vi phạm định hướng đối tượng?
Pooya

7
Không, nó không. Quá tải toán tử (như trong C ++ và một số ngôn ngữ) có một số nhược điểm và các nhà thiết kế Java cảm thấy rằng đó là một khái niệm khó hiểu và bỏ qua nó khỏi Java. Đối với tôi, một ngôn ngữ hướng đối tượng phải có các khái niệm cơ bản về kế thừa, Đa hình và đóng gói mà Java có.
Sư tử

2
đúng, nhưng tôi nghĩ rằng toán tử này đã quá tải cho lớp String
Pooya

3
Có, nạp chồng toán tử được sử dụng trong Java để ghép nối kiểu Chuỗi, tuy nhiên, bạn không thể xác định toán tử của riêng mình (như trong C ++, C # và một số ngôn ngữ khác).
Sư tử

5
@Pooya: thực sự "int / int" so với "int / float" đã quá tải toán tử, do đó, ngay cả C cũng có điều đó. Tuy nhiên, điều mà C (và Java) không có là quá tải toán tử do người dùng định nghĩa: điều duy nhất xác định các cách khác nhau mà một toán tử có thể được sử dụng (trong cả C và Java) là định nghĩa ngôn ngữ (và sự khác biệt được thực hiện trong trình biên dịch). C ++ khác ở chỗ nó cho phép nạp chồng toán tử do người dùng định nghĩa (thường được gọi là "quá tải toán tử").
Joachim Sauer

27

Đây là tính năng biên dịch Java để kiểm tra toán hạng của +toán tử. Và dựa trên các toán hạng, nó tạo ra mã byte:

  • Đối với Chuỗi, nó tạo mã để nối chuỗi
  • Đối với số, nó tạo mã để thêm số.

Đây là những gì đặc tả Java nói :

Các toán tử + và -được gọi là các toán tử phụ gia. AdditiveExpression: MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression

Các toán tử cộng gộp có cùng mức ưu tiên và được liên kết trái cú pháp (chúng nhóm từ trái sang phải). Nếu kiểu toán hạng của +toán tử là String, thì phép toán là nối chuỗi.

Mặt khác, kiểu của mỗi toán hạng của +toán tử phải là loại có thể chuyển đổi (§5.1.8) thành kiểu số nguyên thủy hoặc xảy ra lỗi thời gian biên dịch.

Trong mọi trường hợp, loại của mỗi toán hạng của toán -tử nhị phân phải là loại có thể chuyển đổi (§5.1.8) thành kiểu số nguyên thủy hoặc xảy ra lỗi thời gian biên dịch.


7
Các trích dẫn từ thông số kỹ thuật không liên quan đến câu hỏi này.
Ernest Friedman-Hill

Đây là trích xuất "Nếu loại toán hạng của toán tử + là Chuỗi, thì thao tác là nối chuỗi. Mặt khác, loại của mỗi toán hạng của toán tử + phải là loại có thể chuyển đổi (§5.1.8 ) đến một kiểu số nguyên thủy hoặc xảy ra lỗi thời gian biên dịch ". Bạn có thể vui lòng cho tôi biết tại sao nó không liên quan.
Ramesh PVK

7
Nó không nói bất cứ điều gì về cách nó được thực hiện, đó là câu hỏi. Tôi nghĩ rằng các poster đã hiểu rằng các tính năng tồn tại.
Ernest Friedman-Hill

14

Làm thế nào lớp String ghi đè toán tử +?

Nó không. Trình biên dịch thực hiện nó. Nói đúng ra, trình biên dịch sẽ quá tải toán tử + cho toán hạng String.


6

Trước hết (+) bị quá tải không bị ghi đè

Ngôn ngữ Java cung cấp hỗ trợ đặc biệt cho toán tử nối chuỗi (+), đã bị quá tải cho các đối tượng Chuỗi Java.

  1. Nếu toán hạng bên trái là String, nó hoạt động như ghép.

  2. Nếu toán hạng bên trái là Integer, nó hoạt động như toán tử cộng


3
(2) Nếu toán hạng bên trái là một Integer, nó được tự động bỏ hộp intvà sau đó áp dụng các quy tắc thông thường của Java.
Hầu tước Lorne

2
Hai quy tắc được đưa ra dưới đây trích dẫn là sai: Tôi tin rằng chúng nên là: hai nguyên thủy (hoặc các lớp không thể mở hộp) = phép cộng; ít nhất một chuỗi = nối
mwfearnley

4

Ngôn ngữ Java cung cấp hỗ trợ đặc biệt cho toán tử nối chuỗi (+) và để chuyển đổi các đối tượng khác thành chuỗi. Nối chuỗi được thực hiện thông qua lớp StringBuilder(hoặc StringBuffer) và appendphương thức của nó .


4

Ý nghĩa của +toán tử khi áp dụng cho Stringđược xác định bởi ngôn ngữ, như mọi người đã viết. Vì bạn dường như không thấy điều này đủ thuyết phục, hãy xem xét điều này:

Ints, float và double đều có các biểu diễn nhị phân khác nhau, và do đó, thêm hai int là một hoạt động khác nhau, về mặt thao tác bit, hơn là thêm hai float: Đối với int bạn có thể thêm từng bit, mang theo một bit và kiểm tra tràn; đối với phao bạn phải đối phó với mantissas và số mũ riêng biệt.

Vì vậy, về nguyên tắc, "bổ sung" phụ thuộc vào bản chất của các đối tượng được "thêm". Java định nghĩa nó cho String cũng như ints và float (longs, double, ...)


3

Các +nhà điều hành thường được thay thế bằng một StringBuilderlúc biên dịch. Kiểm tra câu trả lời này để biết thêm chi tiết về vấn đề đó.


Nếu đây là trường hợp, có bất kỳ lý do tại sao StringBuilder tồn tại ở tất cả cho sử dụng công cộng? Có trường hợp nào mà người +vận hành không được thay thế bởi StringBuilder?
Cemre

2
Câu hỏi bạn muốn hỏi là "tại sao toán tử + tồn tại hoàn toàn cho sử dụng công cộng?", Bởi vì đó là sự gớm ghiếc ở đây. Đối với câu hỏi khác của bạn, tôi không biết chính xác, nhưng tôi đoán không có trường hợp nào như vậy.
Bọ Cạp

Trình biên dịch có thể sử dụng concat () thay vào đó, nếu chỉ có hai phần tử. Ngoài ra trình biên dịch không thể thay thế concat () bằng StringBuilder (hoặc sử dụng nhiều StringBuilders và nối chúng lại với nhau) khi lập trình viên đang xây dựng chuỗi theo mã dài / lồng nhau - sử dụng StringBuilder đơn, rõ ràng sẽ tốt hơn cho hiệu suất.
dùng158037
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.