泛型的使用

本文详细介绍了Java泛型的概念、优点、使用细节以及泛型在类、接口和方法中的应用。通过对比传统方法,展示了泛型如何提高代码安全性,减少类型转换,并提供了泛型通配符的使用示例。同时,强调了泛型不具有继承性,但可以通过通配符来限制或扩展其适用范围。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.泛型的理解及优点

1.1 传统方法和泛型比较
  • 使用传统方法会遇到以下问题:

    • 不能对加入到集合中的数据类型进行约束
    • 遍历的时候需要进行类型转换,如果集合数据量较大会影响效率
    // 类的定义
    class Dog {
        private String name;
    
        public Dog(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }
    class Cat {
        private String name;
    
        public Cat(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }
    // 不使用泛型会出现的隐患(编译没有错误,所以写完代码后不会报错,但是运行报错)
    ArrayList list = new ArrayList();
    list.add(new Dog("dog"));
    list.add(new Cat("cat"));
    for (Object o : list) {  // 如果改为Dog o : list的话,编译器会报错
        Dog dog = (Dog) o;
        System.out.println(dog.getName());  // 类型转换错误,有一个对象是Cat类
    }
    
  • 使用泛型:

ArrayList<Dog> list = new ArrayList<Dog>();  // 表示放到集合中的元素是Dog类型
list.add(new Dog("dog"));
// list.add(new Cat("cat"));  // 编译器会检测并报错
for (Dog o : list) {  // 改为Dog o后编译器不会报错,不需要进行类型转换
    Dog dog = (Dog) o;
    System.out.println(dog.getName());
}
1.2 理解
  • 泛型可以理解为广泛的类型(又称参数化类型),对于int类型可以赋值1,2...,对于泛型E可以赋值Integet,Dog...
// public class ArrayList<E>中的E相当于变量,将Dog这个值赋予这个变量
ArrayList<Dog> list = new ArrayList<Dog>();
  • 在类声明或实例化时只要指定需要的具体类型即可
  • 可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型:
class Dog<E> {  // 相当于把E替换为需要的类
    private E name;  // 类中某个属性的类型
    
    public Dog(E name) {
        this.name = name;  // 参数类型
    }
    
    public E test(){  // 方法的返回值类型
        return name;
    }
}
  • 在编译期间即可确定E具体是什么类型:
Dog<String> dog = new Dog<String>(100);  // 编译就报错

tips:

  • 标识可以使用任意字母表示泛型,字母E不是必须的,可以使用K、V、T等字母
1.3 好处
  • 编译时会检查添加元素的类型,提高安全性
  • 减少类型转换的次数,提高效率(传统方法中需要进行两次转换Dog->Object->Dog)
  • 不再提示编译警告

2.使用细节

  • 泛型只能使用引用类型,不能使用基本类型
Dog<int> dog = new Dog<int>(111);  // 报错
  • 在给泛型指定具体类型后,可以传入该类型或者子类类型:
// 类的定义
class Father{}
class Son extends Father{}
class A<E>{
    E e;

    public A(E e) {
        this.e = e;
    }
}
A<Father> a = new A<Father>(new Son());  // 不会报错,可以视为Father f = new Son();
  • 泛型的使用:
A<Father> a = new A<Father>(new Son());
A<Father> a = new A<>(new Son());  // 简化形式

tips:

  • 在传统方法中其实默认泛型EObject类型:
// 定义类
class A<E>{
    E e;
    public A() {

    }
    public A(E e) {
        this.e = e;
    }
}
// 即使使用了泛型定义类,也是可以使用传统方式创建A类对象
A a = new A();  // 等价于A<Object> a = new A<Object>();

3.自定义泛型

3.1 自定义泛型类
  • 语法:
class 类名<T,R,...>{  // 可以定义多个泛型
	成员属性、方法
}
  • 注意细节:
    • 普通成员可以使用泛型,但是静态成员中不能使用类的泛型(类加载时对象还没创建,而泛型在类创建时才会指定,JVM无法完成初始化)
    • 使用泛型的数组不能初始化(类型无法确定就不清楚要开辟多大空间)
class Person<R, T> {
    // 下面三行语句都报错
    static R r;
    T[] t = new T[10];
    public static void test(T r){}
}
3.2 自定义泛型接口
  • 语法:
interface 接口名<T,R,...>{  // 可以定义多个泛型
    成员方法
}
  • 注意细节:

    • 静态成员中不能使用泛型
    • 泛型接口的类型在继承接口或实现接口时确定
    interface Person<R, T> {
        void test(R t);
    }
    class A implements Person<String, Integer>{
        @Override
        public void test(String t) {}
    }
    

tips:

  • 接口中的属性是public static final修饰的,所以在接口中没有成员:
interface Person<R, T> {
    R t;  // 报错
}
  • 如果接口定义的方法中使用到了定义类时使用到的泛型,其他类在实现该接口时就必须指明所有泛型的具体类型,不能认定其中某个为Object
interface Person<R, T> {
    void test(R t);
}
class A implements Person<String>{  // 报错,需要将两个泛型都指定或者都不指定
}
3.3 自定义泛型方法
  • 基本语法:
修饰符<T,R> 返回类型 方法名(参数列表){
}
  • 注意细节:

    • 泛型方法可以定义在普通类中(被调用的时候类型会确定),也可以定义在泛型类中
    // 类的定义
    class Person{
        public<E> void test(E e){
            System.out.println(e.getClass());
        }
    }
    // 使用泛型方法
    Person person = new Person();
    person.test("psj");  // class java.lang.String
    

tips:

  • 泛型方法不等价于使用了泛型:
class Person<E>{
    public void test(E e){  // 使用了泛型,但不是泛型方法。并且类一定要是自定义泛型类
    }
}
class Person<R>{
    public<E> void test(R r){  // 泛型方法可以不使用定义的泛型,并且可以使用泛型类的泛型
    }
}

4.泛型的继承和通配符

  • 继承性:泛型不具备继承性
List<Object> list = new ArrayList<String>();  // 报错
  • 通配符:

    • <?>:表示支持任意泛型类型
    // 参数中使用<?>
    public static void test1(List<?> list){
    }
    // 测试语句
    List<String> list1 = new ArrayList<>();
    List<Integer> list2 = new ArrayList<>();
    // 都不会报错
    test1(list1);
    test1(list2);
    
    • <? extends A>:表示支持A类及A类的子类,规定了泛型的上限
    // 类的定义
    class A{}
    class B extends A{}
    class C extends B{}
    // 参数中使用<? extends A>
    public static void test2(List<? extends A> list){
    }
    // 测试语句
    List<String> list1 = new ArrayList<>();
    List<A> list2 = new ArrayList<>();
    List<B> list3 = new ArrayList<>();
    List<C> list4 = new ArrayList<>();
    test2(list1);  // 报错,String不是A类的子类
    // 下面语句没问题
    test2(list3);  
    test2(list4);
    test2(list5);
    
    • <? super A>:表示支持A类及A类的父类,规定了泛型的下限
    // 类的定义
    class A{}
    class B extends A{}
    class C extends B{}
    // 参数中使用<? super A>
    public static void test3(List<? super A> list){  // 定义该方法的类不需要是泛型类,使用的是?不是具体的字母
    }
    // 测试语句 
    List<String> list1 = new ArrayList<>();
    List<A> list2 = new ArrayList<>();
    List<B> list3 = new ArrayList<>();
    List<C> list4 = new ArrayList<>();
    test3(list1);  // 报错
    test3(list2);  // 没问题
    test3(list3);  // 报错
    test3(list4);  // 报错
    

tips:

  • 在自定义泛型类和自定义泛型方法中不能使用<? ...>
class A<? extends List>{ }  // 报错
class A<T extends List>{ }  // 正确
public<E extends List> void test(E e){}  // 正确
public<? extends List> void test(E e){}  // 错误
  • <R extends List>,即使List是接口也是使用extends不是implements,表示R必须是一个List继承体系中的
  • <?>等价于<? extends object>
  • 在自定义的类/接口中,静态成员不能使用泛型,但是可以使用<?>
class A<T>{
    public static void test1(List<? extends String> list){}  // 正确
    public static void test2(List<? extends T> list){}  // 错误
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值