java的泛型

泛型

==浅浅了解一下泛型:==适用与许多许多类型,从代码上讲,就是一种类不止一种类型使用,就是对类型实现了参数化。
==引出泛型:==实现一个类,类中有数组成员,使得可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
思路:
1、我们知道数组只能存放指定类型的元素,例如:int[] array=new int[10],String array=new String[10];
2、所有类的父类,默认为Object类,数组是否可以创建为Object?

class MyArray{
    public Object[] array=new Object[10];
    public Object getPos(int pos){
        return this.array[pos];
    }
    public void setVal(int pos,int val){
        this.array[pos]=val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray myArray=new MyArray();
        myArray.setVal(0,10);
        //编译错误
        String ret= myArray.getPos(0);
        //ClassCastException类型转换异常,父类转子类存在风险
        String ret2= (String) myArray.getPos(0);
        System.out.println(ret);

    }
}

问题:以上代码实现后发现
1、任何类型数据都可以存放
2、0号下标本身是int类型,然后利用成员方法getPos获取时,成员方法以Object类型返回,在主函数中用String类型接收,之前学习向下转型中知道,父类传给子类是存在风险的(原本是猫,但是向上转型成动物,又向下转型成狗,这时就会发生编译异常),会发生ClassCastException类型转换异常。
总结:上述代码情况下,数组中可以存放任何数据,但是,更多情况下,还是希望它只能够持有一种数据类型,而不是同时持有多种类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,让编译器去做检查。此时,就是把类型,作为参数传递,需要什么类型,就传入什么类型。

上述代码进行改写:(先大致了解泛型类的语法,再看代码)

class MyArray2<T>{
    public T[] array=(T[])new Object[10];//one
    public T getPos(int pos){
        return this.array[pos];
    }
    public void setVal(int pos,T val){
        this.array[pos]=val;
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        MyArray2<Integer> myArray=new MyArray2<>();//two
        myArray.setVal(0,10);
        myArray.setVal(1,12);
        int ret=myArray.getPos(0);//three
        System.out.println(ret);
        //代码编译报错,此时因为在注释2处指定类当前的类型,此时的注释4处,编译器会在存放元素的时候帮助我们进行类型检查
       // myArray.setVal(2,"bit");//four

    }
}

有几点需要解释一下:
one:类名后的代表占位符,表示当前类是一个泛型类
了解:【规范】类型形参一般使用一个大写字母表示,常用的名称有:

  • E表示Element
  • K表示Key
  • V表示Value
  • N表示Number
  • T表示Type
  • S、U、V等等,第二、第三、第四个类型
    one中不能new泛型类型的数组(就相当于抽象类一样,不能确切是哪种类型,因此不能new新的对象)

T[] ts = new T[5] //false
T[] ts = (T[]) new Object[5]//这个表达式是否足够好,答案是未必的,这个问题一会介绍

two:类型后加入指定当前类型,<里面只能是类类型,不能是基本数据类型>
three:不需要强制类型转换–相当于拆包(后面会讲)
four:此时因为在注释2处指定类当前的类型,此时的注释4处,编译器会在存放元素的时候帮助我们进行类型检查。

使用

泛型类

语法

class 泛型类名称<类型形参列表>{
//这里可以使用类型参数
}
//还可以同时持有多种类型
class ClassName<T1,T2,T3……Tn>{
}

class 泛型类名称<类型形参列表> extends 继承类{
//可以是一种范围—这里只能使用继承类的超类或基类
}
class ClassName<T1,T2,……,Tn>extends ParentClass{
//这里只能使用以上列举的类
}

类型推导(Type Inference)
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray myArray=new MyArray<(类型实参)>();//可以推导出实例化需要实参为String

裸类型(Raw Type):
实例化对象时,不定义是什么类型的泛型

MyArray myArray=new MyArray();
默认无参,任何数据都可以,使用时可能需要强制转换
注意:我们不要自己去使用裸类型,裸类型是为了兼容老版本的API保留的机制
下面的类型擦除部分,我们会讲到编译器是如何使用裸类型的

泛型目前为止的优点:
1、就是存放元素的时候,会进行类型的检查
2、取出元素的时候,会自动帮你进行类型转换,因此不需要再进行类型的强转

为什么不能实例化泛型类型数组

