Java 범용 요약(一): 기본 사용법 및 유형 지우기

8446 단어 java범용
소개
Java는 1.5에 범용 메커니즘을 도입했다. 범용 본질은 매개 변수화 유형이다. 즉, 변수의 유형은 하나의 매개 변수이고 사용할 때 구체적인 유형으로 지정한다.범형은 클래스, 인터페이스, 방법에 사용할 수 있으며, 범형을 사용하면 코드를 더욱 간단하고 안전하게 할 수 있다.그러나 자바의 범주형은 유형 지우기를 사용하기 때문에 위범형일 뿐이다.이 글은 범용적인 사용과 존재하는 문제점을 정리하고 주로 을 참고한다.
이 시리즈의 다른 두 문장:
  • Java 범용 요약(二): 범용과 수조
  • Java 범용 요약(3): 와일드카드 사용
  • 기본용법
    범형류
    만약 하나의 변수를 포장하는 데 사용할 클래스 홀더가 있다면, 이 변수의 유형은 임의일 수 있습니다. 홀더를 어떻게 작성합니까?범용이 없기 전에 이렇게 할 수 있다.
    
    public class Holder1 {
     private Object a;
     public Holder1(Object a) {
     this.a = a;
     }
     public void set(Object a) {
     this.a = a;
     }
     public Object get(){
     return a;
     }
     public static void main(String[] args) {
     Holder1 holder1 = new Holder1("not Generic");
     String s = (String) holder1.get();
     holder1.set(1);
     Integer x = (Integer) holder1.get();
     }
    }
    
    Holder1에는 Object로 참조하는 변수가 있습니다.모든 유형이 Object로 전환될 수 있기 때문에 이 Holder는 모든 유형을 받아들일 수 있습니다.꺼낼 때 Holder는 Object 객체가 저장된 것만 알고 해당 유형으로 강제로 변환합니다.main 방법에서holder1은 문자열, 즉 String 대상을 저장한 다음에 Integer 대상을 저장합니다. (매개 변수 1은 자동으로 포장됩니다.)Holder에서 변수를 꺼낼 때 강제 변환하는 것은 매우 번거롭습니다. 여기에 서로 다른 유형을 기억해야 합니다. 잘못 돌리면 실행 시 이상이 발생합니다.
    다음은 Holder의 범용 버전입니다.
    
    public class Holder2<T> {
     private T a;
     public Holder2(T a) {
     this.a = a;
     }
     public T get() {
     return a;
     }
     public void set(T a) {
     this.a = a;
     }
     public static void main(String[] args) {
     Holder2<String> holder2 = new Holder2<>("Generic");
     String s = holder2.get();
     holder2.set("test");
     holder2.set(1);//    1   String  
     }
    }
    
    Holder2에서 변수 a는 매개 변수화 유형 T이고 T는 하나의 표지일 뿐이며 다른 알파벳으로도 가능하다.Holder2 객체를 만들 때 중괄호에 매개변수 T 유형이 전달되면 이 객체에서 T가 나타나는 모든 부분은 String으로 대체되는 것과 같습니다.현재 get에서 꺼낸 것은 Object가 아니라 String 대상이기 때문에 형식 변환이 필요하지 않습니다.또한 set을 호출할 때 String 형식만 불러올 수 있습니다. 그렇지 않으면 컴파일이 통과할 수 없습니다.이것은holder2의 유형이 안전하고 부주의로 잘못된 유형이 전송되지 않도록 보장합니다.
    위의 예를 통해 범용 코드가 더욱 간편하고 안전하다는 것을 알 수 있다.범주를 도입한 후에 Java 라이브러리의 일부 종류, 예를 들어 자주 사용하는 용기 종류도 범주를 지원하는 것으로 바뀌었고 우리가 사용할 때 매개 변수 유형이 전달됩니다. 예를 들어 ArrayListlist=ArrayList<>().
    범용 방법
    범주형은 종류를 겨냥할 수 있을 뿐만 아니라 단독으로 어떤 방법을 범주형으로 할 수 있다. 예를 들어 다음과 같다.
    
    public class GenericMethod {
     public <K,V> void f(K k,V v) {
     System.out.println(k.getClass().getSimpleName());
     System.out.println(v.getClass().getSimpleName());
     }
     public static void main(String[] args) {
     GenericMethod gm = new GenericMethod();
     gm.f(new Integer(0),new String("generic"));
     }
    }
    
     :
     Integer
     String
    
    Generic Method 클래스 자체는 범용이 아닙니다. 대상을 만들 때 범용 파라미터를 전달할 필요가 없습니다. 그러나 그 방법 f는 범용 방법입니다.유형을 되돌리기 전에 매개 변수 표지입니다. 여기에 두 개의 범용 매개 변수가 있기 때문에 범용 매개 변수는 여러 개가 있을 수 있습니다.
    범용 방법을 호출할 때 범용 매개 변수를 현저하게 전달하지 않아도 되고, 위의 호출은 없다.컴파일러가 매개 변수 형식을 사용하여 추정하고, 전송된 실참의 형식 (여기는integer와 String) 에 따라 K와 V의 형식을 추정하기 때문이다.
    유형 삭제
    유형 지우기
    Java의 범주형은 유형 삭제 메커니즘을 사용했는데 이로 인해 많은 논쟁을 일으켰다. Java의 범주형 기능이 제한된 것은'위범형'이라고 할 수 밖에 없다. 유형 삭제란 무엇일까? 간단하게 말하면 유형 매개 변수는 컴파일러에만 존재하고 실행할 때 Jva의 가상 머신(JVM)은 범주형의 존재를 모른다. 먼저 예를 들어 보자.
    
    public class ErasedTypeEquivalence {
     public static void main(String[] args) {
     Class c1 = new ArrayList<String>().getClass();
     Class c2 = new ArrayList<Integer>().getClass();
     System.out.println(c1 == c2);
     }
    }
    위의 코드는 두 개의 다른 Array List: Array List 와 Array List 입니다.우리가 보기에 그것들의 매개 변수화 유형은 다르다. 하나는 정합성을 저장하고, 하나는 문자열을 저장한다.그러나 그들의 클래스 대상을 비교함으로써 위의 코드 출력은true이다.이는 JVM이 동일한 클래스로 간주된다는 것을 의미합니다.그러나 C++, C# 이 일반적인 언어를 지원하는 언어들은 서로 다른 종류이다.
    범용 매개 변수는 첫 번째 경계에 지워집니다. 예를 들어 위의 홀더 2 종류, 매개 변수 형식은 하나의 단독 T입니다. 그러면 Object에 지워집니다. 이는 모든 T가 나타나는 곳을 Object로 바꾸는 것과 같습니다.따라서 JVM에서 보기에 저장된 변수 a는 Object 유형입니다.자동적으로 추출되는 것은 우리가 전송한 매개 변수 형식입니다. 이것은 컴파일러가 컴파일하여 생성한 바이트 파일에 형식 변환 코드를 삽입했기 때문에 우리가 수동으로 변환할 필요가 없습니다.만약 매개 변수 유형에 경계가 있다면 첫 번째 경계를 지우고 다음 절에서 다시 이야기하자.
    문제 해결
    지우기는 다음과 같은 몇 가지 문제가 발생합니다.
    
    class HasF {
     public void f() {
     System.out.println("HasF.f()");
     }
    }
    public class Manipulator<T> {
     private T obj;
     public Manipulator(T obj) {
     this.obj = obj;
     }
     public void manipulate() {
     obj.f(); //    f()
     }
     public static void main(String[] args) {
     HasF hasF = new HasF();
     Manipulator<HasF> manipulator = new Manipulator<>(hasF);
     manipulator.manipulate();
     }
    }
    
    위의 Manipulator는 하나의 범용 클래스로 내부는 범용화된 변수obj를 사용하고manipulate 방법에서obj의 방법 f()를 호출했지만 이 코드는 컴파일할 수 없습니다.형식 지우기 때문에, 컴파일러는obj에 f () 방법이 있는지 확인하지 않습니다.이 문제를 해결하는 방법은 T에게 경계를 주는 것이다.
    
    class Manipulator2<T extends HasF> {
     private T obj;
     public Manipulator2(T x) { obj = x; }
     public void manipulate() { obj.f(); }
    }
    현재 T의 유형은 입니다. 이것은 T가 HasF 또는 HasF의 내보내기 유형이어야 한다는 것을 나타냅니다.이렇게 해야 f() 방법을 호출해야 안전합니다.HasF는 T의 경계이므로 유형 지우기를 통해 T가 나타나는 모든
    장소는 모두 HasF로 교체한다.이렇게 컴파일러는obj가 방법 f()가 있다는 것을 알 수 있다.
    그러나 이렇게 하면 범형이 가져온 장점을 상쇄하고 위의 종류는 완전히 이렇게 고칠 수 있다.
    
    class Manipulator3 {
     private HasF obj;
     public Manipulator3(HasF x) { obj = x; }
     public void manipulate() { obj.f(); }
    }
    그래서 범형은 비교적 복잡한 종류에서만 작용을 나타낸다.하지만 같은 형식의 물건은 전혀 의미가 없는 것이 아니다.클래스 중 T 유형을 되돌리는 방법이 있다면, 범주형은 정확한 유형을 되돌릴 수 있기 때문이다.예:
    
    class ReturnGenericType<T extends HasF> {
     private T obj;
     public ReturnGenericType(T x) { obj = x; }
     public T get() { return obj; }
    }
    이 get () 방법은 HasF가 아닌 일반 매개 변수의 정확한 유형을 되돌려줍니다.
    유형 삭제 보상
    유형 삭제로 인해 범주형은 일부 기능을 상실하여 운행 기간에 정확한 유형을 알아야 하는 코드가 작동하지 않습니다.예:
    
    public class Erased<T> {
     private final int SIZE = 100;
     public static void f(Object arg) {
     if(arg instanceof T) {} // Error
     T var = new T(); // Error
     T[] array = new T[SIZE]; // Error
     T[] array = (T)new Object[SIZE]; // Unchecked warning
     }
    }
    new T () 를 통해 대상을 만들면 안 됩니다. 하나는 형식이 지워지기 때문이고, 다른 하나는 컴파일러가 T에 기본 구조기가 있는지 모르기 때문입니다.하나의 해결 방법은 공장 대상을 전달하고 이를 통해 새로운 실례를 만드는 것이다.
    
    interface FactoryI<T> {
     T create();
    }
    class Foo2<T> {
     private T x;
     public <F extends FactoryI<T>> Foo2(F factory) {
     x = factory.create();
     }
     // ...
    }
    class IntegerFactory implements FactoryI<Integer> {
     public Integer create() {
     return new Integer(0);
     }
    }
    class Widget {
     public static class Factory implements FactoryI<Widget> {
     public Widget create() {
     return new Widget();
     }
     }
    }
    public class FactoryConstraint {
     public static void main(String[] args) {
     new Foo2<Integer>(new IntegerFactory());
     new Foo2<Widget>(new Widget.Factory());
     }
    }
    또 다른 해결 방법은 템플릿 디자인 모델을 활용하는 것이다.
    
    abstract class GenericWithCreate<T> {
     final T element;
     GenericWithCreate() { element = create(); }
     abstract T create();
    }
    class X {}
    class Creator extends GenericWithCreate<X> {
     X create() { return new X(); }
     void f() {
     System.out.println(element.getClass().getSimpleName());
     }
    }
    public class CreatorGeneric {
     public static void main(String[] args) {
     Creator c = new Creator();
     c.f();
     }
    }
    구체적인 형식의 생성은 하위 클래스가 부모 클래스를 계승할 때create 방법에서 실제 형식을 만들고 되돌려줍니다.
    총결산
    본고는 자바 범용 사용과 유형 지우기와 관련된 문제를 소개한다.일반적인 상황에서 범형의 사용은 비교적 간단하지만, 어떤 경우, 특히 범형의 종류나 방법을 스스로 작성할 때 유형의 지우기 문제를 주의해야 한다.다음은 소개할게요수조와 범형의 관계 그리고와일드카드 사용 .
    이상은 본문의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 일정한 도움을 줄 수 있는 동시에 저희를 많이 지지해 주시기 바랍니다!

    좋은 웹페이지 즐겨찾기