JavaSE 고급 프로그래밍 요약 및 교육용 일반 프로그래밍 2
넷째, 제네릭의 실현 원리
4.1, 개요 원리
제네릭은 JDK1.5에서만 생성 되었기 때문에 이전에는 Java 제네릭이 모든 유형의 객체를 수용하기 위해 Object 객체를 사용했습니다. 그러나 JVM은 이전 버전과의 호환성을 유지해야합니다. 즉, JDK의 하위 버전이 상위 버전의 JDK에서 실행될 수 있어야합니다! 이러한 호환성 때문에 제네릭은 컴파일 후 JDK1.5 이전의 바이트 코드와 동일한 클래스 파일이되어야
하므로 제네릭의 원리는 여전히 Object ~~
4.2, 예제보기
예 1 : 공통 제네릭 클래스 및 공통 제네릭 함수
/**
* @author jiangzl
*/
public class MyGeneric7 {
public static <T> void getTemplateValue(MyNumber<T> myNumber){
System.out.println(myNumber.getValue());
}
public static void main(String[] args) {
MyNumber<Double> doubleMyNumber = new MyNumber<>(3.14);
getTemplateValue(doubleMyNumber);
MyNumber<Integer> integerMyNumber = new MyNumber<>(520);
getTemplateValue(integerMyNumber);
}
}
class MyNumber<T>{
private T value;
public MyNumber(T value){
this.value = value;
}
public T getValue(){
return value;
}
}
예제의 출력을 살펴보십시오.
분해 결과를보십시오.
첫째 : MyNumber.class의 결과
요점은 다음을 참조하십시오 : 필드 값
java / lang / Object입니다! ! ! 이것은 분해 후이 값이 Object of Object가됨을 보여줍니다. 그러면 생성 방법도 Object이고 invokespecial은 단순히 가상 함수가 아닌 특정 함수를 의미하는 것입니다 ~~
둘째 : MyGeneric7.class의 결과
제네릭 메소드의 매개 변수를 보면 디스 어셈블리 후 getValue ()와 Field 부분, 즉 매개 변수 부분이 Object 객체가됩니다 ~~
그렇다면 구체적으로 호출 될 때 제네릭 유형을 어떻게 인스턴스화합니까? ? ? 다음을보세요. ~
여기에 기본 메소드의 일부인 일반 인스턴스화가 있습니다. 여기에서 볼 수 있습니다.
# 8과 # 9가 주석 처리 된 후 정적 메서드를 호출하면 Object 객체에 Double.valueOf () 및 Integer.valueOf ()가 수행됩니다! ~ 그래서 인스턴스화가 완료되고 제네릭 메서드가 호출됩니다.
예 2 : 제한이있는 일반
import java.security.Security;
import java.util.Comparator;
import java.util.Scanner;
/**
* 泛型类型的限定
* @author jiangzl
*/
public class MyGeneric3 {
public static <T extends Comparable> T getMax(T... e){
if(null == e || e.length <= 0){
return null;
}
T ret = e[0];
for(T var : e){
if(ret.compareTo(var) < 0){
ret = var;
}
}
return ret;
}
public static void main(String[] args) {
int n = 5;
Student[] s = new Student[n];
Scanner sc = new Scanner(System.in);
for(int i = 0;i < n;++i){
String name = sc.next();
int score = sc.nextInt();
s[i] = new Student(name, score);
}
sc.close();
System.out.println(getMax(s));
String Str[] = {
"b", "a", "c", "d", "e"};
System.out.println(getMax(Str));
}
}
class Student implements Comparable<Student> {
private String name;
private int score;
public Student(String name, int score){
this.name = name;
this.score = score;
}
@Override
public String toString(){
return new String("name = " + name + ", score = " + score);
}
@Override
public int compareTo(Student s) {
if(score != s.score){
return score - s.score;
}
else{
return s.name.compareTo(name);
}
}
}
여기서 제네릭 메서드를 인스턴스화 할 수있는 클래스는 Comparable 인터페이스를 구현하는 클래스 여야합니다! ! !
분해의 결과를 살펴 보겠습니다 : (너무 많은 내용, 발췌 만)
제네릭 메서드 ( public static <T extends Comparable> T getMax(T... e)
)가 디스 어셈블 된 후 매개 변수가 Comparable 유형이되는 것을 알 수 있습니다. 여기에 다음과 같은 요약이 있습니다.
첫째 : 클래스에 클래스가 있고 extends의 인터페이스가 있다면 클래스를 앞에 작성해야하며이 클래스의 유형은 디스 어셈블리 후의 유형입니다!
둘째 : extends에 대한 클래스가 없지만 인터페이스가 있다면 상관 없습니다. 첫 번째 (확장에 가장 가까운) 클래스가 제네릭 유형으로 사용됩니다!
다섯, 공분산 및 반공 분산
5.1 공분산과 반공 분산이란 무엇이며 관계는 무엇입니까?
A <= B가 A가 B의 하위 클래스임을 의미한다면, f (x)는 특정 클래스 x가 특정 데이터 구조로 캡슐화된다는 것을 나타냅니다. 배열, 컬렉션 등 캡슐화 후 f (A)와 f (B)가 상속 관계를 가지면 관계가 있다고한다 ~
관계는 다음과 같습니다.
첫번째:
A <= B 및 f (A) <= f (B) 인 경우 공분산이라고합니다. 즉, A는 B의 하위 유형이고 캡슐화 후에도 A는 여전히 B의 하위 유형입니다. 일반적으로 배열이 있습니다. 예를 들어 A는 B의 하위 유형이고 배열 A []는 여전히 B [] ~의 하위 유형입니다.
둘째:
A <= B 및 f (A)> = f (B) 인 경우 반 변성 (contravariance)이라고하며, 이는 A가 B의 하위 유형이고 캡슐화 후 B는 A의 하위 유형임을 의미합니다. 일반적으로 와일드 카드로 제한되고 하한으로 제한됩니다 ~
제삼:
A <= B 인 경우 f (A) <> f (B)는 변경되지 않는다고합니다. 즉, A는 B의 하위 클래스입니다. 캡슐화 후 A와 B는 상속 관계가 없습니다. 일반적으로 제네릭, 무제한 제네릭이 있으며 List <A> 및 List <B>와 같은 제네릭 이후의 컬렉션은 ~와 관련이 없습니다.
이것은 명확하지 않을 수 있습니다. 그림을 사용하여 보여주세요.
5.2 함수 반환 값의 공분산과 리히터 대체 원리
Richter 대체 원칙에 따라 함수에 전달 된 실제 매개 변수는 형식 매개 변수에서 수신 할 수 있어야합니다. 따라서 유형 (실제 매개 변수) <= 유형 (공식 매개 변수)은 실제 매개 변수가 공식 매개 변수 자체의 유형 또는 하위 유형임을 의미합니다. 설정된 반환 값은 호출의 반환 값, 즉 type (call)> = type (return)으로 수신 될 수 있어야합니다.
예를보십시오.
/**
* @author jiangzl
*/
public class MyGeneric7 {
public static void main(String[] args) {
ClassFather classFather = new ClassSon();
System.out.println(classFather.method(3.14));
}
}
class ClassFather{
public Number method(Number value){
System.out.println(value.getClass());
System.out.println("father");
return value;
}
}
class ClassSon extends ClassFather{
@Override
public Double method(Number value){
System.out.println("son");
return Double.valueOf(value.toString());
}
}
JDK1.5부터 하위 클래스가 상위 메서드를 다시 쓸 수있게되면 공변 (재정의 된 상위 메서드의 반환 값 유형의 하위 클래스)이 반환 값으로 사용됩니다. 하지만이 코드의 출력을 살펴 보겠습니다.
결론에 도달했습니다.
JDK1.5 이후, 서브 클래스는 공분산을 허용하기 위해 슈퍼 클래스 메서드를 재정의합니다. 즉, 반환 값이 슈퍼 클래스 메서드보다 더 구체적인 유형이 될 수 있습니다! !
5.3, 배열 공분산
먼저 샘플 코드를 살펴보십시오.
모듈 1 : 클래스 디자인
class FirstClass{
public FirstClass(){
System.out.println("FirstClass is constructed!!");
}
public void sayHello(){
System.out.println("Helo World!");
}
}
class SecondClass extends FirstClass{
public SecondClass(){
System.out.println("SecondClass is constructed!!");
}
}
class LastClass extends SecondClass{
public LastClass(){
System.out.println("LastClass is constructed!!");
}
}
모듈 2 : 배열의 공분산
FirstClass[] firstClasses = new SecondClass[2];
firstClasses[0] = new SecondClass();
try {
firstClasses[1] = new FirstClass();
firstClasses[1].sayHello();
}
catch (Exception e){
System.out.println("Runtime error!");
}
System.out.println();
SecondClass[] secondClasses = new SecondClass[2];
firstClasses = secondClasses;
firstClasses[0] = new LastClass();
try {
firstClasses[1] = new FirstClass();
firstClasses[1].sayHello();
}
catch (Exception e){
System.out.println("Runtime error!");
}
여기서는 주 메서드에있는 배열의 공 변성 부분에 대한 코드 만 가로 채었습니다. 실행 결과는 다음
과 같습니다. 여기에서 배열이 실제로 공변 성임을 알 수 있지만 새로운 문제도 발생합니다.
상위 클래스 생성자가 실행될 때까지 하위 클래스가 상위 클래스의 객체를 인스턴스화하는 이유는 무엇입니까?
컴파일이 여기에 전달되고 부모 클래스 생성자가 끝날 때까지 예외가 발생하지 않습니다! ! ! JVM이 그렇게 뒤늦은 이유는 무엇입니까? 아는 사람은 메시지를 남기거나 개인 메시지를 보내주세요. 감사합니다! ~
5.4, 일반 공분산
예제를 살펴보십시오.
import java.util.ArrayList;
/**
* @author jiangzl
*/
public class MyGeneric6 {
public static void main(String[] args) {
int nA = 3, nB = 4, nC = 5;
ArrayList<A> asA = new ArrayList<>();
for(int i = 0;i < nA;++i){
asA.add(new A());
}
ArrayList<B> asB = new ArrayList<>();
for(int i = 0;i < nB;++i){
asB.add(new B());
}
ArrayList<C> asC = new ArrayList<>();
for(int i = 0;i < nC;++i){
asC.add(new C());
}
sayName(asA, asB, asC);
}
public static void sayName(ArrayList<? extends A>... arrayList){
int len = arrayList.length;
for(int i = 0;i < len;++i){
int size = arrayList[i].size();
for(int j = 0;j < size;++j){
arrayList[i].get(j).sayName();
}
}
}
}
class A{
public void sayName(){
System.out.println("my name is A");
}
}
class B extends A{
@Override
public void sayName(){
System.out.println("my name is B");
}
}
class C extends A{
@Override
public void sayName(){
System.out.println("my name is C");
}
}
출력을보십시오.
5. 뒤에 쓰십시오 :
사실 일반 공분산과 반공 분산은 이전 섹션에서 이미 언급되었으며 주목할 사항은 다음과 같습니다.
공분산, 즉, 확장 클래스는 설정이 아닌 가져 오기만 가능한 제네릭을 정의합니다!
즉, 슈퍼 클래스의 하한은 제네릭을 제한하므로 설정 만 할 수 있지만 얻을 수는 없습니다!
제네릭의 기본 사항을 이해하지 못하는 경우 저의 마지막 기사를 참조하십시오.
JavaSE Advanced Programming-Generic Foundation 요약 1
또한이 글은 두 가지 의문을 제기하는데, 저의 능력이 제한적이라 잘못 쓰거나 이해가 잘 안되는 부분이 있다면 모두에게 지적 해 주시기 바랍니다. 비판에 감사드립니다! 또한 두 가지 의심이 있습니다. 설명이 있으면 언제든지 저를 깨달으십시오! 다시 한 번 감사드립니다!