Tại sao toán tử + =, - =, * =, / = toán tử gán ghép của Java yêu cầu truyền?


3633

Cho đến hôm nay, tôi nghĩ rằng ví dụ:

i += j;

Chỉ là một phím tắt cho:

i = i + j;

Nhưng nếu chúng ta thử điều này:

int i = 5;
long j = 8;

Sau đó i = i + j;sẽ không biên dịch nhưng i += j;sẽ biên dịch tốt.

Có nghĩa là trên thực tế i += j;là một lối tắt cho một cái gì đó như thế này i = (type of i) (i + j)?


135
Tôi ngạc nhiên khi Java cho phép điều này, là một ngôn ngữ chặt chẽ hơn so với các ngôn ngữ trước. Lỗi trong quá trình truyền có thể dẫn đến lỗi nghiêm trọng, như trường hợp của Ariane5 Chuyến bay 501 trong đó số float 64 bit chuyển sang số nguyên 16 bit dẫn đến sự cố.
SQLDiver

103
Trong một hệ thống điều khiển chuyến bay được viết bằng Java, đây sẽ là điều ít lo lắng nhất của bạn @QueryDiver
Ross Drew

10
Thực tế i+=(long)j;thậm chí sẽ biên dịch tốt.
Tharindu Sathischandra 18/03/2016

6
Sự thúc đẩy liên tục của một nhóm các nhà phát triển về tính chính xác và một nhóm khác để dễ sử dụng thực sự rất thú vị. Chúng tôi gần như cần hai phiên bản ngôn ngữ, một phiên bản chính xác đến kinh ngạc và một phiên bản dễ sử dụng. Đẩy Java từ cả hai hướng sẽ khiến nó không phù hợp với cả hai nhóm.
Bill K

5
Nếu nó yêu cầu đúc, bạn sẽ đặt nó ở đâu? i += (int) f;phôi f trước khi thêm, vì vậy nó không tương đương. (int) i += f;kết quả sau khi gán, không tương đương. sẽ không có nơi nào để đặt một diễn viên có nghĩa là bạn muốn truyền giá trị sau khi thêm, nhưng trước khi gán.
Norill Tempest

Câu trả lời:


2441

Như mọi khi với những câu hỏi này, JLS giữ câu trả lời. Trong trường hợp này §15.26.2 Toán tử chuyển nhượng hợp chất . Trích:

Một biểu thức gán tổng hợp của biểu mẫu E1 op= E2tương đương với E1 = (T)((E1) op (E2)), trong đó Tlà loại E1, ngoại trừ chỉ E1được đánh giá một lần.

Một ví dụ được trích dẫn từ §15.26.2

[...] đoạn mã sau là chính xác:

short x = 3;
x += 4.6;

và kết quả là x có giá trị 7 vì nó tương đương với:

short x = 3;
x = (short)(x + 4.6);

Nói cách khác, giả định của bạn là chính xác.


42
Vì vậy, i+=jbiên dịch khi tôi tự kiểm tra, nhưng nó sẽ dẫn đến mất độ chính xác phải không? Nếu đó là lý do, tại sao nó không cho phép nó xảy ra trong i = i + j? Tại sao lỗi chúng tôi ở đó?
bad_keypoint

46
@ronnieaka: Tôi đoán rằng các nhà thiết kế ngôn ngữ cảm thấy rằng trong một trường hợp ( i += j), sẽ an toàn hơn khi cho rằng sự mất độ chính xác là mong muốn trái ngược với trường hợp khác ( i = i + j)
Lukas Eder

12
Không, nó ở ngay đó, trước mặt tôi! Xin lỗi tôi đã không nhận thấy nó sớm hơn. Như trong câu trả lời của bạn E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), vì vậy, đó giống như là ngầm định kiểu chữ (giảm từ dài xuống int). Trong khi trong i = i + j, chúng ta phải làm điều đó một cách rõ ràng, tức là, cung cấp (T)phần trong E1 = ((E1) op (E2))Không phải sao?
bad_keypoint

11
Một lý do có khả năng giải thích tại sao trình biên dịch Java thêm một kiểu chữ là bởi vì nếu bạn đang cố gắng thực hiện số học trên các loại không tương thích, thì không có cách nào thực hiện một kiểu chữ của kết quả bằng cách sử dụng biểu mẫu được ký hợp đồng. Một kiểu chữ của kết quả thường chính xác hơn một kiểu chữ của đối số có vấn đề. Không có typecast nào làm cho sự co lại trở nên vô dụng khi sử dụng các loại không tương thích, vì nó sẽ luôn khiến trình biên dịch đưa ra lỗi.
ThePyroEagle

