JavaRush /Java Blog /Random-KO /지우기 유형

지우기 유형

Random-KO 그룹에 게시되었습니다
안녕하세요! 우리는 제네릭에 대한 일련의 강의를 계속합니다. 이전에 우리는 그것이 무엇이며 왜 필요한지 일반적인 용어로 파악했습니다. 오늘 우리는 제네릭의 일부 기능에 대해 이야기하고 제네릭을 사용할 때 발생할 수 있는 몇 가지 함정을 살펴보겠습니다. 가다! 지난 강의지우기 유형 - 1 에서는 Generic TypeRaw Type 의 차이점에 대해 이야기했습니다 . 잊어버린 경우를 대비해 Raw Type은 해당 유형이 제거된 일반 클래스입니다.
List list = new ArrayList();
여기에 예가 있습니다. 여기서는 에 어떤 유형의 객체를 배치할지 지정하지 않습니다 List. 하나를 만들고 List여기에 개체를 추가하려고 하면 IDea에 다음과 같은 경고가 표시됩니다.

“Unchecked call to add(E) as a member of raw type of java.util.List”.
하지만 제네릭은 Java 5 버전의 언어에만 등장한다는 사실도 이야기했는데, 출시 당시 프로그래머들은 Raw Types를 사용하여 많은 코드를 작성했기 때문에 작동이 중단되지 않도록 하는 기능이 있었습니다. 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()캐스팅되어야 하며 그 후에 객체에 추가됩니다 . 우리가 만드는 방법 , 즉 품질면에서 우리는 을 갖게 될 것 입니다 . 그러나 동시에 우리는 메소드에 숫자 와 객체를 전달합니다 . 우리 프로그램이 효과가 있을 거라고 생각하시나요? 결국 우리는 매개변수 type 으로 지정했지만 확실히 ! 로 캐스팅할 수는 없습니다 . 메서드를 실행 하고 확인해 보겠습니다. 콘솔 출력: 22.111 테스트 문자열 예상치 못한 결과입니다! 왜 이런 일이 일어났나요? 바로 유형 삭제 때문입니다. 코드 컴파일 중에 객체의 매개변수 유형에 대한 정보가 지워졌습니다. 그는 . 매개변수는 아무 문제 없이 로 변환되었으며 ( 예상했던 대로 로 변환되지 않았습니다 !) 조용히 에 추가되었습니다 . 다음은 유형 삭제에 대한 간단하지만 매우 예시적인 또 다른 예입니다. Object aObject bTTestClassmain()TestClass<Integer>TIntegercreateAndAdd2Values()DoubleStringIntegerStringIntegermain()IntegerTestClass<Integer> testTestClass<Object> testDoubleStringObjectIntegerTestClass
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 trueString 세 가지 다른 매개변수 유형( , 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) {

       //ошибка компиляции! 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행에서는 배열의 단일 요소를 목록으로 바꿉니다 . 결과적으로 우리는 저장 전용인 배열에 배치했습니다 . 코드가 5행에 도달할 때만 오류가 발생합니다. 프로그램 실행 중에 예외가 발생합니다 . 따라서 이러한 배열 생성 금지가 Java 언어에 도입되었습니다. 이를 통해 이러한 상황을 피할 수 있습니다. XХObjectsobjects (List<String>)List<Integer>List<Integer>List<String>ClassCastException

유형 삭제를 어떻게 우회할 수 있나요?

글쎄, 우리는 유형 삭제에 대해 배웠습니다. 시스템을 속이려고 노력하자! :) 작업: 일반 클래스가 있습니다 TestClass<T>. createNewT()유형의 새 객체를 생성하고 반환하는 메서드를 생성해야 합니다 Т. 하지만 이건 불가능한 일이죠? 유형에 대한 모든 정보는 Т컴파일 중에 지워지며 프로그램이 실행되는 동안 생성해야 할 객체 유형이 무엇인지 알 수 없습니다. 사실, 까다로운 방법이 하나 있습니다. 아마도 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문서에는 "T는 이 Class 객체에 의해 모델링된 클래스의 유형입니다."라고 나와 있습니다. 이것을 문서 언어에서 인간 언어로 번역하면 객체에 대한 클래스가 Integer.class단순한 것이 아니라 이라는 Class의미 입니다 Class<Integer>. 객체의 유형은 , 등이 string.class아닙니다 . 여전히 명확하지 않다면 이전 예제에 유형 매개변수를 추가해 보세요. ClassClass<String>
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
이제 이 지식을 사용하여 유형 삭제를 우회하고 문제를 해결할 수 있습니다! 매개변수 유형에 대한 정보를 얻으려고 노력해 보겠습니다. 그 역할은 학급에서 수행됩니다 MySecretClass.
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
실제로 솔루션을 사용하는 방법은 다음과 같습니다.
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();

   }
}
콘솔 출력:

Объект секретного класса успешно создан!
우리는 필요한 클래스 매개변수를 일반 클래스의 생성자에 전달했습니다.
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
덕분에 매개변수 유형에 대한 정보를 저장하고 삭제되지 않도록 보호했습니다. 그 결과, 우리는 객체를 생성할 수 있었습니다 T! :) 이것으로 오늘 강의를 마치겠습니다. 유형 삭제는 제네릭 작업 시 항상 염두에 두어야 할 사항입니다. 이는 그다지 편리해 보이지는 않지만 제네릭은 Java 언어가 생성될 당시에는 해당 언어의 일부가 아니었음을 이해해야 합니다. 이는 형식화된 컬렉션을 생성하고 컴파일 단계에서 오류를 잡는 데 도움이 되는 나중에 추가된 기능입니다. 버전 1부터 제네릭이 사용된 일부 다른 언어(예: C#)에는 유형 삭제 기능이 없습니다. 그러나 제네릭 연구는 아직 끝나지 않았습니다! 다음 강의에서는 이들과 함께 작업하는 몇 가지 추가 기능에 대해 알게 될 것입니다. 그동안 몇 가지 문제를 해결하면 좋을 것 같습니다! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION