안녕하세요! 오늘은 제네릭에 대해 이야기하겠습니다. 나는 당신이 많은 새로운 것을 배울 것이라고 말해야합니다! 이것뿐만 아니라 다음 강의에서도 제네릭에 대해 집중적으로 다루겠습니다. 그러므로 이 주제가 당신에게 흥미가 있다면 당신은 운이 좋은 것입니다. 오늘 당신은 제네릭의 기능에 대해 많은 것을 배울 것입니다. 그렇지 않다면 진정하고 휴식을 취하세요! :) 이것은 매우 중요한 주제이므로 알아야 합니다. "무엇"과 "왜"라는 간단한 것부터 시작해 보겠습니다. 제네릭이란 무엇입니까? 제네릭은 매개변수가 있는 유형입니다. 원본을 생성할 때 해당 유형뿐만 아니라 작업할 데이터 유형도 지정합니다. 가장 분명한 예가 이미 여러분 마음 속에 떠올랐다고 생각합니다. 이것이 바로 ArrayList입니다! 프로그램에서 일반적으로 생성하는 방법은 다음과 같습니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> myList1 = new ArrayList<>();
myList1.add("Test String 1");
myList1.add("Test String 2");
}
}
짐작할 수 있듯이 목록의 특징은 모든 것을 목록에 "채울" 수 없다는 것입니다. 목록은 개체에만 작동합니다 String
. 이제 Java의 역사를 잠시 살펴보고 "왜?"라는 질문에 답해 보겠습니다. 이를 위해 ArrayList 클래스의 단순화된 버전을 직접 작성하겠습니다. 우리 목록은 내부 배열에만 데이터를 추가하고 다음 데이터를 받을 수 있습니다.
public class MyListClass {
private Object[] data;
private int count;
public MyListClass() {
this.data = new Object[10];
this.count = 0;
}
public void add(Object o) {
this.data[count] = o;
count++;
}
public Object[] getData() {
return data;
}
}
목록에 숫자만 저장하기를 원한다고 가정해 보겠습니다 Integer
. 우리는 제네릭이 없습니다. Integer
에서 check의 o 인스턴스를 명시적으로 지정할 수 없습니다 add()
. 그러면 우리의 전체 클래스는 에만 적합할 것이며 Integer
세상에 존재하는 모든 데이터 유형에 대해 동일한 클래스를 작성해야 할 것입니다! 우리는 프로그래머에게 의존하기로 결정하고 그들이 불필요한 것을 추가하지 않도록 코드에 주석을 남기기로 결정했습니다.
//use it ONLY with Integer data type
public void add(Object o) {
this.data[count] = o;
count++;
}
프로그래머 중 한 명이 이 설명을 놓치고 실수로 문자열과 혼합된 숫자를 목록에 넣은 다음 그 합계를 계산하려고 했습니다.
public class Main {
public static void main(String[] args) {
MyListClass list = new MyListClass();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
System.out.println(sum1);
Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
System.out.println(sum2);
}
}
콘솔 출력: 300 스레드 "main"의 예외 java.lang.ClassCastException: java.lang.String은 Main.main(Main.java:14)에서 java.lang.Integer로 캐스팅할 수 없습니다. 이 상황에서 최악의 상황은 무엇입니까? 프로그래머의 부주의함과는 거리가 멀다. 최악의 상황은 잘못된 코드가 우리 프로그램의 중요한 위치에 들어가 성공적으로 컴파일되었다는 것 입니다 . 이제 우리는 코딩 단계가 아닌 테스트 단계에서만 오류를 보게 될 것입니다(이것이 가장 좋은 경우입니다!). 나중에 개발 단계에서 버그를 수정하려면 돈과 시간 모두 훨씬 더 많은 비용이 듭니다. 이것이 바로 제네릭의 장점입니다. 제네릭 클래스를 사용하면 불운한 프로그래머가 오류를 즉시 감지할 수 있습니다. 코드가 컴파일되지 않습니다!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> myList1 = new ArrayList<>();
myList1.add(100);
myList1.add(100);
myList1.add("Lolkek");//error!
myList1.add("Shalala");//error!
}
}
프로그래머는 즉시 "정신을 차리고" 즉각적으로 자신을 바로잡을 것입니다. 그런데 List
이런 종류의 오류를 보기 위해 자체 클래스를 만들 필요는 없었습니다. <Integer>
일반 ArrayList에서 유형 괄호( )를 제거하기만 하면 됩니다 !
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
System.out.println((Integer) list.get(0) + (Integer) list.get(1));
System.out.println((Integer) list.get(2) + (Integer) list.get(3));
}
}
콘솔 출력: 300 "main" 스레드의 예외 java.lang.ClassCastException: java.lang.String은 Main.main(Main.java:16)에서 java.lang.Integer로 캐스팅할 수 없습니다. 즉, "네이티브" 도구를 사용하는 경우에도 마찬가지입니다. Java에서는 이런 실수를 해서 안전하지 않은 컬렉션을 만들 수 있습니다. 그러나 이 코드를 IDEa에 붙여 넣으면 다음과 같은 경고가 표시됩니다. " java.util.List의 원시 유형 멤버로서 add(E)에 대한 확인되지 않은 호출 " 이는 요소를 IDEa에 추가할 때 문제가 발생할 수 있음을 알려줍니다. 제네릭 없는 컬렉션은 이렇지 않습니다. 그런데 "원시 유형"이라는 문구는 무엇을 의미합니까? 문자 그대로의 번역은 " raw type " 또는 " dirty type " 과 같이 매우 정확합니다 . Raw type
해당 유형이 제거된 일반 클래스입니다. 즉, List myList1
이것은 Raw type
. 그 반대는 유형 사양을 사용하여 올바르게 생성된 일반 클래스( class 라고도 함 ) raw type
입니다 . 예를 들어, . 질문이 있을 수 있습니다. 왜 사용이 허용됩니까 ? 이유는 간단합니다. Java 제작자는 호환성 문제를 일으키지 않기 위해 언어 지원을 떠났습니다 . Java 5.0이 출시되었을 때(이 버전에서 처음으로 제네릭이 등장함) 많은 코드가 이미 . 그러므로 이러한 가능성은 오늘날에도 여전히 존재합니다. 우리는 이미 강의에서 Joshua Bloch의 고전 책인 "Effective Java"를 여러 번 언급했습니다. 언어 창시자 중 한 사람으로서 그는 책에서 와 사용이라는 주제를 무시하지 않았습니다 . 이 책의 23장은 “새 코드에서 원시 유형을 사용하지 마십시오”라는 매우 설득력 있는 제목을 가지고 있습니다. 이것은 여러분이 기억해야 할 사항입니다. 일반 클래스를 사용할 때는 절대로 . generic type
parameterized type
List<String> myList1
raw types
raw types
raw types
raw types
generic types
generic type
raw type
유형화된 메소드
Java를 사용하면 개별 메서드를 입력하여 소위 일반 메서드를 만들 수 있습니다. 그러한 방법이 왜 편리한가요? 우선, 다양한 유형의 매개변수로 작업할 수 있기 때문입니다. 동일한 논리를 다양한 유형에 안전하게 적용할 수 있다면 제네릭 메서드가 훌륭한 솔루션입니다. 예를 살펴보겠습니다. 어떤 종류의 목록이 있다고 가정해 보겠습니다myList1
. 우리는 모든 값을 제거하고 모든 여유 공간을 새로운 값으로 채우고 싶습니다. 일반적인 메소드를 사용하는 클래스는 다음과 같습니다.
public class TestClass {
public static <T> void fill(List<T> list, T val) {
for (int i = 0; i < list.size(); i++)
list.set(i, val);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("Старая строка 1");
strings.add("Старая строка 2");
strings.add("Старая строка 3");
fill(strings, "Новая строка");
System.out.println(strings);
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
fill(numbers, 888);
System.out.println(numbers);
}
}
구문에 주의하세요. 약간 이상해 보입니다.
public static <T> void fill(List<T> list, T val)
반환 유형 앞에는 일반 메서드를 나타내는 <T>가 옵니다. 이 경우 메소드는 2개의 매개변수를 입력으로 사용합니다. 즉, 객체 목록 T와 또 다른 별도의 객체 T입니다. <T>를 사용하면 메소드 입력이 이루어집니다. 즉, 문자열 목록과 숫자를 전달할 수 없습니다. 문자열과 문자열의 목록, 숫자와 숫자의 목록, 개체 목록 Cat
과 다른 개체 목록 Cat
- 그게 유일한 방법입니다. 이 방법은 이 방법이 다양한 유형의 데이터에 쉽게 작동한다는 main()
것을 명확하게 보여줍니다 . fill()
먼저 문자열 목록과 문자열을 입력으로 받은 다음 숫자 목록과 숫자를 입력으로 받습니다. 콘솔 출력: [Newline, Newline, Newline] [888, 888, 888]fill()
30개의 서로 다른 클래스에 대한 메서드 로직이 필요하고 일반 메서드가 없다고 상상해 보세요 . 서로 다른 데이터 유형에 대해서만 동일한 메소드를 30번 작성해야 합니다! 하지만 일반적인 방법 덕분에 코드를 재사용할 수 있습니다! :)
유형화된 클래스
Java에서 제공되는 일반 클래스를 사용할 수 있을 뿐만 아니라 자신만의 클래스를 만들 수도 있습니다! 간단한 예는 다음과 같습니다.public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Старая строка");
System.out.println(stringBox.get());
stringBox.set("Новая строка");
System.out.println(stringBox.get());
stringBox.set(12345);//ошибка компиляции!
}
}
우리 클래스 Box<T>
("상자")가 입력되었습니다. 생성 중에 데이터 유형( )을 할당하면 <T>
더 이상 다른 유형의 개체를 여기에 배치할 수 없습니다. 이는 예에서 볼 수 있습니다. 생성할 때 객체가 문자열과 함께 작동하도록 지정했습니다.
Box<String> stringBox = new Box<>();
그리고 코드의 마지막 줄에서 상자 안에 숫자 12345를 넣으려고 하면 컴파일 오류가 발생합니다! 그렇게 해서 우리는 우리만의 일반 클래스를 만들었습니다! :) 이것으로 오늘 강의를 마치겠습니다. 그러나 우리는 제네릭에 작별 인사를 하는 것이 아닙니다! 다음 강의에서는 더 많은 고급 기능에 대해 이야기할 테니 작별 인사는 하지 마세요! ) 공부에 행운이 있기를 바랍니다! :)
GO TO FULL VERSION