理解Java泛型

泛型引入

Java是一种强类型的语言,定义一个变量时需要指明其类型。Object是所有类的基类,在Java 1.5之前,为了让类具有通用性,通常使用Object来实现参数的任意化,如将String、Double等存储为Object类型,这个过程叫做自动装箱或向上转型。但是问题在于取数据时,必须做强制类型转换,将Object向下转型为String或Double等类型。向下转型存在很大的风险,需要事先知道具体的下转型类型是什么,一旦忘记或写错,在运行必然抛出异常,但在编译期间却不易发现,因此存在极大的安全隐患,应该尽量避免使用向下转型。

下面是使用Object的一个示例:

public class ObjectFoo {
    private Object foo;

    public ObjectFoo(Object f) {
        this.foo = f;
    }

    public Object getFoo() {
        return foo;
    }

    public void setFoo(Object foo) {
        this.foo = foo;
    }
}

具体调用如下:

ObjectFoo strFoo1 = new ObjectFoo("hello Object");
ObjectFoo intFoo1 = new ObjectFoo(100);
// 此处需要强制类型转换为String
String strRst = (String) strFoo1.getFoo();
// 此处需要强制类型转换为int
int intRst = (int) intFoo1.getFoo();

System.out.println("strFoo1.getFoo:" + strRst);
System.out.println("intFoo1.getFoo:" + intRst);

正因为这个过程麻烦且容易出错,在Java 1.5中推出了泛型这个新特性,其本质是参数化类型。泛型在使用时会指明具体的类型,因此不需要向下转型,编译的时候也会检查类型安全,泛型还可以提高代码重用性,概括来说就是简单易用并且安全,泛型可用在类、接口、方法中,分别叫做泛型类、泛型接口和泛型方法,下面会一一介绍。

泛型类

依然以上述场景为例,先看一下利用泛型类是如何实现的:

public class GenericsFoo<T> {
    private T foo;

    public GenericsFoo(T f) {
        this.foo = f;
    }

    public T getFoo() {
        return foo;
    }

    public void setFoo(T foo) {
        this.foo = foo;
    }
}

泛型类的调用:

// 使用泛型时必须指定具体类型,如String,不再需要类型转换
GenericsFoo strFoo2 = new GenericsFoo<>("Hello Generics");
// 使用泛型时必须指定具体类型,如Integer,不再需要类型转换
GenericsFoo intFoo2 = new GenericsFoo<>(100);

System.out.println("strFoo2.getFoo:" + strFoo2.getFoo());
System.out.println("intFoo2.getFoo:" + intFoo2.getFoo());

从上面代码可以看到,跟普通的类相比,多出了符号,T是自定义的标识符,也作为参数,用来传递数据类型,称之为类型参数。T可以理解为数据类型的占位符,在运行时会被替换为真正的数据类型,上面示例中T会被替换为String和Integer。

泛型规则

  1. 泛型类型参数只能是类类型,包括自定义类。
  2. 类型参数不一定写为T,实际上可以任意定义。但习惯上使用单个大写字母,并且通常有如下含义:T-Type(表示一般数据类型)、K-Key(表示键)、V-Value(表示值)、N-Number(表示数值类型)、E-Element(集合中的元素,在集合中使用)、?(表示不确定的类型,用作通配符)等等。
  3. 泛型的类型参数可以有多个,用逗号隔开,如
  4. 泛型的参数类型可以使用extends语句,例如,叫做限制泛型。如果有多个类和接口,写法为,但只能存在一个类,因为Java只能继承一个类,多个接口需写在类的后面。
  5. 泛型的参数类型可以是通配符类型,例如Class classType = Class.forName("java.lang.String")

泛型方法

使用泛型方法时不必指明参数类型,编译器会根据传递的参数自动查找具体的类型,这一点与泛型类不同。泛型方法与其所在类是不是泛型类没有关系,普通类也可拥有泛型方法,要定义一个泛型方法,只需要将类型参数放在修饰符之后、返回值之前即可。一旦定义了类型参数,就可以在参数列表、方法体及返回值中使用了。

