CodeGym /Java Blog /무작위의 /자바의 제네릭
John Squirrels
레벨 41
San Francisco

자바의 제네릭

무작위의 그룹에 게시되었습니다
안녕! 우리는 Java Generics에 대해 이야기할 것입니다. 나는 당신이 많은 것을 배울 것이라고 말해야합니다! 이 강의뿐만 아니라 다음 몇 개의 강의도 제네릭에 대해 다룰 것입니다. 따라서 제네릭에 관심이 있다면 오늘은 운이 좋은 날입니다. 제네릭의 기능에 대해 많은 것을 배우게 될 것입니다. 그렇지 않다면 사임하고 긴장을 푸십시오! :) 이것은 매우 중요한 주제이며 이를 알아야 합니다. "무엇"과 "왜"라는 간단한 것부터 시작하겠습니다.

자바 제네릭이란 무엇입니까?

제네릭은 매개변수가 있는 유형입니다. 제네릭 유형을 만들 때 유형뿐만 아니라 작동할 데이터 유형도 지정합니다. 가장 확실한 예가 이미 마음에 떠오른 것 같습니다. 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 만 저장하기를 원한다고 가정합니다 . 제네릭 형식을 사용하지 않습니다. 우리는 add() 메서드에 명시적인 "instanceof Integer " 검사를 포함하고 싶지 않습니다 . 그렇게 한다면 전체 클래스는 Integer 에만 적합할 것이고 전 세계의 다른 모든 데이터 유형에 대해 유사한 클래스를 작성해야 할 것입니다! 우리는 프로그래머에게 의존하고 코드에 주석을 남겨 그들이 우리가 원하지 않는 것을 추가하지 않도록 할 것입니다.

// Use this class ONLY with the 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 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
이 상황의 최악의 부분은 무엇입니까? 확실히 프로그래머의 부주의는 아닙니다. 최악의 부분은 잘못된 코드가 우리 프로그램의 중요한 위치에 있고 성공적으로 컴파일되었다는 것입니다. 이제 우리는 코드를 작성하는 동안이 아니라 테스트하는 동안에만 버그를 만나게 될 것입니다(이것이 최상의 시나리오입니다!). 개발 후반 단계에서 버그를 수정하면 돈과 시간 면에서 훨씬 더 많은 비용이 듭니다. 이것이 바로 제네릭이 우리에게 도움이 되는 부분입니다. 제네릭 클래스를 사용하면 불행한 프로그래머가 오류를 즉시 감지할 수 있습니다. 프로그램이 단순히 컴파일되지 않습니다!

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 클래스를 만들 필요가 없었습니다. 꺾쇠 괄호를 제거하고 일반 ArrayList에서 ( <Integer> )를 입력하기만 하면 됩니다!

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 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
즉, Java의 "네이티브" 메커니즘을 사용하더라도 이러한 종류의 실수를 저지르고 안전하지 않은 컬렉션을 만들 수 있습니다. 그러나 이 코드를 IDE에 붙여 넣으면 "java.util.List의 원시 유형 구성원으로 add(E)에 대한 확인되지 않은 호출"이라는 경고가 표시됩니다. 제네릭 형식이 없는 컬렉션에. 그러나 "원시 유형"이라는 문구는 무엇을 의미합니까? 원시 유형은 유형이 제거된 제네릭 클래스입니다. 즉, List myList1은 원시 유형 입니다 . 원시 유형 의 반대는 매개변수화된 유형 의 표시가 있는 일반 클래스인 제네릭 유형 입니다 . 예: List<String> myList1. 언어에서 원시 유형 의 사용을 허용하는 이유를 물을 수 있습니다 . 그 이유는 간단합니다. Java 제작자는 호환성 문제를 피하기 위해 원시 유형 에 대한 지원을 언어로 남겼습니다. Java 5.0이 출시될 때(이 버전에서 제네릭이 처음 등장) 이미 원시 유형을 사용하여 많은 코드가 작성되었습니다 . 결과적으로 이 메커니즘은 오늘날에도 여전히 지원됩니다. 우리는 레슨에서 Joshua Bloch의 고전 책 "Effective Java"를 반복해서 언급했습니다. 언어의 창시자 중 한 사람으로서 그는 그의 책에서 원시 유형일반 유형을 건너뛰지 않았습니다 .Java에서 제네릭이란 무엇입니까?  - 2이 책의 23장에는 "새 코드에서 원시 유형을 사용하지 마십시오"라는 매우 설득력 있는 제목이 있습니다. 이것이 기억해야 할 내용입니다. 일반 클래스를 사용할 때 일반 유형을 원시 유형 으로 바꾸지 마십시오 .

제네릭 메서드

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("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       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>를 사용하여 메소드의 매개변수 유형을 매개변수화합니다. 문자열 및 정수 목록을 전달할 수 없습니다. String과 String의 목록, Integer와 Integer의 목록, 우리 자신의 Cat 객체와 또 다른 Cat 객체 의 목록 — 이것이 우리가 해야 할 일입니다. main () 메서드는 다양한 유형의 데이터 작업에 fill() 메서드를 쉽게 사용할 수 있는 방법을 보여줍니다. 먼저 문자열 목록과 문자열을 입력으로 사용한 다음 정수 목록과 정수를 사용하여 메서드를 사용합니다. 콘솔 출력:

[New String, New String, New String] [888, 888, 888]
일반적인 메서드가 없고 30개의 서로 다른 클래스에 대해 fill() 메서드의 논리가 필요한 경우를 상상해 보십시오. 서로 다른 데이터 유형에 대해 동일한 메서드를 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("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
Box <T> 클래스는 일반 클래스입니다. 생성하는 동안 데이터 유형( <T> )을 할당하면 더 이상 다른 유형의 객체를 배치할 수 없습니다. 이것은 예제에서 볼 수 있습니다. 개체를 만들 때 문자열과 함께 작동할 것이라고 표시했습니다.

Box<String> stringBox = new Box<>();
그리고 코드의 마지막 줄에서 숫자 12345를 상자 안에 넣으려고 하면 컴파일 오류가 발생합니다! 그렇게 쉽습니다! 고유한 일반 클래스를 만들었습니다! :) 이것으로 오늘의 수업은 끝이 납니다. 그러나 우리는 제네릭에 작별 인사를 하지 않습니다! 다음 강의에서는 더 고급 기능에 대해 이야기할 것이므로 멀리 가지 마세요! ) 학습한 내용을 보강하려면 Java 과정에서 비디오 강의를 시청하는 것이 좋습니다.
귀하의 학업에서 최고의 성공을 거두십시오! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION