>php教程 >PHP开发 >제네릭 - 제네릭 소개

제네릭 - 제네릭 소개

高洛峰
高洛峰원래의
2016-12-19 16:02:195181검색

제네릭 소개

제네릭이 무엇인지 예를 들어 보겠습니다.
다음과 같이 두 가지 클래스가 있습니다. 두 클래스의 객체를 구성하고 해당 멤버 x를 출력해야 합니다.
public class StringFoo {
private String x
public String getX() {
return x
}

public void setX(String x) {
this.x = x;
}
}

public class DoubleFoo {
private Double x
public Double getX() {
return x; 🎜> public void setX(Double x) {
this.x = x;
}
}
Integer, Long, Date 및 기타 유형에 대한 연산을 구현하려면 쓰기도 필요합니다. 해당 수업은 정말 지루해요.
따라서 위의 두 클래스를 하나의 클래스로 리팩터링하려면 다음 사항을 고려하세요.
위 클래스에서는 멤버와 메서드의 로직은 동일하지만 유형이 다릅니다. Object는 모든 클래스의 상위 클래스이므로 Object를 멤버 유형으로 사용하여 보편적으로 사용할 수 있습니다.
public class ObjectFoo {
private Object x
public Object getX() {
return x
}

public void setX(Object x) {
this.x = x;
}
}

이 호출하는 코드는 다음과 같습니다.
public class ObjectFooDemo {
public static void main(String args[]) {
ObjectFoo strFoo = new ObjectFoo();
strFoo.setX("Hello Generics!");
ObjectFoo douFoo = new ObjectFoo()
douFoo.setX(new Double("33")) ;
ObjectFoo objFoo = new ObjectFoo();
objFoo.setX(new Object())

String str = (String)strFoo.getX(); Double)dou Foo .getX();
객체 obj = objFoo.getX();

System.out.println("strFoo.getX=" + str); ;
System.out.println("strFoo.getX=" + obj);
}
}
위는 제네릭 없이 작성한 내용입니다. 코드는 최상위 기본 클래스 Object를 사용합니다. 타입 선언 후 값을 전달하고, 꺼낼 때 강제 타입 변환을 수행합니다.
JDK는 이러한 문제를 우아하게 해결하기 위해 1.5부터 제네릭 개념을 도입했습니다. 일반 기술을 사용하여 작성된 코드는 다음과 같습니다.
public class GenericsFoo {
private T x
public T getX() {
return x; >
public void setX(T x) {
this.x = x;
}
}

호출 코드는 다음과 같습니다.
public class GenericsFooDemo {
public static void main(String args[]){
GenericsFoo strFoo=new GenericsFoo();
strFoo.setX("Hello Generics!")
GenericsFoo();
douFoo.setX(new Double("33");
Object( ))

String str = strFoo.getX(); 🎜> 더블 d = douFoo.getX()
Object obj = objFoo.getX();
System.out.println("strFoo.getX=" + obj);
}
}

몇 가지 명백한 변경 사항이 있습니다.
1. 객체가 생성될 때 GenericsFoo과 같이 유형이 명시적으로 제공되어야 합니다.
2. getX 메소드를 통해 객체를 검색하는 경우 유형 변환이 필요하지 않습니다.
3. 각 메서드를 호출할 때 매개 변수 형식이 메서드 생성 시 지정한 형식과 일치하지 않으면 컴파일러에서 오류를 보고합니다.
그렇다면 왜 제네릭이 필요한가요? 두 가지 장점이 있습니다.
1. 컴파일 타임에 저장된 데이터가 올바른지 확인할 수 있습니다. 우리 개발에서는 가능한 한 빨리 오류를 찾는 경향이 있습니다. 가급적이면 Generics는 이 조건을 충족합니다.
2. 강제 변환을 줄입니다. String str = (String)strList.get(0) 이러한 작업은 List에 저장된 객체가 String에 적합하지 않은 경우 상대적으로 위험한 작업입니다. 예외가 발생합니다.
JDK1.5에서는 java.util 패키지의 다양한 데이터 유형 도구 클래스가 프로그래밍에 널리 사용되며 숙달해야 하는 제네릭을 지원합니다.
제네릭의 가장 일반적인 응용 프로그램은 아래에 소개된 클래스, 인터페이스 및 메서드에 있습니다.
3.4.2 일반 사항은 인터페이스에 적용됩니다.
public 인터페이스 ValuePair {
public A getA()
public B getB()
public String toString( ) ;
}
여기서 A와 B는 모두 유형을 나타냅니다. cusp<>에서는 한 가지 유형 또는 여러 유형을 사용할 수 있습니다.
3.4.3 제네릭은 클래스에 적용됩니다.
public class ValuePairImpl {
public final A 먼저
public ValuePairImpl(A a, B; b) { 첫 번째 = a; 두 번째 = b; }
공개 A getA() { 첫 번째 반환 }
공개 B getB() { 두 번째 반환 }
공개 문자열 toString() > Return "(" + first + ", " + second + ")";
}
}
이 클래스가 일반 인터페이스를 구현하는 경우 해당 쓰기 방법은 다음과 같습니다.
public class ValuePairImpl< ,B> ValuePair 구현 {
… .getName() + “ = “ + v.toString()
           System.out.println(str); 물론 반환 값은 일반 유형일 수도 있습니다.
3.4.5 사용 가능한 제네릭 유형 제한
위에 소개된 세 가지 제네릭 애플리케이션은 인터페이스, 클래스, 메서드에 적용되며 일반적인 접근 방식이며 전달될 수 있는 유형에 대한 제한이 없습니다. 제네릭으로 제한됩니다. 그러나 일부 시나리오에서는 사용 가능한 유형을 제한하고 싶습니다. 예를 들어, 수신 유형이 특정 클래스에서 상속되어야 합니다(즉, 특정 클래스의 하위 클래스 등이어야 합니다). 경우에는 일반 제한 구문이 사용됩니다.
확장: 일반 유형을 이 유형을 포함하여 특정 클래스의 자손으로 제한합니다.
구문:
여기서 T는 제네릭 유형이고, 확장 키워드는 제네릭 유형을 parentClass의 하위 항목으로 제한합니다. parentClass는 인터페이스일 수도 있는 상위 클래스의 유형을 지정합니다.
Java 언어에서 클래스는 단독으로만 상속될 수 있고 인터페이스는 여러 번 상속될 수 있습니다. 특정 클래스에서 상속하도록 지정된 유형을 제한하고 여러 인터페이스를 구현하려는 경우 구문은 다음과 같습니다.
< ;T는 parentClass & parentInterface1 & parentInterface2>를 확장합니다.
클래스는 인터페이스 앞에 있어야 합니다.
예는 다음과 같습니다.
public class BaseClass {
       정수 값; 

