BlazeMaple BlazeMaple
首页
  • 基础知识

    • Java的基本数据类型
    • Java中的常用类String
    • Java中的异常
    • Java中的注解
    • Java中的反射机制
    • Java中的泛型
    • Java为什么是值传递
  • 集合框架

    • Java集合核心知识总结
    • HashMap的7种遍历方式
    • 源码分析
  • Java新特性

    • Java8新特性
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
  • JVM

    • JVM基础总结
  • MySQL

    • MySQL核心知识小结
    • MySQL 45讲
  • Redis

    • Redis核心入门知识简记
  • Spring
  • SpringCloud Alibaba
  • 开发工具

    • Git详解
    • Maven详解
    • Docker详解
    • Linux常用命令
  • 在线工具

    • json (opens new window)
    • base64编解码 (opens new window)
    • 时间戳转换 (opens new window)
    • unicode转换 (opens new window)
    • 正则表达式 (opens new window)
    • md5加密 (opens new window)
    • 二维码 (opens new window)
    • 文本比对 (opens new window)
  • 学习资源

    • 计算机经典电子书PDF
    • hot120
GitHub (opens new window)
首页
  • 基础知识

    • Java的基本数据类型
    • Java中的常用类String
    • Java中的异常
    • Java中的注解
    • Java中的反射机制
    • Java中的泛型
    • Java为什么是值传递
  • 集合框架

    • Java集合核心知识总结
    • HashMap的7种遍历方式
    • 源码分析
  • Java新特性

    • Java8新特性
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
  • JVM

    • JVM基础总结
  • MySQL

    • MySQL核心知识小结
    • MySQL 45讲
  • Redis

    • Redis核心入门知识简记
  • Spring
  • SpringCloud Alibaba
  • 开发工具

    • Git详解
    • Maven详解
    • Docker详解
    • Linux常用命令
  • 在线工具

    • json (opens new window)
    • base64编解码 (opens new window)
    • 时间戳转换 (opens new window)
    • unicode转换 (opens new window)
    • 正则表达式 (opens new window)
    • md5加密 (opens new window)
    • 二维码 (opens new window)
    • 文本比对 (opens new window)
  • 学习资源

    • 计算机经典电子书PDF
    • hot120
GitHub (opens new window)
  • 基础知识

    • Java的基本数据类型
    • 聊一聊Java中的常用类String
    • 聊一聊Java中的异常
    • 聊一聊Java中的注解
    • 聊一聊Java中的反射机制
    • 聊一聊Java中的泛型
      • 聊一聊Java为什么是值传递
    • 集合框架

      • Java集合核心知识总结
      • HashMap 的 7 种遍历方式
      • 源码分析

        • ArrayList源码分析
        • LinkedList源码分析
        • HashMap源码分析
        • ConcurrentHashMap源码分析
        • CopyOnWriteArrayList 源码分析
        • LinkedHashMap 源码分析
        • ArrayBlockingQueue 源码分析
        • PriorityQueue 源码分析
        • DelayQueue 源码分析
    • Java新特性

      • Java8新特性
    • Java基础
    • 基础知识
    BlazeMaple
    2023-10-31
    目录

    聊一聊Java中的泛型

    # 什么是泛型,有什么作用?

    Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList<Person> persons = new ArrayList<Person>() 这行代码就指明了该 ArrayList 对象只能传入 Person 对象,如果传入其他类型的对象就会报错。

    ArrayList<E> extends AbstractList<E>
    
    1

    并且,原生 List 返回类型是 Object ,需要手动转换类型才能使用,使用泛型后编译器自动转换。

    # 泛型的使用方式有哪几种?

    泛型一般有三种使用方式:泛型类、泛型接口、泛型方法

    1.泛型类:

    //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
    //在实例化泛型类时,必须指定T的具体类型
    public class Generic<T>{
    
        private T key;
    
        public Generic(T key) {
            this.key = key;
        }
    
        public T getKey(){
            return key;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    如何实例化泛型类:

    Generic<Integer> genericInteger = new Generic<Integer>(123456);
    
    1

    2.泛型接口:

    public interface Generator<T> {
        public T method();
    }
    
    1
    2
    3

    实现泛型接口,不指定类型:

    class GeneratorImpl<T> implements Generator<T>{
        @Override
        public T method() {
            return null;
        }
    }
    
    1
    2
    3
    4
    5
    6

    实现泛型接口,指定类型:

    class GeneratorImpl<T> implements Generator<String>{
        @Override
        public String method() {
            return "hello";
        }
    }
    
    1
    2
    3
    4
    5
    6

    3.泛型方法:

     public static < E > void printArray( E[] inputArray ){
         for ( E element : inputArray ){
             System.out.printf( "%s ", element );
         }
         System.out.println();
     }
    
    1
    2
    3
    4
    5
    6

    使用:

    // 创建不同类型数组:Integer, Double 和 Character
    Integer[] intArray = { 1, 2, 3 };
    String[] stringArray = { "Hello", "World" };
    printArray( intArray  );
    printArray( stringArray  );
    
    1
    2
    3
    4
    5

    注意: public static < E > void printArray( E[] inputArray ) 一般被称为静态泛型方法;在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的 <E>

    # 什么是桥方法

    桥方法是继承泛型类并指定泛型类型后,IDE会自动为我们创建构造方法调用父类的有参构造函数确保泛型多态类。

    泛型类,指定了一个有参构造函数

    public class Node<T> {
    
        public T data;
    
        public Node(T data) {
            this.data = data;
        }
    
        public void setData(T data) {
            System.out.println("Node.setData");
            this.data = data;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    实现类,自动补充构造方法并调用父类构造方法确保实现泛型的多态性。

    public class MyNode extends Node<Integer>{
    
        //继承泛型类后自动添加的,用于保证泛型的多态性
        public MyNode(Integer data) {
            super(data);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7

    # 泛型有哪些限制?

    1. 泛型不可以被实例化,泛型会在编译器擦除,所以泛型在编译器还未知,所以不可被实例化

    2. 泛型参数不可以是基本类型

    3. 不能声明泛型错误

      如下图所示,泛型会在编译器被擦除,那么下面这段代码的catch就等于catch两个一样的错误,出现执行矛盾。

      try{
      }catch(Problem<String> p){
      }catch(Problem<Object> p){
      }
      
      1
      2
      3
      4
    4. 不能声明两个参数一样泛型不同的方法,编译器擦除后,参数一样,所以编译失败

    5. 泛型不能声明为static。泛型只有在类创建时才知晓,而静态变量在类加载无法知晓,故无法通过编译、

    # 泛型通配符

    # 什么是通配符,它用于解决什么问题

    通配符是解决泛型之间无法协变的问题,当使用一种类型作为泛型参数时,却无法使用他的父类或者子类进行赋值,通配符就是解决这种问题的对策。

    # 上界通配符

    不知道子类的具体类型,上界通配符就是用于解决那些父类引用指向子类泛型引用的场景

    定义父类

    /**
     * 水果父类
     */
    public class Fruit {
    }
    
    1
    2
    3
    4
    5

    子类代码

    /**
     * 水果的子类 苹果
     */
    public class Apple extends Fruit {
    }
    
    1
    2
    3
    4
    5

    容器类代码

    /**
     * 容器类
     * @param <T>
     */
    public class Container<T> {
        private T data;
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    测试代码,如下所示上界通配符使得苹果类可以作为水果类的指向引用。

    /**
     * 泛型测试
     */
    public class TestParttern {
        public static void main(String[] args) {
            Container<? extends Fruit> container=new Container<Apple>();
            Fruit data = container.getData();
            container.setData(new Apple());
       //这里set报错:'setData(capture<? extends com.XX.Fruit>)' in 'com.XX' cannot be applied to '(com.XX.Apple)'
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 为什么上界通配符只能get不能set

    如上代码所示,当我们用上界通配符? extends Fruit,我们用其子类作为泛型参数,这只能保证我们get到的都是这个子类的对象。但我们却忘了一点,当我们用子类apple作为泛型参数时,泛型的工作机制仅仅是对这个对象加个一个编号CAP#1,当我set一个新的对象,编译器无法识别这个对象类型是否和编号匹配。更通俗的理解,上界通配符决定可以指向的容器,但是真正使用是并不知晓这个容器是哪个子类容器。所以无法set。

    # 下界通配符

    这里使用的对象还是上述对象,只不过通配符改为下界通配符

    /**
     * 泛型测试
     */
    public class TestParttern {
    
        public static void main(String[] args) {
            Container<? super Apple> container1=new Container<Fruit>();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 为什么下界通配符只能set不能get(或者说get的是object)

    1. 下界通配符决定泛型的类型上限,所有水果类的父亲都可以作为指向的引用
    2. get时无法知晓其具体为哪个父亲,所以取出来的类型只能是object
     Container<? super Apple> container1=new Container<Fruit>();
     Object data = container1.getData();
    
    1
    2

    # 如何获取泛型类型

    public class GenericType<T> {
        private T data;
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public static void main(String[] args) {
        	//注意这个类要使用子类,笔者为了方便期间使用了 {}
            GenericType<String> genericType = new GenericType<String>() {};
            Type superclass = genericType.getClass().getGenericSuperclass();
            //getActualTypeArguments 返回确切的泛型参数, 如Map<String, Integer>返回[String, Integer]
            Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; 
            System.out.println(type);//class java.lang.String
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    帮助我们改善此页面! (opens new window)
    上次更新: 2024/08/13, 09:07:12
    聊一聊Java中的反射机制
    聊一聊Java为什么是值传递

    ← 聊一聊Java中的反射机制 聊一聊Java为什么是值传递→

    最近更新
    01
    SpringCloud Alibaba实战
    08-22
    02
    SpringCloud Alibaba核心知识
    08-22
    03
    两数之和
    08-08
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 BlazeMaple
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式