Java真的不难(十八)泛型

泛型:

什么是泛型?

泛型是在Java SE 1.5引入的的新特性,本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
简而言之:<>泛型就是用来约束类、方法、属性上的数据类型,比如

List<Integer> list = new ArrayList<Integer>();

new ArrayList这个集合的元素只能添加Integer类型。

为什么需要泛型?
Java推出泛型之前,程序员可以构建一个Object类型的集合,该集合能够存储任何的数据类型,而在使用该 集合的时候,需要程序员明确知道每个元素的具体的类型并向下转型,否则容易引发ClassCastException 类转换异常。现在我们通过泛型就能解决这个问题,通过泛型<>我们就能够约束这个集合插入的类型,就不需要再从Object类型转换成子类类型。

泛型有什么好处?

  • 类型安全,不会插入指定类型以外的数据
  • 消除了强制类型转换

泛型的类型:
泛型可以定义在类上、父类上、接口上、子类上、方法上、参数上、属性上, 泛型类型是可以用任意字母来代替你需要传递的数据,一般为了可读性,我们约定一般会写:

  • E -element 代码集合中存放的元素
  • T -Type表示类型Java类
  • K -key 表示键
  • V -V 表示value
  • N -Number 表示数值类型
  • ? 表示不确定的类型

那么这么多字母有什么用呢?我们来看一段代码:
这是一个普通的Student类:

package Test;

public class Student {
    
    

    private String name;
    private int age;

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }
}

我们升级一下,把这个类定为String的泛型,意思是这个类只能接收String的类型:

public class Student<Stirng> {
    
       //重复的代码就不重写了,和上面的是一样
}

ok,这样没问题,但是我们想一想,如果直接就把这个类的泛型定义死了,如果后面需要在Student传入Integer类型,我们就得重写一个Student类,这样代码的复用性不高,不符合编程思维,所以我们可以这样:

public class Student<T> {
    
    

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

然后当需要用什么泛型的时候直接传入即可:

Student<String> stu1 = new Student<>();
Student<Integer> stu2 = new Student<>();
Student<Double> stu3 = new Student<>();

测试:(接上面的代码)

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //先使用Integer泛型,传入100
        Student<Integer> stu1 = new Student<>(100);
        Integer in = stu1.getT();
        System.out.println(in);
        
    }
}

输出:100 (没问题)
但是如果在定义为Integer下输入String类型,会怎么样?在这里插入图片描述很明显,直接就报错了

这样就更好的利用了代码的复用性。上面部分的知识点也就是泛型类。

泛型父类和子类:

我们先定义一个泛型父类:()

public class Student<T> {
    
    
   
    private T t;
    public T getT() {
    
    return t;}
    public void setT(T t) {
    
    this.t = t;}
}

然后定义一个子类,继承该Student父类:

public class Child<T> extends Student<T>{
    
    

    @Override
    public T getT(){
    
    
        return super.getT();
    }
    @Override
    public void setT(T t){
    
    
        super.setT(t);
    }
}

特别需要注意:

  1. 泛型子类的参数一定要和父类的参数类型一致
  2. 如果子类没有添加泛型,那么父类的参数类型必须明确
    也就是这样:
public class Child extends Student<Integer>{
    
    
}

测试:
下面代码的解释:在父类Student内定义泛型为String,然后创建子类Child的对象child,因为是继承关系,所有child的泛型也是String,

public class Test {
    
    
    public static void main(String[] args) {
    
    

        Student<String> child = new Child<>();  //多态
        child.setT("abc");
        String value = child.getT();
        System.out.println(value);
    }
}

当子类传入非String类型值时:
在这里插入图片描述

泛型接口:

泛型接口其实也很简单,定义如下:

public interface USB<T> {
    
       
}

泛型子类实现泛型接口,除了处理标识父类的泛型标识外,还可以继续扩展泛型:

public class phone<T,V> implements USB<T>{
    
      
//这里的意思是子类除了有USB接口的泛型T类,也可以再扩展一个泛型

    private T color; //手机颜色
    private V phoneName;  //手机名称

    public phone(T color,V phoneName) {
    
    
        this.color = color;
        this.phoneName = phoneName;
    }

    public T getColor() {
    
    
        return color;
    }

    public void setColor(T color) {
    
    
        this.color = color;
    }

    public V getPhoneName() {
    
    
        return phoneName;
    }