6
Nó không tròn. Đó là diễn viên (= cắt ngắn)
Lukas Eder

483

Một ví dụ điển hình của việc truyền này là sử dụng * = hoặc / =

byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57

hoặc là

byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40

hoặc là

char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'

hoặc là

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'

11
@AkshatAgarwal ch là một char. 65 * 1.5 = 97,5 -> Hiểu chưa?
Sajal Dutta

79
Vâng, nhưng tôi chỉ có thể thấy một số người mới bắt đầu đến đây, đọc nó và nghĩ rằng bạn có thể chuyển đổi bất kỳ ký tự nào từ chữ hoa sang chữ thường bằng cách nhân nó với 1,5.
Dawood ibn Kareem

103
@DavidWallace Bất kỳ nhân vật nào miễn là nó A;)
Peter Lawrey

14
@PeterLawrey & @DavidWallace Tôi sẽ tiết lộ bí mật của bạn - ch += 32 = D
Minhas Kamal

256

Câu hỏi rất hay. Đặc tả ngôn ngữ Java xác nhận đề xuất của bạn.

Ví dụ: đoạn mã sau là chính xác:

short x = 3;
x += 4.6;

và kết quả là x có giá trị 7 vì nó tương đương với:

short x = 3;
x = (short)(x + 4.6);

15
Hoặc vui hơn: "int x = 33333333; x + = 1.0f;".
supercat

5
@supercat, đây là mánh khóe gì vậy? Một chuyển đổi mở rộng được làm tròn không chính xác, theo sau là một bổ sung không thực sự thay đổi kết quả, chuyển sang int một lần nữa để tạo ra một kết quả bất ngờ nhất đối với tâm trí con người bình thường.
neXus

1
@neXus: IMHO, các quy tắc chuyển đổi nên được coi double->floatlà mở rộng, trên cơ sở các giá trị của loại floatxác định các số thực ít cụ thể hơn các quy tắc loại double. Nếu một người xem doublelà một địa chỉ bưu chính hoàn chỉnh và floatnhư một mã bưu chính gồm 5 chữ số, bạn có thể đáp ứng yêu cầu về một mã bưu chính được cung cấp một địa chỉ đầy đủ, nhưng không thể chỉ định chính xác yêu cầu cho một địa chỉ đầy đủ chỉ được cung cấp một mã bưu chính . Chuyển đổi địa chỉ đường phố thành mã bưu chính là một hoạt động mất mát, nhưng ...
supercat

1
... Ai đó cần một địa chỉ đầy đủ thường sẽ không yêu cầu mã bưu chính. Chuyển đổi từ float->doubletương đương với chuyển đổi mã bưu chính Hoa Kỳ 90210 với "Bưu điện Hoa Kỳ, Beverly Hills CA 90210".
supercat

181

Đúng,

về cơ bản khi chúng ta viết

i += l; 

trình biên dịch chuyển đổi nó thành

i = (int)(i + l);

Tôi chỉ kiểm tra .classmã tập tin.

Thực sự là một điều tốt để biết


3
Bạn có thể cho tôi biết đây là classfile nào không?
nanofarad

6
@hexafraction: ý của bạn là gì trong tập tin lớp? nếu bạn hỏi về tệp lớp tôi đã đề cập trong bài đăng của mình thì đó là phiên bản tuân thủ của lớp java của bạn
Umesh Awasthi

3
Ồ, bạn đã đề cập đến "mã" tệp lớp, khiến tôi tin rằng một tệp lớp cụ thể có liên quan. Tôi hiểu ý của bạn lúc này.
nanofarad

@Bogdan Đó không phải là vấn đề với phông chữ được sử dụng đúng cách. Một lập trình viên chọn phông chữ sai khi lập trình nên suy nghĩ rõ ràng về cách tiến hành ...
glglgl 16/2/2015

7
@glglgl Tôi không đồng ý rằng người ta nên dựa vào phông chữ để phân biệt trong những trường hợp đó ... nhưng mọi người đều có quyền tự do lựa chọn điều gì là tốt nhất.
Bogdan Alexandru

