CodeGym /Java Blog /무작위의 /유형 삭제
John Squirrels
레벨 41
San Francisco

유형 삭제

무작위의 그룹에 게시되었습니다
안녕! 우리는 제네릭에 대한 일련의 수업을 계속합니다. 우리는 이전에 그들이 무엇이고 왜 필요한지에 대한 일반적인 아이디어를 얻었습니다. 오늘 우리는 제네릭의 일부 기능과 그 기능에 대해 자세히 알아볼 것입니다. 갑시다! 지난 수업유형 삭제 - 1 에서 제네릭 타입원시 타입의 차이점에 대해 이야기했습니다 . 원시 유형은 유형이 제거된 제네릭 클래스입니다.

List list = new ArrayList();
다음은 예입니다. 여기서는 에 어떤 유형의 객체를 배치할지 지정하지 않습니다 List. 이러한 것을 만들고 List여기에 일부 개체를 추가하려고 하면 IDEA에 경고가 표시됩니다.

"Unchecked call to add(E) as a member of raw type of java.util.List".
그러나 우리는 제네릭이 Java 5에만 등장한다는 사실에 대해서도 이야기했습니다. 이 버전이 출시되었을 때 프로그래머는 이미 원시 유형을 사용하여 많은 코드를 작성했기 때문에 언어의 이 기능은 작동을 멈출 수 없었고 Java에서 원시 유형 생성이 보존되었습니다. 그러나 문제는 더 널리 퍼졌습니다. 아시다시피 Java 코드는 바이트코드라는 특수 컴파일된 형식으로 변환된 다음 Java 가상 머신에서 실행됩니다. 그러나 변환 프로세스 중에 바이트코드에 유형 매개변수에 대한 정보를 넣으면 Java 5 이전에는 유형 매개변수가 없었기 때문에 이전에 작성된 모든 코드가 손상됩니다! 제네릭으로 작업할 때 기억해야 할 매우 중요한 개념이 하나 있습니다. 유형 삭제 라고합니다.. 이는 클래스에 유형 매개변수에 대한 정보가 없음을 의미합니다. 이 정보는 컴파일 중에만 사용할 수 있으며 런타임 전에 지워집니다(액세스할 수 없게 됨). 에 잘못된 유형의 객체를 넣으려고 하면 List<String>컴파일러에서 오류가 발생합니다. 이것이 바로 언어 제작자가 제네릭을 만들 때 달성하고자 하는 것입니다. 바로 컴파일 타임 검사입니다. 그러나 모든 Java 코드가 바이트코드로 바뀌면 더 이상 유형 매개변수에 대한 정보가 포함되지 않습니다. 바이트코드에서 List<Cat>고양이 목록은 문자열과 다르지 않습니다 List<String>. 바이트코드에서 그것이 객체 cats의 목록이라고 말하는 것은 없습니다 Cat. 이러한 정보는 컴파일하는 동안 지워집니다. 목록이 있다는 사실만 List<Object> cats프로그램의 바이트코드에 남게 됩니다. 이것이 어떻게 작동하는지 봅시다:

