원시 유형은 무엇이며 왜 사용해서는 안됩니까? 사용해서는 안된다고 자주 들립니까? 원시 유형을 사용할 수없는

질문 :

  • Java의 원시 유형은 무엇이며 왜 새 코드에서 사용해서는 안된다고 자주 들립니까?
  • 원시 유형을 사용할 수없는 경우 대안은 무엇이며 어떻게 더 좋습니까?


답변

원시 유형은 무엇입니까?

Java 언어 사양은 다음과 같이 원시 유형 을 정의 합니다.

JLS 4.8 원시 유형

원시 유형은 다음 중 하나로 정의됩니다.

  • 함께 제공되는 형식 인수 목록없이 일반 형식 선언의 이름을 사용하여 구성된 참조 형식입니다.

  • 요소 유형이 원시 유형 인 배열 유형입니다.

  • 의 수퍼 클래스 또는 수퍼 인터페이스에서 상속되지 않은 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>A는 파라미터 화 된 형태 ( 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!

당신이 경우 물론, DO 원하는 names수 있도록 Boolean, 당신은 그것을 선언 할 수 있습니다 List<Object> names, 그리고 위의 코드를 컴파일합니다.

또한보십시오


원시 유형 <Object>은 유형 매개 변수로 사용하는 것과 어떻게 다릅니 까?

다음은 Effective Java 2nd Edition, Item 23 의 인용문 입니다. 새 코드에서 원시 유형을 사용하지 마십시오 .

원시 유형 List과 매개 변수화 유형 의 차이점은 무엇 List<Object>입니까? 느슨하게 말해서 전자는 일반 유형 검사를 선택하지 않았으며 후자는 명시 적으로 컴파일러에게 모든 유형의 객체를 보유 할 수 있다고 말했습니다. List<String>a 유형의 매개 변수에 a 를 전달할 수 있지만 type의 매개 변수에 List전달할 수는 없습니다 List<Object>. 제네릭에 대한 하위 입력 규칙 List<String>이 있으며 원시 유형의 하위 유형 List이지만 매개 변수화 된 유형 의 하위 유형은 아닙니다 List<Object>. 결과적으로 와 같은 원시 유형을 사용하면 유형 안전성을 잃지 List만 매개 변수화 된 유형을 사용하면 그렇지 않습니다List<Object> .

요점을 설명하기 위해 a를 사용 List<Object>하고 a 를 추가하는 다음 방법을 고려하십시오 new Object().

void appendNewObject(List<Object> list) {
   list.add(new Object());
}

Java의 제네릭은 변하지 않습니다. A List<String>는이 아니므로 List<Object>다음은 컴파일러 경고를 생성합니다.

List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!

appendNewObject원시 형식 List을 매개 변수 로 사용하도록 선언 하면 컴파일되어 제네릭에서 얻는 형식 안전성이 손실됩니다.

또한보십시오


원시 유형 <?>은 유형 매개 변수로 사용하는 것과 어떻게 다릅니 까?

List<Object>, List<String>등은 모두 List<?>이므로 그냥 List대신 있다고 말하는 것이 유혹적 일 수 있습니다 . 그러나 중요한 차이점은 다음과 같습니다. a List<E>는 정의 만하기 때문에 add(E)임의의 객체 만에 추가 할 수 없습니다 List<?>. 원시 타입이 있기 때문에 다른 한편으로는, List형태의 안전성이 없습니다, 당신은 할 수있는 add단지에 대해서는 아무것도 List.

이전 스 니펫의 다음 변형을 고려하십시오.

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 제네릭은 통일되지 않기 때문에 새로운 코드에서 raw 유형을 사용해야하는 두 가지 예외가 있습니다.

  • 클래스 리터럴, 예를 들면 List.class,하지List<String>.class
  • instanceof피연산자, 예를 들면 o instanceof Set,하지o instanceof Set<String>

또한보십시오


답변

Java의 원시 유형은 무엇이며 왜 새 코드에서 사용해서는 안된다고 자주 들립니까?

원시 유형은 Java 언어의 고대 역사입니다. 태초에 있었다 Collections그들은 개최 Objects아무것도 더 적은 아무것도. Collections필요한 캐스트 에 대한 모든 작업에서 Object원하는 유형으로 캐스트 합니다.

List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);

대부분의 시간 동안 작동했지만 오류가 발생했습니다.

List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here

