java泛型学习笔记(未更新完毕)

1. Java泛型概述

1.1 Object实现参数任意化
  • 考虑自己实现一个容器类,可以支持对不同类型数据的增删改查。

  • JDK 1.5之前,由于不支持泛型,所以需要使用Object创建数组作为容器。获取item时,需要显式地进行强制类型转换。

    class MyList {
          
          
        public static final int LENGTH = 10;
        private Object[] list = new Object[LENGTH];
        private int size = 0;
    
        public void add(Object object) {
          
          
            list[size++] = object;
        }
    
        public Object get(int index) {
          
          
            return list[index];
        }
    
        public int size() {
          
          
            return size;
        }
    }
    // 使用方式
    String str = (String) list.get(1);
    
  • 针对上述代码,java编译时不会检查非法的强制类型转换,在运行时才会报错。

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    	at ObjectGeneric.main(ObjectGeneric.java:8)
    
  • 总结:

  1. 通过使用Object来实现参数的任意化,带来的缺点是需要进行显式地强制类型转换。
  2. 针对类型转换错误的情况,编译器不会提示错误,在运行时才会抛出ClassCastException
  3. 因此,使用Object实现参数的任意化存在安全隐患。
1.2 泛型的引入
  • 在JDK源码中,我们经常看到类似代码,他们使用大写字母去限定自己的参数类型。这就是泛型!
public interface List<E> extends Collection<E>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
  • 泛型的意思就是泛指的类型(参数化类型),即所操作的数据类型可以以参数的形式进行指定。

    class Printer<T>{
          
          
        private T item;
        public Printer(T item){
          
          
            this.item=item;
        }
        public void print(){
          
          
            System.out.println(item.getClass().getName());
        }
    }
    
  • Java 泛型(generics)是 JDK 5 中引入的一个新特性,:

  1. 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型转换,提升代码的健壮性。
  2. 泛型会进行隐式地、自动的强制类型转换,可以提高代码的利用率。

2. 泛型的类型

2.1 泛型类
  • 泛型类型的语法:
class ClassName<泛型通配符identify>{
    
     // 泛型通配符可以是A-Z的任意大写字母以及'?'
	private identify item; // 使用泛型通配符定义成员变量
	public identify  getItem(){
    
     // 返回泛型值的方法,并非是泛型方法
		return item;
	}
	...
}
  • 泛型类在Java的容器(CollectionMap)中最常见,通过泛型实现了数据类型的任意化,灵活、安全、易维护。

  • 泛型类的简单实例:

    class MyGeneric<T> {
          
          
        private T item;
    
        public void setItem(T item) {
          
          
            this.item = item;
        }
    
        public T getItem() {
          
          
            return item;
        }
    }
    
    // 使用泛型类
    public static void main(String[] args) {
          
          
        // 创建泛型类对象时不指定类型,使用时与Object定义的类一样,需要进行显式转换
        MyGeneric generic1 = new MyGeneric();
        generic1.setItem(new Double(12.5));
        Double number = (Double) generic1.getItem();
        System.out.println("number: " + number);
        // 创建泛型类对象是指定类型,可以充分利用泛型的优势
        MyGeneric<Integer> generic2=new MyGeneric<>();
        // generic2.setItem(12.3);  // 编译无法通过,提示double类型无法转换成Integer类型
        generic2.setItem(12);
        if (generic2.getItem() instanceof Integer){
          
          
            System.out.println("Item is Integer");
        }
    }
    
2.2 泛型方法
  • 泛型方法: 在权限修饰符和返回值之间增加了泛型通配符,就让普通方法成为了泛型方法。

    // 有参数的泛型方法
    public <E> void printInfo(E input){
          
          
       System.out.println(input);
    }
    // 调用泛型方法
    obj.printInfo("sunrise");
    // 无参数的泛型方法
    public <E> List<E> createList(){
          
          
        return new ArrayList<>();
    }
    // 调用泛型方法
    List<String> list = obj.createList(); // 创建的是String类型的list
    
  • 注意:

  1. 泛型方法与泛型类是相互独立的:
    ① 泛型类使用T作为通配符,泛型方法也使用T。实际使用时,泛型类传入类型为String,而泛型方法可以为任意类型,不受泛型类的限制。
    ② 泛型方法可以在泛型类中定义,也可以在普通类中定义;泛型类不一定包含泛型方法。
  2. 泛型类中,使用了泛型成员的方法并非泛型方法,泛型方法一定要有单独的声明。
  • 泛型方法与可变参数
  1. Java方法中,通常使用...来定义可变长度的参数。

    public void printNames(String... names) {
          
          
        for (String name : names) {
          
          
            System.out.print(name + " ");
        }
    }
    // 传入任意数量的参数
    generic.printNames("lucy", "grace", "john", "张三");
    
  2. 泛型参数会被编译器转型为一个数组,因此可以当做普通的数组进行遍历。

    public void printNames(String... names) {
          
          
        for (int i=0;i<names.length;i++){
          
          
            System.out.print(names[i] + " ");
        }
    }
    
  3. 可变参数规定了传入的参数类型必需是一致的,可以将泛型方法与可变参数结合,传入不同类型的参数。

    public <T> void printNumbers(T... args) {
          
          
        for (T arg : args) {
          
          
            System.out.print(arg + " ");
        }
    }
    // 方法的调用
    generic.printNumbers("12.3", 24, 24.5, 2.4f);
    
