Java泛型详解

文章目录

  • 01 什么是泛型
  • 02 泛型类
    • 泛型类的使用
    • 泛型的派生子类
  • 03 泛型接口
  • 04 泛型方法
  • 05 类型通配符
  • 06 类型擦除
  • 07 泛型和数组
  • 08 泛型和反射


01 什么是泛型

泛型产生的背景:Java推出泛型以前,是构建一个元素类型为 Object 的集合,该集合能够存储任意的对象,而在使用该集合的过程中,需要明确知道存储每个元素的数据类型,否则很容易引发 ClassCastException 异常(类型转换异常)。

  • 泛型的概念

       Java泛型(generics)是 JDK5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型数据结构。

       泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。

  • 泛型相当于提供了一个安全限制,不符合类型转换的会报错
import java.util.ArrayList;

public class Test01 {
    public static void main(String[] args) {
        /*   
        ArrayList list = new ArrayList();//创建一个集合
        list.add("java");//默认数据类型为Object
        list.add(100);
        list.add(true);

        for (int i = 0 ;i < list.size();i++){
            Object o = list.get(i);
            String str = (String)o;
            System.out.println(str); //会报错 ClassCastException
        }  */

        //使用泛型(作为一个安全检测限制)
        ArrayList<String> strList = new ArrayList<>();
        strList.add("a");
        strList.add("b");
        strList.add("c");

        for (int i = 0; i < strList.size(); i++) {
            String s = strList.get(i);
            System.out.print(s);//abc
        }
        //泛型的好处:编译期间检查类型,减少了数据类型转换
        // <> 数据类型作为参数 
        ArrayList<Integer> intList = new ArrayList<>();
        intList.add(100);//自动转换为int类型
        //如果输入浮点数、字符串等其他类型就会报错
    }
}

02 泛型类

泛型类的使用

泛型类的定义语法:

class 类名称 < 泛型标识,泛型标识 ,... > {
	private 泛型标识 变量名;
	....
}
  • 泛型标识可以理解为类型的形参
  • 常用的泛型标识: T 、 E 、 K 、 V
    • E ----Element(在集合中使用,因为集合中存放的是元素)

    • T ----Type(Java类)

    • K ----Key(键)

    • N ----Number(数值类型)

    • V ----Value(值)

    • ?----表示不确定的java类型

      • 这些标记并不是限定只有对应的类型才能使用,即使你统一使用A-Z英文字母的其中一个,编译器也不会报错。之所以又不同的标记符,这是一种约定
/*
泛型类的定义:
     泛型标识--类型形参
     T 创建对象的时候里指定具体的数据类型
 */
public class Test02 <T>{
    // T 是由外部使用类的时候来指定
    private T key;
    
    public Test02 (T key) {
        this.key = key;
    }
   
    public T getKey(){
        return key;
    }
   
    public void setKey(T key){
        this.key = key;
    }

    @Override
    public String toString() {
        return "Test02{" +
                "key=" + key +
                '}';
    }
}

泛型类的使用:

  • 使用语法:
类名<具体的数据类型> 对象名 = new 类名 <具体的数据类型>();
  • Java1.7以后,后面的<>中的具体的数据类型可以省略不写
类名<具体的数据类型> 对象名 = new 类名<>();
/*
泛型类的定义:
     泛型标识--类型形参
     T 创建对象的时候里指定具体的数据类型
 */
public class Test02 <T>{
    // T 是由外部使用类的时候来指定