92

bạn cần truyền từ longđến int explicitlytrong trường hợp i = i + l sau đó nó sẽ biên dịch và đưa ra đầu ra chính xác. giống

i = i + (int)l;

hoặc là

i = (int)((long)i + l); // this is what happens in case of += , dont need (long) casting since upper casting is done implicitly.

nhưng trong trường hợp +=nó chỉ hoạt động tốt bởi vì toán tử hoàn toàn thực hiện việc truyền kiểu từ loại biến phải sang loại biến trái nên không cần truyền rõ ràng.


7
Trong trường hợp này, "diễn viên ngầm" có thể bị mất. Trên thực tế, như bang @LukasEder trong câu trả lời của mình, các diễn viên để intđược thực hiện sau khi các +. Trình biên dịch sẽ (nên?) Đưa ra một cảnh báo nếu nó thực sự đã truyền longtới int.
Romain

63

Vấn đề ở đây liên quan đến việc đúc kiểu.

Khi bạn thêm int và dài,

  1. Đối tượng int được truyền thành dài & cả hai được thêm vào và bạn nhận được đối tượng dài.
  2. nhưng đối tượng dài không thể được ngầm định chuyển sang int. Vì vậy, bạn phải làm điều đó một cách rõ ràng.

Nhưng +=được mã hóa theo cách mà nó thực hiện kiểu đúc.i=(int)(i+m)


54

Trong các chuyển đổi loại Java được thực hiện tự động khi loại biểu thức ở phía bên phải của một hoạt động gán có thể được thăng cấp một cách an toàn thành loại biến ở phía bên trái của phép gán. Do đó chúng ta có thể gán một cách an toàn:

 byte -> ngắn -> int -> dài -> float -> double. 

Điều tương tự sẽ không làm việc theo cách khác. Ví dụ, chúng ta không thể tự động chuyển đổi một khoảng dài thành một int vì lần đầu tiên yêu cầu lưu trữ nhiều hơn lần thứ hai và do đó thông tin có thể bị mất. Để buộc chuyển đổi như vậy, chúng tôi phải thực hiện một chuyển đổi rõ ràng.
Loại - Chuyển đổi


2
Này, nhưng longlớn hơn gấp 2 lần float.
Tên hiển thị

11
Không floatthể giữ mọi intgiá trị có thể và doublekhông thể giữ mọi longgiá trị có thể .
Alex MDC

2
"Chuyển đổi an toàn" nghĩa là gì? Từ phần sau của câu trả lời tôi có thể suy luận rằng bạn có nghĩa là chuyển đổi tự động (diễn viên ngầm) tất nhiên không đúng trong trường hợp thả nổi -> dài. phao pi = 3,14f; dài b = pi; sẽ dẫn đến lỗi trình biên dịch.
Lu-ca

1
Bạn nên phân biệt các kiểu nguyên thủy dấu phẩy động với các kiểu nguyên thủy nguyên. Chúng không giống nhau.
ThePyroEagle

Java có các quy tắc chuyển đổi đơn giản yêu cầu sử dụng các phôi trong nhiều mẫu trong đó hành vi không có phôi sẽ phù hợp với mong đợi, nhưng không yêu cầu các phôi trong nhiều mẫu thường bị lỗi. Ví dụ, một trình biên dịch sẽ chấp nhận double d=33333333+1.0f;mà không có khiếu nại, mặc dù kết quả 33333332.0 có thể không phải là mục đích (tình cờ, câu trả lời đúng về mặt số học của 33333334.0f sẽ có thể được biểu diễn dưới dạng floathoặc int).
supercat

46

Đôi khi, một câu hỏi như vậy có thể được hỏi tại một cuộc phỏng vấn.

Ví dụ: khi bạn viết:

int a = 2;
long b = 3;
a = a + b;

không có typecasting tự động. Trong C ++ sẽ không có bất kỳ lỗi nào khi biên dịch mã ở trên, nhưng trong Java bạn sẽ nhận được một cái gì đó như thế Incompatible type exception.

Vì vậy, để tránh nó, bạn phải viết mã của bạn như thế này:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting

6
Cảm ơn sự hiểu biết sâu sắc về việc so sánh việc opsử dụng trong C ++ với việc sử dụng nó trong Java. Tôi luôn thích nhìn thấy những mẩu chuyện nhỏ nhặt này và tôi nghĩ rằng họ đóng góp một cái gì đó cho cuộc trò chuyện thường có thể bị bỏ lại.
Thomas