    public void setPhoneName(V phoneName) {
    
    
        this.phoneName = phoneName;
    }

}

注意: 泛型接口也和泛型父子类一样,如果子类没有添加泛型参数,那么父类一定要明确的指定类型!

public class phone implements USB<String>{
    
    
}

测试
分别把T和V的泛型都定义为String:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        phone<String,String> ph = new phone<>("黑色","华为");
        String str1 = ph.getColor();
        String str2 = ph.getPhoneName();
        System.out.println(str1+str2);
    }
}

泛型方法:

定义泛型方法:在public和返回值之间定义<>的才是泛型方法:

    public<E> void printType(){
    
    
        
    }

然而这个方法的返回值类型就是取决于E的类型,如果E是Integer,那么这个方法的返回值类型就是整数。用法基本和上述的一致,需要什么类型的返回值,在调用的时候去传递泛型类型即可。

通配符:

通配符一般是使用 ? 代替具体的类型实参(此处是类型实参,而不是类型形参)。当操作类型时不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用 ? 通配符来表未知类型。例如 List<?> 在逻辑上是List、List 、List等所有List<具体类型实参>的父类。

有界的类型参数:
有的时候需要限制那些被允许传递到一个类型参数的类型种类范围,例如一个操作数字的方法可能只希望接受Number或者Number子类的实例。这时就需要为泛型添加上边界,即传入的类型实参必须是指定类型的子类型。要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends或super关键字,最后紧跟它的上界或下界。由此可以知道泛型的上下边界的添加必须与泛型的声明在一起 。

上限: <? extends T>
表示该通配符所代表的类型是T类型的子类。 例如往集合中添加元素时,既可以添加T类型对象,又可以添加T的子类型对象。

下限:<? super T>
表示该通配符所代表的类型是T类型的父类,就是只能获取到T类及以上的泛型,任何继承T类的泛型将得不到。
举例说明:
建一个动物的父类:

public class Animal {
    
    
}

建一个猫类,继承父类:

public class Cat extends Animal{
    
    
}

建一个小猫类,继承猫类:

public class MiniCat extends Cat{
    
    
}

现在的关系是Animal>Cat>MiniCat

通配符上限的测试类:

public class test {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Animal> animals = new ArrayList<>();
        ArrayList<Cat> cats = new ArrayList<>();
        ArrayList<MiniCat> minicats = new ArrayList<>();

        showAnimals(cats);
        showAnimals(minicats);
        //这样会报错,因为在showAnimals方法内的通配符最高继承自Cat
        showAnimals(animals);
    }
    
    //使用通配符,把上线限制到Cat,意思是Animal这个类不能被获取
    private static void showAnimals(ArrayList<? extends Cat> cats) {
    
    
        for (Cat cat:cats
             ) {
    
    
            System.out.println(cat);
        }
    }
}

在这里插入图片描述
可以看到使用通配符限制到Cat类后,Animals这个类就已经获取不到了。这就是上限。
如果把上限设置成Animal的时候:
在这里插入图片描述

通配符下限的测试类:(其他代码与上面的一致)

public class test2 {
    
    
        public static void main(String[] args) {
    
    
            ArrayList<Animal> animals = new ArrayList<>();
            ArrayList<Cat> cats = new ArrayList<>();
            ArrayList<MiniCat> minicats = new ArrayList<>();
            showAnimals(animals);
            showAnimals(cats);
            //设置下限:ArrayList<?  super Cat>
            showAnimals(minicats);
        }
        
        //使用通配符设置下限,ArrayList<? super Cat,意思是只能获取Cat及以上的类型
        private static void showAnimals(ArrayList<? super Cat> cats) {
    
    
            for (Object cat:cats
            ) {
    
    
                System.out.println(cat);
        }
    }
}

因为通配符设置了下限到Cat类,所以MiniCat是获取不到的:
在这里插入图片描述
以上就是关于泛型和通配符的介绍,泛型可以在类、接口、方法中使用,分别简称之泛型类、泛型接口、泛型方法,并且通过泛型实现数据类型的任意化,即灵活、又有安全性,易于维护。在编译过中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

总而言之,泛型是在编译的时候检查类型安全,所有的强制转换都是自动和隐式的,提高代码的重用率。并且消除强制类型转换,在传入参数的时候就已经限制的类型。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_57310550/article/details/123227450