下面代码是在上述泛型类的基础上增加了一个泛型方法:

public class GenericsFoo<T> {
    private T foo;

    public GenericsFoo(T f) {
        this.foo = f;
    }

    public T getFoo() {
        return foo;
    }

    public void setFoo(T foo) {
        this.foo = foo;
    }

    // 泛型方法
    public  void getValue(V f) {
        System.out.println("The value is:" + f);
    }
}

泛型方法除了定义不同,调用方法就跟普通方法一样:

GenericsFoo strFoo2 = new GenericsFoo<>("Hello");

strFoo2.getValue(strFoo2.getFoo());
strFoo2.getValue("Generics Method");
strFoo2.getValue(255);

输出结果:

The value is:Hello
The value is:Generics Method
The value is:255

泛型接口

泛型接口跟泛型类定义很像,直接放上示例代码,相信大家都能看懂。

定义泛型接口:

public interface Info<T> {
    public T getVar();
}

泛型接口的实现类:

public class InfoImp<T> implements Info<T> {
    private T var;

    public InfoImp(T var) {
        this.var = var;
    }

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

    @Override
    public T getVar() {
        return var;
    }
}

调用示例:

public class GenericsInterface {
    public static void main(String[] args) {
        Info strObj = new InfoImp<>("Hello Generics Interface");
        System.out.println("The string value is:" + strObj.getVar());

        Info intObj = new InfoImp<>(1024);
        System.out.println("The integer value is:" + intObj.getVar());
    }
}

输出结果:

The string value is:Hello Generics Interface
The integer value is:1024

限制泛型

上面泛型类的示例中public class GenericsFoo 并没有限制类型参数T的类型,实际上这里相当于Object,可以是任何类型。有时候我们需要对此处的类型做特定限制,这就是限制泛型了,听起来有点抽象,举个例子很容易就明白了。

限制泛型的基本语法为,这里的extends需要广义理解,接口也是用它,这就表示T的类型只能是该SuperClass的子类或者是任何实现了这个接口的类的类型,这样就对泛型做了限制。

假设定义一个父类Geometry:

public class Geometry {
    public String name;
    public double area;
}

再定义3个类,其中Circle、Square继承自Geometry,Keyboard没有继承Geometry。

public class Circle extends Geometry {

}
public class Square extends Geometry {

}
public class Keyboard {

}

限制泛型类的定义如下:

public class GenericsRestrict<T extends Geometry> {

}

如上所示,泛型类型仅能是Geomerty或其子类,其他类型则会报错。

调用测试,如果是限制之外的类型就会报错:

public class TestClass {
    public static void main(String[] args) {
        GenericsRestrict square = new GenericsRestrict<>();
        GenericsRestrict circle = new GenericsRestrict<>();
        // 因为Keyboard没有继承Geometry,下面语句报错
        GenericsRestrict keyboard = new GenericsRestrict<>();
    }
}

当然extends后面可以是接口或类与多个接口,规范及原理相同,这里不再赘述。

通配符泛型

为了解决类型被限制死了而不能动态根据需要来确定的缺点,引入了“通配符泛型”, 如,?代表未知类型,这个类型可以是Collection接口的所有实现类。使用通配符有以下两点需要注意:

  1. 如果只指定了,而没有extends,则可以是任意类型。
  2. 使用通配符?不但可以限制类型的上限,还可以限制下限。限制下限使用 super 关键字,例如 表示只能接受 Number 及其父类,可接受的最低类型为Number。上限限制则是使用上面提到的extends关键字。

注意

如果在使用泛型时没有指定具体的数据类型,就会擦除泛型类型,并向上转型为 Object,在获取数据时必须向下强制类型转换,这与不使用泛型是一样的。

你可能感兴趣的:(Java)