玖叶教程网

前端编程开发入门

Java路径-39-Java的泛型(java泛型中通配符的形式不包括以下哪一种)

1 Java泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型 或者是 某个方法的返回值类型及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法

泛型(generics) 这个技术是在JDK 5中引入的新特性,它的本质其实是类型参数化, 利用泛型可以实现一套代码对多种数据类型的动态处理,保证了更好的代码重用性。并且泛型还提供了编译时对类型安全进行检测的机制,该机制允许我们在编译时就能够检测出非法的类型, 提高了代码的安全性。

这种特性,使得泛型成了一种 “代码模板” ,让我们利用一套代码就能实现对各种类型的套用。也就是说,我们只需要编写一次代码,就可以实现万能匹配,这也是”泛型“这个概念的含义,你可以将其理解为”广泛的类型“、”非特定的类型“。咱们上面的那个需求,利用泛型就能轻松实现,还不需要进行类型的强制转换,并且也保证了数据的类型安全。

泛型的引入背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

2 泛型类的定义

(1)类型参数用于类的定义中,则该类被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map等。

泛型类的基本语法如下:

class 类名称 <泛型标识> {
  private 泛型标识 /*(成员变量类型)*/ 变量名; 
  .....

  }
}

尖括号 <> 中的 泛型标识被称作是类型参数,用于指代任何数据类型。

泛型标识是任意设置的(如果你想可以设置为 Hello都行),Java 常见的泛型标识以及其代表含义如下:

T :代表一般的任何类。 E :代表 Element 元素的意思,或者 Exception 异常的意思。 K :代表 Key 的意思。 V :代表 Value 的意思,通常与 K 一起配合使用。 S :代表 Subtype 的意思,文章后面部分会讲解示意。

举例如下:

public class Generic<T> { 
    // key 这个成员变量的数据类型为 T, T 的类型由外部传入  
    private T key;

    // 泛型构造方法形参 key 的类型也为 T,T 的类型由外部传入
    public Generic(T key) { 
        this.key = key;
    }
    
    // 泛型方法 getKey 的返回值类型为 T,T 的类型由外部指定
    public T getKey(){ 
        return key;
    }
}

在泛型类中,类型参数定义的位置有三处,分别为:

  • 非静态的成员属性类型
  • 非静态方法的形参类型(包括非静态成员方法和构造器)
  • 非静态的成员方法的返回值类型

(2)泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数

代码如下:

public class Test<T> {    
    public static T one;   // 编译错误    
    public static T show(T one){ // 编译错误    
        return null;    
    }    
} 

泛型类中的类型参数的确定是在创建泛型类对象的时候(例如 ArrayList< Integer >)。

而静态变量和静态方法在类加载时已经初始化,直接使用类名调用;在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。

(3)静态泛型方法中可以使用自身的方法签名中新定义的类型参数(即泛型方法,后面会说到),而不能使用泛型类中定义的类型参数。

代码如下:

public class Test2<T> {   
    // 泛型类定义的类型参数 T 不能在静态方法中使用  
    public static <E> E show(E one){ // 这是正确的,因为 E 是在静态方法签名中新定义的类型参数    
        return null;    
    }    
}  

(4)泛型类不只接受一个类型参数,它还可以接受多个类型参数。

代码如下:

public class MultiType <E,T> {
    E value1;
    T value2;
    
    public E getValue1(){
        return value1;
    }
    
    public T getValue2(){
        return value2;
    }
}

3 泛型方式

3.1 泛型类

泛型类的写法是在类上指明参数,并在属性或者方法中使用。

先来看一下在没有泛型之前的操作方式,采用Object的方式,使得调用点获取到这个对象,如果需要获取到对象自身实现的某个方法,就需要进行强制类型装换,所以可能会出现类型装换异常。

public static class Animal {

  private Object animal;

  public void set(Object animal){
    this.animal = animal;
  }
  public Object get(){
    return animal;
  }
}

再泛型引入之后采用如下方式,调用点在通过get方法获取到对象时,直接是调用点设置的参数类型,所以无需进行强制类型转换。

public static class Animal<T> {

  private T animal;

  public void set(T animal){
    this.animal = animal;
  }
  public T get(){
    return animal;
  }
}

3.2 泛型接口

泛型也可以应用于接口,使用上与泛型类类似。

泛型接口对调用点来说有两种方式,一种是实现类指定了参数类型,则调用点无需再参数化类型;另一种是实现类依旧采用泛型的方式继承,则调用点就需要参数化类型。

public interface People<T> {
    public T display();
}

class Girl implements People<String>{
    public void display(String str){
        System.out.println("display: "+ str);
    }
}

