Home  >  Article  >  Java  >  Detailed explanation of Java generics, easy to understand in just 5 minutes

Detailed explanation of Java generics, easy to understand in just 5 minutes

高洛峰
高洛峰Original
2016-12-19 15:22:121238browse

We know that variables must be defined before using them. When defining a variable, you must specify its data type and what data type is assigned to what value.

If we now want to define a class to represent coordinates, the data type of the required coordinates can be integers, decimals and strings, for example:

x = 10, y = 10

x = 12.88, y = 129.65

x = "Tokyo 180 degrees", y = "North latitude 210 degrees"


For different data types, in addition to method overloading, you can also use automatic boxing and upward transformation. We know that basic data types can be automatically boxed and converted into corresponding packaging classes; Object is the ancestor class of all classes, and instances of any class can be upcast to the Object type, for example:

int --> Integer --> Object

double -->Double --> Object

String --> Object


In this way, you only need to define one method to receive all types of data. Please look at the code below:

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;
    }
}

In the above code, there will be no problem when generating coordinates, but when taking out the coordinates, you need to transform downward. As we mentioned, there are risks in downward transformation, and it is not easy to find during compilation. , an exception will be thrown only during runtime, so try to avoid using downcasting. Running the above code, a java.lang.ClassCastException exception will be thrown on line 12.

So, is there a better way that can avoid using overloading (duplicate code) and minimize the risk?

Yes, you can use generic classes (Java Class), which can accept any type of data. The so-called "generic" refers to a "broad data type", any data type.

Change the above code and use the generic class:

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;
    }
}

Running result:
This point is: 10, 20
This point is: 25.4, Tokyo 180 degrees

Compared with the definition of ordinary classes, the above code is There are 5d766edf0ee4a0fde742e7cd6e836235 after the class name. T1 and T2 are custom identifiers and parameters. They are used to pass the type of data instead of the value of the data. We call them type parameters. In generics, not only the value of data can be passed through parameters, but the type of data can also be passed through parameters. T1, T2 are just placeholders for data types and will be replaced by the real data types at runtime.

Value parameters (what we usually call parameters) are surrounded by parentheses, such as (int x, double y), type parameters (generic parameters) are surrounded by angle brackets, and multiple parameters are separated by commas, such as c3342ed510f93f1bc30a91fc05305978.

Type parameters need to be given after the class name. Once the type parameters are given, they can be used within the class. The type parameter must be a legal identifier, and it is customary to use a single uppercase letter. Typically, K represents a key, V represents a value, E represents an exception or error, and T represents a data type in a general sense.

A generic class must indicate the specific type when instantiating it, that is, passing a value to the type parameter. The format is:
className variable3e7ff64bb1c9404d6a7b37b44d70da47 = new className3e7ff64bb1c9404d6a7b37b44d70da47();
can also be omitted, etc. The data type on the right side of the number, but a warning will be generated, namely:
className variable3e7ff64bb1c9404d6a7b37b44d70da47 = new className();

Because the data type is specified when using a generic class, assigning values ​​to other types will throw Exceptions do not require downward transformation and have no potential risks. They are more practical than the automatic boxing and upward transformation introduced at the beginning of this article.

Note:

Generics are a new feature of Java 1.5. It is based on C++ templates and is essentially the application of parameterized types.

Type parameters can only be used to represent reference types, not basic types, such as int, double, char, etc. However, passing basic types will not cause an error because they are automatically boxed into corresponding wrapper classes.

Generic methods

In addition to defining generic classes, you can also define generic methods. For example, define a generic method for printing coordinates:

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);
}
}

Running results:
This point is: 10, 20
This point is: 25.4, Tokyo 180 degrees

The above code defines a generic method printPoint(), which has both ordinary parameters and type parameters. The type parameters need to be placed after the modifier and before the return value type. Once type parameters are defined, they can be used in parameter lists, method bodies, and return types.

Unlike using generic classes, you do not need to specify the parameter type when using a generic method. The compiler will automatically find the specific type based on the passed parameters. Except for the different definitions, generic methods are called just like ordinary methods.

Note: Generic methods are not necessarily related to generic classes. Generic methods have their own type parameters, and generic methods can also be defined in ordinary classes. The type parameters T1 and T2 in the generic method printPoint() are not necessarily related to T1 and T2 in the generic class Point. Other identifiers can also be used instead:

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;
}

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



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

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Previous article:Generic methods in JavaNext article:Generic methods in Java