       public BaseClass(int value) { 
              this.value = value; 
       } 
       
       public int getValue() { 
              반환 값; 
       } 

       public void setValue(int value) { 
              this.value = value; 
       } 
       


public class SubClass 확장 BaseClass{ 
       public SubClass(int value) { 
              super(value*2); 
       } 


공개 클래스 GenericBound
       
       공개 장기 합계(List tList) { 
              long iValue = 0; 
              for (BaseClass base : tList) { 
                    iValue += base.getValue(); 
              } 
              
              iValue 반환; 
       } 

       public static void main(String[] args) { 
              GenericBound obj = 새로운
GenericBound(); 
              
              List list = new LinkedList(); 
              list.add(new SubClass(5)); 
              list.add(new SubClass(6)); 
             
              System.out.println(obj.sum(list)); 
       } 

运行,输流结果为22. 
接着,我们再深入探讨一下。把상면적例子该写如下: 
공개 클래스 GenericBound
       
       공개 장기 합계(List tList) { 
              long iValue = 0; 
              for (BaseClass base : tList) { 
                    iValue += base.getValue(); 
              } 
              
              iValue 반환; 
       } 

       public static void main(String[] args) { 
              // 주의! Class>改为GenericBound ,无法通过编译 
              GenericBound obj = 새로운
GenericBound(); 
              
List list = new LinkedList();
list.add(new SubClass(5))
list.add(new SubClass(6))
시스템. (obj.sum(list));
}  
}
GenericBound obj = new GenericBound(); 명령문은 GenericBound 클래스 선언을 통과할 수 없습니다. 는 이러한 유형의 인스턴스를 생성할 때 T를 특정 유형으로 제한하며 이 유형은 BaseClass의 자손입니다. 그러나 SubClass3, SubClass4 등 BaseClass의 자손이 많기 때문에 각각에 대해 특정 하위 클래스 유형을 작성해야 한다면 Object를 사용하여 일반화하는 것이 더 좋습니다. 일반 클래스처럼 다양한 하위 클래스의 인스턴스를 도입하기 위해 부모 클래스의 유형을 사용할 수 있습니까? 대답은 '예'입니다. 제네릭은 이러한 상황에 대해 더 나은 솔루션을 제공하는데, 이는 아래에서 자세히 설명할 "와일드카드 제네릭"입니다.
3.4.6 와일드카드 제네릭
Java의 제네릭 유형은 java.lang.String 및 java.io.File과 같은 일반적인 Java 유형입니다. 예를 들어, 다음 두 변수의 유형은 서로 다릅니다.
Box boxObj = new Box()
Box boxStr = new Box();
String은 Object의 하위 클래스이지만 Box 사이에는 관계가 없습니다. Box은 Box의 하위 클래스 또는 하위 유형이 아닙니다. :
boxObj = boxStr; // 컴파일할 수 없습니다
따라서 제네릭을 사용할 때 부모 클래스 유형을 사용하여 일반 클래스와 마찬가지로 다양한 하위 클래스의 인스턴스를 도입하여 프로그램 개발을 단순화할 수 있기를 바랍니다. Java의 제네릭은 이 요구 사항을 충족하기 위해 ? 와일드카드 문자를 제공합니다.
코드 예시는 다음과 같습니다.
public class WildcardGeneric {
public void print(List lst) {
for (int i = 0; i < lst.size() ; i++) {
               System.out.println(lst.get(i));                                            ;       strList.add("2")
              wg.print(strList) ;                                       25)
intList.add(30)
                                             .그러나 이 경우 WildcardGeneric.print 메소드의 매개변수가 허용할 수 있는 유형은 프로그래머의 설계 의도에 비해 너무 광범위할 수 있습니다. 왜냐하면 우리는 List를 받아들이도록 print를 원할 수도 있지만 이 List의 요소는 Number의 자손이어야 하기 때문입니다. 따라서 와일드카드에 제한을 가해야 합니다. 이 경우 이 요구 사항을 충족하기 위해 제한된 와일드카드 형식을 사용할 수 있습니다. 인쇄 방법을 다시 수정해 보겠습니다.
public void print(List lst) {
           for (int i = 0; i < lst.size(); i++)                                  .out. 인쇄( lst.get(i)); List과 같은 요소 List 유형의 일반 유형 변수를 인쇄 메소드에 전달하는 것은 불법입니다.
제외? 상한 와일드카드를 확장하는 것 외에도 List와 같은 하한 와일드카드를 사용할 수도 있습니다.
마지막으로 와일드카드를 사용하여 세 가지 형태의 일반 유형을 요약해 보겠습니다.
GenericTypeGenericType>3.4.7 일반 유형 심층
처음에는 제네릭의 기본 사용법을 익힌 다음 심층적인 주제를 살펴보겠습니다.
먼저 코드를 살펴보겠습니다.
public class GenericsFoo {
private T x
public T getX() {
return x; 🎜 >
public void setX(T x) {
this.x = x
}

public static void main(String[] args) {
GenericsFoo gf = new GenericsFoo();
gf.setX("Hello")

GenericsFoo> gf2 = World");                             ! !
String str = gf2.getX(); // 오류! ! !
gf2.setX(gf2.getX()) // 오류! ! !
}
}
main 메소드의 마지막 세 줄은 불법이며 컴파일할 수 없습니다. 원래는 의 일반 유형입니다. 를 통해 참조된 후 setX()는 문자열을 전달할 때 오류를 보고하며 getX()의 반환 값 유형은 문자열이 아닙니다. 더 이상한 점은 gf2.setX(gf2.getX()); 값을 빼낸 다음 변경하지 않고 다시 설정하더라도 작동하지 않는다는 것입니다. 무슨 일이야?
이러한 문제를 완전히 이해하려면 JDK에서 제네릭의 내부 구현 원리를 이해해야 합니다. 먼저 두 가지 예를 살펴보겠습니다.
public class GenericClassTest {
public static void main(String[] args) {
Class c1 = new ArrayList().getClass()
클래스 c2 = new ArrayList().getClass();
System.out.println(c1 == c2)
System .out.println(c1 == c3)
} } 🎜>}
실행 후 출력 결과는 다음과 같습니다.
true
true
이 예는 일반 ArrayList, ArrayList 및 일반 항목이 없는 ArrayList가 실제로 동일한 클래스임을 보여줍니다. 제네릭을 사용하지 않는 것과 같습니다.
두 번째 예를 살펴보세요.
class Element {}
class Box > 공개 정적 공허 메인(문자열 [] ARGS) {
목록 & LT; 목록 = 새 ArrayList & LT 요소 (); 새 HashMap & LT; 문자열, 요소 & GT;
            Box();                    System.out.toString ( Class().getTypeParameters()) ;
System.out.println(Arrays.toString(
box.getClass().getTypeParameters()))
System.out.println(
               p.getClass() .getTypeParameters( )));
}  
}
을 실행한 후 출력 결과는 다음과 같습니다.
[E]
[K, V]
[T]
[ KEY, VALUE]
JDK 문서를 확인하세요. Class.getTypeParameters() 메서드는 TypeVariable 객체의 배열을 반환합니다. 배열의 각 TypeVariable 객체는 제네릭에 선언된 유형을 설명합니다. 이는 TypeVariable 객체에서 제네릭이 인스턴스화될 때 실제 유형을 찾을 수 있음을 의미하는 것 같습니다. 그러나 프로그램 실행 결과에서 Class.getTypeParameters()가 반환한 일련의 TypeVariable 개체는 제네릭 선언 중 매개변수화된 형식 자리 표시자만 나타내고 실제 인스턴스화 중 형식은 삭제된다는 것을 알 수 있습니다. 따라서 Java 제네릭에 대한 진실은 다음과 같습니다.
제네릭 코드에는 매개변수화된 유형에 대한 정보가 전혀 없습니다.
이러한 이유는 JDK가 제네릭의 내부 구현을 위해 삭제 방법을 사용하기 때문입니다.
1) ArrayList, ArrayList?> ArrayList로 지워짐
2) ArrayList는 모두 ArrayList로 지워집니다.
ArrayList 삭제 구현 메커니즘을 이해하고 이전 예제로 돌아가서 왜 컴파일할 수 없는지 분석해 보겠습니다.
public class GenericsFoo {
private T x
public T getX() {
return x;
}

public void setX(T x) {
this.x = x
}

public static void main(String[ ] args) {
GenericsFoo gf = new GenericsFoo();
gf.setX("Hello") ; GenericsFoo> >            gf2.setX("세계"); ! !
String str = gf2.getX(); // 오류! ! !
gf2.setX(gf2.getX()) // 오류! ! !
}
}
삭제 메커니즘으로 인해 GenericsFoo 유형이 손실된 후 해당 메서드 선언은 다음과 같습니다.
public Object getX( ; 값을 설정하는 유형 클래스의 set 메소드를 사용하면 매개변수 유형이 null이 됩니다. 어떤 유형도 널 유형으로 변환할 수 없으므로 모든 set 메소드를 호출할 수 없습니다.
이것은 흥미로운 현상을 형성합니다. 일반적인 유형의 에서는 얻을 수만 있고 설정할 수는 없습니다.
위에서는 Java 제네릭의 삭제 메커니즘을 설명하여 일부 유용한 유형 정보가 손실됩니다. 그러나 우리는 set 메소드가 정상적으로 호출될 수 있도록 컴파일러가 유형 정보를 재구성하도록 몇 가지 트릭을 사용할 수 있습니다. 다음 코드를 참조하세요.
public void setGeneric(GenericsFoo foo) {
setGenericHelper(foo)
}

private foo ) {
foo.setX(foo.getX());
}
setGenericHelper()는 일반 메서드입니다(반환 유형 앞의 꺾쇠 괄호 안에). 매개변수 및/또는 메소드의 반환 값 사이의 유형 제약 조건을 표현하는 데 사용됩니다. setGenericHelper () 이 선언 메소드를 사용하면 컴파일러가 (유형 인터페이스를 통해) GenericsFoo 제네릭의 유형 매개변수 이름을 지정할 수 있습니다. 그러나 유형은 상위 클래스, 상위 클래스를 가질 수 있으며 여러 인터페이스를 구현할 수도 있습니다. 그렇다면 컴파일러는 어떤 유형으로 변환할까요?
다음 코드를 사용하여 이를 확인합니다.
public class GenericClassTest3 {
public static String getType(T arg) {
return arg.getClass().getName(); > }

public static void main(String[] args) {
Integer i = new Integer(5)
System.out.println(GenericClassTest3.get Type(i)); 🎜>} }
프로그램이 실행된 후 출력 결과는 다음과 같습니다.
java.lang.integer
따라서 컴파일러는 T를 정수, 숫자, 직렬화 가능 또는 객체로 추론할 수 있지만 제약 조건을 충족하는 가장 구체적인 유형으로 Integer를 선택합니다.
또한 제네릭의 삭제 메커니즘으로 인해 다음과 같은 제네릭 유형에 new 연산자를 직접 사용할 수 없습니다.
public class GenericNew {
                 T obj = new T(); ! !
                  return obj 따라서 컴파일에 실패합니다.
그러나 어떤 경우에는 여전히 제네릭 유형의 동적 인스턴스화가 필요합니다. 단일 개체를 생성하고 배열을 생성하는 경우 코드 예제는 다음과 같습니다.
public class GenericNew Object obj = cls.newInstance();
                                                                                          } catch(예외 e) {
                                                                                                                           ; int len) {
try {
Object obj = java.lang.reflect.Array.newInstance(cls, len)
return (T[]) obj;
} catch(예외 e) {
                        return null;                                 
}  
}  
위에서 코드에서 create 메소드는 일반 유형 인스턴스의 동적 생성을 실현합니다. createArray 메소드는 일반 유형의 인스턴스 배열을 동적으로 생성합니다.




제네릭 관련 기사 - 제네릭 소개를 보려면 PHP 중국어 웹사이트를 주목하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.