Java Generics는 Java 언어의 가장 중요한 기능 중 하나입니다. 제네릭의 이면에 있는 아이디어는 매우 간단하지만 관련된 일반적인 구문에서 벗어나기 때문에 때때로 복잡해 보입니다.
이 튜토리얼의 목적은 이해하기 쉬운 방식으로 이 유용한 제네릭 개념을 소개하는 것입니다.
그러나 제네릭 자체에 대해 알아보기 전에 먼저 Java 제네릭이 필요한 이유를 알아보겠습니다.
자바 제네릭의 목적
Java 5에 제네릭이 도입되기 전에는 오류나 경고 없이 다음과 같은 코드 조각을 작성하고 컴파일할 수 있었습니다.
List list = new ArrayList();
list.add("hey");
list.add(new Object());
저장하는 데이터 유형을 선언하지 않고도 목록이나 다른 Java 컬렉션에 모든 유형의 값을 추가할 수 있습니다. 그러나 목록에서 값을 검색할 때 특정 유형으로 명시적으로 캐스트해야 합니다.
위의 목록을 반복하는 것을 고려하십시오.
for (int i=0; i< list.size(); i++) {
String value = (String) list.get(i); //CastClassException when i=1
}
저장된 데이터 유형을 먼저 선언하지 않고 목록 생성을 허용하면 우리가 했던 것처럼 프로그래머가 위와 같은 실수를 하여 ClassCastExceptions
를 던질 수 있습니다. 실행 중입니다.
프로그래머가 이러한 실수를 하지 않도록 제네릭이 도입되었습니다.
제네릭을 사용하면 다음 예제와 같이 Java 컬렉션을 생성할 때 저장될 데이터 유형을 명시적으로 선언할 수 있습니다.
참고:저장된 데이터 유형을 지정하지 않고 Java 컬렉션 객체를 생성할 수 있지만 권장하지 않습니다.List<String> stringList = new ArrayList<>();
이제 컴파일 시간 오류를 발생시키지 않고 실수로 정수를 String 유형 목록에 저장할 수 없습니다. 이렇게 하면 프로그램에서 런타임 오류가 발생하지 않습니다.
stringList.add(new Integer(4)); //Compile time Error
Java에 제네릭을 도입한 주요 목적은 ClassCastExceptions
런타임 중.
자바 제네릭 생성
제네릭을 사용하여 Java 클래스 및 메서드를 만들 수 있습니다. 각 유형의 제네릭을 만드는 방법의 예를 살펴보겠습니다.
일반 클래스
제네릭 클래스를 생성할 때 클래스의 타입 매개변수는 <>
각도 내에서 클래스 이름 끝에 추가됩니다. 대괄호.
public class GenericClass<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return this.item;
}
}
여기, T
데이터 유형 매개변수입니다. T
, N
, 및 E
Java 규칙에 따라 데이터 유형 매개변수에 사용되는 문자 중 일부입니다.
위의 예에서 GenericClass 객체를 생성할 때 특정 데이터 유형을 전달할 수 있습니다.
public static void main(String[] args) {
GenericClass<String> gc1 = new GenericClass<>();
gc1.setItem("hello");
String item1 = gc1.getItem(); // "hello"
gc1.setItem(new Object()); //Error
GenericClass<Integer> gc2 = new GenericClass<>();
gc2.setItem(new Integer(1));
Integer item2 = gc2.getItem(); // 1
gc2.setItem("hello"); //Error
}
제네릭 클래스 객체를 생성할 때 기본 데이터 유형을 데이터 유형 매개변수에 전달할 수 없습니다. Object 유형을 확장하는 데이터 유형만 유형 매개변수로 전달할 수 있습니다.
예:
GenericClass<float> gc3 = new GenericClass<>(); //Error
일반 방법
제네릭 메서드를 만드는 것은 제네릭 클래스를 만드는 것과 유사한 패턴을 따릅니다. 제네릭 클래스와 제네릭이 아닌 클래스 내에서 제네릭 메서드를 구현할 수 있습니다.
public class GenericMethodClass {
public static <T> void printItems(T[] arr){
for (int i=0; i< arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void main(String[] args) {
String[] arr1 = {"Cat", "Dog", "Mouse"};
Integer[] arr2 = {1, 2, 3};
GenericMethodClass.printItems(arr1); // "Cat", "Dog", "Mouse"
GenericMethodClass.printItems(arr2); // 1, 2, 3
}
}
여기에서 특정 유형의 배열을 전달하여 메소드를 매개변수화할 수 있습니다. 일반 메서드 PrintItems()
전달된 배열을 반복하고 일반 Java 메서드처럼 저장된 항목을 인쇄합니다.
제한된 유형 매개변수
지금까지 위에서 만든 제네릭 클래스와 메서드는 기본 형식 이외의 모든 데이터 형식으로 매개 변수화할 수 있습니다. 그러나 제네릭에 전달할 수 있는 데이터 유형을 제한하려면 어떻게 해야 할까요? 이것은 제한된 유형 매개변수가 들어오는 곳입니다.
다른 데이터 유형의 하위 클래스여야 함을 지정하여 일반 클래스 또는 메서드에서 허용하는 데이터 유형을 바인딩할 수 있습니다.
예:
//accepts only subclasses of List
public class UpperBoundedClass<T extends List>{
//accepts only subclasses of List
public <T extends List> void UpperBoundedMethod(T[] arr) {
}
}
여기서 UpperBoundedClass
및 UpperBoundedMethod
List
의 하위 유형을 사용해서만 매개변수화할 수 있습니다. 데이터 유형.
List
데이터 유형은 유형 매개변수에 대한 상한 역할을 합니다. List
의 하위 유형이 아닌 데이터 유형을 사용하려는 경우 , 컴파일 타임 오류가 발생합니다.
경계는 클래스에만 국한되지 않습니다. 인터페이스도 전달할 수 있습니다. 이 경우 인터페이스를 확장한다는 것은 인터페이스를 구현하는 것을 의미합니다.
매개변수는 이 예와 같이 여러 경계를 가질 수도 있습니다.
//accepts only subclasses of both Mammal and Animal
public class MultipleBoundedClass<T extends Mammal & Animal>{
//accepts only subclasses of both Mammal and Animal
public <T extends Mammal & Animal> void MultipleBoundedMethod(T[] arr){
}
}
허용 데이터 유형은 Animal 및 Mammal 클래스의 하위 클래스여야 합니다. 이러한 경계 중 하나가 클래스인 경우 경계 선언에서 맨 처음에 와야 합니다.
위의 예에서 Mammal이 클래스이고 Animal이 인터페이스인 경우 위와 같이 Mammal이 먼저 와야 합니다. 그렇지 않으면 코드에서 컴파일 타임 오류가 발생합니다.
자바 일반 와일드카드
와일드카드는 제네릭 유형의 매개변수를 메소드에 전달하는 데 사용됩니다. 제네릭 메서드와 달리 여기에서 제네릭 매개 변수는 위에서 논의한 데이터 유형 매개 변수와 다른 메서드에서 허용하는 매개 변수에 전달됩니다. 와일드카드는 ? 기호.
public void printItems(List<?> list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
}
}
위의 printItems()
메소드는 모든 데이터 유형의 목록을 매개변수로 허용합니다. 이렇게 하면 프로그래머가 제네릭이 없는 경우처럼 서로 다른 데이터 유형 목록에 대해 코드를 반복해야 하는 것을 방지할 수 있습니다.
상한 와일드카드
메소드에서 허용하는 목록에 저장된 데이터 유형을 제한하려면 경계 와일드카드를 사용할 수 있습니다.
예:
public void printSubTypes(List<? extends Color> list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
}
}
printSubTypes()
메서드는 Color의 하위 유형을 저장하는 목록만 허용합니다. RedColor 또는 BlueColor 개체 목록은 허용하지만 Animal 개체 목록은 허용하지 않습니다. 이는 Animal이 Color의 하위 유형이 아니기 때문입니다. 이것은 상한 와일드카드의 예입니다.
하한 와일드카드
마찬가지로 다음과 같은 경우:
public void printSuperTypes(List<? super Dog> list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
}
}
그런 다음 printSuperTypes()
메소드는 Dog 클래스의 수퍼 유형을 저장하는 목록만 허용합니다. LabDog는 Dog의 상위 클래스가 아니라 하위 클래스이기 때문에 Mammal 또는 Animal 개체 목록은 허용하지만 LabDog 개체 목록은 허용하지 않습니다. 이것은 하한 와일드카드의 예입니다.
결론
Java Generics는 도입 이후 프로그래머가 없이는 살 수 없는 기능이 되었습니다.
이 인기는 프로그래머의 삶을 더 쉽게 만드는 데 미치는 영향 때문입니다. 코딩 실수를 방지하는 것 외에 제네릭을 사용하면 코드가 덜 반복됩니다. 다른 데이터 유형에 대해 코드를 반복할 필요가 없도록 클래스와 메소드를 일반화하는 방법을 알고 계셨습니까?
제네릭을 잘 이해하는 것은 언어 전문가가 되는 데 중요합니다. 따라서 이 튜토리얼에서 배운 내용을 실제 코드에 적용하는 것이 지금 진행하는 방법입니다.