class Boy<T> implements People<T>{
    public void display(T t){
        System.out.println("display:"+t);
    }
}

3.3 泛型方法

泛型也可以应用于方法上,并且这个方法对应的类可以是泛型类也可以不是泛型类。定义泛型方法只需要将泛型列表置于返回值之前。

class Test{
	public <T>  void display(T t){
		System.out.println(t);
	}
}

显式的类型说明: 对泛型方法的调用,可以显式的指明类型,语法是在点操作符与方法名之间插入尖括号,然后将类型置于尖括号中,这种方式广泛用于静态方法,使用tk.mybatis过都知道Example中创建的静态泛型方法。

// ========= WeekendSqls ============
public class WeekendSqls<T> implements SqlsCriteria {
    public static <T> WeekendSqls<T> custom() {
        return new WeekendSqls();
    }
}

// 调用点
public static void main(String[] args) {
	WeekendSqls<People> weekendSql = WeekendSqls.<People>custom();
}

4 泛型中类型擦除

Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”,将所有的泛型表示都替换为具体的类型。

原理:根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型

  • 无限制类型擦除


如果类型参数是无限制通配符或没有上下界限定则替换为Object。

  • 有限制类型擦除




如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型,< T extends String><? extends String>的类型参数被替换为Number,<? super String>被替换为Object。

类型擦除引起的问题

  • 泛型类型变量不能是基本数据类型

如果是ArrayList< int >,当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。

  • 无法创建泛型类型的实例

由于类型擦除,无法直接使用泛型类型参数来创建实例。例如,无法直接创建 new T()。

  • 无法重载泛型方法

Java中不允许重载泛型方法,因为擦除会导致在字节码中多个泛型方法的签名相同。

5 泛型中边界

5.1上界

泛型上界采用<? extends T>表示当前泛型参数只能由T类型的子类构成。

<? extends T>指明了这个泛型类参数化类型的参数的只能是T的子类,且会影响到泛型类中入参为参数化类型的方法。

class Food{}
class Fruit extend Food{}
class Apple extends Fruit {}
class Orange extends Fruit{}

class Plate<T>{
  private T item;
  public Plate(T t){item = t;}
  public void set(T t){item = t;}
  public T get(){return item;}
}

public static void main(String[] args) {
  Plate<? extends Fruit> plate = new Plate<>(new Apple());
  // 两个set方法均报错,由于限定了参数化类型的上界,而对于Fruit来说有很多子类
  // 编译器在这时不知道应该使用哪个类来创建引用。
  plate.set(new Apple());
  plate.set(new Fruit());
  
  Fruit f = plate.get();
  // 报错,只能通过上界类获取引用
  Apple a = plate.get();
}

5.2下界

泛型下界采用<? super T>表示当前泛型的参数只能有T类型的父类构成。

<? super T>指明了这个泛型类参数化类型的参数的只能是T的父类,且会影响到泛型类中返回值为参数化类型的方法。还是上面的例子

public static void main(String[] args) {
  Plate<? super Fruit> pf = new Plate<>(new Fruit());
  // 由于限定参数类型为Fruit的超类,所以添加的元素只要是Fruit以及Fruit的
  // 子类都会成功
  pf.set(new Apple());
  pf.set(new Fruit());
	
  // 报错,由于限定参数为Fruit的超类,不能用Fruit来引用,当然了就算是Food也不行
  Fruit ff = pf.get();
}

5.3无界(?通配符)

?称为无界通配符,表示的是一种未知类型,所以一般如果采用了?定义一个泛型,对其调用的是与具体类型无关的方法。最常用的应该是Class<?>,因为就算是使用泛型Class<T>也并没有依赖于T

如果看过jdk容器相关的源码,都应该知道在容器中有很多的方法都采用这种写法,即无需关心具体的类型。

public boolean containsAll(Collection<?> c) { 
  return c.isEmpty(); 
}

?表示的未知类型,相比于Object应该来说是一个更大的概念,所以List<?> != List<Object>,并且List<Object>不能指向List<?>的引用;而List<?>可以指向List<Object>的引用。

但是有一点需要注意若List<?>指向List<Object>之后,由于类型是未知的,所以List中使用泛型的方法都不能使用,也就是add(E e)不能使用,编译器报错;而remove(Object o)参数没有使用泛型,则可以使用。

List<?> list = new ArrayList<>();
List<Object> objects = list;

List<Object> objects = new ArrayList<>();
List<?> list = objects;

6 示例

public class Box<T> {
   
  private T t;
 
  public void add(T t) {
    this.t = t;
  }
 
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<>();
    Box<String> stringBox = new Box<>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言