> Java> Java베이스> 본문

Java 제네릭에 대한 자세한 소개

풀어 주다: 2019-11-27 14:31:50
앞으로
1618명이 탐색했습니다.

Java 제네릭에 대한 자세한 소개

1. 제네릭 개념 도입(제네릭이 필요한 이유)?(권장:java 비디오 튜토리얼)

먼저 다음 짧은 코드를 살펴보겠습니다.

public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } }
로그인 후 복사

define List 유형에는 String 유형의 두 값이 먼저 추가된 다음 Integer 유형의 값이 추가됩니다. 목록의 기본 유형이 개체이기 때문에 이는 완전히 허용됩니다.

다음 루프에서는 이전에 목록에 정수 유형 값을 추가하는 것을 잊었거나 다른 인코딩 이유로 인해 //1과 유사한 오류가 쉽게 발생할 수 있습니다. 컴파일 단계는 정상인데 런타임 중에 "java.lang.ClassCastException" 예외가 발생하기 때문입니다. 따라서 코딩 중에 이러한 오류를 감지하기가 어렵습니다.

위의 코딩 과정에서 우리는 두 가지 주요 문제가 있음을 발견했습니다.

1 객체를 컬렉션에 넣을 때 컬렉션은 이 객체를 기억하지 못합니다. 이 객체를 컬렉션에서 다시 가져오면 객체의 컴파일된 유형이 Object 유형으로 변경되지만 런타임 유형은 여전히 자체 유형입니다.

2. 따라서 //1에서 컬렉션 요소를 꺼낼 때 해당 유형을 특정 대상 유형으로 강제 변환해야 하며 "java.lang.ClassCastException"이 발생하기 쉽습니다. 예외가 발생합니다.

그래서 컬렉션이 컬렉션의 요소 유형을 기억하고 컴파일 중에 문제가 없는 한 "java.lang.ClassCastException"이라는 목표를 달성할 수 있는 방법이 있습니까? 런타임 중에는 예외가 발생하지 않습니까? 대답은 제네릭을 사용하는 것입니다.

2. 제네릭이란 무엇입니까?

Generics, 즉 "매개변수화된 유형"입니다.매개변수에 관해 가장 익숙한 것은 메소드를 정의할 때 형식적인 매개변수가 있고, 이 메소드를 호출할 때 실제 매개변수가 전달된다는 것입니다.

그럼 매개변수화된 유형을 어떻게 이해하나요? 이름에서 알 수 있듯이, 메소드의 가변 매개변수와 유사하게 원래의 특정 유형에서 유형을 매개변수화합니다. 이때 유형도 매개변수(유형 매개변수라고 할 수 있음)의 형태로 정의됩니다. 그런 다음 유형(유형 인수)을 사용/호출할 때 특정 유형이 전달됩니다.

조금 복잡해 보이는데 먼저 위의 예시를 작성하는 일반적인 방법을 살펴보겠습니다.

public class GenericTest { public static void main(String[] args) { /* List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); */ List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); //list.add(100); // 1 提示编译错误 for (int i = 0; i < list.size(); i++) { String name = list.get(i); // 2 System.out.println("name:" + name); } } }
로그인 후 복사

일반 쓰기를 사용한 후 //1에 Integer 유형의 객체를 추가하려고 하면 컴파일 오류가 발생합니다. List 을 통해 다음 항목만 포함할 수 있는 목록 컬렉션으로 직접 제한됩니다. String 유형이므로 //2에서 강제 유형 변환을 수행할 필요가 없습니다. 왜냐하면 이때 컬렉션은 요소의 유형 정보를 기억할 수 있고 컴파일러는 이미 String 유형임을 확인할 수 있기 때문입니다.

위의 일반 정의와 결합하면 List 에서 String이 실제 매개변수 유형이라는 것을 알 수 있습니다. 즉, 해당 List 인터페이스에는 유형 매개변수가 포함되어야 합니다. 그리고 get() 메소드의 반환 결과도 바로 이 형식 매개변수의 유형입니다(즉, 해당 수신 유형 실제 매개변수). List 인터페이스의 구체적인 정의를 살펴보겠습니다.

public interface List extends Collection { int size(); boolean isEmpty(); boolean contains(Object o); Iterator iterator(); Object[] toArray();  T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection c); boolean addAll(Collection c); boolean addAll(int index, Collection c); boolean removeAll(Collection c); boolean retainAll(Collection c); void clear(); boolean equals(Object o); int hashCode(); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator listIterator(); ListIterator listIterator(int index); List subList(int fromIndex, int toIndex); }
로그인 후 복사

List 인터페이스의 일반 정의를 사용한 후 의 E는 유형 매개변수를 나타냅니다. 특정 유형의 실제 매개변수를 수신하며, 이 인터페이스 정의에서 E가 나타날 때마다 외부에서 수신된 동일한 유형의 실제 매개변수를 나타냅니다.

당연히 ArrayList는 List 인터페이스의 구현 클래스이며 정의 형식은 다음과 같습니다.

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } //...省略掉其他具体的定义过程 }
로그인 후 복사

이를 통해 Integer가 추가된 이유를 소스 코드 관점에서 이해할 수 있습니다. //1 유형 객체가 잘못 컴파일되었으며 //2에서 get()으로 얻은 유형은 바로 String 유형입니다.