根据上面所学习的,在这里我想问几个问题:
1、泛型是如何进行编译的?(面试题)
通过命令:javap -c 查看字节码文件,所有的T都是Object
在这里插入图片描述编译过程中,将所有的T替换成Object这种机制,我们称为擦除机制。

Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间并不会包含泛型的信息,意思就是由于编译的擦除机制,使得生成的字节码文件中全部T替换成Object,自然在运行时,没有看到关于T被类型替换的痕迹
2、那为什么.T[] ts=new T[5];是不对的
解答:编译的时候,替换为Object,不是相当于:Object[] ts=new Object了吗,这不就回到一开始出现的问题了吗,一旦访问,就会引起向下转型(不安全)
为什么不能实例化泛型类数组?
在这里插入图片描述在这里插入图片描述原因:根本原因还是类型的继承关系问题,Integer[]并不是Object[]的子类。虽然,Integer继承自Object,但Integer[]的直接父类是Object。即所有数组类型的直接父类都是Object,可以通过反射来验证。数组类型是写在jvm里得,就像8种基本类型,我们无法在java的标准库中找到这个类。

数组和泛型之间重要区别是它们如何强制执行类型检查,具体来说,数组在运行时存储和检查类型信息,泛型是在编译时检查类型信息。
因此,运行时T已经被Object替换,直接转给Interfer类型的数组,编译器认为是不安全。

正确方式:
正规初始化

import java.lang.reflect.Array;

/**
 * Created with IntelliJ IDEA
 * Description:
 * User:恋恋
 * Date:2022-10-18
 * Time:13:36
 */
class MyArray3<T>{
    public T[] array;
    /**
     * 通过反射创建,指定类型的数组
     * clazz:指定类型
     * capacity:指定容量
     */
   public MyArray3(Class<T> clazz,int capacity){
       array=(T[])Array.newInstance(clazz,capacity);
   }
    public T getPos(int pos){
        return this.array[pos];
    }
    public void setVal(int pos,T val){
        this.array[pos]=val;
    }
    public T[] getArray(){
        return array;
    }
}
public class TestDemo3 {
    public static void main(String[] args) {
        MyArray3<Integer> myArray=new MyArray3<>(Integer.class,10);
       Integer[] integers=myArray.getArray();

    }
}

泛型方法

泛型能够使一个方法同时兼容多种不同类型的参数,那有没有一种方法不随着参数的变换而去创建,这就是泛型方法,适用于自定义的方法,不用重载太多的方法。

静态泛型方法不用依赖对象,那如何定义她的类型呢
泛型静态方法的语法

方法限定符<类型形参列表> 返回值类型 方法名称(形参列表){
}

  public static <T> void print(T str){
        System.out.println(str);
    }

在这里插入图片描述泛型非静态方法的语法

方法限定符 返回值类型 方法名称(T 形参列表)

在这里插入图片描述

通配符

?用于在泛型的使用,即为通配符
通配符解决什么问题:
这个是泛型也无法解决协变的问题,协变指的就是如果Student是Person的子类,但是泛型是不支持这样的父子类关系的。
泛型T是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更加是是用于补充参数的范围。
总结:为了解决泛型方法中接送参数的不同,有可能接送泛型类类数组,这时泛型就有可能是Integer或者是String等,利用通配符就会解决泛型无法协变的问题和不同类型的问题。
例子:使用通配符

public class TestDemo1 {
//此时使用通配符“?”描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(MyArray2<?> temp){
        System.out.println(temp.getPos(0));
    }
    public static void main(String[] args) {
        MyArray2<Integer> myArray=new MyArray2<>();//two
        myArray.setVal(0,10);
        myArray.setVal(1,12);
        fun(myArray);

    }
}

在这里插入图片描述

通配符上界

上界只能读不能写
语法:

<? extends 上界>

<? extends Number>//可以传入的实参类型是Number或者Number的子类
class Food{

}
class Fruit extends Food{

}
class Apple extends Food{

}
class Banana extends Food{

}
class Massage<T>{
    private T massage;//定义message
    public T getMassage(){//获得message
        return massage;
    }
    public void setMassage(T massage){//初始化message
        this.massage=massage;
    }
}
public class TestDemo5 {
    public static void fun(Massage<? extends Food> temp){//只能接收Food的本身或者子类
        System.out.println("====写入时===");
      //  temp.setMassage(new Food());
       // temp.setMassage(new Fruit());
        //temp.setMassage(new Apple());
        //temp.setMassage(new Banana());
        //写入时,全部编译异常,原因:此时的temp只能接收Food的本身或者它的子类,当temp为Food的子类时,setMassage方法(接送了Food的实例化对象),就会产生向下转型-不安全
        System.out.println("====读取时====");
        Food food=temp.getMassage();
        //Apple apple=temp.getMassage();--编译异常
        //读取时,只能以最高的父类来接收,否则会出现向下转型
      
    }
    public static void main(String[] args) {
        Massage<Apple> massage1=new Massage<>();
        massage1.setMassage(new Apple());
        fun(massage1);
        Massage<Banana> massage2=new Massage<>();
        massage2.setMassage(new Banana());
        fun(massage2);
        //Fruit fruit=(Fruit)massage1.getMassage();
        Massage<Food> massage3=new Massage<>();
        massage3.setMassage(new Food());
        fun(massage3);

    }
}

