프로그래밍 언어/JAVA

JAVA의 원시 유형은 무엇이며 왜 사용하지 않아야 하는가?

Rateye 2021. 9. 27. 10:48
728x90
반응형
질문 : 원시 유형은 무엇이며 왜 사용하지 않아야합니까?

  • 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 .

mt1mt2 는 모두 실제 유형 매개 변수로 선언되므로 원시 유형이 아닙니다.


원시 유형의 종류가 뭐가 그렇게 특별한가?

기본적으로 원시 유형은 제네릭이 도입되기 전과 똑같이 작동합니다. 즉, 다음은 컴파일 타임에 전적으로 합법적입니다.

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
728x90
반응형