Lựa chọn phương thức bị quá tải dựa trên kiểu thực của tham số


115

Tôi đang thử nghiệm với mã này:

interface Callee {
    public void foo(Object o);
    public void foo(String s);
    public void foo(Integer i);
}

class CalleeImpl implements Callee
    public void foo(Object o) {
        logger.debug("foo(Object o)");
    }

    public void foo(String s) {
        logger.debug("foo(\"" + s + "\")");
    }

    public void foo(Integer i) {
        logger.debug("foo(" + i + ")");
    }
}

Callee callee = new CalleeImpl();

Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();

callee.foo(i);
callee.foo(s);
callee.foo(o);

Bản này in foo(Object o)ba lần. Tôi mong đợi việc lựa chọn phương thức sẽ xem xét đến kiểu tham số thực (không phải được khai báo). Tui bỏ lỡ điều gì vậy? Có cách nào để sửa đổi mã này để nó sẽ in không foo(12), foo("foobar")foo(Object o)?

Câu trả lời:


96

Tôi mong đợi việc lựa chọn phương thức sẽ xem xét đến kiểu tham số thực (không phải được khai báo). Tui bỏ lỡ điều gì vậy?

Đúng. Kỳ vọng của bạn là sai. Trong Java, điều phối phương thức động chỉ xảy ra đối với đối tượng mà phương thức được gọi, không xảy ra đối với các kiểu tham số của phương thức được nạp chồng.

Trích dẫn Đặc tả Ngôn ngữ Java :

Khi một phương thức được gọi (§15.12), số lượng đối số thực tế (và mọi đối số kiểu rõ ràng) và kiểu thời gian biên dịch của các đối số được sử dụng, tại thời điểm biên dịch, để xác định chữ ký của phương thức sẽ được gọi ( §15.12.2). Nếu phương thức được gọi là một phương thức thể hiện, phương thức thực tế sẽ được gọi sẽ được xác định tại thời điểm chạy, sử dụng tra cứu phương thức động (§15.12.4).


4
Bạn có thể giải thích các thông số kỹ thuật bạn đã trích dẫn xin vui lòng. Hai câu có vẻ mâu thuẫn với nhau. Ví dụ trên sử dụng các phương thức thể hiện, nhưng phương thức đang được gọi rõ ràng không được xác định tại thời điểm chạy.
Alex Worden

15
@Alex Worden: kiểu thời gian biên dịch của các tham số phương thức được sử dụng để xác định chữ ký của phương thức sẽ được gọi, trong trường hợp này foo(Object). Trong thời gian chạy, lớp của đối tượng mà phương thức được gọi sẽ xác định phương thức triển khai nào của phương thức đó được gọi, có tính đến việc nó có thể là một thể hiện của lớp con của kiểu đã khai báo ghi đè phương thức.
Michael Borgwardt

86

Như đã đề cập trước khi giải quyết quá tải được thực hiện tại thời điểm biên dịch.

Java Puzzlers có một ví dụ hay cho điều đó:

Câu đố 46: Trường hợp của nhà xây dựng khó hiểu

Câu đố này giới thiệu cho bạn hai hàm tạo khó hiểu. Phương thức main gọi một phương thức khởi tạo, nhưng phương thức nào? Đầu ra của chương trình phụ thuộc vào câu trả lời. Chương trình in cái gì, hoặc nó có hợp pháp không?

public class Confusing {

    private Confusing(Object o) {
        System.out.println("Object");
    }

    private Confusing(double[] dArray) {
        System.out.println("double array");
    }

    public static void main(String[] args) {
        new Confusing(null);
    }
}

Giải pháp 46: Trường hợp của trình tạo khó hiểu

... Quá trình giải quyết quá tải của Java hoạt động theo hai giai đoạn. Giai đoạn đầu tiên chọn tất cả các phương thức hoặc hàm tạo có thể truy cập và áp dụng được. Giai đoạn thứ hai chọn phương pháp hoặc hàm tạo cụ thể nhất được chọn trong giai đoạn đầu. Một phương thức hoặc phương thức khởi tạo ít cụ thể hơn phương thức khác nếu nó có thể chấp nhận bất kỳ tham số nào được truyền cho phương thức khác [JLS 15.12.2.5].

Trong chương trình của chúng tôi, cả hai hàm tạo đều có thể truy cập và áp dụng được. Phương thức khởi tạo Gây nhầm lẫn (Đối tượng) chấp nhận bất kỳ tham số nào được truyền cho Gây nhầm lẫn (double []) , vì vậy Gây nhầm lẫn (Đối tượng) ít cụ thể hơn. (Mọi mảng kép là một Đối tượng , nhưng không phải mọi Đối tượng đều là một mảng kép .) Do đó, hàm tạo cụ thể nhất là Khó hiểu (double []) , giải thích kết quả đầu ra của chương trình.

Hành vi này có ý nghĩa nếu bạn truyền một giá trị kiểu double [] ; nó là phản trực giác nếu bạn vượt qua null . Chìa khóa để hiểu câu đố này là việc kiểm tra phương thức hoặc hàm tạo nào cụ thể nhất không sử dụng các tham số thực tế : các tham số xuất hiện trong lời gọi. Chúng chỉ được sử dụng để xác định mức quá tải nào được áp dụng. Khi trình biên dịch xác định được cách nạp chồng nào có thể áp dụng và có thể truy cập được, nó sẽ chọn cách nạp chồng cụ thể nhất, chỉ sử dụng các tham số chính thức: các tham số xuất hiện trong khai báo.