2
Tuy nhiên, bản thân câu hỏi rất thú vị, hỏi điều này trong một cuộc phỏng vấn là ngu ngốc. Nó không chứng minh rằng người đó có thể tạo ra một mã chất lượng tốt - nó chỉ chứng minh rằng anh ta có đủ kiên nhẫn để chuẩn bị cho một kỳ thi chứng chỉ Oracle. Và "tránh" các loại không tương thích bằng cách sử dụng chuyển đổi tự động nguy hiểm và do đó che giấu lỗi tràn có thể xảy ra, thậm chí có thể chứng minh rằng người đó không thể tạo mã có thể có chất lượng tốt. Chết tiệt các tác giả Java cho tất cả các chuyển đổi tự động và tự động đấm bốc và tất cả!
Honza Zidek

25

Sự khác biệt chính là với a = a + b, không có lỗi đánh máy đang diễn ra, và vì vậy trình biên dịch tức giận với bạn vì không đánh máy. Nhưng với a += b, những gì nó thực sự làm là đánh máy bkiểu tương thích a. Vì vậy, nếu bạn làm

int a=5;
long b=10;
a+=b;
System.out.println(a);

Những gì bạn đang thực sự là:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);

5
Toán tử gán hợp chất thực hiện chuyển đổi thu hẹp kết quả của phép toán nhị phân, không phải toán hạng bên phải. Vì vậy, trong ví dụ của bạn, 'a + = b' không tương đương với 'a = a + (int) b' nhưng, như được giải thích bởi các câu trả lời khác ở đây, với 'a = (int) (a + b)'.
Lew Bloch

13

Điểm tinh tế ở đây ...

Có một kiểu chữ ngầm cho i+jkhi nào jlà một đôi và ilà một int. Java LUÔN chuyển đổi một số nguyên thành gấp đôi khi có một hoạt động giữa chúng.

Để làm rõ i+=jvị trí của imột số nguyên và jmột số kép có thể được mô tả là

i = <int>(<double>i + j)

Xem: mô tả về đúc ngầm

Bạn có thể muốn định kiểu jđể (int)trong trường hợp này cho rõ ràng.


1
Tôi nghĩ rằng một trường hợp thú vị hơn có thể được int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Thêm zero để someIntkhông ảnh hưởng đến giá trị của nó, nhưng thúc đẩy someIntđể floatcó thể thay đổi giá trị của nó.
supercat

5

Đặc tả ngôn ngữ Java định nghĩa E1 op= E2tương đương với E1 = (T) ((E1) op (E2))nơi Tlà một loại E1E1được đánh giá một lần .

Đó là một câu trả lời kỹ thuật, nhưng bạn có thể tự hỏi tại sao đó là một trường hợp. Vâng, hãy xem xét các chương trình sau đây.

public class PlusEquals {
    public static void main(String[] args) {
        byte a = 1;
        byte b = 2;
        a = a + b;
        System.out.println(a);
    }
}

Chương trình này in cái gì?

Bạn có đoán được 3 không? Quá tệ, chương trình này sẽ không được biên dịch. Tại sao? Vâng, điều đó xảy ra rằng việc thêm byte trong Java được định nghĩa để trả về mộtint . Điều này, tôi tin là bởi vì Máy ảo Java không định nghĩa các hoạt động byte để lưu trên mã byte (rốt cuộc, có một số lượng hạn chế), sử dụng các hoạt động số nguyên thay vì là một chi tiết triển khai được thể hiện bằng ngôn ngữ.

Nhưng nếu a = a + bkhông hoạt động, điều đó có nghĩa là a += bsẽ không bao giờ hoạt động đối với byte nếu nó E1 += E2được xác định là E1 = E1 + E2. Như ví dụ trước đây cho thấy, đó thực sự là trường hợp. Là một hack để làm cho +=toán tử hoạt động cho byte và quần short, có một diễn viên ngầm liên quan. Nó không phải là một hack tuyệt vời, nhưng trở lại trong quá trình Java 1.0, trọng tâm là bắt đầu phát hành ngôn ngữ. Bây giờ, vì khả năng tương thích ngược, bản hack được giới thiệu trong Java 1.0 này không thể bị xóa.

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.