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); */ Listlist = 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
위의 일반 정의와 결합하면 List
public interface Listextends 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 extends E> c); boolean addAll(int index, Collection extends E> 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 인터페이스의 일반 정의를 사용한 후
당연히 ArrayList는 List 인터페이스의 구현 클래스이며 정의 형식은 다음과 같습니다.
public class ArrayListextends 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) { Boxname = 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) { Boxname = 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
为了弄清这个问题,我们继续看下下面这个例子:
public class GenericTest { public static void main(String[] args) { Boxname = 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
public class GenericTest { public static void main(String[] args) { Boxa = 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
好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。
这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box
在逻辑上是Box
public class GenericTest { public static void main(String[] args) { Boxname = 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) { Boxname = 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 extends Number> data){ System.out.println("data :" + data.getData()); } }
此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。
类型通配符上限通过形如Box 形式定义,相对应的,类型通配符下限为Box 形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。
更多java知识请关注java基础教程栏目。
위 내용은 Java 제네릭에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!