오래된 타입리스 컬렉션은 타입 안전을 강제 할 수 없었으므로 프로그래머는 컬렉션 내에 저장된 것을 기억해야했습니다.
이 한계를 극복하기 위해 고안된 제네릭은 개발자가 저장된 유형을 한 번 선언하면 컴파일러가 대신 수행합니다.

List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine

비교하려고:

// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings

비교 가능한 인터페이스가 더 복잡합니다.

//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
   int id;
   public int compareTo(Object other)
   {return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
   int id;
   public int compareTo(MyCompareAble other)
   {return this.id - other.id;}
}

원시 유형으로 CompareAble인터페이스 를 구현하는 것은 불가능합니다 compareTo(MyCompareAble). 왜 사용하지 말아야합니까?

  • Object저장된 모든 제품 Collection을 사용하려면 먼저 캐스팅해야합니다
  • 제네릭을 사용하면 컴파일 시간을 확인할 수 있습니다
  • 원시 유형을 사용하는 것은 각 값을 저장하는 것과 같습니다. Object

컴파일러의 기능 : 제네릭은 이전 버전과 호환되며 원시 유형과 동일한 Java 클래스를 사용합니다. 마술은 대부분 컴파일 타임에 발생합니다.

List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);

다음과 같이 컴파일됩니다 :

List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);

이는 원시 유형을 직접 사용한 경우 작성하는 코드와 동일합니다. CompareAble인터페이스에서 어떤 일이 발생하는지 잘 모르겠지만 compareTo, 하나는 a 함수 MyCompareAble와 다른 하나는 Object캐스트 후 첫 번째 함수로 전달하는 두 가지 기능을 생성한다고 생각 합니다.

사용 : 원시 유형의 대안은 무엇입니까 제네릭


답변

원시 유형은 유형 인수가없는 일반 클래스 또는 인터페이스의 이름입니다. 예를 들어, 일반 Box 클래스는 다음과 같습니다.

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

의 매개 변수화 된 유형을 작성하려면 Box<T>공식 유형 매개 변수에 대한 실제 유형 인수를 제공하십시오 T.

Box<Integer> intBox = new Box<>();

실제 유형 인수가 생략되면 다음과 같은 원시 유형이 작성됩니다 Box<T>.

Box rawBox = new Box();

따라서 Box일반 유형의 원시 유형입니다 Box<T>. 그러나 제네릭이 아닌 클래스 또는 인터페이스 유형은 원시 유형이 아닙니다.

원시 클래스는 JDK 5.0 이전에는 많은 API 클래스 (예 : Collections 클래스)가 일반적이지 않기 때문에 레거시 코드에 표시됩니다. 원시 유형을 사용하는 경우, 당신은 기본적으로 사전 제네릭 동작을 얻을 – A는 Box당신이 제공 Object에요. 이전 버전과의 호환성을 위해 매개 변수화 된 형식을 원시 형식에 할당 할 수 있습니다.

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

그러나 원시 유형을 매개 변수화 된 유형에 지정하면 경고가 표시됩니다.

Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

원시 유형을 사용하여 해당 일반 유형에 정의 된 일반 메소드를 호출하면 경고가 표시됩니다.

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)

경고는 원시 유형이 일반 유형 검사를 무시하고 안전하지 않은 코드의 캐치를 런타임으로 지연시키는 것을 보여줍니다. 따라서 원시 유형을 사용하지 않아야합니다.

Type Erasure 섹션에는 Java 컴파일러가 원시 유형을 사용하는 방법에 대한 자세한 정보가 있습니다.

확인되지 않은 오류 메시지

앞에서 언급했듯이 레거시 코드와 일반 코드를 혼합 할 때 다음과 유사한 경고 메시지가 나타날 수 있습니다.

참고 : Example.java는 검사되지 않거나 안전하지 않은 작업을 사용합니다.

참고 : 자세한 내용은 -Xlint : unchecked를 사용하여 다시 컴파일하십시오.

이는 다음 예제와 같이 원시 유형에서 작동하는 이전 API를 사용할 때 발생할 수 있습니다.

public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}

“체크되지 않은”이라는 용어는 컴파일러에 형식 안전성을 보장하는 데 필요한 모든 형식 검사를 수행하기에 충분한 형식 정보가 없음을 의미합니다. 컴파일러가 힌트를 제공하지만 “확인되지 않은”경고는 기본적으로 비활성화되어 있습니다. 모든 “확인되지 않은”경고를 보려면 -Xlint : unchecked로 다시 컴파일하십시오.

