변수를 사용하기 전에 먼저 정의해야 한다는 것을 알고 있습니다. 변수를 정의할 때는 해당 데이터 유형을 지정하고 어떤 데이터 유형이 어떤 값에 할당되는지를 지정해야 합니다.
이제 좌표를 나타내는 클래스를 정의하려는 경우 필요한 좌표의 데이터 유형은 정수, 소수 및 문자열이 될 수 있습니다. 예:
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도입니다.
일반 클래스의 정의와 비교하면 위의 코드에는 클래스 이름 뒤에
값 매개변수(우리가 일반적으로 매개변수라고 부르는 것)는 (int x, double y)와 같이 괄호로 묶이고, 유형 매개변수(일반 매개변수)는 꺾쇠괄호로 묶이며, 여러 매개변수는 쉼표로 구분됩니다. 예를 들어
클래스 이름 뒤에 유형 매개변수를 주어야 합니다. 유형 매개변수가 제공되면 클래스 내에서 사용할 수 있습니다. 유형 매개변수는 합법적인 식별자여야 하며 단일 대문자를 사용하는 것이 일반적입니다. 일반적으로 K는 키를 나타내고, V는 값을 나타내고, E는 예외 또는 오류를 나타내고, T는 일반적인 의미에서 데이터 유형을 나타냅니다.
일반 클래스는 인스턴스화할 때, 즉 값을 유형 매개변수에 전달할 때 특정 유형을 나타내야 합니다.
className 변수
등호 오른쪽에 있는 데이터 유형을 생략할 수도 있지만 다음과 같은 경고가 발생합니다.
className Variable
일반 클래스를 사용할 때 데이터 유형을 지정하면 다른 유형의 값을 할당할 때 예외가 발생하기 때문에 하향 변환이 필요하지 않으며 잠재적인 위험도 없습니다. 이 기사의 시작 부분에서 상향 변환이 소개되었습니다.
참고:
제네릭은 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; }
更多Java泛型详解相关文章请关注PHP中文网!