泛型的基本使用

泛型的基本使用

什么是泛型

可以接受数据类型的数据类型

泛型的好处

  1. 编译时,检查添加元素的类型,提高了安全性, Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastExcepiton异常。代码更加简介,健壮。
  2. 减少了类型转换的次数,提高了效率(假如说一个List集合没有指定泛型,那么它在存放数据的时候就会默认存放成Object类型,第一次转型,取出数据时也是Object类型,如果想要使用其中某个对象的特定方法需要向下转型,第二次转型,效率比较低)

泛型使用的注意事项和细节

  1. 泛型代表的类型只能是引用数据类型,不能是基本数据类型。如果传入的是一个基本数据类型,会自动进行装箱操作。
  2. 在给泛型指定具体类型后,可以传入该类型或者其子类类型(多态:父类引用可以指向子类对象)。
  3. 如果不给泛型指定具体类型,编译器会默认将类型指定为Object。

自定义泛型的使用

自定义泛型类

基本语法

class 类名<T,R…>{}//…表示可以有多个泛型

注意细节

1)普通成员可以使用泛型(属性,方法)

2) 使用泛型的数组,不能初始化 , T[] = new T[8];不被允许,因为类型不确定就不能确定开辟多大空间

3)静态成员不能使用泛型,静态方法中不能使用类的泛型,因为静态方法是和类相关的,在类加载的时候,对象还没有创建,也就是说类型还没有确定,JVM就无法完成初始化。注意这里静态方法不能使用类的泛型的意思是,静态方法中的泛型不能更类的泛型绑定在一起,因为二者的创建时间,和存在时间都不同。静态方法可以使用自己定义的泛型。在自定义泛型方法中有说明。

4)泛型类的类型,是在创建对象时确定的

5)如果创建对象时没有指定泛型,则认为类型为Object

6)泛型类被继承时,也可以指定泛型。

class GenericsDemo1<T,R>{

	public static T a;//cannot be referenced from a static context
    
    private T t;

    private R r;

    public GenericsDemo1() {
    }

