이 코드를 실험하고 있습니다.
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 이지만 모든 Object 가 double 배열 인 것은 아닙니다 .) 따라서 가장 구체적인 생성자는 프로그램의 출력을 설명하는 Confusing (double []) 입니다.
이 동작은 double [] 유형의 값을 전달하면 의미가 있습니다 . null 을 전달하면 직관적이지 않습니다 . 이 퍼즐을 이해하기위한 핵심은 어떤 메서드 나 생성자가 가장 구체적인 테스트가 실제 매개 변수를 사용하지 않는다는 것 입니다. 즉 호출에 나타나는 매개 변수입니다. 적용 가능한 과부하를 결정하는 데만 사용됩니다. 컴파일러가 적용 가능하고 액세스 가능한 오버로딩을 결정하면 공식 매개 변수 (선언에 나타나는 매개 변수) 만 사용하여 가장 구체적인 오버로딩을 선택합니다.
null 매개 변수를 사용 하여 Confusing (Object) 생성자 를 호출하려면 new Confusing ((Object) null)을 작성 합니다. 이렇게하면 Confusing (Object) 만 적용 할 수 있습니다. 보다 일반적으로 컴파일러가 특정 오버로딩을 선택하도록 강제하려면 실제 매개 변수를 선언 된 형식 매개 변수 형식으로 캐스팅합니다.
답변
인수 유형에 따라 메서드에 대한 호출을 디스패치하는 기능을 다중 디스패치 라고 합니다 . Java에서는 방문자 패턴으로 수행됩니다 .
그러나 Integer
s 및 String
s를 다루기 때문에이 패턴을 쉽게 통합 할 수 없습니다 (이 클래스를 수정할 수는 없습니다). 따라서 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 번 호출했습니다.