-Xlint : unchecked를 사용하여 이전 예제를 다시 컴파일하면 다음과 같은 추가 정보가 표시됩니다.

WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning

확인되지 않은 경고를 완전히 비활성화하려면 -Xlint : -unchecked 플래그를 사용하십시오. @SuppressWarnings("unchecked")주석은 확인 경고를 억제한다. @SuppressWarnings구문에 익숙하지 않은 경우 주석을 참조하십시오.

원본 출처 : Java Tutorials


답변

Java에서 “원시”유형은 제네릭이 아니며 유형 안전 일반 유형 매개 변수가 아닌 “원시”오브젝트를 처리하는 클래스입니다.

예를 들어, Java 제네릭을 사용하기 전에 다음과 같은 컬렉션 클래스를 사용합니다.

LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);

객체를 목록에 추가 할 때는 객체의 유형이 중요하지 않으며 목록에서 객체를 가져 오면 예상 한 유형으로 명시 적으로 캐스팅해야합니다.

제네릭을 사용하면 목록에 들어갈 수있는 개체 유형을 명시 적으로 지정해야하기 때문에 “알 수없는”요소를 제거합니다.

LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);

제네릭을 사용하면 get 호출에서 오는 객체를 캐스트 할 필요가 없으며 컬렉션은 MyObject에서만 작동하도록 미리 정의되어 있습니다. 이 사실은 제네릭의 주요 동인입니다. 런타임 오류 소스를 컴파일 타임에 확인할 수있는 것으로 변경합니다.


답변

 private static List<String> list = new ArrayList<String>();

type-parameter를 지정해야합니다.

경고는 제네릭 을 지원하도록 정의 된 형식은 원시 형식을 사용하지 않고 매개 변수화해야합니다.

List제네릭을 지원하도록 정의되었습니다 public class List<E>. 이를 통해 컴파일시 확인되는 많은 유형 안전 작업이 가능합니다.


답변

원시 유형은 무엇이며 왜 새 코드에서 사용해서는 안된다는 말을 자주 듣습니까?

“원시 유형”은 매개 변수화 된 유형에 유형 인수를 지정하지 않고 일반 클래스를 사용하는 List것입니다 ( 예 : 대신 사용) List<String>. 제네릭이 Java에 도입되었을 때 제네릭을 사용하도록 여러 클래스가 업데이트되었습니다. 이 클래스를 “원시 유형”(유형 인수를 지정하지 않고)으로 사용하면 레거시 코드가 여전히 컴파일 될 수있었습니다.

“원시 유형”은 이전 버전과의 호환성을 위해 사용됩니다. 형식 인수와 함께 제네릭 클래스를 사용하면 더 강력한 타이핑이 가능 해져 코드 이해도를 향상시키고 잠재적 인 문제를 조기에 발견 할 수 있으므로 새 코드에서는 사용하지 않는 것이 좋습니다.

원시 유형을 사용할 수없는 경우 대안은 무엇이며 어떻게 더 좋습니까?

선호되는 대안은 적절한 형식 인수 (예 :)를 사용하여 일반 클래스를 의도 한대로 사용하는 것 List<String>입니다. 이를 통해 프로그래머는 유형을보다 구체적으로 지정할 수 있으며 변수 또는 데이터 구조의 의도 된 사용에 대해 미래의 관리자에게 더 많은 의미를 전달하고 컴파일러가 더 나은 유형 안전을 시행 할 수 있습니다. 이러한 장점을 함께 사용하면 코드 품질이 향상되고 일부 코딩 오류가 발생하는 것을 방지 할 수 있습니다.

예를 들어, 프로그래머가 ‘names’라는 List 변수에 문자열 만 포함 시키도록하려는 방법의 경우 :

List<String> names = new ArrayList<String>();
names.add("John");          // OK
names.add(new Integer(1));  // compile error

답변

컴파일러는 다음과 같이 작성하기를 원합니다.

private static List<String> list = new ArrayList<String>();

그렇지 않으면에 원하는 유형을 추가 list하여 인스턴스화를 new ArrayList<String>()무의미 하게 만들 수 있기 때문 입니다. Java 제네릭은 컴파일 타임 기능 일 뿐이므로 “원시 유형”의 참조에 할당 된 경우 생성 된 객체 new ArrayList<String>()는 행복하게 받아 들여 Integer지거나 JFrame요소를 받아들입니다 List. 객체 자체는 포함해야하는 유형에 대해 아무것도 모르지만 컴파일러 만 수행합니다.