在这里插入图片描述

总结:
只能读取get,并且用最高的父类接送,否则会发生向下转型
不能写入set,有可能T是Food、或者Banana、Apple,设置的时候会不安全

通配符下界

下界只能写不能读
语法:

<? super 下界> <? super Integer> //代表可以传入的实参的类型是Integer或者Integer的父类类型
class Food0{

}
class Fruit0 extends Food0{

}
class Apple0 extends Fruit0{

}
class Message0<T>{
    private T message;

    public T getMessage() {
        return message;
    }

    public void setMessage(T message) {
        this.message = message;
    }
}
public class TestDemo6 {
    public static void fun(Message0<? super Fruit0> temp){ //?只能是Fruit0本身或者是Fruit0父类
        System.out.println("========写入==============");
        //temp.setMessage(new Food0());
        //编译错误,原因是此时temp只可能是Fruit0本身或者是Fruit0的父类,当temp为Fruit0本身时,传递过去会产生向下转型-不安全
        temp.setMessage(new Fruit0());
        temp.setMessage(new Apple0());
        //只可以写入Fruit0的子类或者它本身
        System.out.println("========读取==============");
        System.out.println(temp.getMessage());//可以直接读取
        //Fruit0 fruit0=temp.getMessage();
        //编译错误,原因:此时temp只可能是Fruit0本身或者是Fruit0的父类,当temp为Fruit0的父类时,传递给变量时,会发生向上转型-不安全

    }
    public static void main(String[] args) {
                Message0<Apple> message0=new Message0<>();
                message0.setMessage(new Apple());
                //fun(message0);--编译错误
                //  message0类型是Apple,不符合fun的方法
                Message0<Fruit0> message2=new Message0<>();
                message2.setMessage(new Fruit0());
                fun(message2);//message2类型是Fruit本身,符合fun参数的要求
        Message0<Food0> message3=new Message0<>();
        message3.setMessage(new Food0());
        fun(message3);//message3类型是Fruit的父类,符合fun参数的要求


         }

     }


总结:当限制参数条件了通配符的下界时,成员方法只能接收本身或者父类;写入时,只能写入本身或者子类;读取时,只能直接读取(sout)。
只能写入,不能读取
总而言之,无论是通配符上界或者下界,只需知道它的判定标准是是否为向下转型,若为向下转型,则编译异常。总之,知道通配符是如何利用即可。

泛型的上界

在定义泛型类时,对传入的类型变量做一定的约束,可以通过类型边界来约束
语法

class 泛型类名称<类型形参 extends 类型边界> {
……
}

实例

public class ClassName < E extends Number >{
}
只接送Number的子类型作为E的类型实参
ClassName < Integer >

若没有指定类型边界E,可以默认为E extends Object
还有复杂实例:

public class MyArray < E extends Comparable< E >>{
}

E必须是实现了Comparable接口的

包装类

在java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,java给每个基本类型都对应了一个包装类型。

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFlat
doubleDouble
charCharacter
booleanBoolean

装包和拆包

装包
基本数据类型变成包装类

//手动装包
int i=10;
Integer i1=Integer.valueOf(i);
Integer i2=new Integer(i);
//自动装包
Integer i3=i;
Integer i4=(Integer)i;

拆包
包装类变成基本数据类型

//手动拆包
Integer j=20;
int j1=j.intValue();
//自动拆包
int j2=j;
int j3=(int)j;

【面试题:】

public class TestDemo7 {
    public static void main(String[] args) {
        Integer a=127;//涉及到了装包
        Integer b=127;

        Integer c=128;
        Integer d=128;
        System.out.println(a==b);
        System.out.println(c==d);
    }
}

在这里插入图片描述这个Integer a=127,涉及了自动装包,也相当于手动装包Integer a=Integer.valueOf(127)。
我们来分析以下源代码:
在这里插入图片描述
在这里插入图片描述从这个可以知道cache是个静态final修饰的数组,静态说明数组的大小在运行的时候不变,final说明数组类型不变且指向的对象不能改变,是Integer类型。
cache=new Integer[high- -128 +1]说明cache是长度为127+128+1=256的数组。
在谈谈valueOf函数:
如果装包的数字是-128~127以内,则直接存入到数组中,若不在以内的化,就重新new一个对象。
因此,上面127是ture,128是false
这样有个优点是,频繁使用到的小数据,不用每次都new新对象,只需从字符串中取出即可

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值