ทำไม + +,, =, * =, / = ตัวดำเนินการกำหนดผสมของ Java ไม่จำเป็นต้องส่ง


3633

จนถึงวันนี้ฉันคิดอย่างนั้น:

i += j;

เป็นเพียงทางลัดสำหรับ:

i = i + j;

แต่ถ้าเราลองทำสิ่งนี้:

int i = 5;
long j = 8;

จากนั้นi = i + j;จะไม่รวบรวม แต่i += j;จะรวบรวมได้

มันหมายความว่าในความเป็นจริงi += j;เป็นทางลัดสำหรับบางสิ่งบางอย่างเช่นนี้ i = (type of i) (i + j)?


135
ฉันแปลกใจที่ Java อนุญาตให้ใช้นี้ซึ่งเป็นภาษาที่เข้มงวดกว่ารุ่นก่อน ข้อผิดพลาดในการคัดเลือกสามารถนำไปสู่ความล้มเหลวที่สำคัญเช่นเดียวกับกรณีที่ Ariane5 Flight 501 ซึ่งการส่ง 64 บิตไปยังเลขจำนวนเต็ม 16 บิตส่งผลให้เกิดความผิดพลาด
SQLDiver

103
ในระบบควบคุมการบินที่เขียนด้วยภาษาจาวานี่จะเป็นสิ่งที่คุณกังวลน้อยที่สุด @SQLDiver
Ross Drew

10
จริงๆแล้วi+=(long)j;แม้จะรวบรวมได้ดี
Tharindu Sathischandra

6
การผลักดันอย่างต่อเนื่องโดยนักพัฒนาชุดหนึ่งเพื่อความแม่นยำและอีกชุดเพื่อความสะดวกในการใช้งานเป็นสิ่งที่น่าสนใจจริงๆ เราต้องการเกือบสองภาษาซึ่งเป็นภาษาที่แม่นยำอย่างน่าอัศจรรย์และเป็นภาษาที่ใช้งานง่าย การกด Java จากทั้งสองทิศทางจะเป็นการเลื่อนไปที่ไม่เหมาะสมสำหรับทั้งสองกลุ่ม
Bill K

5
ถ้ามันต้องการการคัดเลือกคุณจะวางที่ไหน? i += (int) f;ปลดเปลื้อง f ก่อนเพิ่มดังนั้นจึงไม่เทียบเท่า (int) i += f;ปลดเปลื้องผลลัพธ์หลังจากการมอบหมายไม่เท่ากับ ไม่มีสถานที่ที่จะนำนักแสดงที่จะหมายถึงว่าคุณต้องการที่จะโยนค่าหลังจากเพิ่ม แต่ก่อนที่จะได้รับมอบหมาย
Norill Tempest

คำตอบ:


2441

เช่นเดียวกับคำถามเหล่านี้ JLS ถือเป็นคำตอบ ในกรณีนี้§15.26.2ผู้ประกอบการกำหนด Compound สารสกัด:

นิพจน์การมอบหมายผสมของฟอร์มE1 op= E2เทียบเท่ากับE1 = (T)((E1) op (E2))โดยที่Tเป็นประเภทE1ยกเว้นว่าE1ถูกประเมินเพียงครั้งเดียว

ตัวอย่างที่อ้างถึงจาก§15.26.2

[... ] รหัสต่อไปนี้ถูกต้อง:

short x = 3;
x += 4.6;

และส่งผลให้ x มีค่า 7 เพราะมันเท่ากับ:

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

กล่าวอีกนัยหนึ่งสมมติฐานของคุณถูกต้อง


42
ดังนั้นi+=jคอมไพล์เป็นฉันจะตรวจสอบตัวเอง แต่มันจะส่งผลให้เกิดการสูญเสียของความแม่นยำใช่มั้ย? หากเป็นเช่นนั้นเหตุใดจึงไม่อนุญาตให้เกิดขึ้นใน i = i + j ด้วย ทำไมเราถึงมีปัญหา?
bad_keypoints

46
@ronnieaka: ฉันคาดเดาว่านักออกแบบภาษารู้สึกว่าในกรณีหนึ่ง ( i += j) มันปลอดภัยกว่าที่จะคิดว่าการสูญเสียความแม่นยำเป็นที่ต้องการเมื่อเทียบกับกรณีอื่น ๆ ( i = i + j)
Lukas Eder

