유효한 Java입니까?
유효한 Java입니까?
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static String f(List<String> list) {
System.out.println("strings");
return null;
}
public static Integer f(List<Integer> list) {
System.out.println("numbers");
return null;
}
public static void main(String[] args) {
f(Arrays.asList("asdf"));
f(Arrays.asList(123));
}
}
- Eclipse 3.5에서 예 라고 표시됨
- Eclipse 3.6은 아니오 라고 말합니다.
- Intellij 9가 예 라고 말합니다.
- Sun javac 1.6.0_20에 예 라고 표시됨
- GCJ 4.4.3에 예 라고 표시됨
- GWT 컴파일러는 예 라고 말합니다.
- 이전 Stackoverflow 질문 에서 군중이 아니오 라고 말합니다.
내 자바 이론 이해에 따르면 아니오 !
JLS 가 그것에 대해 말하는 것을 아는 것은 흥미로울 것입니다.
이러한 메서드를 호출하는 방법에 따라 다릅니다. 다른 Java 소스 코드에서 이러한 메서드를 호출하려는 경우 Edwin의 답변에 설명 된 이유로 유효하지 않은 것으로 간주됩니다 . 이것은 Java 언어의 제한 사항입니다.
그러나 모든 클래스가 Java 소스 코드에서 생성 될 필요는 없습니다 (JVM을 런타임으로 사용하는 모든 언어를 고려하십시오 : JRuby, Jython 등 ...). 바이트 코드 수준 에서 JVM은 바이트 코드 명령어 가 예상 하는 반환 유형 을 지정하기 때문에 두 메서드를 명확하게 할 수 있습니다 . 예를 들어, 다음 메서드 중 하나를 호출 할 수 있는 Jasmin으로 작성된 클래스가 있습니다.
.class public CallAmbiguousMethod
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit stack 3
.limit locals 1
; Call the method that returns String
aconst_null
invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String;
; Call the method that returns Integer
aconst_null
invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer;
return
.end method
다음 명령을 사용하여 클래스 파일로 컴파일합니다.
java -jar jasmin.jar CallAmbiguousMethod.j
다음을 사용하여 호출하십시오.
자바 CallAmbiguousMethod
출력은 다음과 같습니다.
> 자바 CallAmbiguousMethod 문자열 번호
최신 정보
Simon은 다음 메서드를 호출 하는 예제 프로그램 을 게시 했습니다 .
import java.util.Arrays;
import java.util.List;
class RealyCompilesAndRunsFine {
public static String f(List<String> list) {
return list.get(0);
}
public static Integer f(List<Integer> list) {
return list.get(0);
}
public static void main(String[] args) {
final String string = f(Arrays.asList("asdf"));
final Integer integer = f(Arrays.asList(123));
System.out.println(string);
System.out.println(integer);
}
}
다음은 생성 된 Java 바이트 코드입니다.
> javap -c RealyCompilesAndRunsFine "RealyCompilesAndRunsFine.java"에서 컴파일 됨 RealyCompilesAndRunsFine 클래스는 java.lang.Object {를 확장합니다. RealyCompilesAndRunsFine (); 암호: 0 : aload_0 1 : invokespecial # 1; // 메소드 java / lang / Object. "":() V 4 : 반환 공개 정적 java.lang.String f (java.util.List); 암호: 0 : aload_0 1 : 아이콘 2 : invokeinterface # 2, 2; // InterfaceMethod java / util / List.get : (I) Ljava / lang / Object; 7 : 체크 캐스트 # 3; // 클래스 java / lang / String 10 : 아레 턴 공개 정적 java.lang.Integer f (java.util.List); 암호: 0 : aload_0 1 : 아이콘 2 : invokeinterface # 2, 2; // InterfaceMethod java / util / List.get : (I) Ljava / lang / Object; 7 : 체크 캐스트 # 4; // 클래스 java / lang / Integer 10 : 아레 턴 public static void main (java.lang.String []); 암호: 0 : iconst_1 1 : anewarray # 3; // 클래스 java / lang / String 4 : 복제 5 : 아이콘 6 : ldc # 5; // 문자열 asdf 8 : 아 스토어 9 : invokestatic # 6; // Method java / util / Arrays.asList : ([Ljava / lang / Object;) Ljava / util / List; 12 : Invokestatic # 7; // 메소드 f : (Ljava / util / List;) Ljava / lang / String; 15 : astore_1 16 : iconst_1 17 : 아네와 레이 # 4; // 클래스 java / lang / Integer 20 : 복제 21 : 아이콘 22 : 비 푸시 123 24 : Invokestatic # 8; // Method java / lang / Integer.valueOf : (I) Ljava / lang / Integer; 27 : 아 스토어 28 : Invokestatic # 6; // Method java / util / Arrays.asList : ([Ljava / lang / Object;) Ljava / util / List; 31 : Invokestatic # 9; // 메소드 f : (Ljava / util / List;) Ljava / lang / Integer; 34 : astore_2 35 : getstatic # 10; // 필드 java / lang / System.out : Ljava / io / PrintStream; 38 : aload_1 39 : invokevirtual # 11; // Method java / io / PrintStream.println : (Ljava / lang / String;) V 42 : getstatic # 10; // 필드 java / lang / System.out : Ljava / io / PrintStream; 45 : aload_2 46 : invokevirtual # 12; // Method java / io / PrintStream.println : (Ljava / lang / Object;) V 49 : 반환
Sun 컴파일러가 메서드를 명확하게하는 데 필요한 바이트 코드를 생성하는 것으로 나타났습니다 (마지막 메서드의 지침 12 및 31 참조).
업데이트 # 2
Java 언어 사양 이 실제로 유효한 Java 소스 코드가 될 수 있음을 시사한다. 449 페이지 (§15.12 메서드 호출 식)에서 다음과 같이 표시됩니다.
최대한 구체적인 방법이 두 개 이상 있으므로 가장 구체적인 방법이 없을 수 있습니다. 이 경우 :
- 최대로 특정하는 모든 메서드에 재정의 동등 (§8.4.2) 서명이있는 경우 :
- 최대한 구체적인 방법 중 정확히 하나가 추상으로 선언되지 않은 경우 가장 구체적인 방법입니다.
- 그렇지 않고 모든 최대 특정 방법이 추상으로 선언되고 모든 최대 특정 방법의 시그니처가 동일한 삭제 (§4.6) 를 갖는 경우 가장 구체적인 방법은 다음을 갖는 최대 특정 방법의 하위 집합 중에서 임의로 선택됩니다. 가장 구체적인 반환 유형 . 그러나 가장 구체적인 메서드는 해당 예외 또는 해당 삭제가 최대로 구체적인 각 메서드의 throws 절에서 선언 된 경우에만 확인 된 예외를 throw하는 것으로 간주됩니다.
- 그렇지 않으면 메서드 호출이 모호하고 컴파일 타임 오류가 발생한다고 말합니다.
내가 착각하지 않는 한,이 동작은 추상으로 선언 된 메서드에만 적용되어야합니다.
업데이트 # 3
ILMTitan의 의견에 감사드립니다.
@Adam Paynter : 굵게 표시된 텍스트는 중요하지 않습니다. 댄이 보여준 것처럼 두 가지 메서드가 재정의와 동등한 경우에만 해당되기 때문입니다. 따라서 가장 구체적인 방법을 결정할 때 JLS가 제네릭 유형을 고려하는 경우 결정 요소가되어야합니다. – ILMTitan
--- 아래 댓글에 대한 응답으로 수정 됨 ---
좋아, 그래서 그것은 유효한 Java이지만 그렇지 않아야합니다. 핵심은 반환 유형에 실제로 의존하지 않고 지워진 Generics 매개 변수에 의존한다는 것입니다.
이것은 비 정적 메서드에서는 작동하지 않으며 비 정적 메서드에서는 명시 적으로 금지되어 있습니다. 클래스에서 그렇게하려는 시도는 추가 문제로 인해 실패 할 수 있습니다. 첫 번째는 일반적인 클래스가 클래스 클래스 처럼 최종적 이지 않다는 것 입니다.
다소 일관된 언어의 불일치입니다. TI는 기술적으로 허용 되더라도 불법 이어야 한다고 말을 걸어 나갈 것입니다 . 그것은 언어의 가독성에 실제로 아무것도 추가하지 않으며 의미있는 문제를 해결하는 능력에 거의 추가하지 않습니다. 해결되는 유일한 의미있는 문제는 유형 삭제, 제네릭 및 결과 메서드 서명을 해결하는 데있어 언어의 내부 불일치로 인해 핵심 원칙 이 위반되는 것처럼 보일 때 언어에 대해 충분히 잘 알고 있는지 여부 입니다.
더 의미있는 방법으로 동일한 문제를 해결하는 것은 사소한 일이기 때문에 코드를 피해야합니다. 유일한 이점은 검토 자 / 확장자가 언어 사양의 더럽고 더러운 부분을 알고 있는지 확인하는 것입니다.
--- 원본 게시물은 다음과 같습니다 ---
컴파일러가 허용했을 수도 있지만 대답은 여전히 아니요입니다.
Erasure는 List <String> 및 List <Integer> 모두를 장식되지 않은 목록으로 바꿉니다. 즉, 두 "f"메서드는 모두 동일한 서명을 가지지 만 반환 유형이 다릅니다. 반환 유형은 메서드를 구별하는 데 사용할 수 없습니다. 일반적인 수퍼 유형으로 돌아 가면 실패하기 때문입니다. 처럼:
Object o = f(Arrays.asList("asdf"));
반환 된 값을 변수로 캡처 해 보셨습니까? 아마도 컴파일러는 올바른 오류 코드를 밟지 않는 방식으로 작업을 최적화했을 것입니다.
답변되지 않은 질문 중 하나는 왜 Eclipse 3.6에서 컴파일 오류 만 트리거합니까?
이유는 다음과 같습니다 . 기능 입니다.
javac 7에서 두 메서드는 반환 유형에 관계없이 중복 (또는 이름 충돌 오류)으로 간주됩니다.
이 동작은 이제 메서드에 대한 이름 충돌 오류를보고하고 반환 유형을 무시한 javac 1.5와 더 일치합니다. 1.6에서만 중복 메서드를 감지 할 때 반환 유형을 포함하는 변경이있었습니다.
3.6 릴리스의 모든 규정 준수 수준 (1.5, 1.6, 1.7)에서이 변경을 수행하기로 결정했기 때문에 사용자가 javac 7을 사용하여 코드를 컴파일하는 경우 변경에 놀라지 않을 것입니다.
글쎄요, 만약 제가 스펙 섹션 8.4.2의 첫 번째 목록에서 세 번째 글 머리 기호를 올바르게 이해했다면 f () 메서드가 동일한 서명을 가지고 있다고 말합니다.
http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649
컴파일러 X 또는 IDE X의 관찰 된 동작이 아니라이 질문에 실제로 답하는 것은 사양입니다. 도구를 살펴보면서 말할 수있는 것은 도구 작성자가 사양을 해석 한 방법뿐입니다.
3 번 항목을 적용하면 다음과 같은 결과가 나타납니다.
... 공개 정적 문자열 f (목록 <문자열> 목록) { System.out.println ( "문자열"); null을 반환합니다. } public static Integer f (List <String> list) { System.out.println ( "숫자"); null을 반환합니다. } ...
서명이 일치하므로 충돌이 발생하고 코드가 컴파일되지 않아야합니다.
사양 에 유효한 꽃병입니다 .
메소드
m1
서명은 다음m2
중 하나 에 해당하는 경우 메소드 서명의 하위 서명입니다.
m2
와 동일한 서명이m1
있거나the signature of
m1
is the same as the erasure of the signature ofm2
.
So these are not subsignatures of eachother because the erasure of List<String>
is not List<Integer>
and vice versa.
Two method signatures
m1
andm2
are override-equivalent iff eitherm1
is a subsignature ofm2
orm2
is a subsignature ofm1
.
So these two are not override-equivalent (note the iff). And the rule for overloading is:
If two methods of a class (whether both declared in the same class, or both inherited by a class, or one declared and one inherited) have the same name but signatures that are not override-equivalent, then the method name is said to be overloaded.
Therefore, these two methods are overloaded and everything should work.
Also working (with sun java 1.6.0_16 this time) is
import java.util.Arrays;
import java.util.List;
class RealyCompilesAndRunsFine {
public static String f(List<String> list) {
return list.get(0);
}
public static Integer f(List<Integer> list) {
return list.get(0);
}
public static void main(String[] args) {
final String string = f(Arrays.asList("asdf"));
final Integer integer = f(Arrays.asList(123));
System.out.println(string);
System.out.println(integer);
}
}
From what I can tell the .class file can hold both methods since the method descriptor holds the parameters, as well as the return type. If the return type would be the same, then the descriptors would be the same and the methods would be indistinguishable after the type erasure (hence it doesn't work with void either). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035
Now, calling the method with invoke_virtual requires the method descriptor, so you can in fact say which one of the methods you want to call, so it seems that all those compilers, which still have the generic information, simply put the descriptor for the method that matches the generic type of the parameter, so then it's hardcoded in the bytecode which method to call (as distinguished by their descriptors, or more exactly by the return type in those descriptors), even if the parameter is now a List, without generic information.
While I find this practice a little questionable, I must say I find it kind of cool that you can do this, and think that generics should have been designed to be able to work like this in the first place (yes, I know that would create problems with backwards compatibility).
Java type inference (what's going on when you call static, generic methods like Array.asList) is complicated and not well-specified in the JLS. This paper from 2008 has a very interesting description of some of the issues and how it might be fixed:
Java Type Inference is Broken: How Can We Fix it?
Eclipse can produce byte code out of this:
public class Bla {
private static BigDecimal abc(List<BigDecimal> l) {
return l.iterator().next().multiply(new BigDecimal(123));
}
private static String abc(List<String> l) {
return l.iterator().next().length() + "";
}
public static void main(String[] args) {
System.out.println(abc(Arrays.asList("asdf")));
System.out.println(abc(Arrays.<BigDecimal>asList(new BigDecimal(123))));
}
}
Output:
4
15129
Looks like the compiler chooses the most specific method based on the generics.
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static Object f(List<?> list) {
System.out.println("strings");
return null;
}
public static Integer f(List<Integer> list) {
System.out.println("numbers");
return null;
}
public static void main(String[] args) {
f(Arrays.asList("asdf"));
f(Arrays.asList(123));
}
}
Output:
strings
numbers
참고URL : https://stackoverflow.com/questions/3110014/is-this-valid-java
'program story' 카테고리의 다른 글
프로토 타입을 통해 메서드를 정의하는 것과 생성자에서 이것을 사용하는 것-정말 성능 차이? (0) | 2020.12.09 |
---|---|
User-Agent "Test Certificate Info"를 보내는 소프트웨어는 무엇입니까? (0) | 2020.12.09 |
클래스 템플릿 파라미터로서의 Lambda 표현식 (0) | 2020.12.09 |
일반적인 Task.WaitAll이 있습니까? (0) | 2020.12.09 |
AnkhSVN 대 VisualSVN (0) | 2020.12.09 |