【Java SE】泛型

目录

什么是泛型

泛型的基本语法

泛型的擦除机制

泛型的上界

通配符

包装类


什么是泛型

一般的类和方法只能使用具体的类型,而通过泛型能对类型实现参数化,使类和方法适用多种类型。

例如:当我们要实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值时我们可以借助Object类来完成:

class MyArray {
    public Object[] obj = new Object[10];
    public void setVal(int pos, Object val) {
        obj[pos] = val;
    }
    public Object getVal(int pos) {
        return obj[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0, 1);
        myArray.setVal(1, 1.5);
        myArray.setVal(2, "haha");
        int val1 = (int)myArray.getVal(0);//需要进行强制类型转换
        double val2 = (double)myArray.getVal(0);//需要进行强制类型转换
        String val3 = (String)myArray.getVal(0);//需要进行强制类型转换
    }
}
虽然使用Object类也能让当前数组可以存放任何数据,但取数组时却不好确定取到哪种数据类型 。而泛型把类型作为参数传递,能指定当前的容器,要持有什么类型的对象,同时让编译器去做检查,实现需要什么类型,就传入什么类型。
class MyArray<T> {
    public T[] obj = (T[])new Object[10];
    public void setVal(int pos, T val) {
        obj[pos] = val;
    }
    public T getVal(int pos) {
        return obj[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray();//指定类型为Integer
        myArray.setVal(0, 1);
        //myArray.setVal(1, 1.5);//类型不匹配,编译报错
        //myArray.setVal(2, "haha");//类型不匹配,编译报错
        int val1 = myArray.getVal(0);
    }
}

通过对比可以发现泛型可以帮助我们在存放元素的时候进行类型检查,在取出元素的时候进行类型转换。

泛型的基本语法

泛型类 class 泛型类名称 < 类型形参列表 > { // 这里可以使用类型参数 }
class Demo1<T> {
    T t;
}
class Demo2<T1, T2> {
    T1 t1;
    T2 t2;
}
定义一个泛型类引用和实例化一个泛型类对象泛型类 < 类型实参 > 变量名  =  new 泛型类 类型实参 > ( 构造方法实参 )
MyArray<Integer> list = new MyArray<Integer>();
泛型方法 :方法限定符 < 类型形参列表 > 返回值类型 方法名称 ( 形参列表 ){...}
public class Test {
    //静态的泛型方法需要在static后用<>声明泛型类型参数
    public static<T> void test() {
        
    }
}
类名后的 <T> 代表占位符,表示当前类是一个泛型类
类型形参一般使用一个大写字母表示,常用的名称有:
• E  表示 Element(元素,在集合中使用)
• K  表示 Key(键)
• V  表示 Value(值)
• N  表示 Number(数值类型)
• T  表示 Type(Java类)

• ? 表示不确定的java类

注:

• 泛型只能接受类,所有的基本数据类型必须使用包装类;

• 泛型类并不会改变方法的返回值(不用考虑重载)

泛型的擦除机制

我们已经知道泛型是在编译时根据传递的数据类型进行类型检查和转换,其实在编译过程中泛型还会将所有的T替换成 Object,这种机制叫做擦除机制。擦除机制是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息。

其实前面 T[] obj = (T[])new Object 这种写法是非常危险的。当 getval() 方法返回的是T[],且测试代码中用 Integer[] 数组来接收时在运行时就会得到 ClassCastException 错误。

class MyArray<T> {
    public T[] obj = (T[])new Object[10];
    public void setVal(int pos, T val) {
        obj[pos] = val;
    }
    public T[] getVal(int pos) {
        return obj;
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray();
        myArray.setVal(0, 1);
        Integer[] val1 = myArray.getVal(0);//将Object[]分配给Integer[]引用,程序报错
    }
}

注:数组在运行时存储和检查类型信息,泛型在编译时检查类型错误。

泛型的上界

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

语法:  class 泛型类名称 < 类型形参 extends 类型边界 > { ... }
其中 类型参数必须是 类型边界其子类 定(可以看成当泛型有类型边界时,擦除机制只会擦除到它的类型边界)
当类型形参为 E,类型边界为 Number 时:
class Demo<E extends Number> {
    
}
public class Test {
    public static void main(String[] args) {
        Demo<Integer> demo1 = new Demo<>();//Integer是Number的子类
        Demo<Number> demo2 = new Demo<>();//Number是Number本身
        //Demo<String> demo3 = new Demo<>();//error:String不是Number的子类或者Number本身
    }
}
注:如果没有指定类型边界 ,可以视为 E extends Object
泛型的上界还可以是一个接口,这时候的类型参数就必须要有实现这个接口的方法。
当我们要写一个泛型类求数组的最大值时:
class Person implements Comparable<Person> {
    public int age;
    public Person (int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

class Max<T extends Comparable<T>> {
    public T findMax(T[] arr) {
        T max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (max.compareTo(max) < 0) {
                max = arr[i];
            }
        }
        return max;
    }
}
public class Test {
    public static void main(String[] args) {
        Max<Person> max = new Max<>();
        Person[] per = {new Person(10), new Person(20), new Person(30)};
        Person person = max.findMax(per);
    }
}

通配符

通配符(?)在形参中使用,可以用于扩充参数的范围。

class A<T> {
    private T a;
    public void setA(T a) {
        this.a = a;
    }
    public T getA() {
        return a;
    }
}
public class Test {
    public static void fun(A<?> a) {//此时使用通配符"?"描述的是它可以接收任意泛型类型
        //a.setA(10);//error:'?'类型不确定,无法进行修改
        System.out.println(a.getA());
    }
    public static void main(String[] args) {
        A<Integer> a1 = new A<>();
        a1.setA(10);
        A<String> a2 = new A<>();
        a2.setA("ABC");
        fun(a1);
        fun(a2);
    }
}

泛型存在泛型上界,通配符除了有上界还有下界。

•通配符的上界

语法: ?extends 上界类

传递的参数只能是上界类或其子类

class Grandfather {

}
class Father extends Grandfather {

}
class Son extends Father {

}
class A<T> {
    private T a;
    public void setA(T a) {
        this.a = a;
    }
    public T getA() {
        return a;
    }
}
public class Test {
    public static void fun(A<? extends Father> a) {

    }
    public static void main(String[] args) {
        A<Grandfather> a1 = new A<>();
        a1.setA(new Grandfather());
        A<Father> a2 = new A<>();
        a2.setA(new Father());
        A<Son> a3 = new A<>();
        a3.setA(new Son());
        //fun(a1);//error:不是Father或其子类
        fun(a2);//是Father本身
        fun(a3);//是Father的子类
    }
}
此时无法在  fun  函数中对  进行添加元素,因为 a  接收的是  Father 和他的子类,此时无法确定存储的元素应该是哪个类。虽然无法在 fun 函数中 进行添加元素,但是在其中可以获取元素
    public static void fun(A<? extends Father> a) {
        //a.setA(new Father());//error:?接受的类型可能为Father或Father的子类,可能发生不安全的向下转型
        //a.setA(new Son());//error
        Father father = a.getA();//a一定是father或其子类,可能发生向上转型
    }
•通配符的下界

语法: ?super 下界类

传递的参数只能是下界类或其父类

class Grandfather {

}
class Father extends Grandfather {

}
class Son extends Father {

}
class A<T> {
    private T a;
    public void setA(T a) {
        this.a = a;
    }
    public T getA() {
        return a;
    }
}
public class Test {
    public static void fun(A<? super Father> a) {
            
    }
    public static void main(String[] args) {
        A<Grandfather> a1 = new A<>();
        a1.setA(new Grandfather());
        A<Father> a2 = new A<>();
        a2.setA(new Father());
        A<Son> a3 = new A<>();
        a3.setA(new Son());
        fun(a1);//是Father的父类类
        fun(a2);//是Father本身
        //fun(a3);//error:是Father的子类
    }
}
与通配符的上界相反,通配符的下界在 fun 函数中不能进行读取数据,只能写入数据(写入的数据只能是下界类及其父类)。
    public static void fun(A<? super Father> a) {
        //Father father = a.getA();//error:'?'接受的类型可能为Father或Father的父类,可能发生危险的向下转型
        //Grandfather grandfather = a.getA();//error
        a.setA(new Father());//可以添加Father本身,a可能是Father或其本身,可能发生向上转型
        a.setA(new Son());//可以添加Father的子类
    }

包装类

Java 中,由于基本类型不是继承自 Object ,为了在泛型代码中可以支持基本类型, Java 给每个基本类型都对应了一个包装类型。
下面是基本数据类型及其所对应的包装类:
基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

基本类型与包装类型的相互转换叫作装箱和拆箱:

int a = 10;
// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer i1 = Integer.valueOf(a);//手动装箱
Integer i2 = new Integer(a);//手动装箱
Integer i3 = a; // 自动装箱
Integer i4 = (Integer)a; // 自动装箱
// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j1 = i1.intValue();//手动拆箱
int j2 = ii; // 自动拆箱
int j3 = (int)i1; // 自动拆箱
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记得开心一点啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值