Skip to content

泛型

传统方法的问题

  1. 不能对加入集合的元素进行数据类型的约束
  2. 遍历的时候需要进行类型转换,如果集合的数据量太大,会对效率有影响
java
@Data
class Dog{
    private String name;
    private int age;
}
@Date
class Cat{
    private String name;
    private int age;
}
java
import java.util.ArrayList;
import java.util.List;

public class Introduce {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Dog("旺财",20));
        arrayList.add(new Dog("小航",10));
        arrayList.add(new Dog("小驴",1));

        arrayList.add(new Cat("小驴",1));
        for (Object o : arrayList) {
            Dog dog = (Dog) o;
            System.out.println(dog.getName()+"-"+dog.getAge());
        }
    }
    }

泛型的好处

  • 编译时检查添加元素的类型 提高了安全性
  • 减少了类型转换的次数
  • 如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:
    java
    private static <T extends Number> double add(T a, T b) {
        System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
        return a.doubleValue() + b.doubleValue();
    }
java
public class Generic{
    public static void main(String[] args) {
        ArrayList<Dog> arrayList = new ArrayList<Dog>();
        arrayList.add(new Dog("旺财",20));
        arrayList.add(new Dog("小航",10));
        arrayList.add(new Dog("小驴",1));

//        arrayList.add(new Cat("小驴",1));  不能加入
        for (Dog dog : arrayList) {
            System.out.println(dog.getName()+"-"+dog.getAge());
        }
    }
    }

Generic

  1. 泛型 又称 参数化类型 是jdk5以后出现的特性,解决了数据类型的安全问题
  2. 在类声明或者实例化的时候只需要指定具体的类型即可
  3. java泛型可以保证在编译时没有发出警告,在运行时就不会有ClassCastException
  4. 可以在类声明时通过一个标识来表示 类中某个属性的类型
  5. 方法的返回值类型
  6. 参数类型
java
class Person<E>{
    E s;  //在定义person对象时指定,在编译期间就确定了e的类型

    public Person(E s) {
        this.s= s;
    }

    public E method(){
        return s;
    }

    public void show(){
        System.out.println(s.getClass());
    }
}
java
public class Introduce{
    public static void main(String[] args) {
        Person<String> person1 = new Person<String>("hh");
        person1.show();
        Person<Integer> person2 = new Person<Integer>(100);
        person2.show();
    }
}
java
run:
class java.lang.String
class java.lang.Integer

泛型的声明

TKV 表示类型 可以有多个

TKV只能是引用类型

指定泛型具体类型后,可以传入其子类型

java
class Animal{

}
class Dog extends Animal{
}

public class Introduce{
    public static void main(String[] args) {
        Person<Animal> person1 = new Person<>(new Animal());
        Person<Animal> person2 = new Person<>(new Dog());
   //后面<>可以不写,编译器会进行类型推断
 }
}
java
ArrayList arrayList = new ArrayList()

//ArrayList<Object> arrayList = new ArrayList()
//默认泛型是Object

泛型的实例化

在类名后面指定参数的类型

自定义泛型类 class Person

  1. 普通成员可以使用泛型(
  • 某个属性的类型
  • 方法的返回值类型
  • 参数类型)
  1. 使用泛型的数组不能初始化 数组在new不能确定T的类型,就无法在内存中开辟空间
java
T  array = new T[8];//错误
  1. 静态方法中不能使用泛型 静态是和类相关的,静态方法在类加载(在类new 之前,对象还没有创建)的时候就会被调用,不能确定 T的类型
java
static R r ; //错

静态成员也不能使用泛型泛型接口的类型

继承和实现接口时确定没有指定类型,默认是object

自定义泛型接口

java
public interface Generator<T> {
    public T method();
}

自定义泛型方法

修饰符 <T,R> 返回类型 方法名 (参数)

  1. 可以在普通类 也可以在泛型类
  2. 方法被调用时 类型会确定
java
//自定义泛型类
class car<T,E>{
//    自定义泛型方法    U M 提供给方法使用的
    public <U,M> void run(U u,M m){
    }
}

public static void main[String[] args]{
    Car car = new Car();
       car.fly("宝马",20);//在调用的时候确定类型  U  M
//传入参数编译器会自动确定什么类型
}
public static < E > void printArray( E[] inputArray )
   {
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
java
public static < E > void printArray( E[] inputArray )
   {
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的 <E>

泛型的继承性和通配符

泛型没有继承性

<?>支持任意泛型

<? extends A> 支持A及A的子类 规定了泛型的上限

<? super A> 支持A已及A的父类 规定了泛型的下限

语法糖

Java 虚拟机并不支持语法糖。语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖

java
Map<String, String> map = new HashMap<String, String>();
map.put("name", "hollis");
map.put("wechat", "Hollis");
map.put("blog", "www.hollischuang.com");

解语法糖

java
Map map = new HashMap();
map.put("name", "hollis");
map.put("wechat", "Hollis");
map.put("blog", "www.hollischuang.com");

泛型擦除

在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型)

在运行时期间,JVM是不知道泛型信息的,所有的泛型类和方法都会被当作它们的原生类型(Raw Type)来处理

java
package com.jasper.generic;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class Demo1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        Method add = list.getClass().getDeclaredMethod("add", Object.class);
        add.invoke(list,"jasper");
        System.out.println(list);
    }
}
java
[1, jasper]
  • 与使用Object相比,提供了一种扩展能力,参数可以由外部传递进来
  • 安全检测机制,确定类型后传值不匹配会报错
  • 增加了代码的可读性

一、当泛型遇到重载

public class GenericTypes {

    public static void method(List<String> list) {
        System.out.println("invoke method(List<String> list)");
    }

    public static void method(List<Integer> list) {
        System.out.println("invoke method(List<Integer> list)");
    }
}

上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List<String>另一个是List<Integer> ,但是,这段代码是编译通不过的。因为我们前面讲过,参数List<Integer>List<String>编译之后都被擦除了,变成了一样的原生类型 List,擦除动作导致这两个方法的特征签名变得一模一样。

如何理解基本类型不能作为泛型类型?

比如,我们没有ArrayList<int>,只有ArrayList<Integer>, 为何?

因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。

另外需要注意,我们能够使用list.add(1)是因为Java基础类型的自动装箱拆箱操作。

泛型擦除和多态的冲突

虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。

todo