12
ไม่ถูกต้องอยู่ตรงหน้าฉัน! ขออภัยฉันไม่ได้สังเกตเห็นก่อนหน้านี้ เช่นเดียวกับในคำตอบของคุณE1 op= E2 is equivalent to E1 = (T)((E1) op (E2))ดังนั้นสิ่งที่คล้ายกันก็คือ typecasting ลง (จากยาวไปจนถึง int) ในขณะที่ใน i = i + j เราต้องทำอย่างชัดเจนนั่นคือให้มี(T)ส่วนในE1 = ((E1) op (E2))มันใช่ไหม?
bad_keypoints

11
เหตุผลที่เป็นไปได้ว่าทำไมคอมไพเลอร์ Java เพิ่ม typecast เพราะถ้าคุณพยายามทำเลขคณิตในประเภทที่เข้ากันไม่ได้จะไม่มีวิธีในการพิมพ์ typecast ของผลลัพธ์โดยใช้แบบฟอร์มสัญญา โดยทั่วไป typecast ของผลลัพธ์มีความแม่นยำมากกว่า typecast ของอาร์กิวเมนต์ที่มีปัญหา ไม่มี typecast ที่จะทำให้การหดตัวไร้ประโยชน์เมื่อใช้ประเภทที่เข้ากันไม่ได้เนื่องจากจะทำให้คอมไพเลอร์ทิ้งข้อผิดพลาดออกไปเสมอ
ThePyroEagle

6
มันไม่โค้งมน มันหล่อ (= ตัดทอน)
ลูคัสอีเดอร์

483

ตัวอย่างที่ดีของการคัดเลือกนักแสดงนี้ใช้ * = หรือ / =

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

หรือ

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

หรือ

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

หรือ

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

11
@AkshatAgarwal ch เป็น char 65 * 1.5 = 97.5 -> เข้าใจไหม
Sajal Dutta

79
ใช่ แต่ฉันเห็นผู้เริ่มต้นมาที่นี่อ่านและจะคิดว่าคุณสามารถแปลงตัวละครจากตัวพิมพ์ใหญ่เป็นตัวเล็กโดยการคูณมันด้วย 1.5
Dawood ibn Kareem

103
@DavidWallace ตัวอักษรใด ๆ ตราบใดที่มันเป็นA;)
ปีเตอร์ Lawrey

14
@PeterLawrey & @DavidWallace ฉันจะเปิดเผยความลับของคุณ- ch += 32 = D
Minhas Kamal

256

คำถามที่ดีมาก ข้อกำหนด Java Languageยืนยันคำแนะนำของคุณ

ตัวอย่างเช่นรหัสต่อไปนี้ถูกต้อง:

short x = 3;
x += 4.6;

และส่งผลให้ x มีค่า 7 เพราะมันเท่ากับ:

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

15
หรือสนุกสนานยิ่งขึ้น: "int x = 33333333; x + = 1.0f;"
supercat

5
@supercat กลอุบายนี่คืออะไร? การแปลงแบบขยายที่ถูกปัดอย่างไม่ถูกต้องตามด้วยการเติมที่ไม่ได้เปลี่ยนผลลัพธ์จริง ๆ แล้วส่งกลับไปที่ int อีกครั้งเพื่อสร้างผลลัพธ์ที่ไม่คาดคิดสำหรับจิตใจมนุษย์ปกติ
neXus

1
@neXus: IMHO กฎการแปลงควรมีความdouble->floatกว้างเป็นพิเศษโดยค่าของประเภทจะfloatระบุจำนวนจริงน้อยกว่าของประเภทdoubleนั้น ๆ หากมีใครเห็นdoubleว่าเป็นที่อยู่ทางไปรษณีย์ที่สมบูรณ์และfloatเป็นรหัสไปรษณีย์5 หลักเป็นไปได้ที่จะตอบสนองคำขอสำหรับรหัสไปรษณีย์ที่ให้ที่อยู่ที่สมบูรณ์ แต่ไม่สามารถระบุคำขอสำหรับที่อยู่สมบูรณ์ที่ระบุเพียงรหัสไปรษณีย์ได้อย่างถูกต้อง . การแปลงที่อยู่เป็นรหัสไปรษณีย์เป็นการดำเนินการที่สูญเสียไปแต่ ...
supercat

