∮explotación≒ 개발

java Introducing Generics

파란형 2023. 3. 8.
반응형

제네릭 소개

 

제네릭을 사용하는 이유

간단히 말해서 제네릭은 클래스, 인터페이스 및 메서드를 정의할 때 형식(클래스 및 인터페이스)이 매개 변수가 되도록 합니다. 메서드 선언에 사용되는 보다 친숙한 형식 매개 변수와 마찬가지로 형식 매개 변수는 입력이 다른 동일한 코드를 재사용할 수 있는 방법을 제공합니다. 형식 매개변수에 대한 입력은 값인 반면 유형 매개변수에 대한 입력은 유형이라는 차이점이 있습니다.

제네릭을 사용하는 코드는 제네릭이 아닌 코드에 비해 많은 이점이 있습니다.

  • 컴파일 타임에 더 강력한 유형 검사. Java 컴파일러는 일반 코드에 강력한 유형 검사를 적용하고 코드가 유형 안전을 위반하는 경우 오류를 발생시킵니다. 컴파일 타임 오류를 수정하는 것이 찾기 어려울 수 있는 런타임 오류를 수정하는 것보다 쉽습니다.
  • 캐스트 제거. 제네릭이 없는 다음 코드 스니펫은 캐스팅이 필요합니다.
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
 

제네릭을 사용하도록 다시 작성할 때 코드는 캐스팅이 필요하지 않습니다.

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast
 
  • 프로그래머가 일반 알고리즘을 구현할 수 있도록 합니다. 제네릭을 사용하여 프로그래머는 다양한 유형의 컬렉션에서 작동하고 사용자 정의할 수 있으며 유형이 안전하고 읽기 쉬운 제네릭 알고리즘을 구현할 수 있습니다.

 

제네릭 유형

간단한 박스 클래스

제네릭 형식 은 형식에 대해 매개 변수화되는 제네릭 클래스 또는 인터페이스입니다. 다음 Box클래스는 개념을 설명하기 위해 수정됩니다.

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}
 

해당 메서드는 를 수락하거나 반환하므로 Object기본 유형 중 하나가 아닌 경우 원하는 모든 항목을 자유롭게 전달할 수 있습니다. 컴파일 시간에 클래스가 어떻게 사용되는지 확인할 방법이 없습니다. 코드의 한 부분은 Integer상자에 를 배치하고 상자에서 유형의 개체를 가져올 것으로 예상하는 Integer반면, 코드의 다른 부분은 실수로 a 를 전달하여 String런타임 오류가 발생할 수 있습니다.

Box 클래스의 일반 버전

제네릭 클래스는 다음 형식으로 정의됩니다.

class name<T1, T2, ..., Tn> { /* ... */ }
 

꺾쇠 괄호( )로 구분된 유형 매개변수 섹션은 <>클래스 이름 뒤에 옵니다. 유형 매개변수(유형 변수라고도 함) T1, T2, ... 및 을 지정합니다 Tn.

제네릭을 사용하도록 클래스를 업데이트하려면 Box코드 " public class Box"를 " public class Box<T>"로 변경하여 제네릭 형식 선언을 만듭니다. T이렇게 하면 클래스 내부 어디에서나 사용할 수 있는 유형 변수 가 도입됩니다 .

이 변경으로 Box클래스는 다음과 같이 됩니다.

/**
 * Generic version of the Box class.
 * @param <T> the type of the value being boxed
 */
public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
 

보시다시피 의 모든 항목이 Object로 대체됩니다 T. 유형 변수는 클래스 유형, 인터페이스 유형, 배열 유형 또는 다른 유형 변수와 같이 지정하는 기본이 아닌 모든 유형이 될 수 있습니다.

이와 동일한 기술을 일반 인터페이스를 만드는 데 적용할 수 있습니다.

유형 매개변수 명명 규칙

규칙에 따라 유형 매개변수 이름은 단일 대문자입니다. 이는 여러분이 이미 알고 있는 변수 명명 규칙과 뚜렷한 대조를 이루며 그럴 만한 이유가 있습니다. 이 규칙이 없으면 유형 변수와 일반 클래스 또는 인터페이스 이름 간의 차이를 구분하기 어려울 것입니다.