public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
우리는 고유한 일반 TestClass클래스를 만들었습니다. 매우 간단합니다. 실제로는 개체가 생성되는 즉시 저장되는 2개 개체의 작은 "컬렉션"입니다. 2개의 필드가 있습니다 T. 메서드가 실행될 때 createAndAdd2Values()두 개의 전달된 객체( Object a및 를 유형으로 캐스팅한 다음 객체에 추가 Object b해야 합니다 . 메서드에서 우리는 a 를 생성합니다 . 즉 , 유형 인수가 유형 매개변수를 대체합니다 . 우리는 또한 a 및 a 를 우리 프로그램 이 잘 될 것 같나요? 결국 우리는 type 인수로 지정했지만 a는 확실히 캐스트할 수 없습니다 !TTestClassmain()TestClass<Integer>IntegerIntegerDoubleStringcreateAndAdd2Values()IntegerStringIntegermain()방법 및 확인. 콘솔 출력:

22.111 
Test String
그것은 예상치 못한 일이었습니다! 왜 이런 일이 일어났습니까? 유형 삭제의 결과입니다. Integer개체를 인스턴스화하는 데 사용된 형식 인수 에 대한 정보는 TestClass<Integer> test코드가 컴파일될 때 지워졌습니다. 필드가 됩니다 TestClass<Object> test. 우리의 DoubleString인수는 객체로 쉽게 변환되었고 ( 예상한 대로 객체 Object로 변환되지 않았습니다 !) 에 조용히 추가되었습니다 . 다음은 유형 삭제의 단순하지만 매우 드러나는 또 다른 예입니다. IntegerTestClass

import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
콘솔 출력:

true 
true
String, Integer및 자체 클래스 라는 세 가지 유형의 인수를 사용하여 컬렉션을 만든 것 같습니다 Cat. 그러나 바이트코드로 변환하는 동안 세 목록 모두 가 되므로 List<Object>프로그램이 실행될 때 세 가지 경우 모두에서 동일한 클래스를 사용하고 있음을 알려줍니다.

배열 및 제네릭으로 작업할 때 유형 삭제

배열 및 일반 클래스(예: )로 작업할 때 명확하게 이해해야 하는 매우 중요한 사항이 있습니다 List. 또한 프로그램의 데이터 구조를 선택할 때 이를 고려해야 합니다. 제네릭은 형식 삭제가 적용됩니다. 유형 매개변수에 대한 정보는 런타임에 사용할 수 없습니다. 반대로 배열은 프로그램이 실행 중일 때 데이터 유형에 대한 정보를 알고 사용할 수 있습니다. 유효하지 않은 유형을 배열에 넣으려고 하면 예외가 발생합니다.

public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
콘솔 출력:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
배열과 제네릭 사이에는 큰 차이가 있기 때문에 호환성 문제가 있을 수 있습니다. 무엇보다도 일반 개체의 배열이나 매개 변수화된 배열을 만들 수 없습니다. 조금 혼란스럽게 들리나요? 한 번 보자. 예를 들어 Java에서는 다음을 수행할 수 없습니다.

new List<T>[]
new List<String>[]
new T[]
객체 배열을 만들려고 하면 List<String>일반 배열 생성에 대해 불평하는 컴파일 오류가 발생합니다.

import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       // Compilation error! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
그런데 왜 이렇게 되었습니까? 이러한 어레이 생성이 허용되지 않는 이유는 무엇입니까? 이것은 모두 형식 안전을 제공하기 위한 것입니다. 컴파일러가 그러한 일반 객체의 배열을 만들도록 허용한다면 우리는 수많은 문제를 스스로 만들 수 있습니다. 다음은 Joshua Bloch의 저서 "Effective Java"의 간단한 예입니다.

public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
List<String>[] stringLists다음과 같은 배열을 만드는 것이 허용되고 컴파일 오류가 발생하지 않는다고 상상해 봅시다 . 이것이 사실이라면 다음과 같이 할 수 있습니다. 1행에서 목록 배열을 만듭니다. List<String>[] stringLists. 우리 배열에는 하나가 포함되어 있습니다 List<String>. 2행에서 숫자 목록을 작성합니다. List<Integer>. 3행에서는 우리를 변수 List<String>[]에 할당합니다 Object[] objects. Java 언어는 다음을 허용합니다. 객체 배열은 모든 하위 클래스의 객체와 객체를 X저장할 수 있습니다 . 따라서 배열에는 무엇이든 넣을 수 있습니다 . 4행에서 배열의 유일한 요소(a )를 a로 바꿉니다 . 따라서 우리는 단지 저장하기 위한 배열에 a를 넣었습니다.XXObjectobjects()List<String>List<Integer>List<Integer>List<String>사물! 5행을 실행할 때만 오류가 발생합니다. A는 ClassCastException런타임에 발생합니다. 따라서 이러한 배열 생성에 대한 금지가 Java에 추가되었습니다. 이를 통해 이러한 상황을 피할 수 있습니다.

유형 삭제를 어떻게 해결할 수 있습니까?

글쎄, 우리는 유형 삭제에 대해 배웠습니다. 시스템을 속이려고 해보자! :) 작업: 일반 클래스가 있습니다 TestClass<T>. createNewT()우리는 이 클래스에 대해 새 객체를 생성하고 반환하는 메서드를 작성하려고 합니다 T. 하지만 이건 불가능하죠? 유형 에 대한 모든 정보는 T컴파일 중에 지워지며 런타임에는 어떤 유형의 객체를 만들어야 하는지 결정할 수 없습니다. 실제로 이를 수행하는 까다로운 방법이 하나 있습니다. Java에 클래스가 있다는 것을 기억할 것입니다 Class. 이를 사용하여 객체의 클래스를 결정할 수 있습니다.

public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
콘솔 출력:

class java.lang.Integer 
class java.lang.String
그러나 여기에 우리가 이야기하지 않은 한 가지 측면이 있습니다. Oracle 설명서에서 Class 클래스가 일반적임을 알 수 있습니다! 유형 삭제 - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

설명서에는 "T - 이 Class 개체에 의해 모델링된 클래스의 유형"이라고 나와 있습니다. 이것을 문서 언어에서 평이한 말로 번역하면 객체의 클래스가 단지 가 Integer.class아니라 이라는 것을 이해합니다 . 개체 의 유형은 그냥 이 아니라 등 입니다 . 여전히 명확하지 않은 경우 이전 예제에 유형 매개변수를 추가해 보세요. ClassClass<Integer>String.classClassClass<String>

public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       // Compilation error!
       Class<String> classInt2 = Integer.class;
      
      
       Class<String> classString = String.class;
       // Compilation error!
       Class<Double> classString2 = String.class;
   }
}
이제 이 지식을 사용하여 유형 삭제를 우회하고 작업을 수행할 수 있습니다! 유형 매개변수에 대한 정보를 가져오도록 합시다. 우리의 유형 인수는 다음과 같습니다 MySecretClass.

public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
솔루션을 실제로 사용하는 방법은 다음과 같습니다.

public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
콘솔 출력:

A MySecretClass object was created successfully!
필요한 클래스 인수를 제네릭 클래스의 생성자에 전달했습니다.

TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
이를 통해 형식 인수에 대한 정보를 저장하여 완전히 지워지는 것을 방지할 수 있습니다. 그 결과, 우리는T물체! :) 이것으로 오늘의 수업은 끝이 납니다. 제네릭으로 작업할 때는 항상 유형 삭제를 기억해야 합니다. 이 해결 방법은 그다지 편리해 보이지 않지만 제네릭이 생성될 때 Java 언어의 일부가 아니라는 점을 이해해야 합니다. 매개변수화된 컬렉션을 생성하고 컴파일 중에 오류를 포착하는 데 도움이 되는 이 기능은 나중에 추가되었습니다. 첫 번째 버전의 제네릭이 포함된 일부 다른 언어에서는 형식 삭제가 없습니다(예: C#). 그건 그렇고, 우리는 제네릭 연구를 마치지 않았습니다! 다음 레슨에서는 제네릭의 몇 가지 추가 기능에 대해 알게 될 것입니다. 지금은 몇 가지 작업을 해결하는 것이 좋습니다! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION