> Java > java지도 시간 > 본문

단 5분 만에 이해하기 쉬운 Java 제네릭에 대한 자세한 설명

高洛峰
풀어 주다: 2016-12-19 15:22:12
원래의
1283명이 탐색했습니다.

변수를 사용하기 전에 먼저 정의해야 한다는 것을 알고 있습니다. 변수를 정의할 때는 해당 데이터 유형을 지정하고 어떤 데이터 유형이 어떤 값에 할당되는지를 지정해야 합니다.

이제 좌표를 나타내는 클래스를 정의하려는 경우 필요한 좌표의 데이터 유형은 정수, 소수 및 문자열이 될 수 있습니다. 예:

x = 10, y = 10

x = 12.88, y = 129.65

x = "도쿄 180도", y = "북위 210도"


다른 데이터 유형의 경우 추가로 메소드 오버로딩을 사용하는 방법과 Autoboxing 및 상향 변환을 사용하는 방법도 있습니다. 기본 데이터 유형은 자동으로 boxing되어 해당 패키징 클래스로 변환될 수 있다는 것을 알고 있습니다. Object는 모든 클래스의 조상 클래스이며 모든 클래스의 인스턴스는 Object 유형으로 업캐스트될 수 있습니다. 예:

int - -> 정수 --> 객체

double -->Double --> 객체

문자열 --> 객체


, 모든 유형의 데이터를 수신하기 위한 메소드를 정의해야 합니다. 다음 코드를 살펴보시기 바랍니다.

public class Demo {
    public static void main(String[] args){
        Point p = new Point();

        p.setX(10);  // int -> Integer -> Object
        p.setY(20);
        int x = (Integer)p.getX();  // 必须向下转型
        int y = (Integer)p.getY();
        System.out.println("This point is:" + x + ", " + y);
       
        p.setX(25.4);  // double -> Integer -> Object
        p.setY("东京180度");
        double m = (Double)p.getX();  // 必须向下转型
        double n = (Double)p.getY();  // 运行期间抛出异常
        System.out.println("This point is:" + m + ", " + n);
    }
}

class Point{
    Object x = 0;
    Object y = 0;

    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}
로그인 후 복사

위 코드에서는 좌표를 생성할 때는 문제가 없으나, 좌표를 꺼낼 때에는 앞서 언급한 대로 하향 변환을 해야 하기 때문에 위험성이 있습니다. 더욱이, 컴파일 중에는 찾기가 쉽지 않습니다. 예외는 런타임 중에만 발생하므로 다운캐스팅을 사용하지 마십시오. 위 코드를 실행하면 12행에서 java.lang.ClassCastException 예외가 발생합니다.

그렇다면 오버로딩(중복 코드)을 피하고 위험을 최소화할 수 있는 더 좋은 방법은 없을까요?

예. 모든 유형의 데이터를 수용할 수 있는 일반 클래스(Java 클래스)를 사용할 수 있습니다. 소위 "일반"은 "광범위한 데이터 유형", 모든 데이터 유형을 나타냅니다.

위 코드를 변경하고 일반 클래스를 사용합니다.

public class Demo {
    public static void main(String[] args){
        // 实例化泛型类
        Point<Integer, Integer> p1 = new Point<Integer, Integer>();
        p1.setX(10);
        p1.setY(20);
        int x = p1.getX();
        int y = p1.getY();
        System.out.println("This point is:" + x + ", " + y);
       
        Point<Double, String> p2 = new Point<Double, String>();
        p2.setX(25.4);
        p2.setY("东京180度");
        double m = p2.getX();
        String n = p2.getY();
        System.out.println("This point is:" + m + ", " + n);
    }
}

// 定义泛型类
class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}
로그인 후 복사

실행 결과:
이 지점은 10, 20
이 지점은 25.4, 도쿄 180도입니다.

일반 클래스의 정의와 비교하면 위의 코드에는 클래스 이름 뒤에 가 있습니다. T1, T2는 데이터 유형을 전송하는 데 사용되는 사용자 정의 식별자입니다. 데이터의 값을 유형 매개변수라고 부릅니다. 제네릭에서는 데이터 값이 매개변수를 통해 전달될 수 있을 뿐만 아니라 데이터 유형도 매개변수를 통해 전달될 수 있습니다. T1, T2는 데이터 유형에 대한 자리 표시자일 뿐이며 런타임 시 실제 데이터 유형으로 대체됩니다.

값 매개변수(우리가 일반적으로 매개변수라고 부르는 것)는 (int x, double y)와 같이 괄호로 묶이고, 유형 매개변수(일반 매개변수)는 꺾쇠괄호로 묶이며, 여러 매개변수는 쉼표로 구분됩니다. 예를 들어 또는

클래스 이름 뒤에 유형 매개변수를 주어야 합니다. 유형 매개변수가 제공되면 클래스 내에서 사용할 수 있습니다. 유형 매개변수는 합법적인 식별자여야 하며 단일 대문자를 사용하는 것이 일반적입니다. 일반적으로 K는 키를 나타내고, V는 값을 나타내고, E는 예외 또는 오류를 나타내고, T는 일반적인 의미에서 데이터 유형을 나타냅니다.

일반 클래스는 인스턴스화할 때, 즉 값을 유형 매개변수에 전달할 때 특정 유형을 나타내야 합니다.
className 변수 ;() ;
등호 오른쪽에 있는 데이터 유형을 생략할 수도 있지만 다음과 같은 경고가 발생합니다.
className Variable = new className();