2.3 泛型接口
  • 泛型接口与泛型的定义基本一致

    //定义一个泛型接口
    public interface Generator<T> {
          
          
        public T next();
    }
    
  • 如何实现泛型接口?

  1. 泛型类在实现泛型接口时,未向泛型接口传入泛型实参。则在定义泛型类时,需要为该类加入泛型声明。

    class GenericClass2<T> implements GenericInterface<T>{
          
          
        @Override
        public T next() {
          
          
            return null;
        }
    }
    
  2. 泛型类在实现泛型接口时,向泛型接口传入了泛型实参。则在定义泛型类时是,无需为该类加入泛型声明。

    class GenericClass1 implements GenericInterface<String>{
          
          
        @Override
        public String next() {
          
          
            return null;
        }
    }
    

3. 泛型通配符

3.1 泛型通配符概述
  • 泛型通配符可以使用A-Z的任意一个字符,不影响泛型的效果,但会影响理解。

    class GenericClass2<A> implements GenericInterface<A>{
          
          
    
        @Override
        public A next() {
          
          
            return null;
        }
    }
    
  • 通常使用的通配符包括TKVE?,它们有一些约定俗成的含义:

  1. ?表示不确定的 Java 类型。
  2. T (type) 表示具体的一个Java类型
  3. KV ,即key、value,表示Java键值中的健和值。
  4. E (element) 代表
3.2 无界通配符?
  • 当传入的类型不确定时,可以使用无界通配符?,它可以接收所有未知类型的泛型
    public void printList(List<?> list){
          
          
        list.add("2845"); // 编译报错
        list.add(null); // 运行时错误,java.lang.UnsupportedOperationException
        String j = list.get(0); //编译期报错
        for (Object obj:list){
          
          
            System.out.print(obj+" ");
        }
    }
    
  • ?使用注意事项:
  1. 只能往使用List<?>里添加null,但运行时会报错。
  2. 由于不知道List<?>中元素的具体类型,只能对其进行读操作,且读取的元素应作为Object对象进行操作。
3.3 上界通配符<? extends T>
  • <? extends T>用于表示传入的泛型参数必需是类T或类T的子类/接口T或T的实现类。
    在这里插入图片描述

  • 例如,<? extends Number>传入的参数必需是Number及其子类。

    List<Integer> list1=Arrays.asList(1,2,3,4);
    List<String> list2=Arrays.asList("sunrise","john","grace");
    getList(list1);
    getList(list2); // 编译报错,类型不兼容
    
    public static List getList(List<? extends Number> list){
          
          
        return list;
    }
    
  • PESC原则: 上界通配符,一般用于读取的场景。

  1. 为泛型指定的类型只能是T类型或者其子类。
  2. 只能为其列表添加null
  3. get方法获取的值只能赋值给T类或者其超类。
3.4 下界通配符<? super T>
  • <? super T>表示传入的泛型参数必需是类T或类T的父类。
    在这里插入图片描述
  • 例如,List<? super Integer>传入的参数必须是Integer或其父类。
    List<Number> list=Arrays.asList(12,13,45);
    getSuperList(list);
    List<String> list2=Arrays.asList("sunrise","john","grace");
    getSuperList(list2); // 编译报错
    
    public static List getSuperList(List<? super Integer> list){
          
          
        return list;
    }
    
  • PECS原则: 下界通配符,一般用于写入的场景。
  1. 为泛型指定的类型必须为T,或者其超类。
  2. 可以为其列表添加任意T类型,或者其子类。
  3. get方法获取的类型,只能赋值给Object类型。
  1. 上界<? extends T>不能往里存,只能往外取,适合频繁往外面读取内容的场景。
  2. 下界<? super T>不影响往里存,但往外取只能放在Object对象里,适合经常往里面插入数据的场景。

后续学习:

猜你喜欢

转载自blog.csdn.net/u014454538/article/details/108196646