11. 자바 기초 - 제네릭
기본 개념
제네릭의 핵심은 Parameterized Type을 적용한 것입니다. , 연산되는 데이터 유형을 매개 변수로 지정하고 사용 시 특정 유형을 지정합니다.
이 매개변수 유형은 각각 일반 클래스, 일반 인터페이스 및 일반 메소드라고 하는 클래스, 인터페이스 및 메소드를 생성하는 데 사용할 수 있습니다.
1. 개발
JDK 1.5 이전에는 Object만이 모든 유형의 상위 클래스이고 유형 캐스팅입니다. 특성은 유형 일반화를 달성할 수 있습니다.
따라서 컴파일 중에 컴파일러는 이 개체의 캐스팅이 성공했는지 여부를 확인할 수 없으며 이로 인해 ClassCastException(캐스트 예외)이 발생할 수 있습니다.
제네릭의 역할을 이해하기 위해 예를 살펴보겠습니다.
제네릭을 사용하지 않는 경우(1.5 이전)
ArrayList arrayList = new ArrayList(); arrayList.add(100); arrayList.add("abc");//因为不知道取出来的值的类型,类型转换的时候容易出错 String str = (String) arrayList.get(0);
generic 사용(1.5 이후)
ArrayList<String> arrayList = new ArrayList<String>(); arrayList.add("abc");//因为限定了类型,所以不能添加整形,编译器会提示出错arrayList.add(100);
2. 용어
// 以 ArrayList<E>,ArrayList<Integer> 为例:ArrayList<E>:泛型类型 E:类型变量(或者类型参数) ArrayList<Integer> :参数化的类型 Integer:类型参数的实例(或实际类型参数) ArrayList :原始类型
3. 탐색
일반 클래스
class Demo<T> { private T value; Demo(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } }public class Test { public static void main(String[] args) { Demo<String> demo = new Demo("abc"); demo.setValue("cba"); System.out.println(demo.getValue()); // cba } }
일반 인터페이스
interface Demo<K, V> { void print(K k, V v); } class DemoImpl implements Demo<String, Integer> { @Override public void print(String k, Integer v) { System.out.println(k + "-" + v); } }public class Test { public static void main(String[] args) { Demo<String, Integer> demo = new DemoImpl(); demo.print("abc", 100); } }
일반 메서드
public class Test { public static void main(String[] args) { int num = get("abc", 100); System.out.println(num); } // 关键 --> 多了 <K, V> ,可以理解为声明此方法为泛型方法 public static <K, V> V get(K k, V v) { if (k != null) { return v; } return null; } }
유형 Quality
형 한정은 제네릭 클래스, 제네릭 인터페이스, 제네릭 메서드에서 사용할 수 있지만, 다음 사항에 주의하시기 바랍니다.
자격이 클래스 또는 인터페이스인 경우 키워드 확장
& 기호를 사용하여 여러 자격을 부여할 수 있습니다
-
자격이 둘 다 있는 경우 인터페이스와 클래스가 있는 경우 클래스는 하나만 있어야 하며 먼저 배치되어야 합니다. 예를 들면 다음과 같습니다.
public static <T extends Comparable&Serializable> T get(T t1,T t2)
유형 제한의 역할을 분석해 보겠습니다.
1 유형 매개변수에 제한을 설정하지 마세요.
다음 코드를 관찰하면 유형 매개변수가 유형 한정되지 않은 경우 컴파일 오류가 발생합니다. 그 이유는 다음과 같습니다.- 컴파일하기 전에 컴파일러는 제네릭 유형(T)이 어떤 유형인지 확인할 수 없기 때문에
- 을 기본값으로 설정합니다. to T는 기본 유형(객체)입니다.
- 따라서 Object 메서드만 호출할 수 있고 CompareTo 메서드는 호출할 수 없습니다.
public static <T> T get(T t1,T t2) { //编译错误 if(t1.compareTo(t2)>=0); return t1; }
2. 유형 매개변수에 경계 설정
유형 매개변수 T에 경계를 설정할 때 컴파일 오류가 발생하지 않습니다. 다시. 현재 컴파일러는 기본적으로 T의 원래 유형을 Comparable로 설정하기 때문입니다.public static <T extends Comparable> T get(T t1,T t2) { if(t1.compareTo(t2)>=0); return t1; }
유형 삭제
- Java의 제네릭은 기본적으로 컴파일러 수준에서 구현됩니다.
- 생성된 Java 바이트코드에는 제네릭의 유형 정보가 포함되지 않습니다.
- 제네릭을 사용할 때 추가된 유형 매개변수는 컴파일 중에 컴파일러에 의해 제거됩니다.
다음 예를 살펴보세요.
public class Test { public static void main(String[] args) { ArrayList<String> arrayList1 =new ArrayList<String>(); ArrayList<Integer> arrayList2 = new ArrayList<Integer>(); // true System.out.println(arrayList1.getClass() == arrayList2.getClass()); } }
- 하나는 문자열만 저장할 수 있는 ArrayList 일반 유형이고, 다른 하나는 정수만 저장할 수 있는 ArrayList 일반 유형입니다.
- 클래스 객체를 비교한 결과, 결과는 사실입니다.
- 일반 유형인 String 및 Integer는 컴파일 과정에서 지워지고 원래 유형(예: Object)만 남게 된다는 설명입니다.
다른 예를 살펴보겠습니다.
public class Test { public static void main(String[] args) throws Exception{ ArrayList<String> arrayList =new ArrayList<String>(); arrayList.add("abc"); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, 100); for (int i=0;i<arrayList.size();i++) { System.out.println(arrayList.get(i)); } } }
- add 메소드를 직접 호출하면 정수 데이터만 저장할 수 있습니다.
- 리플렉션을 사용하여 add 메소드를 호출하지만 문자열을 저장할 수 있습니다.
- 설명 Integer 제네릭 인스턴스는 컴파일 후에 지워지고 원래 유형만 남습니다.
1. Raw type
- Raw type(raw type)은 일반적인 정보를 지우고, 마지막으로
바이트코드 에 있는 유형 변수의 실제 유형입니다.
- 모든 일반 유형 매개변수에는 해당 기본 변수가 있습니다.
- 유형 변수가 삭제되면 정규화된 유형으로 대체됩니다(한정되지 않은 변수는 객체임).
// 此时 T 是一个无限定类型,所以原始类型就是 Objectclass Pair<T> { } // 类型变量有限定,原始类型就用第一个边界的类型变量来替换,即Comparableclass Pair<T extends Comparable& Serializable> { } // 此时原始类型为 Serializable,编译器在必要的时要向 Comparable 插入强制类型转换 // 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界限定列表的末尾class Pair<T extends Serializable&Comparable>
2. 유형 매개변수의 유형
다음 예에서 유형 매개변수는 T를 참조하고, 유형은 T는 소위 [유형 매개변수]의 유형입니다. 코드를 관찰하면 다음과 같은 결론을 내릴 수 있습니다.- [유형 매개변수 T]의 유형을 지정하지 마십시오. 원본 유형은 동일한 상위 클래스를 사용합니다.
- 의 최소 수준 [유형 매개변수 T]의 유형을 지정할 때 원본 유형은 지정된 유형 또는 < 유형의 하위 클래스만 될 수 있습니다. 🎜>
public class Test { // 定义泛型方法 public static <T> T add(T x, T y) { return y; } public static void main(String[] args) { // 1.不指定泛型 // 两个参数都是 Integer,所以 T 为 Integer 类型 int i = Test.add(1, 2); // 两个参数分别是 Integer,Float,取同一父类的最小级,T 为 Number 类型 Number f = Test.add(1, 1.2); // T 为 Object Object o = Test.add(1, "asd"); // 2.指定泛型 // 指定了Integer,所以只能为 Integer 类型或者其子类 int a = Test.<Integer> add(1, 2); //编译错误,指定了 Integer,不能为Float int b=Test.<Integer>add(1, 2.2); // 指定为Number,所以可以为 Integer,Float Number c = Test.<Number> add(1, 2.2); } }
로그인 후 복사
3. 유형 검사
제네릭의 유형 검사는 참조를 위한 것이지 참조된 객체 자체를 위한 것이 아닙니다.
아래 예에서 list는 참조 객체이므로 유형 검사는 이에 반대됩니다.
// 没有进行类型检查,等价于 ArrayList list = new ArrayLis()ArrayList list = new ArrayList<String>(); list.add(100); list.add("hello");// 进行编译检查,等价于 ArrayList<String> list = new ArrayList<String>();ArrayList<String> list = new ArrayList(); list.add("hello"); list.add(100); // 编译错误
4.类型擦除与多态的冲突
来看下面的例子,这里定义了一个泛型类 Parent,一个实现它的子类 Son,并在子类中重写了父类的方法。
class Parent<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } class Son extends Parent<String>{ @Override public void setValue(String value) { super.setValue(value); } @Override public String getValue(){ return super.getValue();} }
在上面提到过泛型的类型参数在编译时会被类型擦除,因此编译后的 Parent 类如下:
class Parent { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
此时对比 Parent 与 Son 的 getValue/setValue 方法,发现方法的参数类型已经改变,从 Object -> String,这也意味着不是重写(overrride) 而是重载(overload)。
然而调用 Son 的 setValue 方法, 发现添加 Object 对象时编译错误。说明也不是重载。
public class Test { public static void main(String[] args) { Son son = new Son(); son.setValue("hello"); // 关键 -->编译错误 son.setValue(new Object()); } }
那么问题来了,通过上面的分析?Son 中定义的方法到底是重写还是重载?答案是:重写。这里 JVM 采用了桥方法(Brige)来解决类型擦除和多态引起的冲突。
我们对 Son 进行反编译(”Javap -c 类名.class”),得到如下内容:
Compiled from "Test.java"class Son extends Parent<java.lang.String> { Son(); Code: 0: aload_0 1: invokespecial #8 // Method Parent."<init>":()V 4: return public void setValue(java.lang.String); Code: 0: aload_0 1: aload_1 2: invokespecial #16 // Method Parent.setValue:(Ljava/lang/Object;)V 5: return public java.lang.String getValue(); Code: 0: aload_0 1: invokespecial #23 // Method Parent.getValue:()Ljava/lang/Object; 4: checkcast #26 // class java/lang/String 7: areturn public java.lang.Object getValue(); Code: 0: aload_0 1: invokevirtual #28 // Method getValue:()Ljava/lang/String; 4: areturn public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #26 // class java/lang/String 5: invokevirtual #30 // Method setValue:(Ljava/lang/String;)V 8: return }
发现这里共有 4 个 setValue/getValue 方法,除了 Son 表面上重写的 String 类型,编译器又自己生成了 Object 类型的方法,也称为桥方法。结果就是,编译器通过桥方法真正实现了重写,只是在访问时又去调用表面的定义的方法。
注意事项
不能用基本类型实例化类型参数,可以用对应的包装类来实例化类型参数
// 编译错误ArrayList<int> list = new ArrayList<int>();// 正确写法ArrayList<Integer> list = new ArrayList<Integer>();
参数化类型的数组不合法
Demo<T >{ }public class Test { public static void main(String[] args) { // 编译错误 --> 类型擦除导致数组变成 Object [],因此没有意义 Demo<String>[ ] demo =new Demo[10]; } }
不能实例化类型变量
// 编译错误,需要类型参数需要确定类型Demo<T> demo = new Demo<T>
泛型类的静态上下文中不能使用类型变量
public class Demo<T> { public static T name; public static T getName() { ... } }
不能抛出也不能捕获泛型类的对象
//异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。会导致这里捕获的类型一致try{ }catch(Problem<Integer> e1){ //do Something... }catch(Problem<Number> e2){ // do Something ...}
以上就是11.Java 基础 - 泛型的内容,更多相关内容请关注PHP中文网(www.php.cn)!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

Java의 TimeStamp to Date 안내. 여기서는 소개와 예제와 함께 Java에서 타임스탬프를 날짜로 변환하는 방법에 대해서도 설명합니다.

캡슐은 3 차원 기하학적 그림이며, 양쪽 끝에 실린더와 반구로 구성됩니다. 캡슐의 부피는 실린더의 부피와 양쪽 끝에 반구의 부피를 첨가하여 계산할 수 있습니다. 이 튜토리얼은 다른 방법을 사용하여 Java에서 주어진 캡슐의 부피를 계산하는 방법에 대해 논의합니다. 캡슐 볼륨 공식 캡슐 볼륨에 대한 공식은 다음과 같습니다. 캡슐 부피 = 원통형 볼륨 2 반구 볼륨 안에, R : 반구의 반경. H : 실린더의 높이 (반구 제외). 예 1 입력하다 반경 = 5 단위 높이 = 10 단위 산출 볼륨 = 1570.8 입방 단위 설명하다 공식을 사용하여 볼륨 계산 : 부피 = π × r2 × h (4

Java는 초보자와 숙련된 개발자 모두가 배울 수 있는 인기 있는 프로그래밍 언어입니다. 이 튜토리얼은 기본 개념부터 시작하여 고급 주제를 통해 진행됩니다. Java Development Kit를 설치한 후 간단한 "Hello, World!" 프로그램을 작성하여 프로그래밍을 연습할 수 있습니다. 코드를 이해한 후 명령 프롬프트를 사용하여 프로그램을 컴파일하고 실행하면 "Hello, World!"가 콘솔에 출력됩니다. Java를 배우면 프로그래밍 여정이 시작되고, 숙달이 깊어짐에 따라 더 복잡한 애플리케이션을 만들 수 있습니다.
