[Java]泛型

简介: [Java]泛型

🥽 泛型简介

🌊 泛型的概念

JDK1.5以后,Java引入了“参数化类型(Parameterized type)”(泛型)的概念,允许我们在创建集合时再指定集合元素的类型。JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时指定元素的类型。

泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参

🌊 使用泛型的原因

不使用泛型可能出现的问题:

public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        // 需求:存放学生成绩
        list.add(89);
        list.add(88);
        list.add(87);
        list.add(86);
        // 问题1:类型不安全,可以存放不同类型的元素
        list.add("TOM");
        for (Object score: list ) {
            // 问题2:强转时,可能出现类型转换异常
            int stuScore = (int)score;
            System.out.println(stuScore);
        }
    }

使用泛型:

泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。

public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        // 需求:存放学生成绩
        list.add(89);
        list.add(88);
        list.add(87);
        list.add(86);
        // 编译时,会进行类型的检查,保证数据的安全
        // list.add("TOM");
        // 获取时,获取的数据为使用泛型指定元素类型
        for (Integer score: list ) {
            // 避免了强转操作
            int stuScore = score;
            System.out.println(stuScore);
        }
    }

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁(可以不用强转)、健壮。

🌊 小结

① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。

② 在实例化集合类时,可以指明具体的泛型类型

③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如:add(E e) —>实例化以后:add(Integer e)

④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

🌊 类型推断

// ArrayList<Integer> list = new ArrayList<Integer>();
// 如果在变量前面指明了泛型的类型,则在new的后面可以不用再写泛型的类型
// jdk7新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();

🥽 自定义泛型

🌊 自定义泛型类

public class Order<T> {
    int orderId;
    String orderName;
    // 给自定义类加上泛型后,
    // 在类的内部结构就可以使用类的泛型
    // 某个属性的类型为T,属性类型T由实例化类时指定
    T orderT;
    public Order() {}
    public Order(int orderId, String orderName, T orderT) {
        this.orderId = orderId;
        this.orderName = orderName;
        this.orderT = orderT;
    }
    public T getOrderT() {
        return orderT;
    }
    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }
}
public static void main(String[] args) {
        // 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object
        // 如果定义了泛型,建议在实例化时指明类的泛型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("Tom");
        // 建议:实例化时指明类的泛型
        Order<String> order1 = new Order<>();
        // 报错
        // order1.setOrderT(123);
        order1.setOrderT("Order1");
    }

定义泛型类的子类,并且指明泛型的类型:

// SubOrder1 指明了泛型类型,不为泛型类
public class SubOrder extends Order<Integer> {
    public SubOrder() {}
}
public static void main(String[] args) {
        SubOrder sub1 = new SubOrder();
        // 由于子类在继承带泛型的父类时,指明了泛型类型
        // 实例化时不再需要指明泛型
        // 且在子类中用到泛型的位置都为继承时指定的泛型类型
        sub1.setOrderT(123);
    }

定义泛型类的子类,不指明泛型的类型:

// SubOrder1<T> 没有指定泛型类型,仍然为泛型类
public class SubOrder1<T> extends Order<T> {
    public SubOrder1() {}
}
public static void main(String[] args) {
        SubOrder1<String> sub1 = new SubOrder1();
        sub1.setOrderT("ABC");
    }

🌊 自定义泛型类/泛型接口注意点

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内,使用逗号进行分隔。比如:<E1,E2,E3>
  2. 泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass<E>(){}。声明泛型类的构造器不需要写<E>
  3. 实例化后,使用到泛型的位置的类型都为指定的泛型类型,没有指定则为Object。
  4. 泛型不同的引用(泛型类不同的实例化对象)之间不能相互赋值。
  5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。泛型要使用一路都用。要不用,一路都不要用。
  6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象(接口或抽象类不能实例化)。
  7. jdk1.7,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();
  8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
  9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。类的泛型是在实例化时进行指定,而静态结构在类加载时就被加载,静态结构的使用早于类的实例化。
  10. 异常类不能是泛型的
  11. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
public void test() {
        // 编译不通过
        // 此时new需要一个具体的类型,而T不确定
        // T[] arr = new T[];
        // 编译通过,Object为具体的类型
        // 运行时运行到此处会将其强转为T类型
        T[] arr = (T[])new Object[10];
    }
  1. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
  • 子类不保留父类的泛型:按需实现
  • 没有类型 擦除
  • 具体类型
  • 子类保留父类的泛型:泛型子类
  • 全部保留
  • 部分保留
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
// A B 为子类额外提供的泛型,从父类继承的泛型都为Object类型,在此基础上子类多了 A B 两个泛型
// A B 属于子类
class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
// 从父类继承的泛型为Integer, String类型,在此基础上子类多了 A B 两个泛型
// A B 属于子类
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
// 保留父类的全部泛型,子类又额外定义了 A B 泛型
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
// 保留父类的T2泛型,子类又额外定义了 A B 泛型
class Son4<T2, A, B> extends Father<Integer, T2> {
}
  1. 结论:子类除了指定或保留父类的泛型,还可以增加自己的泛型

🌊 自定义泛型方法

泛型方法:在方法的结构中出现了泛型的结构,且方法的泛型和类的泛型没有任何关系,即类的泛型参数与方法的泛型参数两个不同的独立的参数,泛型方法与所属的类是否为泛型类没有关系。

public static void main(String[] args) {
        _1 v = new _1();
        Integer[] arr = new Integer[]{1, 2, 3, 4};
        // 泛型方法在调用时指明泛型参数的类型
        // 泛型方法的泛型类型与泛型类的泛型类型无关
        List<Integer> list = v.test(arr);
    }
    // <E> 表明后面使用的E为泛型
    // 否则E会被认为类进行处理 public List<E> test(E[] arr)
    public <E> List<E> test(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e: arr) {
            list.add(e);
        }
        return list;
    }

泛型方法可以声明为静态的,因为泛型方法的泛型参数是在调用方法时确定的,并非在实例化时确定。

public static void main(String[] args) {
        Integer[] arr = new Integer[]{1, 2, 3, 4};
        // 泛型方法在调用时指明泛型参数的类型
        // 泛型方法的泛型类型与泛型类的泛型类型无关
        List<Integer> list = test(arr);
    }
    // <E> 表明后面使用的E为泛型
    // 否则E会被认为类进行处理 public List<E> test(E[] arr)
    public static <E> List<E> test(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e: arr) {
            list.add(e);
        }
        return list;
    }

🥽 泛型在继承上的体现

如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型!比如:String是Object的子类,但是List<String>并不是List<Object>的子类。

public void test1() {
        List<Object> l1 = new ArrayList<>();
        List<String> l2 = new ArrayList<>();
        // 如果 l1 = l2
        // 则可以通过 l1 = l2, l1.add(123)向l2中加入不为String类型的数据
    }

如果B是A的一个子类型,且A与B是具有泛型声明的类或接口,B<G>A<G>的子类型

public void test2() {
        List<String> l1 = null;
        ArrayList<String> l2 = null;
        l1 = l2;
    }

🥽 通配符的使用

类型通配符:?

比如:List<?> Map<?,?>List<?>List<String>List<Object>等各种泛型List的父类。

如果B是A的一个子类型,G<A>G<B>没有关系,二者共同的父类为G<?>

public void test3() {
        List<String> l1 = new ArrayList<>();
        List<Integer> l2 = new ArrayList<>();
        print(l1);
        print(l2);
    }
    public void print(List<?> l) {
        l.forEach(System.out::println);
    }

对于List<?>不能向其内部添加数据,因为我们不知道需要向其中添加的元素类型,不能向其中添加对象。唯一的例外是null,它是所有类型的成员。

读取List<?>的对象中的元素时,永远是安全的,因为不管List<?>对象中包含的真实类型是什么,都可以赋值给Object。

🥽 有限制的通配符

  • <?>
  • 允许所有泛型的引用调用
  • 通配符指定上限
  • 上限extends:泛型使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
  • 通配符指定下限
  • 下限super:泛型使用时指定的类型不能小于操作的类,即>=
  • 举例:
  • <? extends Number>(无穷小 , Number]
  • 只允许泛型为Number及Number子类的引用调用
  • <? super Number>[Number , 无穷大)
  • 只允许泛型为Number及Number父类的引用调用
  • <? extends Comparable>
  • 只允许泛型为实现Comparable接口的实现类的引用调用
// Student为Person的子类
    @Test
    public void test4(){
        List<? extends Person> list1 = null;
        List<? super Person> list2 = null;
        List<Student> list3 = new ArrayList<Student>();
        List<Person> list4 = new ArrayList<Person>();
        List<Object> list5 = new ArrayList<Object>();
        list1 = list3;
        list1 = list4;
//        list1 = list5; // Object的范围大于Person
//        list2 = list3; // Student的范围小于Person
        list2 = list4;
        list2 = list5;
        //读取数据:
        list1 = list3;
        Person p = list1.get(0); // 最大不超过Person
        //编译不通过
        //Student s = list1.get(0);  // Person的子类不一定都为Student 
        list2 = list4;
        Object obj = list2.get(0); // 最小不低于Person
        编译不通过
//        Person obj = list2.get(0); // 都为Person的父类或Person类
        //写入数据:
        // 注意允许的范围
    // 子类可以放进去,子类可以赋值给父类,范围小的可以赋值给范围大的
        //编译不通过
        // 可能存在比Student范围还小的类,父类对象赋值给子类
//        list1.add(new Student());
        //编译通过
        list2.add(new Person());
        list2.add(new Student());
    }


相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
75 2
|
16天前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
14 0
[Java]泛型
|
26天前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
10 1
|
1月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
26 5
|
1月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
17 1
|
2月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
35 9
Java——包装类和泛型
|
1月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
18 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
30天前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
13 0
|
2月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。