가장 일반적으로 사용되는 유형 매개변수 이름은 다음과 같습니다.

  • E - 요소(Java Collections Framework에서 광범위하게 사용됨)
  • K - 키
  • N - 숫자
  • T - 유형
  • V - 가치
  • S, U, V 등 - 2차, 3차, 4차
  • Java SE API 및 이 섹션의 나머지 부분에서 이러한 이름이 사용되는 것을 볼 수 있습니다.

제네릭 유형 호출 및 인스턴스화

Box코드 내에서 제네릭 클래스를 참조하려면 T다음과 같은 구체적인 값으로 대체하는 제네릭 형식 호출을 수행해야 합니다 Integer.

Box<Integer> integerBox;
 

제네릭 형식 호출을 일반 메서드 호출과 유사하게 생각할 수 있지만 메서드에 인수를 전달하는 대신 형식 인수( Integer이 경우) 를 Box클래스 자체에 전달합니다.

유형 매개변수 및 유형 인수 용어 : 많은 개발자가 "유형 매개변수"와 "유형 인수"라는 용어를 같은 의미로 사용하지만 이 용어는 동일하지 않습니다. 코딩할 때 매개변수화된 유형을 만들기 위해 유형 인수를 제공합니다. 따라서 Tin Foo<T>은 유형 매개변수이고 Stringin Foo<String> f은 유형 인수입니다. 이 섹션에서는 이러한 용어를 사용할 때 이 정의를 준수합니다.

다른 변수 선언과 마찬가지로 이 코드는 실제로 새 Box개체를 만들지 않습니다. 단순히 integerBox"Integer 상자"에 대한 참조를 보유할 것이라고 선언합니다. 이것이 어떻게 Box<Integer>읽히는가입니다.

일반 유형의 호출은 일반적으로 매개변수화된 유형으로 알려져 있습니다.

이 클래스를 인스턴스화하려면 new평소와 같이 키워드를 사용하되 <Integer>클래스 이름과 괄호 사이에 배치합니다.

Box<Integer> integerBox = new Box<Integer>();
 

다이아몬드

<>Java SE 7 이상에서는 컴파일러가 컨텍스트에서 형식 인수를 결정하거나 유추할 수 있는 한 일반 클래스의 생성자를 호출하는 데 필요한 형식 인수를 빈 형식 인수 집합( )으로 바꿀 수 있습니다 . 이 꺽쇠 괄호 쌍 은 <>비공식적으로 다이아몬드라고 합니다. Box<Integer>예를 들어 다음 문을 사용하여 인스턴스를 만들 수 있습니다 .

Box<Integer> integerBox = new Box<>();
 

다이아몬드 표기법 및 유형 유추에 대한 자세한 내용은 이 자습서의 유형 유추 섹션을 참조하십시오.

다중 유형 매개변수

앞에서 언급했듯이 제네릭 클래스는 여러 유형 매개변수를 가질 수 있습니다. OrderedPair예를 들어 일반 인터페이스를 구현하는 일반 클래스는 다음 Pair과 같습니다.

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
    this.key = key;
    this.value = value;
    }

    public K getKey()    { return key; }
    public V getValue() { return value; }
}
 

다음 문은 OrderedPair클래스의 두 인스턴스화를 만듭니다.

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");
 

코드 는 및 로 new OrderedPair<String, Integer>()인스턴스화 됩니다 . 따라서 의 생성자 의 매개변수 유형은 각각 및 입니다 . 오토박싱으로 인해 a 와 an을 클래스에 전달하는 것이 유효합니다 .KStringVIntegerOrderedPairStringIntegerStringint

The Diamond 섹션에서 언급했듯이 Java 컴파일러는 선언에서 K및 유형을 추론할 수 있으므로 다이아몬드 표기법을 사용하여 이러한 명령문을 단축할 수 있습니다.VOrderedPair<String, Integer>

OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String>  p2 = new OrderedPair<>("hello", "world");
 

제네릭 인터페이스를 만들려면 제네릭 클래스를 만들 때와 동일한 규칙을 따릅니다.

매개변수화된 유형

유형 매개변수(즉, K또는 V)를 매개변수화된 유형(즉, )으로 대체할 수도 있습니다 List<String>. 예를 들어 다음 OrderedPair<K, V>예를 사용합니다.

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
 

 

원시 유형

원시 유형은 유형 인수가 없는 일반 클래스 또는 인터페이스의 이름입니다. 예를 들어 다음과 같은 일반 Box클래스가 있다고 가정합니다.

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}
 

의 매개변수화된 유형을 작성하려면 Box<T>정식 유형 매개변수에 대한 실제 유형 인수를 제공합니다 T.

Box<Integer> intBox = new Box<>();
 

실제 유형 인수가 생략되면 다음과 같은 원시 유형을 생성합니다 Box<T>.

Box rawBox = new Box();
 

따라서 Box는 일반 유형의 원시 유형입니다 Box<T>. 그러나 제네릭이 아닌 클래스 또는 인터페이스 유형은 원시 유형이 아닙니다.

많은 API 클래스(예: Collections 클래스)가 JDK 5.0 이전에는 일반적이지 않았기 때문에 원시 유형은 레거시 코드에 표시됩니다. 원시 유형을 사용할 때 본질적으로 사전 제네릭 동작을 얻습니다. Box는 객체를 제공합니다. 이전 버전과의 호환성을 위해 매개변수화된 유형을 원시 유형에 할당하는 것이 허용됩니다.

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK
 

그러나 원시 유형을 매개변수화된 유형에 할당하면 다음과 같은 경고가 표시됩니다.

Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion
 

원시 유형을 사용하여 해당 제네릭 유형에 정의된 제네릭 메서드를 호출하는 경우에도 경고가 표시됩니다.

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)
 

경고는 원시 유형이 일반 유형 검사를 우회하여 안전하지 않은 코드의 포착을 런타임으로 연기함을 보여줍니다. 따라서 원시 유형을 사용하지 않아야 합니다.

Type Erasure 섹션에는 Java 컴파일러가 원시 유형을 사용하는 방법에 대한 자세한 정보가 있습니다.

확인되지 않은 오류 메시지

앞서 언급했듯이 레거시 코드와 일반 코드를 혼합하면 다음과 유사한 경고 메시지가 나타날 수 있습니다.

Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
 

이는 다음 예제와 같이 원시 유형에서 작동하는 이전 API를 사용할 때 발생할 수 있습니다.

public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}
 

"확인되지 않음"이라는 용어는 컴파일러에 형식 안전성을 보장하는 데 필요한 모든 형식 검사를 수행할 수 있는 충분한 형식 정보가 없음을 의미합니다. "확인되지 않음" 경고는 기본적으로 비활성화되어 있지만 컴파일러에서 힌트를 제공합니다. 모든 "확인되지 않은" 경고를 보려면 -Xlint:unchecked.

이전 예제를 다시 컴파일하면 -Xlint:unchecked다음과 같은 추가 정보가 표시됩니다.

WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning
 

확인되지 않은 경고를 완전히 비활성화하려면 -Xlint:-unchecked플래그를 사용하십시오. 주석은 @SuppressWarnings("unchecked")확인되지 않은 경고를 표시하지 않습니다. 구문 에 익숙하지 않은 경우 @SuppressWarnings주석 섹션을 참조하십시오.

 

제네릭 메서드

제네릭 메서드는 고유한 형식 매개 변수를 도입하는 메서드입니다. 이것은 제네릭 형식을 선언하는 것과 유사하지만 형식 매개 변수의 범위는 선언된 메서드로 제한됩니다. 정적 및 비정적 제네릭 메서드와 제네릭 클래스 생성자가 허용됩니다.

