[java] 매개 변수의 실제 유형을 기반으로 오버로드 된 메소드 선택

이 코드를 실험하고 있습니다.

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);

이것은 foo(Object o)세 번 인쇄 됩니다. 메서드 선택은 실제 (선언되지 않은) 매개 변수 유형을 고려할 것으로 예상합니다. 내가 뭔가를 놓치고 있습니까? 그것은 인쇄 해드립니다 있도록이 코드를 수정하는 방법이 foo(12), foo("foobar")그리고 foo(Object o)?



답변

메서드 선택은 실제 (선언되지 않은) 매개 변수 유형을 고려할 것으로 예상합니다. 내가 뭔가를 놓치고 있습니까?

예. 당신의 기대가 잘못되었습니다. Java에서 동적 메소드 디스패치는 오버로드 된 메소드의 매개 변수 유형이 아니라 메소드가 호출 된 객체에 대해서만 발생합니다.

Java 언어 사양 인용 :

메서드가 호출 될 때 (§15.12) 실제 인수 (및 명시 적 형식 인수)의 수 및 의 컴파일 타임 형식이
컴파일 타임에 사용되어 호출 될 메서드의 서명을 결정합니다 ( §15.12.2). 호출 할 메서드가 인스턴스 메서드 인 경우 호출 할 실제 메서드는 동적 메서드 조회 (§15.12.4)를 사용하여 런타임에 결정됩니다.


답변

앞서 언급했듯이 오버로드 해결은 컴파일 타임에 수행됩니다.

Java Puzzlers 에는 이에 대한 좋은 예가 있습니다.

퍼즐 46 : 혼란스러운 생성자의 사례

이 퍼즐은 두 개의 Confusing 생성자를 제공합니다. 메인 메소드는 생성자를 호출하지만 어떤 생성자를 호출합니까? 프로그램의 출력은 답변에 따라 다릅니다. 프로그램은 무엇을 인쇄합니까? 아니면 합법적입니까?

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);
    }
}

솔루션 46 : 혼란스러운 생성자의 경우

… Java의 과부하 해결 프로세스는 두 단계로 작동합니다. 첫 번째 단계에서는 액세스 가능하고 적용 가능한 모든 메서드 또는 생성자를 선택합니다. 두 번째 단계 에서는 첫 번째 단계에서 선택한 가장 구체적인 메서드 또는 생성자를 선택합니다. 하나의 메서드 또는 생성자가 덜 구체적입니다. 다른 전달 된 매개 변수를 받아 들일 수 있다면 다른 것보다 [JLS 15.12.2.5].

우리 프로그램에서 두 생성자는 모두 액세스 가능하고 적용 가능합니다. Confusing (Object) 생성자
Confusing (double [])에 전달 된 모든 매개 변수를 허용 하므로
Confusing (Object) 은 덜 구체적입니다. (모든 double 배열Object 이지만 모든 Objectdouble 배열 인 것은 아닙니다 .) 따라서 가장 구체적인 생성자는 프로그램의 출력을 설명하는 Confusing (double []) 입니다.

이 동작은 double [] 유형의 값을 전달하면 의미가 있습니다 . null 을 전달하면 직관적이지 않습니다 . 이 퍼즐을 이해하기위한 핵심은 어떤 메서드 나 생성자가 가장 구체적인 테스트가 실제 매개 변수를 사용하지 않는다는 것 입니다. 즉 호출에 나타나는 매개 변수입니다. 적용 가능한 과부하를 결정하는 데만 사용됩니다. 컴파일러가 적용 가능하고 액세스 가능한 오버로딩을 결정하면 공식 매개 변수 (선언에 나타나는 매개 변수) 만 사용하여 가장 구체적인 오버로딩을 선택합니다.

null 매개 변수를 사용 하여 Confusing (Object) 생성자 를 호출하려면 new Confusing ((Object) null)을 작성 합니다. 이렇게하면 Confusing (Object) 만 적용 할 수 있습니다. 보다 일반적으로 컴파일러가 특정 오버로딩을 선택하도록 강제하려면 실제 매개 변수를 선언 된 형식 매개 변수 형식으로 캐스팅합니다.


답변

인수 유형에 따라 메서드에 대한 호출을 디스패치하는 기능을 다중 디스패치 라고 합니다 . Java에서는 방문자 패턴으로 수행됩니다 .

그러나 Integers 및 Strings를 다루기 때문에이 패턴을 쉽게 통합 할 수 없습니다 (이 클래스를 수정할 수는 없습니다). 따라서 switch객체 런타임에 거인 이 선택의 무기가 될 것입니다.


답변

Java에서 호출 할 메서드 (사용할 메서드 서명)는 컴파일 타임에 결정되므로 컴파일 타임 유형과 함께 사용됩니다.

이 문제를 해결하기위한 일반적인 패턴은 Object 서명을 사용하여 메서드의 개체 유형을 확인하고 캐스트를 사용하여 메서드에 위임하는 것입니다.

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

유형이 많고 관리 할 수없는 경우 메서드 오버로딩이 올바른 접근 방식이 아닐 수 있습니다. 오히려 공용 메서드는 Object를 취하고 개체 유형별로 적절한 처리를 위임하는 일종의 전략 패턴을 구현해야합니다.


답변

String, Integer, Boolean, Long 등과 같은 몇 가지 기본 Java 유형을 사용할 수있는 “Parameter”라는 클래스의 올바른 생성자를 호출하는 것과 비슷한 문제가있었습니다. 주어진 객체 배열이 있으면이를 배열로 변환하고 싶습니다. 입력 배열의 각 개체에 대해 가장 구체적인 생성자를 호출하여 내 Parameter 개체의. 또한 IllegalArgumentException을 throw하는 생성자 Parameter (Object o)를 정의하고 싶었습니다. 물론이 메서드가 배열의 모든 개체에 대해 호출되는 것을 발견했습니다.

내가 사용한 해결책은 리플렉션을 통해 생성자를 찾는 것입니다.

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;
}

추악한 instanceof, switch 문 또는 방문자 패턴이 필요하지 않습니다! 🙂


답변

Java는 호출 할 메소드를 판별 할 때 참조 유형을 확인합니다. 코드를 강제로 ‘올바른’방법을 선택하려면 필드를 특정 유형의 인스턴스로 선언 할 수 있습니다.

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

매개 변수 유형으로 매개 변수를 캐스팅 할 수도 있습니다.

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


답변

메서드 호출에 지정된 인수의 수와 유형과 오버로드 된 메서드의 메서드 서명 사이에 정확히 일치하는 것이 호출되는 메서드입니다. Object 참조를 사용하고 있으므로 Java는 컴파일 타임에 Object 매개 변수에 대해 Object를 직접 받아들이는 메서드가 있다고 결정합니다. 그래서 그 방법을 3 번 호출했습니다.