    public static void main(String[] args) {
        //泛型类在创建对象的时候,来指定操作的具体数据类型
        Test02<String> strTest = new Test02<>("abc");//只能传指定的类型数据,不然会报错
        String key1 = strTest.getKey();
        System.out.println(key1);//abc
        //定义一个泛型类,可以操作不同的数据类型,减少了转换,提高了代码的复用率
        Test02<Integer> intTest = new Test02<>(100);//只能传指定的类型数据,不然会报错
        int key2 = intTest.getKey();
        System.out.println(key2);//100
        //泛型类不支持基本数据类型(如错误),只支持类类型(来继承Object类接收类型转换)

        //泛型类在创建对象的时候,没有指定类型,将按照Object类型来操作
        Test02 test = new Test02("ABC");
        Object key3 = test.getKey();
        System.out.println(key3);//ABC

        System.out.println(strTest.getClass());//class com.practice.fanxing.Test02
        System.out.println(intTest.getClass());//class com.practice.fanxing.Test02
        System.out.println(intTest.getClass() == strTest.getClass());//true 说明就是同一个类
        //同一泛型类,根据不同类型的数据创建的对象,本质上是同一类型
    }
    private T key;

    public Test02 (T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }

    public void setKey(T key){
        this.key = key;
    }

    @Override
    public String toString() {
        return "Test02{" +
                "key=" + key +
                '}';
    }
}

泛型类注意事项:

  1. 泛型类,如果没有指定具体的数据类型,此时操作类型是Object;
  2. 泛型的类型参数只能是类类型,不能是基本数据类型;
  3. 泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型;

泛型的派生子类

  • 子类也是泛型类,子类和父类的泛型类型要一致;
class ChildGeneric<T> extends Generic<T>;

Java泛型详解_第1张图片

  • 子类不是泛型类,父类要明确泛型的数据类型;
class ChildGeneric extends Generic<String>;

Java泛型详解_第2张图片


03 泛型接口

  • 泛型接口的定义语法:
interface 接口名称 <泛型标识,泛型标识,...> {
	泛型标识 方法名();
	   ...
}
  • 泛型接口的使用:
    • 实现类不是泛型类,接口要明确数据类型;
    • 实现类也是泛型类,实现类和接口的泛型类型要一致;

实现类不是泛型类:


实现类是泛型类:
Java泛型详解_第3张图片


04 泛型方法

  • 泛型类,是在实例化类的时候指明泛型的具体类型;
  • 泛型方法,是在调用方法的时候指明泛型的具体类型;

语法:

修饰符 <T,E,...> 返回值类型 方法名(形参列表){
	方法体...
}
  • public和返回值中间的 < T > 可以理解为声明此方法为泛型方法;
  • 只有声明了 < T > 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法;
  • < T > 表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T;
  • 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如 T、E、K、V 等形式的参数常用于表示泛型;

Java泛型详解_第4张图片

public class Method01<E> {

    //这不是泛型方法
    public E method00(){return null;}
    /* 不能声明为静态Static的 */
    
    //这才是泛型方法 可以声明为静态的Static
    public <E> E method01 ( E input ){
        return input;
    }

    public static void main(String[] args) {
        Method01<String> str = new Method01<>();
        Method01<Integer> integer = new Method01<>();
        String s = str.method01("Hello");
        Integer i = integer.method01(100);
        System.out.println(s);//Hello
        System.out.println(i);//100
    }
}

泛型方法和可变参数:

public <E> void print (E... e){ //就是加上三个点
	for (E eee : e){
	System.out.println(e);
	}
}

Java泛型详解_第5张图片

  • 泛型方法能使方法独立于类而产生变化;
  • 如果 static 方法要使用泛型能力,就必须要定义为泛型方法;

05 类型通配符

什么是类型通配符:

  • 类型通配符一般是使用 < ? > 代替具体的类型实参;
  • 所以,类型通配符是类型实参,而不是类型形参;

为什么要用通配符:

  • 例如一个方法定义了< Number >类型,而调用时声明的< Integer >类型(虽然Integer继承Number,所以也没法重写)会报错,这时想支持多种类型,可以用通配符 < ? >;
  • < ? > 定义的 用 Object 类型来接收
public static void show(Box<?> box){
	 Object value = box.getValue();
}

类型通配符的上限:

  • 语法