일반 클래스를 사용할 때 데이터 유형을 지정하면 다른 유형의 값을 할당할 때 예외가 발생하기 때문에 하향 변환이 필요하지 않으며 잠재적인 위험도 없습니다. 이 기사의 시작 부분에서 상향 변환이 소개되었습니다.

참고:

제네릭은 Java 1.5의 새로운 기능이며 C++ 템플릿을 기반으로 하며 기본적으로 매개변수화된 유형의 응용 프로그램입니다.

유형 매개변수는 참조 유형을 나타내는 데에만 사용할 수 있으며 int, double, char 등과 같은 기본 유형을 나타내는 데는 사용할 수 없습니다. 그러나 기본 유형을 전달하면 해당 래퍼 클래스에 자동으로 포함되므로 오류가 발생하지 않습니다.

일반 메서드

일반 클래스 정의 외에도 일반 메서드를 정의할 수도 있습니다. 예를 들어 좌표 인쇄를 위한 일반 메서드를 정의합니다.

public class Demo {
public static void main(String[] args){
// 实例化泛型类
Point<Integer, Integer> p1 = new Point<Integer, Integer>();
p1.setX(10);
p1.setY(20);
p1.printPoint(p1.getX(), p1.getY());

Point<Double, String> p2 = new Point<Double, String>();
p2.setX(25.4);
p2.setY("东京180度");
p2.printPoint(p2.getX(), p2.getY());
}
}

// 定义泛型类
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}

// 定义泛型方法
public <T1, T2> void printPoint(T1 x, T2 y){
T1 m = x;
T2 n = y;
System.out.println("This point is:" + m + ", " + n);
}
}
로그인 후 복사

결과 실행. :
이 점은 10, 20
이 점은 25.4, 도쿄 180도입니다.

위 코드는 일반 매개변수와 유형을 모두 갖는 일반 메소드 printPoint()를 정의합니다. 매개변수는 수정자 뒤, 반환 값 유형 앞에 배치되어야 합니다. 유형 매개변수가 정의되면 매개변수 목록, 메소드 본문 및 반환 유형에서 사용할 수 있습니다.

제네릭 클래스를 사용하는 것과 달리 제네릭 메서드를 사용할 때는 매개변수 유형을 지정할 필요가 없습니다. 컴파일러는 전달된 매개변수를 기반으로 특정 유형을 자동으로 찾습니다. 다른 정의를 제외하면 일반 메소드는 일반 메소드와 동일하게 호출됩니다.

참고: 제네릭 메서드는 반드시 제네릭 클래스와 관련이 있는 것은 아닙니다. 제네릭 메서드에는 고유한 유형 매개변수가 있으며 일반 클래스에서도 제네릭 메서드를 정의할 수 있습니다. 일반 메소드 printPoint()의 유형 매개변수 T1 및 T2는 일반 클래스 Point의 T1 및 T2와 반드시 관련되지는 않습니다. 대신 다른 식별자를 사용할 수도 있습니다.

public static <V1, V2> void printPoint(V1 x, V2 y){
V1 m = x;
V2 n = y;
System.out.println("This point is:" + m + ", " + n);
}
로그인 후 복사

泛型接口

在Java中也可以定义泛型接口,这里不再赘述,仅仅给出示例代码:

public class Demo {
public static void main(String arsg[]) {
Info<String> obj = new InfoImp<String>("www.weixueyuan.net");
System.out.println("Length Of String: " + obj.getVar().length());
}
}

//定义泛型接口
interface Info<T> {
public T getVar();
}

//实现接口
class InfoImp<T> implements Info<T> {
private T var;

// 定义泛型构造方法
public InfoImp(T var) {
this.setVar(var);
}

public void setVar(T var) {
this.var = var;
}

public T getVar() {
return this.var;
}
}
로그인 후 복사

运行结果:
Length Of String: 18

类型擦除

如果在使用泛型时没有指明数据类型,那么就会擦除泛型类型,请看下面的代码:

public class Demo {
public static void main(String[] args){
Point p = new Point();  // 类型擦除
p.setX(10);
p.setY(20.8);
int x = (Integer)p.getX();  // 向下转型
double y = (Double)p.getY();
System.out.println("This point is:" + x + ", " + y);
}
}

class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
로그인 후 복사

运行结果:
This point is:10, 20.8

因为在使用泛型时没有指明数据类型,为了不出现错误,编译器会将所有数据向上转型为 Object,所以在取出坐标使用时要向下转型,这与本文一开始不使用泛型没什么两样。

限制泛型的可用类型

在上面的代码中,类型参数可以接受任意的数据类型,只要它是被定义过的。但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误。例如,编写一个泛型函数用于返回不同类型数组(Integer 数组、Double 数组、Character 数组等)中的最大值:

public <T> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}
로그인 후 복사

上面的代码会报错,doubleValue() 是 Number 类的方法,不是所有的类都有该方法,所以我们要限制类型参数 T,让它只能接受 Number 及其子类(Integer、Double、Character 等)。

通过 extends 关键字可以限制泛型的类型,改进上面的代码:

public <T extends Number> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}
로그인 후 복사

表示 T 只接受 Number 及其子类,传入其他类型的数据会报错。这里的限定使用关键字 extends,后面可以是类也可以是接口。但这里的 extends 已经不是继承的含义了,应该理解为 T 是继承自 Number 类的类型,或者 T 是实现了 XX 接口的类型。



更多Java泛型详解相关文章请关注PHP中文网!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