1
... คนที่ต้องการที่อยู่ที่สมบูรณ์แบบโดยทั่วไปจะไม่ขอรหัสไปรษณีย์เพียงอย่างเดียว การแปลงจากfloat->doubleนั้นเทียบเท่ากับการแปลงรหัสไปรษณีย์ของสหรัฐอเมริกา 90210 ด้วย "US Post Office, Beverly Hills CA 90210"
supercat

181

ใช่,

โดยทั่วไปเมื่อเราเขียน

i += l; 

คอมไพเลอร์แปลงสิ่งนี้เป็น

i = (int)(i + l);

ฉันเพิ่งตรวจสอบ.classรหัสไฟล์

เป็นเรื่องดีที่ควรรู้


3
คุณบอกฉันได้ไหมว่า classfile นี้คืออะไร?
nanofarad

6
@hexafraction: สิ่งที่คุณหมายถึงไฟล์คลาส? ถ้าคุณถามเกี่ยวกับไฟล์ชั้นฉันกล่าวถึงในการโพสต์ของฉันกว่ามันเป็นรุ่นที่ได้มาตรฐานตามระดับของ Java ของคุณ
Umesh Awasthi

3
โอ้คุณพูดถึง "รหัส" ไฟล์คลาสซึ่งทำให้ฉันเชื่อว่า classfile เฉพาะที่เกี่ยวข้อง ฉันเข้าใจสิ่งที่คุณหมายถึงตอนนี้
nanofarad

@Bogdan นั่นไม่น่าจะมีปัญหากับแบบอักษรที่ใช้อย่างถูกต้อง โปรแกรมเมอร์ที่เลือกตัวอักษรที่ไม่ถูกต้อง whe สำหรับการเขียนโปรแกรมอย่างชัดเจนควรคิดเกี่ยวกับวิธีการดำเนินการ ...
glglgl

7
@glglgl ฉันไม่เห็นด้วยว่าควรใช้แบบอักษรเพื่อแยกความแตกต่างในกรณีเหล่านั้น ... แต่ทุกคนมีอิสระในการเลือกสิ่งที่คิดว่าดีที่สุด
Bogdan Alexandru

92

คุณจำเป็นต้องหล่อจากlongไปint explicitlyในกรณีของi = i + l แล้วมันจะรวบรวมและให้ผลผลิตที่ถูกต้อง ชอบ

i = i + (int)l;

หรือ

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

แต่ในกรณีที่+=มันใช้งานได้ดีเพราะโอเปอเรเตอร์ทำชนิดการส่งข้อมูลจากประเภทของตัวแปรด้านขวาเป็นประเภทของตัวแปรด้านซ้ายดังนั้นไม่จำเป็นต้องทำการแปลงอย่างชัดเจน


7
ในกรณีนี้ "การส่งโดยนัย" อาจสูญเสีย ในความเป็นจริงเป็น @LukasEder ระบุในคำตอบของเขาโยนไปintจะดำเนินการหลังจาก +คอมไพเลอร์จะ (ควร?) โยนคำเตือนถ้ามันส่งlongไปintจริงๆ
Romain

63

ปัญหาที่นี่เกี่ยวข้องกับการคัดเลือกนักแสดง

เมื่อคุณเพิ่ม int และ long

  1. วัตถุ int ถูก casted เป็น long & ทั้งสองอย่างถูกเพิ่มเข้ามาและคุณจะได้วัตถุยาว
  2. แต่วัตถุที่มีความยาวจะไม่สามารถใช้กับ int ได้ ดังนั้นคุณต้องทำอย่างชัดเจน

แต่+=มีการเข้ารหัสในลักษณะที่จะพิมพ์แบบหล่อi=(int)(i+m)


54

ในการแปลงประเภท Java จะดำเนินการโดยอัตโนมัติเมื่อประเภทของการแสดงออกทางด้านขวามือของการดำเนินงานที่ได้รับมอบหมายสามารถส่งเสริมประเภทของตัวแปรทางด้านซ้ายมือของการมอบหมายอย่างปลอดภัย ดังนั้นเราสามารถกำหนด:

 byte -> short -> int -> long -> float -> double 