제네릭 메서드의 구문에는 메서드의 반환 형식 앞에 표시되는 꺾쇠 괄호 안에 형식 매개 변수 목록이 포함되어 있습니다. 정적 제네릭 메서드의 경우 형식 매개 변수 섹션이 메서드의 반환 형식 앞에 나타나야 합니다.

이 Util클래스에는 두 개체를 비교하는 일반 메서드인 compare가 포함되어 있습니다 Pair.

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

public class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
 

이 메서드를 호출하기 위한 전체 구문은 다음과 같습니다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
 

유형은 굵게 표시된 것처럼 명시적으로 제공되었습니다. 일반적으로 이것은 생략할 수 있으며 컴파일러는 필요한 유형을 유추합니다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
 

유형 유추라고 하는 이 기능을 사용하면 꺾쇠 괄호 사이에 유형을 지정하지 않고 제네릭 메서드를 일반 메서드로 호출할 수 있습니다. 이 주제는 다음 섹션 유형 유추에서 자세히 설명합니다.

 

제한된 유형 매개변수

매개변수화된 유형에서 유형 인수로 사용할 수 있는 유형을 제한하려는 경우가 있을 수 있습니다. 예를 들어, 숫자에 대해 작동하는 메서드는 인스턴스 Number또는 해당 하위 클래스만 허용하려고 할 수 있습니다. 이것이 제한된 유형 매개변수의 용도입니다.

제한된 유형 매개변수를 선언하려면 유형 매개변수의 이름, 키워드 extends, 상한값을 차례로 나열합니다. 이 예에서는 입니다 Number. 이 문맥에서 " "(클래스에서와 같이) 또는 " "(인터페이스에서와 같이)를 extends의미하는 일반적인 의미로 사용됩니다 .extendsimplements

public class Box<T> {

    private T t;          

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}
 

이 제한된 유형 매개변수를 포함하도록 일반 메서드를 수정하면 inspect 호출에 여전히 다음이 포함되어 있으므로 컴파일이 실패합니다 String.

Box.java:21: <U>inspect(U) in Box<java.lang.Integer> cannot
  be applied to (java.lang.String)
                        integerBox.inspect("10");
                                  ^
1 error
 

제네릭 유형을 인스턴스화하는 데 사용할 수 있는 유형을 제한하는 것 외에도 제한된 유형 매개변수를 사용하면 범위에 정의된 메서드를 호출할 수 있습니다.

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}
 

메서드는 를 통해 Integer 클래스에 정의된 메서드를 isEven()호출합니다 .intValue()n

다중 경계

앞의 예제는 단일 경계가 있는 유형 매개변수의 사용을 보여주지만 유형 매개변수는 여러 경계를 가질 수 있습니다.

<T extends B1 & B2 & B3>
 

경계가 여러 개인 유형 변수는 경계에 나열된 모든 유형의 하위 유형입니다. 범위 중 하나가 클래스인 경우 먼저 지정해야 합니다. 예를 들어:

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }
 

bound가 A먼저 지정되지 않으면 컴파일 타임 오류가 발생합니다.

class D <T extends B & A & C> { /* ... */ }  // compile-time error
 

 

제네릭 메서드 및 제한된 형식 매개 변수

제한된 유형 매개변수는 제네릭 알고리즘 구현의 핵심입니다. T[]지정된 요소보다 큰 배열의 요소 수를 계산하는 다음 방법을 고려하십시오 elem.

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}
 

메서드의 구현은 간단하지만 보다 큼 연산자( )가 , , , , , 및 와 >같은 기본 유형에만 적용되기 때문에 컴파일되지 않습니다 . 연산자를 사용하여 개체를 비교할 수 없습니다 . 문제를 해결하려면 인터페이스로 묶인 유형 매개변수를 사용하세요 .shortintdoublelongfloatbytechar>Comparable<T>

public interface Comparable<T> {
    public int compareTo(T o);
}
 

결과 코드는 다음과 같습니다.

public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}
 

 

제네릭, 상속 및 하위 유형

