Java 基础-泛型

135 阅读4分钟

概述

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型可以理解成接收数据类型的一种数据类型!

Java泛型介绍

类结构是面向对象中最基本的元素,如果我们的类需要有很好的扩展性,那么我们可以将其设置成泛型的。
泛类型在编译时定义,运行时才能确定具体类,Java 基础-面向对象

class DataHolder<T>{
    T item;
    
    public void setData(T t) {
    	this.item=t;
    }
    
    public T getData() {
    	return this.item;
    }
}

泛型语法

  1. 在泛型 <T,V,...> 可以有任意
  2. 在泛型 <T,V,...>,中 T,V,.. 等等只能是引用类型,不能是基本数据类型
  3. 在给泛型指定具体类型后,可以传入该类型或者该子类型
  4. 简写 ArrayList<Dog> integers = new ArrayList<>() 等价ArrayList<Dog> integers = new ArrayList<Dog>()
  5. ArrayList integers = new ArrayList() 等价ArrayList<Object> integers = new ArrayList<Object>()
interface Car<T>{}
interface Car<T,V>{}
class Car<T>{}
class Car<T,V>{}

泛型自定义类

  • 注意
  1. 普通成员可以使用泛型(属性,方法)
  2. 使用泛型的数组不能初始化
  3. 静态方法中不能使用类型的泛型
  4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  5. 如果在创建对象时,没有指定类型,默认Object
public class Cat <T,R ,M> {
    String name;

    T t;
    R r;
    M m;

    T[] ts;
    // ***报错 :因为数组在 new 的时候不能确定 T 的类型
    //  T[] ts = new T[8];
    public Cat(String name, T t, R r, M m) {
        this.name = name;
        this.t = t;
        this.r = r;
        this.m = m;
    }

    /* 因为静态和类相关,在类加载时,对象还没有创建,
       所以如果静态方法和静态属性使用了泛型,JVM无法完成初始化
   */
    /*
    static R r2;
    public  static void m1(M m){

    }*/


}

泛型自定义接口

  • 注意
  1. 接口中,静态成员也不能使用泛型(这个和泛型类规定的一样)
  2. 泛型接口的类型,在继承接口或者实现接口时确定
  3. 没有指定类型,默认 Object
public interface IUsb<U,R> {

    int n = 10;

    // 静态成员不能使用泛型
//    U name = "";

    R get(U u);

    void  hi(R r);

    void run(R r1,R r2 ,U u1 ,U u2);

    // jdk1.8中,可以在接口中使用默认方法,也可以使用泛类型
    default  R method(U u){
        return null;
    }
}


public interface IAi extends IUsb<String,Double> {
    void  say(String r);
}

/**
 * 继承接口:
 * 当我们去实现IAi接口时,因为IA在继承IUsb接口的时候指定了 U->String R->Double,在实现IUsb接口的方法时,
 * 使用String替换U,是Double替换R
 */
public class Ai implements IAi {
    @Override
    public Double get(String s) {
        return null;
    }

    @Override
    public void hi(Double aDouble) {

    }

    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }

    @Override
    public void say(String r) {

    }
}


/**
 * 实现接口:
 * 直接指定泛型接口的类型给 U 指定 Integer,给 R 指定 Float,
 * 所以当实现 IUsb 方法时,会使用 Integer 替换 U, Float 替换 R
 */
public class Usb implements  IUsb<Integer,Float>{
    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {

    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {

    }
}

泛型自定义方法

  • 注意
  1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
  2. 当泛型方法被调用时,类型会确定
public class Car <E,F>  {
    public void run(){}
    // 泛型方法
    public <T,R> void  fly(T t, R r){}

    // 方法使用泛型
    public void  hello(E e){}

    public <T,R> void  hi(T t, R r){}
    
    // 泛型方法也也可以使用类泛型混用
    public <K> void  ok(E e, K k){}
}

// 当调用方法时,传入参数,编译器,就会确定类型
Car car = new Car();
car.fly("宝马"100);
car.fly(100100.1);

泛型的继承

1.泛型不具备继承性 , ArrayList<Object> integers2 = new ArrayList<String>();

泛型的通配符

  1. <?>无限通配符: 支持任意泛型类型
  2. <? extends A>上界通配符: 支持 A 类以及A类的子类,规定了泛型的上限
  3. <? super A>下界通配符: 支持 A 类以及A类的父类,不限于直接父类,规定了泛型的下限

PECS原则

最后简单介绍下Effective Java这本书里面介绍的PECS原则。

  • 上界<? extends T>不能往里存,只能往外取,适合频繁往外面读取内容的场景。
  • 下界<? super T>不影响往里存,但往外取只能放在Object对象里,适合经常往里面插入数据的场景。