3. 맞춤형 제네릭 인터페이스, 제네릭 클래스 및 제네릭 메서드

위 내용을 통해 모두가 제네릭의 특정 작업 프로세스를 이해했습니다. 유형. 또한 인터페이스, 클래스 및 메서드도 제네릭을 사용하여 정의하고 그에 따라 사용할 수 있다는 것을 알고 있습니다. 예, 특정 용도에서는 일반 인터페이스, 일반 클래스 및 일반 메서드로 나눌 수 있습니다.

사용자 정의 일반 인터페이스, 일반 클래스 및 일반 메소드는 위 Java 소스 코드의 List 및 ArrayList와 유사합니다. 다음과 같이 제네릭 클래스와 메소드의 가장 간단한 정의를 살펴보겠습니다.

public class GenericTest { public static void main(String[] args) { Box name = new Box("corn"); System.out.println("name:" + name.getData()); } } class Box { private T data; public Box() { } public Box(T data) { this.data = data; } public T getData() { return data; } }
로그인 후 복사

제네릭 인터페이스, 제네릭 클래스 및 제네릭 메소드의 정의 과정에서 우리는 일반적으로 T, E, K 매개변수 형식을 봅니다. of, V 등은 외부 사용에서 전달된 형식 인수를 받기 때문에 일반 매개변수를 나타내는 데 자주 사용됩니다. 그러면 전달된 다양한 유형 인수에 대해 해당 객체 인스턴스의 유형이 동일하게 생성됩니까?

public class GenericTest { public static void main(String[] args) { Box name = new Box("corn"); Box age = new Box(712); System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box System.out.println(name.getClass() == age.getClass()); // true } }
로그인 후 복사

이를 통해 제네릭 클래스를 사용할 때 다른 제네릭 인수가 전달되더라도 실제로는 다른 유형이 생성되지 않는다는 사실을 발견했습니다. 메모리에는 제네릭 클래스가 하나만 있습니다. , 이는 원래의 가장 기본적인 유형입니다(이 예에서는 Box). 물론 논리적으로 우리는 이를 여러 다른 일반 유형으로 이해할 수 있습니다.

이유는 Java에서 제네릭 개념의 목적은 코드 컴파일 단계에서만 작동한다는 것입니다. 컴파일 과정에서 제네릭 결과가 올바르게 검증되면 제네릭이 Type이 됩니다. -관련 정보가 지워집니다. 즉, 성공적으로 컴파일된 클래스 파일에는 일반 정보가 포함되어 있지 않습니다. 일반 정보는 런타임 단계에 들어가지 않습니다.

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

四.类型通配符

接着上面的结论,我们知道,Box 和Box 实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box 和Box 是否可以看成具有父子关系的泛型类型呢?

为了弄清这个问题,我们继续看下下面这个例子:

public class GenericTest { public static void main(String[] args) { Box name = new Box(99); Box age = new Box(712); getData(name); //The method getData(Box) in the type GenericTest is //not applicable for the arguments (Box) getData(age); // 1 } public static void getData(Box data){ System.out.println("data :" + data.getData()); } }
로그인 후 복사

我们发现,在代码//1处出现了错误提示信息:The method getData(Box ) in the t ype GenericTest is not applicable for the arguments (Box )。显然,通过提示信息,我们知道Box 在逻辑上不能视为Box 的父类。那么,原因何在呢?

public class GenericTest { public static void main(String[] args) { Box a = new Box(712); Box b = a; // 1 Box f = new Box(3.14f); b.setData(f); // 2 } public static void getData(Box data) { System.out.println("data :" + data.getData()); } } class Box { private T data; public Box() { } public Box(T data) { setData(data); } public T getData() { return data; } public void setData(T data) { this.data = data; } }
로그인 후 복사

这个例子中,显然//1和//2处肯定会出现错误提示的。在此我们可以使用反证法来进行说明。

假设Box 在逻辑上可以视为Box 的父类,那么//1和//2处将不会有错误提示了,那么问题就出来了,通过getData()方法取出数据时到底是什么类型呢?Integer? Float? 还是Number?且由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因此,在逻辑上Box 不能视为Box 的父类。

好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。

这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box 和Box 的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box 在逻辑上是Box 、Box ...等所有Box <具体类型实参> 的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

public class GenericTest { public static void main(String[] args) { Box name = new Box("corn"); Box age = new Box(712); Box number = new Box(314); getData(name); getData(age); getData(number); } public static void getData(Box data) { System.out.println("data :" + data.getData()); } }
로그인 후 복사

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

public class GenericTest { public static void main(String[] args) { Box name = new Box("corn"); Box age = new Box(712); Box number = new Box(314); getData(name); getData(age); getData(number); //getUpperNumberData(name); // 1 getUpperNumberData(age); // 2 getUpperNumberData(number); // 3 } public static void getData(Box data) { System.out.println("data :" + data.getData()); } public static void getUpperNumberData(Box data){ System.out.println("data :" + data.getData()); } }
로그인 후 복사

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box 形式定义,相对应的,类型通配符下限为Box 形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。

更多java知识请关注java基础教程栏目。

위 내용은 Java 제네릭에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:cnblogs.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!