สิ่งเดียวกันจะไม่ทำงานในทางกลับกัน ตัวอย่างเช่นเราไม่สามารถแปลง long เป็น int ได้โดยอัตโนมัติเพราะอันแรกต้องการพื้นที่เก็บข้อมูลมากกว่าวินาทีและอาจทำให้ข้อมูลสูญหายได้ เพื่อบังคับให้เกิดการแปลงเราจะต้องทำการแปลงที่ชัดเจน
ประเภท - การแปลง


2
เฮ้ แต่longเป็นครั้งที่ 2 floatมีขนาดใหญ่กว่า
ชื่อที่แสดง

11
A floatไม่สามารถเก็บintค่าได้ทุกค่าและdoubleไม่สามารถเก็บlongค่าได้ทุกค่า
อเล็กซ์ MDC

2
คุณหมายถึงอะไรโดย "แปลงอย่างปลอดภัย"? จากส่วนหลังของคำตอบฉันสามารถอนุมานได้ว่าคุณหมายถึงการแปลงโดยอัตโนมัติ (การส่งโดยนัย) ซึ่งแน่นอนว่าไม่เป็นความจริงในกรณีที่มีการลอย -> ยาว float pi = 3.14f; ยาว b = pi; จะทำให้คอมไพเลอร์ผิดพลาด
ลุค

1
คุณควรที่จะแยกแยะความแตกต่างของชนิดจุดเริ่มต้นด้วยความแตกต่างของชนิดจำนวนเต็ม พวกเขาไม่เหมือนกัน
ThePyroEagle

Java มีกฎการแปลงแบบง่าย ๆ ที่ต้องการการใช้งาน casts ในหลายรูปแบบที่พฤติกรรมที่ไม่มี casts จะตรงกับความคาดหวัง แต่ไม่จำเป็นต้องใช้ casts ในหลายรูปแบบที่มักจะผิดพลาด ยกตัวอย่างเช่นคอมไพเลอร์จะยอมรับdouble d=33333333+1.0f;โดยไม่มีการร้องเรียนถึงแม้ว่าผลที่ 33,333,332.0 อาจจะไม่ได้เป็นสิ่งที่ตั้งใจ (อนึ่งคำตอบที่ถูกต้อง arithmetically ของ 33333334.0f จะแทนได้อย่างใดอย่างหนึ่งfloatหรือint)
supercat

46

บางครั้งคำถามดังกล่าวสามารถถามได้ในการสัมภาษณ์

ตัวอย่างเช่นเมื่อคุณเขียน:

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

ไม่มีการพิมพ์อัตโนมัติ ใน C ++ จะไม่มีข้อผิดพลาดใด ๆ รวบรวมโค้ดข้างต้น แต่ใน Java Incompatible type exceptionคุณจะได้รับสิ่งที่ต้องการ

เพื่อหลีกเลี่ยงมันคุณต้องเขียนโค้ดของคุณเช่นนี้:

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

6
ขอบคุณสำหรับข้อมูลเชิงลึกเกี่ยวกับการเปรียบเทียบการopใช้ใน C ++ กับการใช้งานใน Java ฉันชอบดูเรื่องไม่สำคัญเหล่านี้อยู่เสมอและฉันคิดว่าพวกเขามีส่วนร่วมในการสนทนาที่มักถูกทิ้งไว้
โทมัส

2
อย่างไรก็ตามคำถามตัวเองนั้นน่าสนใจการถามคำถามนี้ในการสัมภาษณ์นั้นโง่ ไม่ได้พิสูจน์ว่าบุคคลนั้นสามารถสร้างรหัสที่มีคุณภาพที่ดีได้ - เพียงพิสูจน์ให้เห็นว่าเขามีความอดทนพอที่จะเตรียมสอบใบรับรอง Oracle และ "หลีกเลี่ยง" ประเภทที่เข้ากันไม่ได้โดยใช้การแปลงอัตโนมัติที่เป็นอันตรายและซ่อนข้อผิดพลาดที่อาจเกิดขึ้นได้แม้อาจพิสูจน์ได้ว่าบุคคลนั้นไม่สามารถสร้างรหัสที่มีคุณภาพได้ ประณามผู้เขียน Java สำหรับการแปลงอัตโนมัติและมวยอัตโนมัติเหล่านี้และทั้งหมด!
Honza Zidek

