Java--泛型

本文详细阐述了为何使用泛型,其带来的编译期类型安全优势,以及泛型类、接口和方法的用法。讲解了泛型的约束、局限性及类型擦除,涉及C#和Java的区别。重点介绍了通配符类型的应用,以及在不同场景下的继承规则。
摘要由CSDN通过智能技术生成

泛型:参数化类型

为什么要使用泛型

因为泛型提供了编译期的类型安全,确保只能把正确类型的对象放入集合中,避免在运行时出现ClassCastException。

使用泛型的好处:

1、可以让多种数据类型执行相同的代码;

2、泛型在使用的时候指定类型,不需要强制类型转换。

泛型类

将类型由原来的具体类型参数化:

public class MyClass<T> {
  private T data;
  public MyClass(){};
}

public class MyClass<T,K> {
  private T data1;
  private K data2;
}

泛型接口

public interface MyInter<T> {
  public void doSth();
}

实现泛型接口有两种实现方式:

未传入泛型实参:

public class MyClass<T> implements MyInterFace<T> {
    private T data;
    @Override
    public T doSth() {
        return null;
    }
}

//这样在new该类的时候需要指定具体的类型,不然程序无法识别我们需要的类型,默认是Object
public static void main(String[] args) {
        MyClass<Person> personMyClass = new MyClass<>();
        personMyClass.doSth();
    }

传入了泛型实参:

public class MyClass implements MyInterFace<Person> {
    private Person data;
    @Override
    public Person doSth() {
        return null;
    }
}

//这样就是我们平时正常的new一个对象调用构造方法
public static void main(String[] args) {
    MyClass personMyClass = new MyClass();
    personMyClass.doSth();
}

泛型方法

泛型方法的格式是:  权限修订符  <E> 返回值类型  方法名(){}

并不是说写在泛型类中的方法就叫泛型方法,泛型方法要遵守上面的那个格式,而且方法的类型参数可以和类的类型参数不同。

public class MyClass<T>{
    //普通方法
    public void doRunning(T obj) {
    }
    //普通方法
    public T doSpeaking(){
        return null;
    }
    //泛型方法,方法的类型E可以和类的类型T不同
    public <E> void doWorking (T obj){
    }
}

限定类型变量

public static <T extends Comparable> T min(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

这里 extends 实为派生,不单单指继承的范畴,也包括实现,这里实现该接口就是为了保证参数a 和 b都有compareTo方法可以使用,同样可以这样用:

public static <T extends Person & Comparable> T min(T a, T b) {
    a.doPersonFun();
    return a.compareTo(b) > 0 ? a : b;
}

以上代码中,T类型派生自Person 类和Comparable接口,这里的泛型类型T可以同时继承类或者实现接口,但是继承和实现同时存在的时候,继承的类要放在第一个,而且只能继承一个父类,像我们平时写代码自定义类的时候也是一样的道理:

public class MyClass<T> extends Person implements MyInterFace<T>,Comparable{
    @Override
    public T doSth() {
        return null;
    }
    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

而且extends的左右都允许有多个比如:

public static <T,E extends Person & Comparable & Serializable> void min(T a, E b, E c) {
    a.hashCode();
    Object m = b.compareTo(c) > 0 ? b : c;
}

public static <T extends Comparable,E extends Person & Comparable & Serializable> void min(T a, E b) {
    Object m = a.compareTo(b) > 0 ? a : b;
}

extends前面的多个,使用逗号隔开,每个逗号隔开的泛型都是独立的,如第一个方法中 a (类型T)就只有普通类型的方法可用,b(类型E),就可以使用Compareble和Serializable中的接口方法。

泛型的约束性和局限性

不能用基本类型实例化类型参数:使用泛型在传入类型时,需要是封装类型,基本数据类型不可用;

MyClass myClass = new MyClass<Integer>(); // 如果传int 会报错

运行时类型查询只适用于原始类型:不能使用instanceof来判断是否是某个泛型类

// 这里的MyClass<Integer> 和 MyClass<T> 会报错
if (myClass instanceof MyClass<Integer>) { }

if (myClass instanceof MyClass<T>) { }
//只能是 myClass1 instanceof MyClass

变量myClass的类型只和原生类MyClass有关,和传入的泛型的类型无关

MyClass myClass1 = new MyClass<Integer>();
MyClass myClass2 = new MyClass<String>();
System.out.println(myClass1.getClass() == myClass2.getClass()); // true

泛型类的静态上下文中类型变量失效:静态变量和静态方法中不能用泛型做参数或者方法返回值类型:

// 下面代码的T都会报错
private static T intstance;
public static T getInstance() {
   return null;
}

// 泛型方法除外
public static <T>T getInstance() {
   return null;
}

这个跟JVM中的编译顺序有关系,因为编译时是先编译static的,但是泛型是要在使用的时候才知道是什么类型,这样一来就没办法在编译static的时候知道类型,所以会报错。

不能创建参数化类型的数组

泛型不能用于实例化类型变量

不能捕获泛型类的实例:泛型类不能继承 Exception和Throwable

正确的使用是:

private <T extends Exception> void doWork(T t) throws T {
    try {
    } catch (Exception e) {
        throw t;
    }
}

泛型类型的继承规则

有几个类存在这样的继承关系:

其中Dog 继承自Animal,但是 MyClass<Dog> 并没有继承自 MyClass<Animal>,

但是泛型类可以存在继承关系使用:

通配符类型

<? extends T>

以上方法在执行喂养Animal时是正常的,但是喂养Dog的时候就报错了,不让养,为了解决这种问题,就可以使用上界通配符,T是类型的上界,类型的参数是T的子类,使用方法如下:

看一下我们的MyClass,是一个简单的泛型类:

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

我们在调用get 和set方法的时候会发现:

extends通配符,只允许get操作,不允许set操作,因此可以安全地获取数据,获取数据时要注意返回值类型:

? extends T  表示类型的上界,类型参数是T的子类,get方法返回的一定是个T(不管是T或者T的子类)编译器是可以确定知道的。但是set方法只知道传入的是个T,至于具体是T的那个子类编译器并不知道。

<? super T>

为了解决不能写入的问题,引入了super通配符,直接看案例:

使用时需要注意,set的时候传的参数需要是T及其字类:

? super  T 表示类型的下界,类型参数是T的父类(包括T本身),get方法返回的一定是个T的超类,那么到底是哪个父类?不知道,但是Object一定是它的超类,所以get方法返回Object,编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是T和T的子类可以安全的转型为T。

类型擦除

c# 和Java 分别如何实现泛型呢?

C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符),或是运行期的CLR中,都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。

Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值