/接口 <? extends 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的子类类型;
(也就是泛型的类型最高的上限)

public static void show(Box<? extends Number> box){
	 Number value = box.getValue();//类型最大为Number
}

类型通配符的下限:

  • 语法
/接口 <? super 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的父类类型;


06 类型擦除

       泛型是Java1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容,是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称为----类型擦除

import java.util.ArrayList;

public class Test04 {
    public static void main(String[] args) {
        ArrayList<Integer> intList = new ArrayList<>();
        ArrayList<String> strList = new ArrayList<>();
        // 输出类型是一样的,说明经过了类型擦除
        System.out.println(intList.getClass().getSimpleName());//ArrayList
        System.out.println(strList.getClass().getSimpleName());//ArrayList
        System.out.println(intList.getClass() == strList.getClass());//true
    }
}

Java泛型详解_第6张图片
Java泛型详解_第7张图片

Java泛型详解_第8张图片
Java泛型详解_第9张图片

Java泛型详解_第10张图片

Java泛型详解_第11张图片
Java泛型详解_第12张图片

  • 当重写的方法返回值是父类方法返回值的子类时会生成一个桥接方法来实现父类的重写

Java泛型详解_第13张图片

  • 实现类对接口的方法进行了重写实现,桥接方法又对重写的方法再重写,保证输入和返回值和接口的一致

07 泛型和数组

泛型数组的创建:

  • 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象;
import java.util.ArrayList;

//泛型和数组
public class Test07 {
    public static void main(String[] args) {
      /*  ArrayList[] listArray = new ArrayList[5];
                 可以声明带泛型的数组        但不能直接创建带泛型的数组对象
       */
        ArrayList[] list1 = new ArrayList[5];
        ArrayList<String>[] listArray1 = list1;
        //先创建一个普通数组,再赋给泛型数组
        //   但这样是有弊端的,不安全,
        // 因为定义了是String类型的,如果给原生list1赋值传递Integer类型会导致类型转换异常
        // 把list1隐藏起来,就可以解决,因为编译器会进行泛型检查
        ArrayList<String>[] listArray2 = new ArrayList[5];
        
        ArrayList<String> strList = new ArrayList<>();
        strList.add("abc");
        listArray2[0] = strList;
        String s = listArray2[0].get(0);
        System.out.println(s);
    }
}

  • 可以通过 java.lang.reflect.Array 的 newInstance (Class< T >,int) 创建 T [ ] 数组;

Java泛型详解_第14张图片
先用一个泛型类来封装一个泛型数组

import java.lang.reflect.Array;

//泛型会经历一个擦除,但数组会从始至终保持数据类型;
//所以JDK不允许直接创建带泛型的数组;
//可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
public class Test08<T> {
    private T[] array;
    //通过构造方法创建泛型数组  传递一个泛型类和数组长度
    public Test08(Class<T> clz,int length){
        array = (T[])Array.newInstance(clz,length);//需要强转
    }
    //封装起来
    public void put(int index,T item){
        array[index] = item;  //填充数组
    }

    public T get(int index){
        return array[index]; //获取数组元素
    }

    public T[] getArray(){
        return array;  //获取整个数组
    }
}

再通过调用来操作数组

import java.util.Arrays;

public class Test09 {
    public static void main(String[] args) {
        Test08<String> arr = new Test08<>(String.class,3);
        arr.put(0,"apple");
        arr.put(1,"banana");
        arr.put(2,"orange");
        //先获取数组再通过Arrays.toString方法全部打印出来 (就不用遍历)
        System.out.println(Arrays.toString(arr.getArray()));
           // [apple, banana, orange]
        String s1 = arr.get(2);
        System.out.println(s1);//orange
    }
}

08 泛型和反射

反射常用的泛型类:

  • Class < T >
  • Constructor < T >

Java泛型详解_第15张图片

你可能感兴趣的:(Java学习,java,jvm,开发语言)