25

ความแตกต่างที่สำคัญa = a + bคือไม่มีการพิมพ์ดีดเกิดขึ้นดังนั้นคอมไพเลอร์ก็โกรธคุณเพราะไม่พิมพ์ดีด แต่ด้วยa += bสิ่งที่มันทำจริงๆคือ Typecasting เป็นชนิดเข้ากันได้กับb aดังนั้นหากทำ

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

สิ่งที่คุณทำจริงๆคือ:

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

5
ตัวดำเนินการที่ได้รับมอบหมายผสมทำการแปลงที่แคบของผลลัพธ์ของการดำเนินการแบบไบนารีไม่ใช่ตัวถูกดำเนินการทางขวา ดังนั้นในตัวอย่างของคุณ 'a + = b' ไม่เท่ากับ 'a = a + (int) b' แต่ตามที่อธิบายโดยคำตอบอื่น ๆ ที่นี่เพื่อ 'a = (int) (a + b)'
Lew Bloch

13

จุดที่ลึกซึ้งที่นี่ ...

มี typecast โดยนัยi+jเมื่อjเป็น double และiint Java ALWAYSแปลงจำนวนเต็มเป็นสองเท่าเมื่อมีการดำเนินการระหว่างกัน

เพื่อชี้แจงi+=jที่iเป็นจำนวนเต็มและjคู่สามารถอธิบายเป็น

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

โปรดดู: คำอธิบายของการคัดเลือกโดยนัย

คุณอาจต้องการที่จะ typecast jไป(int)ในกรณีนี้เพื่อความชัดเจน


1
ฉันคิดว่ากรณีที่น่าสนใจกว่านี้อาจเป็นint someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;ได้ การเพิ่มศูนย์ถึงsomeIntไม่ควรมีผลต่อค่าของมัน แต่การส่งเสริมsomeIntการfloatอาจมีการเปลี่ยนแปลงค่าของมัน
supercat

5

Java Language ข้อกำหนดกำหนดE1 op= E2ที่จะเทียบเท่ากับE1 = (T) ((E1) op (E2))ที่Tเป็นประเภทของE1และE1ได้รับการประเมินในครั้งเดียว

นั่นเป็นคำตอบทางเทคนิค แต่คุณอาจสงสัยว่าทำไมถึงเป็นเช่นนั้น ลองพิจารณาโปรแกรมต่อไปนี้

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

โปรแกรมนี้พิมพ์อะไร

คุณเดา 3 น่าเสียดายที่โปรแกรมนี้จะไม่คอมไพล์ ทำไม? ดีก็จึงเกิดขึ้นนอกเหนือจากไบต์ใน Java ที่ถูกกำหนดให้กลับ intสิ่งนี้ฉันเชื่อว่าเป็นเพราะ Java Virtual Machine ไม่ได้กำหนดการดำเนินการไบต์เพื่อบันทึกบนไบต์ (มีจำนวน จำกัด ) การใช้การดำเนินการจำนวนเต็มแทนเป็นรายละเอียดการใช้งานที่แสดงในภาษา

แต่ถ้าa = a + bไม่ได้ทำงานนั่นก็หมายความว่าa += bจะไม่ทำงานสำหรับไบต์ถ้ามันถูกกำหนดให้เป็นE1 += E2 E1 = E1 + E2ดังตัวอย่างก่อนหน้านี้แสดงให้เห็นว่าเป็นกรณีจริง ในฐานะที่เป็นแฮ็คที่จะทำให้+=ผู้ปฏิบัติงานทำงานเป็นไบต์และกางเกงขาสั้น มันไม่ได้เป็นการแฮ็กที่ยอดเยี่ยม แต่กลับมาในระหว่างการทำงานของ Java 1.0 จุดเน้นคือการปล่อยภาษาเริ่มต้น ตอนนี้เนื่องจากความเข้ากันได้ย้อนหลังแฮ็คนี้ที่นำมาใช้ใน Java 1.0 ไม่สามารถลบได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.