Để gọi phương thức khởi tạo Gây nhầm lẫn (Đối tượng) với một tham số null , hãy viết mới Gây nhầm lẫn ((Đối tượng) null) . Điều này đảm bảo rằng chỉ có thể áp dụng được (Đối tượng) gây nhầm lẫn . Nói một cách tổng quát hơn, để buộc trình biên dịch chọn một quá trình nạp chồng cụ thể, hãy ép các tham số thực tế thành các kiểu đã khai báo của các tham số chính thức.


4
Tôi hy vọng không quá muộn để nói - "một trong những lời giải thích hay nhất về SOF". Cảm ơn :)
TheLostMind

5
Tôi tin rằng nếu chúng ta cũng thêm phương thức khởi tạo 'private Confusing (int [] iArray) "thì nó sẽ không biên dịch được, phải không? Bởi vì bây giờ có hai hàm tạo có cùng độ đặc hiệu.
Risser

Nếu tôi sử dụng các loại trở lại năng động như chức năng đầu vào, nó luôn luôn sử dụng ít cụ thể hơn ... nói rằng phương pháp có thể được sử dụng cho tất cả các giá trị trả lại có thể ...
kaiser

16

Khả năng gửi một cuộc gọi đến một phương thức dựa trên các loại đối số được gọi là nhiều điều phối . Trong Java điều này được thực hiện với mẫu khách truy cập .

Tuy nhiên, vì bạn đang xử lý Integers và Strings, bạn không thể dễ dàng kết hợp mẫu này (bạn chỉ không thể sửa đổi các lớp này). Vì vậy, một người khổng lồ switchvề thời gian chạy đối tượng sẽ là vũ khí mà bạn lựa chọn.


11

Trong Java, phương thức để gọi (như chữ ký phương thức sẽ sử dụng) được xác định tại thời điểm biên dịch, vì vậy nó đi cùng với kiểu thời gian biên dịch.

Mô hình điển hình để giải quyết vấn đề này là kiểm tra kiểu đối tượng trong phương thức bằng chữ ký Đối tượng và ủy quyền cho phương thức bằng một kiểu.

    public void foo(Object o) {
        if (o instanceof String) foo((String) o);
        if (o instanceof Integer) foo((Integer) o);
        logger.debug("foo(Object o)");
    }

Nếu bạn có nhiều kiểu và điều này không thể quản lý được, thì việc nạp chồng phương thức có lẽ không phải là cách tiếp cận phù hợp, thay vào đó phương thức công khai chỉ nên lấy Đối tượng và triển khai một số loại mẫu chiến lược để ủy quyền xử lý thích hợp cho mỗi loại đối tượng.


4

Tôi đã gặp vấn đề tương tự với việc gọi hàm tạo bên phải của một lớp có tên "Tham số" có thể nhận một số kiểu Java cơ bản như Chuỗi, Số nguyên, Boolean, Long, v.v. Với một mảng Đối tượng, tôi muốn chuyển chúng thành một mảng của các đối tượng Tham số của tôi bằng cách gọi hàm tạo cụ thể nhất cho từng Đối tượng trong mảng đầu vào. Tôi cũng muốn xác định Tham số phương thức khởi tạo (Đối tượng o) sẽ ném một IllegalArgumentException. Tất nhiên tôi thấy phương thức này đang được gọi cho mọi Đối tượng trong mảng của tôi.

Giải pháp tôi đã sử dụng là tra cứu hàm tạo thông qua phản xạ ...

public Parameter[] convertObjectsToParameters(Object[] objArray) {
    Parameter[] paramArray = new Parameter[objArray.length];
    int i = 0;
    for (Object obj : objArray) {
        try {
            Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
            paramArray[i++] = cons.newInstance(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
        }
    }
    return paramArray;
}

Không yêu cầu phiên bản xấu, câu lệnh chuyển đổi hoặc mẫu khách truy cập! :)


2

Java xem xét kiểu tham chiếu khi cố gắng xác định phương thức nào sẽ gọi. Nếu bạn muốn buộc mã của mình, bạn chọn phương pháp 'đúng', bạn có thể khai báo các trường của mình dưới dạng các trường hợp của loại cụ thể:

Integeri = new Integer(12);
String s = "foobar";
Object o = new Object();

Bạn cũng có thể sử dụng các tham số của mình dưới dạng loại tham số:

callee.foo(i);
callee.foo((String)s);
callee.foo(((Integer)o);

1

Nếu có sự trùng khớp chính xác giữa số lượng và kiểu đối số được chỉ định trong lệnh gọi phương thức và chữ ký phương thức của một phương thức được nạp chồng thì đó là phương thức sẽ được gọi. Bạn đang sử dụng tham chiếu Object, vì vậy java quyết định tại thời điểm biên dịch rằng đối với Object param, có một phương thức chấp nhận Object trực tiếp. Vì vậy, nó đã gọi phương pháp đó 3 lần.

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.