การส่งเสริมประเภท Java ในพารามิเตอร์


20

ฉันสะดุดกับตัวอย่างนี้:

public class ParamTest {
    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

สิ่งนี้จะส่งผลให้เกิดข้อผิดพลาดในการคอมไพล์:

ข้อผิดพลาด: (15, 9) java: การอ้างอิงถึง printSum นั้นไม่ชัดเจนทั้งสองวิธี printSum (int, double) ใน ParamTest และวิธี printSum (ยาว, ยาว) ในการจับคู่ ParamTest

ความคลุมเครือนี้เป็นอย่างไร ไม่ควรสนับสนุนเฉพาะพารามิเตอร์ตัวที่สองในกรณีนี้เนื่องจากพารามิเตอร์ตัวแรกเป็น int อยู่แล้ว? ไม่จำเป็นต้องเลื่อนชั้นแรกในกรณีนี้ใช่ไหม

การรวบรวมสำเร็จหากฉันอัปเดตรหัสเพื่อเพิ่มวิธีอื่น:

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

ขอผมขยายให้ชัดเจนหน่อย โค้ดด้านล่างส่งผลให้เกิดความกำกวม:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

จากนั้นโค้ดด้านล่างนี้ยังส่งผลให้เกิดความกำกวม:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

อย่างไรก็ตามอันนี้ไม่ได้ทำให้เกิดความกำกวม:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, double b) {
        System.out.println("In longDBL " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

2
คอมไพเลอร์สามารถแมป printSum (1, 2) ของคุณ เพื่อ printSum (ยาว a, ยาว b) หรือ printSum (int a, double b) ดังนั้นจึงไม่ชัดเจน คุณต้องช่วยคอมไพเลอร์ให้ชัดเจนโดยให้ประเภทดังนี้: printSum (1, 2d)
Ravindra Ranwala

6
คุณใส่ข้อความแสดงข้อผิดพลาดผิดและความแตกต่างนั้นสำคัญมาก ข้อความแสดงข้อผิดพลาดที่แท้จริงคือ: Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match- ไม่ใช่วิธีที่ไม่ชัดเจน แต่เป็นการเรียกไปยังวิธีที่ไม่ชัดเจน
Erwin Bolwidt

1
@ErwinBolwidt ข้อความแสดงข้อผิดพลาดมาจาก eclipse ขออภัยฉันมีข้อผิดพลาด ยังไงฉันก็ยังไม่เข้าใจเพราะการเพิ่ม printSum (int a, long b) ลบข้อความข้อผิดพลาด
riruzen

2
JLS-5.3 => หากประเภทของนิพจน์ไม่สามารถแปลงเป็นประเภทของพารามิเตอร์ได้โดยการแปลงที่อนุญาตในบริบทการเรียกใช้ที่หลวมนั่นจะเกิดข้อผิดพลาดในการรวบรวมเวลา ดูเหมือนว่าจะใช้กับบริบท แต่ไม่ตรงไปตรงมาจริง ๆ เพื่อสรุปว่า +1
Naman

1
เราต้องการคำถามที่ยอมรับได้แน่นอน: stackoverflow.com/… . "
Marco13

คำตอบ:


17

ฉันคิดว่าสิ่งนี้เกี่ยวข้องกับกฎเฉพาะของ JLS เกี่ยวกับ15.12.2.5 เลือกวิธีที่เฉพาะเจาะจงมากที่สุด มันระบุว่า:

หากมีวิธีสมาชิกมากกว่าหนึ่งวิธีที่สามารถเข้าถึงได้และสามารถใช้ได้กับการเรียกใช้เมธอดจำเป็นต้องเลือกวิธีหนึ่งเพื่อให้ descriptor สำหรับวิธีการจัดส่ง ภาษาการเขียนโปรแกรม Java ใช้กฎที่เลือกวิธีการเฉพาะที่สุด

วิธีที่ Java เลือกวิธีที่เฉพาะเจาะจงที่สุดถูกอธิบายเพิ่มเติมโดยข้อความ:

สัญชาตญาณแบบไม่เป็นทางการคือวิธีการหนึ่งมีความเฉพาะเจาะจงมากกว่าวิธีอื่นหากการเรียกใช้ที่จัดการโดยวิธีแรกสามารถส่งผ่านไปยังอีกวิธีหนึ่งโดยไม่มีข้อผิดพลาดในการรวบรวมเวลา ในกรณีเช่นอาร์กิวเมนต์การแสดงออกแลมบ์ดาที่พิมพ์อย่างชัดเจน (§15.27.1) หรือการร้องขอตัวแปร arity (§15.12.2.4) ความยืดหยุ่นบางอย่างได้รับอนุญาตให้ปรับหนึ่งลายเซ็นให้กับอีกแบบหนึ่ง

ในกรณีของตัวอย่างของคุณวิธีการทั้งหมดจะสามารถเข้าถึงและใช้บังคับกับวิธีการอุทธรณ์จึง Java ต้องการที่จะตรวจสอบว่าพวกเขาเป็นที่เฉพาะเจาะจงมากที่สุด

สำหรับวิธีการเหล่านี้ไม่สามารถระบุได้ว่าเฉพาะเจาะจงมากขึ้น:

public static void printSum(int a, double b) {
    System.out.println("In intDBL " + (a + b));
} // int, double cannot be passed to long, long or double, long without error

public static void printSum(long a, long b) {
    System.out.println("In long " + (a + b));
} // long , long cannot be passed to int, double or double, long without error

public static void printSum(double a, long b) {
    System.out.println("In doubleLONG " + (a + b));
} // double, long cannot be passed to int, double or long, long without error

วิธีที่สี่ล้างคลุมเครืออย่างแม่นยำเพราะมันตอบสนองเงื่อนไขที่จำเป็นที่จะเฉพาะเจาะจงมากที่สุด

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

นั่นคือ (int, long) สามารถส่งผ่านไปยัง (int, double), (long, long) หรือ (double, long) โดยไม่มีข้อผิดพลาดในการรวบรวม


2
ดังนั้นในฤดูร้อนหากวิธีการทั้งหมดสามารถเข้าถึงได้โดยการโทรแล้วจาวาระบุวิธีการซึ่งสามารถโทรไปยังวิธีการอื่น ๆ โดยไม่มีข้อผิดพลาดในการรวบรวมเวลาหากพบว่าใช้แล้วเป็นวิธีที่เฉพาะเจาะจงมากที่สุดและเรียกมันว่า ฉันคิดว่าอันนี้เป็นคำตอบที่ถูกต้อง @riruzen
Sandeep Kokate

ขอบคุณ! ฉันลองสิ่งนี้ด้วย (double, int) vs (double, long) และมันทำให้ความคลุมเครือนั้นชัดเจนมากขึ้นจากนั้น (double, int) vs (long, double) นั้นไม่ชัดเจนเท่าที่ควร
riruzen

7

เป็นคำถามที่น่าสนใจอย่างมาก ลองทำตามข้อกำหนดภาษา Java ทีละขั้นตอน

  1. เมื่อคอมไพเลอร์จะพยายามที่จะระบุวิธีการที่อาจบังคับสิ่งแรกที่มันไม่เป็นserching สำหรับวิธีการบังคับโดยการภาวนาอย่างเข้มงวด

  2. ในกรณีของคุณไม่มีวิธีการดังกล่าวดังนั้นขั้นตอนต่อไปคือการค้นหาวิธีการที่ใช้งานได้โดย Loose Invocation

  3. ณ จุดนี้วิธีการทั้งหมดตรงกับดังนั้นวิธีที่เฉพาะเจาะจงมากที่สุด ( .1215.12.2.5 ) ถูกเลือกในวิธีการที่ใช้โดยการเรียกหลวม

นี่เป็นช่วงเวลาสำคัญดังนั้นเรามาดูสิ่งนี้อย่างใกล้ชิด

วิธีการหนึ่งที่ใช้บังคับ m1 นั้นมีความเฉพาะเจาะจงกว่าวิธีอื่น ๆ ที่บังคับใช้ m2 สำหรับการเรียกใช้ที่มีนิพจน์อาร์กิวเมนต์ e1, ... , ek ถ้าสิ่งใดสิ่งหนึ่งต่อไปนี้เป็นจริง:

(เราสนใจในกรณีต่อไปนี้เท่านั้น):

  • m2 ไม่ทั่วไปและ m1 และ m2 สามารถใช้งานได้โดยการเรียกใช้ที่เข้มงวดหรือหลวมและที่ m1 มีประเภทพารามิเตอร์ที่เป็นทางการ S1, ... , Sn และ m2 มีพารามิเตอร์ที่เป็นทางการประเภท T1, ... , Tn ประเภท Si เป็นมากกว่า เจาะจงกว่า Ti สำหรับอาร์กิวเมนต์ ei สำหรับ i ทั้งหมด (1 ≤ i ≤ n, n = k)

ใส่เพียงแค่วิธีการที่เป็นที่เฉพาะเจาะจงมากขึ้นถ้าทุกประเภทพารามิเตอร์ที่มีความเฉพาะเจาะจงมากขึ้น และ

ประเภท S นั้นเฉพาะเจาะจงยิ่งกว่าประเภท T สำหรับนิพจน์ใด ๆ หาก S <: T ( §4.10 )

การแสดงออกS <: Tหมายความว่าเป็นชนิดย่อยของS Tสำหรับดั้งเดิมเรามีความสัมพันธ์ดังต่อไปนี้:

double > float > long > int

ดังนั้นเรามาดูวิธีการของคุณและดูว่าวิธีใดที่เฉพาะเจาะจงกว่าวิธีอื่น

public static void printSum(int a, double b) {  // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(double a, long b) { // method 2
    System.out.println("In doubleLONG " + (a + b));
}

ในตัวอย่างนี้พารามิเตอร์แรกของวิธีการที่ 1 เป็นเฉพาะที่เห็นได้ชัดมากขึ้นกว่าพารามิเตอร์แรกของวิธีการที่ 2 (ถ้าคุณเรียกพวกเขามีค่าจำนวนเต็ม: printSum(1, 2)) แต่พารามิเตอร์ที่สองเป็นที่เฉพาะเจาะจงมากขึ้นสำหรับวิธีการที่ 2long < doubleเพราะ ดังนั้นไม่มีวิธีการใดที่เฉพาะเจาะจงกว่าวิธีอื่น นั่นเป็นเหตุผลที่คุณมีความคลุมเครือที่นี่

ในตัวอย่างต่อไปนี้:

public static void printSum(int a, double b) { // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(long a, double b) { // method 2
    System.out.println("In longDBL " + (a + b));
}

พารามิเตอร์ชนิดแรกของวิธีที่ 1 มีความเฉพาะเจาะจงมากกว่าหนึ่งในวิธีที่ 2 เนื่องจากint < longและประเภทพารามิเตอร์ที่สองเหมือนกันสำหรับทั้งคู่นั่นคือสาเหตุที่เลือกวิธีที่ 1


ฉันไม่ได้ลงคะแนนคำตอบของคุณ แต่คำตอบนี้ไม่ได้อธิบายว่าทำไม (int, double) คลุมเครือด้วย (ยาวยาว) เนื่องจากชัดเจน (int, double) ควรมีความเฉพาะเจาะจงมากขึ้นเกี่ยวกับพารามิเตอร์แรก
riruzen

3
@riruzen มันไม่อธิบาย: เป็นที่เฉพาะเจาะจงไม่เกินdouble longและสำหรับวิธีการที่จะเลือกพารามิเตอร์ประเภททั้งหมดจะต้องเฉพาะเจาะจงมากขึ้น: ประเภทศรีมีความเฉพาะเจาะจงมากกว่า Ti สำหรับอาร์กิวเมนต์ ei สำหรับฉันทั้งหมด (1 ≤ฉัน≤ n, n = k)
คิริลล์ Simonov

มันควรจะเป็น +1
Tea

0

เพราะค่า int สามารถพิจารณาเป็น double ใน java ได้ หมายถึงdouble a = 3ถูกต้องและเหมือนกันกับระยะเวลานานlong b = 3ดังนั้นนั่นคือสาเหตุที่สร้างความคลุมเครือ คุณโทรมา

printSum(1, 2);

สับสนทั้งสามวิธีเพราะทั้งสามวิธีนี้ใช้ได้:

int a = 1;
double b =1;
long c = 1;

คุณสามารถวาง L ไว้ท้ายสุดเพื่อระบุว่าเป็นค่าที่ยาว ตัวอย่างเช่น:

printSum(1L, 2L);

สำหรับสองเท่าคุณต้องแปลงมัน:

printSum((double)1, 2L);

อ่านความคิดเห็นของ @Erwin Bolwidt ด้วย


ใช่คอมไพเลอร์สับสนสำหรับทั้ง 3 วิธีคอมไพเลอร์แรกมองหาประเภทที่แน่นอนและหากไม่พบให้ลองหาประเภทที่เข้ากันได้และในกรณีนี้ทั้งหมดเข้ากันได้
coder อีก

2
หากฉันมีการประกาศ 4 วิธีแทน 3 ความคลุมเครือจะหายไป: public void แบบคงที่ printSum (int a, double b) public void แบบคงที่ printSum (int a, long b) public void แบบคงที่ printSum (long a, long b) public static เป็นโมฆะ printSum (สองเท่า, ยาว b) ดังนั้นในขณะที่ฉันเห็นด้วยกับคำตอบของคุณก็ยังไม่ตอบคำถาม Java ทำการส่งเสริมการพิมพ์อัตโนมัติถ้าประเภทที่แน่นอนไม่สามารถใช้ได้หากฉันไม่ผิด ฉันแค่ไม่เข้าใจว่าทำไมมันจึงคลุมเครือกับสิ่งเหล่านี้ 3.
384
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.