이미 알고 있듯이 유형이 호환되는 경우 한 유형의 개체를 다른 유형의 개체에 할당할 수 있습니다. 예를 들어, an은 의 상위 유형 중 하나이므로 an Integer에 할당할 수 있습니다 .ObjectObjectInteger

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // OK
 

객체지향 용어에서는 이것을 "is a" 관계라고 합니다. an은 IntegerObject의 일종이므로 할당이 허용됩니다. 그러나 Integer또한 일종의 이므로 Number다음 코드도 유효합니다.

public void someMethod(Number n) { /* ... */ }

someMethod(new Integer(10));   // OK
someMethod(new Double(10.1));   // OK
 

제네릭도 마찬가지입니다. 형식 인수로 전달하여 일반 형식 호출을 수행할 수 Number있으며 인수가 다음과 호환되는 경우 추가의 모든 후속 호출이 허용됩니다 Number.

Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK
 

이제 다음 방법을 고려하십시오.

public void boxTest(Box<Number> n) { /* ... */ }
 

어떤 유형의 주장을 받아들입니까? 서명을 보면 유형이 인 단일 인수를 허용함을 알 수 있습니다 Box<Number>. 그러나 그것은 무엇을 의미합니까? 예상대로 Box<Integer>또는 를 통과할 수 있습니까 ? Box<Double>대답은 "아니오"입니다. 왜냐하면 Box<Integer>및 는 Box<Double>의 하위 유형이 아니기 때문입니다 Box<Number>.

이것은 제네릭을 사용한 프로그래밍에 관한 일반적인 오해이지만 배워야 할 중요한 개념입니다. 는 의 하위 유형 이지만 은 의 하위 유형이 Box<Integer>아닙니다 .Box<Number>IntegerNumber

Introducing Generics
매개변수화된 유형의 서브타이핑.

참고: 두 가지 구체적인 유형 Aand 가 주어 지면 B예를 들어 Numberand 는 and 가 관련되어 있는지 여부에 관계없이 와 관계가 없습니다 . 및 의 공통 상위는 입니다 .IntegerMyClass<A>MyClass<B>ABMyClass<A>MyClass<B>Object

유형 매개변수가 관련되어 있을 때 두 제네릭 클래스 간에 하위 유형과 같은 관계를 만드는 방법에 대한 정보는 와일드카드 및 하위 유형 지정 섹션을 참조하세요 .

제네릭 클래스 및 하위 유형 지정

일반 클래스 또는 인터페이스를 확장하거나 구현하여 하위 유형을 지정할 수 있습니다. 한 클래스 또는 인터페이스의 형식 매개 변수와 다른 클래스 또는 인터페이스의 형식 매개 변수 간의 관계는 extends 및 implements 절에 의해 결정됩니다.

Collections 클래스를 예로 사용하여 ArrayList<E>를 구현 List<E>하고 List<E>확장합니다 Collection<E>. 의 하위 유형인 의 하위 유형 도 마찬가지 ArrayList<String>입니다 . 유형 인수를 변경하지 않는 한 하위 유형 지정 관계는 유형 간에 유지됩니다.List<String>Collection<String>

Introducing Generics
샘플 컬렉션 계층.

PayloadList이제 일반 유형의 선택적 값을 각 요소와 연관시키는 고유한 목록 인터페이스를 정의한다고 상상해 보십시오 P. 선언은 다음과 같을 수 있습니다.

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  ...
}
 

다음 매개변수화는 다음 PayloadList의 하위 유형입니다 List<String>.

  • PayloadList<String,String>
  • PayloadList<String,Integer>
  • PayloadList<String,Exception>
Introducing Generics
샘플 페이로드 계층 구조.

 


 

 

※ 쿠팡 파트너스 활동을 통해 일정액의 수수료를 제공 받을 수 있습니다 

반응형

'∮explotación≒ 개발' 카테고리의 다른 글

java 주석  (0) 2023.03.15
java Lambda Expressions  (0) 2023.03.08
java Interfaces  (0) 2023.03.04
Oracle 오라클 조인 [ ANSI JOIN, Oracle Join ]  (0) 2023.02.28
java Record  (0) 2023.02.25

댓글