질문 : 원시 유형은 무엇이며 왜 사용하지 않아야합니까?
- Java의 원시 유형은 무엇이며 새 코드에서 사용해서는 안된다는 말을 자주 듣는 이유는 무엇입니까?
- 원시 유형을 사용할 수없는 경우 대안은 무엇이며 어떻게 더 나은가요?
답변
Java 언어 사양은 다음과 같이 원시 유형을 정의합니다.
원시 유형은 다음 중 하나로 정의됩니다.
- 수반되는 형식 인수 목록없이 제네릭 형식 선언의 이름을 사용하여 형성되는 참조 형식입니다.
- 요소 유형이 원시 유형 인 배열 유형입니다.
- 비
static
원시 형의 부재 형R
의 수퍼 또는 슈퍼로부터 상속되지R
.
다음은 설명을위한 예입니다.
public class MyType<E> {
class Inner { }
static class Nested { }
public static void main(String[] args) {
MyType mt; // warning: MyType is a raw type
MyType.Inner inn; // warning: MyType.Inner is a raw type
MyType.Nested nest; // no warning: not parameterized type
MyType<Object> mt1; // no warning: type parameter given
MyType<?> mt2; // no warning: type parameter given (wildcard OK!)
}
}
여기서 MyType<E>
는 매개 변수화 된 유형 ( JLS 4.5 )입니다. 구어 적으로이 유형을 간단히 MyType
이라고하는 것이 일반적이지만 기술적으로 이름은 MyType<E>
입니다.
mt
는 위의 정의에서 첫 번째 글 머리 기호에 의해 원시 유형 (및 컴파일 경고 생성)을 가지고 있습니다. inn
은 또한 세 번째 글 머리 기호로 원시 유형을 가지고 있습니다.
MyType.Nested
이 파라미터 화 된 형태의 회원 유형에도 불구하고, 매개 변수화 된 유형이 아닌 MyType<E>
, 그것의 때문에 static
.
mt1
및 mt2
는 모두 실제 유형 매개 변수로 선언되므로 원시 유형이 아닙니다.
원시 유형의 종류가 뭐가 그렇게 특별한가?
기본적으로 원시 유형은 제네릭이 도입되기 전과 똑같이 작동합니다. 즉, 다음은 컴파일 타임에 전적으로 합법적입니다.
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!
위의 코드는 잘 실행되지만 다음도 있다고 가정합니다.
for (Object o : names) {
String name = (String) o;
System.out.println(name);
} // throws ClassCastException!
// java.lang.Boolean cannot be cast to java.lang.String
names
instanceof String
이 아닌 것이 포함되어 있기 때문에 런타임에 문제가 발생합니다.
당신이 원한다면 아마도, names
만 포함하는 String
, 당신은 아마도 여전히 원시 유형을 사용할 수 있으며, 수동으로 모든 검사 add
자신을 한 후 수동으로 캐스팅 String
에서 모든 항목을 names
. 더 좋은 점 은 원시 유형을 사용하지 않고 컴파일러가 Java 제네릭의 힘을 활용하여 모든 작업을 수행하도록하는 것입니다.
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
당신이 찾는 경우 물론, names
허용 및 Boolean
, 당신은로 선언 할 수 있습니다 List<Object> names
, 그리고 위의 코드를 컴파일합니다.
참고 항목
원시 유형은 <Object>를 유형 매개 변수로 사용하는 것과 어떻게 다른가?
다음은 Effective Java 2nd Edition, 항목 23의 인용문입니다. 새 코드에서 원시 유형을 사용하지 마십시오 .
List
와 매개 변수화 된 유형 List<Object>
의 차이점은 무엇입니까? 느슨하게 말하면 전자는 제네릭 유형 검사를 옵트 아웃 한 반면 후자는 컴파일러에게 모든 유형의 객체를 보유 할 수 있다고 명시 적으로 말했습니다. List<String>
List
유형의 매개 변수에 List<Object>
유형의 매개 변수에는 전달할 수 없습니다. 제네릭에 대한 하위 유형 지정 규칙이 있으며 List<String>
은 원시 유형 List
의 하위 유형이지만 매개 변수화 된 유형 List<Object>
아닙니다. 결과적으로 List
와 같은 원시 유형을 사용하면 유형 안전성이 손실 List<Object>
와 같은 매개 변수화 된 유형을 사용하는 경우에는 그렇지 않습니다 .
요점을 설명하기 위해 List<Object>
new Object()
추가하는 다음 메서드를 고려하십시오.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Java의 제네릭은 변하지 않습니다. List<String>
은 List<Object>
가 아니므로 다음은 컴파일러 경고를 생성합니다.
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
List
을 매개 변수로 취하도록 appendNewObject
를 선언 한 경우, 이것은 컴파일 될 것이며 따라서 제네릭에서 얻은 유형 안전성을 잃게됩니다.
참고 항목
Raw type을 type 매개 변수로 <?>를 사용하는 것과 어떻게 다른가?
List<Object>
, List<String>
등은 모두 List<?>
이므로 대신 List
라고 말하고 싶을 수 있습니다. 그러나 큰 차이점이 있습니다. List<E>
add(E)
만 정의 List<?>
임의의 개체 만 추가 할 수 없습니다. 반면에 원시 유형 List
에는 유형 안전성이 없기 때문에 거의 모든 것을 List
add
할 수 있습니다.
이전 스 니펫의 다음 변형을 고려하십시오.
static void appendNewObject(List<?> list) {
list.add(new Object()); // compilation error!
}
//...
List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!
List<?>
의 유형 불변을 위반하지 않도록 보호하는 멋진 작업을 수행했습니다! 매개 변수를 원시 유형 List list
로 선언 한 경우 코드가 컴파일되고 List<String> names
유형 불변을 위반하게됩니다.
원시 유형은 해당 유형의 소거입니다.
JLS 4.8로 돌아 가기 :
이 종류로서 파라미터 화 된 형태 또는 유형의 소자 파라미터 화 된 형태 인 어레이 형의 소거의 소거를 사용하는 것이 가능하다. 이러한 유형을 원시 유형 이라고합니다.
[...]
원시 유형의 수퍼 클래스 (각각 수퍼 인터페이스)는 제네릭 유형의 매개 변수화의 수퍼 클래스 (수퍼 인터페이스)를 지 웁니다.
생성자 예에있어서, 또는 비의 유형 static
원시 타입 필드 C
수퍼 클래스 또는 슈퍼로부터 상속되지 일반 선언의 유형의 소거에 대응에 대응하는 원시 타입 C
.
간단히 말해서 원시 유형을 사용하면 생성자, 인스턴스 메서드 및 비 static
필드 도 지워 집니다.
다음 예를 살펴보십시오.
class MyType<E> {
List<String> getNames() {
return Arrays.asList("John", "Mary");
}
public static void main(String[] args) {
MyType rawType = new MyType();
// unchecked warning!
// required: List<String> found: List
List<String> names = rawType.getNames();
// compilation error!
// incompatible types: Object cannot be converted to String
for (String str : rawType.getNames())
System.out.print(str);
}
}
MyType
을 사용하면 getNames
지워 지므로 원시 List
반환합니다!
JLS 4.6 은 계속해서 다음을 설명합니다.
형식 삭제는 또한 생성자 또는 메서드의 서명을 매개 변수가있는 형식이나 형식 변수가없는 서명에 매핑합니다. 생성자 또는 메소드 서명 소거 s
과 동일한 이름으로 구성된 서명 인 s
과 주어진 모든 파라미터 형태의 소거 s
.
메서드 또는 생성자의 서명이 지워지면 메서드의 반환 형식과 제네릭 메서드 또는 생성자의 형식 매개 변수도 지워집니다.
제네릭 메서드의 서명 삭제에는 형식 매개 변수가 없습니다.
다음 버그 보고서에는 컴파일러 개발자 인 Maurizio Cimadamore와 JLS 작성자 중 한 명인 Alex Buckley가 이러한 종류의 동작이 발생해야하는 이유에 대한 몇 가지 생각이 포함되어 있습니다. https://bugs.openjdk.java.net/browse / JDK-6400189 (요약하면 사양이 더 간단 해집니다.)
만약 그것이 안전하지 않다면, 왜 원시 유형을 사용하는 것이 허락되는가?
다음은 JLS 4.8의 또 다른 인용문입니다.
원시 유형의 사용은 레거시 코드의 호환성에 대한 양보로만 허용됩니다. Java 프로그래밍 언어에 일반성을 도입 한 후 작성된 코드에서 원시 유형을 사용하는 것은 권장되지 않습니다. Java 프로그래밍 언어의 향후 버전에서는 원시 유형의 사용이 허용되지 않을 수 있습니다.
유효한 Java 2nd Edition 에는 다음을 추가해야합니다.
원시 유형을 사용해서는 안된다는 점을 감안할 때 언어 디자이너가 허용 한 이유는 무엇입니까? 호환성을 제공합니다.
Java 플랫폼은 제네릭이 소개 된 20 년이되었을 때 제네릭을 사용하지 않는 엄청난 양의 Java 코드가 존재했습니다. 이 모든 코드가 제네릭을 사용하는 새로운 코드와 합법적이고 상호 운용 가능한 상태로 유지되는 것이 중요하다고 간주되었습니다. 매개 변수화 된 유형의 인스턴스를 일반 유형과 함께 사용하도록 설계된 메서드에 전달하는 것은 합법적이어야하며 그 반대의 경우도 마찬가지입니다. 마이그레이션 호환성 이라고하는이 요구 사항으로 인해 원시 유형을 지원하기로 결정되었습니다.
요약하면, 원시 유형은 새 코드에서 절대 사용해서는 안됩니다. 항상 매개 변수화 된 유형을 사용해야합니다 .
불행히도 Java 제네릭은 수정되지 않았기 때문에 새 코드에서 원시 유형을 사용해야하는 두 가지 예외가 있습니다.
- 클래스 리터럴, 예 :
List<String>.class
아닌List.class
instanceof
피연산자 (예 :o instanceof Set
o instanceof Set<String>
아님)
참고 항목
출처 : https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Java에서 두 배열(List)을 결합하는 방법 (0) | 2021.09.29 |
---|---|
Java에서 List를 Set로 변환하는 가장 쉬운 방법 (0) | 2021.09.29 |
Java 7에서 다이아몬드 연산자 (<>)를 사용해야 하는 이유 (0) | 2021.09.17 |
Java의 "final" 의 작동 원리 (0) | 2021.09.15 |
AngularJS에서 데이터 바인딩의 작동 원리 (0) | 2021.09.15 |