    public GenericsDemo1(T t, R r) {
        this.t = t;
        this.r = r;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public R getR() {
        return r;
    }

    public void setR(R r) {
        this.r = r;
    }

    //泛型做形参
    public void test01(T t,R r){
        System.out.println(t.getClass());
        System.out.println(r.getClass());
    }
    //泛型做返回值
    public T test02(){
        return t;
    }

//    cannot be referenced from a static context,无法从静态上下文中引用非静态 类型变量 T
//    public static T test03(){
//        return t;
//    }

自定义泛型接口

基本语法

修饰符 interface 接口名<T,R…>

注意细节
  1. 接口中,所有变量都不能使用泛型,因为接口中的变量会默认定义成public static final类型
  2. 泛型接口中的类型,在继承接口或者实现接口时确定。
  3. 没有指定类型,默认为Object
interface Generics05<T,R>{
    T test(T t);
}

interface Generics06 extends Generics05<String,Integer>{
    
}

class Generics07 implements Generics05<String,Integer>{

    @Override
    public String test(String s) {
        return null;
    }
}

自定义泛型方法

基本语法

修饰符 <T,R> 返回值 方法名(T t ,R r){}

注意细节
  1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
  2. 当泛型方法被调用时,类型会确认
  3. public void eat(E e){} ,修饰符后没有<T,R>,表明此方法不是泛型方法,而是使用了泛型
  4. 泛型方法可以使用类声明的泛型
//泛型类中使用,非泛型类类似
public class Generics04 {

    public static void main(String[] args) {
        Generics05<String> objectGenerics05 = new Generics05<>();
        objectGenerics05.test(1);
//        objectGenerics05.test01(1);
        Generics05.test02("hello");//静态泛型方法
        
    }
}

class Generics05<T>{

    public void test01(T t){
        System.out.println(t.getClass());
    }

    public <T> void test(T t){
        System.out.println(t.getClass());
    }
    
    public static  <T> void test02(T t){
        System.out.println(t.getClass());
    }
}

泛型的继承

  1. 泛型不具备继承性。//List list = new ArrayList();会报错,虽然String是Object的子类

  2. <?>:支持任意泛型类型。

<? extends T>和<? super T>的理解

? 通配符类型

<? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类; <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object;

这里非常重要,注意:假如说有这样一个List集合List<? super Chicken>(存在这样三个类,Food,Chicken,Egg按照顺序继承)这个List集合的实例化对象可以为new ArrayList< Food>()和new ArrayList< Chicken>();但是往里面存入的数据只能为Chicken和Chicken的子类,原因在下面,假如存在这样的集合List<? extends Chicken> 它的实例化对象只能为new ArrayList< Chicken>();和new ArrayList< Egg>();不能存入数据只能取出数据原因也在下面.

上界<? extends T>不能往里存,只能往外取

比如,我们现在定义:List<? extends T>首先你很容易误解它为继承于T的所有类的集合,你可能认为,你定义的这个List可以用来put任何T的子类,那么我们看下面的代码:

import java.util.LinkedList;
import java.util.List;

public class test {
    public static void main(String[] args) {
        List<? extends Father> list = new LinkedList<>();
        list.add(new Son());
    }
}
class Human{
}
class Father extends Human{
}
class Son extends Father{
}
class LeiFeng extends Father {
}

list.add(new Son());这行会报错:The method put(Son) is undefined for the type List<capture#1-of ? extends Father>

List<? extends Father> 表示 “具有任何从Father继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List 赋值。就是说可以创建好一个存有数据的集合赋值给这个变量,这样就可以实现取得功能,而不用存入数据。

那么为什么会存入数据失败呢?

原因是编译器只知道容器内是Father或者它的派生类,但具体是什么类型不知道。可能是Father?可能是Son?也可能是LeiFeng,XiaoMing?编译器在看到后面用Father赋值以后,集合里并没有限定参数类型是“Father“。而是标上一个占位符:CAP#1,来表示捕获一个Father或Father的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入Son或者LeiFeng或者Father编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。

List<? extends Father> list不能进行add,但是,这种形式还是很有用的,虽然不能使用add方法,但是可以在初始化的时候一个Season指定不同的类型。比如:

List<? extends Father> list1 = getFatherList();//getFatherList方法会返回一个Father的子类的list

另外,由于我们已经保证了List中保存的是Father类或者他的某一个子类,所以,可以用get方法直接获得值:

List<? extends Father> list1 = new ArrayList<>();
Father father = list1.get(0);//读取出来的东西只能存放在Father或它的基类里。
Object object = list1.get(0);//读取出来的东西只能存放在Father或它的基类里。
Human human = list1.get(0);//读取出来的东西只能存放在Father或它的基类里。
Son son = (Son)list1.get(0);

<?> 和类型参数的区别

<?> 和类型参数的区别就在于,对编译器来说所有的T都代表同一种类型。比如下面这个泛型方法里,三个T都指代同一个类型,要么都是String,要么都是Integer。
public <T> List<T> fill(T... t);

但通配符<?>没有这种约束,List<?>单纯的就表示:集合里放了一个东西,是什么我不知道。

下界

//super只能添加Father和Father的子类,不能添加Father的父类,读取出来的东西只能存放在Object类里
List<? super Father> list = new ArrayList<>();
list.add(new Father());
list.add(new Human());//compile error 
list.add(new Son());
Father person1 = list.get(0);//compile error 
Son son = list.get(0);//compile error 
Object object1 = list.get(0);

因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Father的基类,那往里存粒度比Father小的都可以。出于对类型安全的考虑,我们可以加入Father对象或者其任何子类(如Son)对象,但由于编译器并不知道List的内容究竟是Father的哪个超类,因此不允许加入特定的任何超类(如Human)。而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。但这样的话,元素的类型信息就全部丢失了。

PECS(Producer Extends Consumer Super)原则:

频繁往外读取内容的,适合用上界Extends。
经常往里插入的,适合用下界Super

总结

  • extends 可用于返回类型限定,不能用于参数类型限定(换句话说:? extends xxx 只能用于方法返回类型限定,jdk能够确定此类的最小继承边界为xxx,只要是这个类的父类都能接收,但是传入参数无法确定具体类型,只能接受null的传入)。
  • super 可用于参数类型限定,不能用于返回类型限定(换句话说:? supper xxx 只能用于方法传参,因为jdk能够确定传入为xxx的子类,返回只能用Object类接收)。
  • ? 既不能用于方法参数传入,也不能用于方法返回。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值