首页 > 其他分享 >泛型

泛型

时间:2024-07-13 23:22:20浏览次数:13  
标签:last Pair 类型 泛型 Integer public first

自定义泛型

class MyArrayList<E> {
    private Object[] elementData;
    private int size = 0;

    public MyArrayList(int initialCapacity) {
        this.elementData = new Object[initialCapacity];
    }

    public boolean add(E e) {
        elementData[size++] = e;
        return true;
    }

    E elementData(int index) {
        return (E) elementData[index];
    }

    // <T>表示泛型方法
    // T[]表示方法返回类型
    // T[] a 表示方法参数类型
    public <T> T[] toArray(T[] a) {
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    }
}
  • Pair类
class Pair<T> {
    private T first;
    private T last;

    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }

    public T getFirst() {
        return first;
    }

    public T getLast() {
        return last;
    }

    public void setFirst(T first) {
        this.first = first;
    }

    public void setLast(T last) {
        this.last = last;
    }
}

泛型擦除

  • 泛型信息只存在于代码编译阶段,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程为类型擦除

  • Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型

  • 编译器看到的代码

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
  • 虚拟机执行的代码
public class Pair {
    private Object first;
    private Object last;
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    public Object getFirst() {
        return first;
    }
    public Object getLast() {
        return last;
    }
}

Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
  • 泛型擦除导致编译器把类型<T>视为Object;编译器根据<T>实现安全的强制转型

java泛型局限性

  1. <T>不能是基本类型,例如int,因为实际类型是ObjectObject类型无法持有基本类型
Pair<int> p = new Pair<>(1, 2); // compile error!
  1. 无法取得带泛型的Class,例如:Pair<String>.class
public class Test2 {
    public static void main(String[] args) {
        Pair<String> p1 = new Pair<>("Hello", "world");
        Pair<Integer> p2 = new Pair<>(123, 456);
        // 对Pair<String>和Pair<Integer>类型获取Class时,获取到的是同一个Class,也就是Pair类的Class
        Class c1 = p1.getClass();
        Class c2 = p2.getClass();
        // class test.genericTypeTest.Pair
        System.out.println(c1);
        System.out.println(c1 == c2); // true
        System.out.println(c1 == Pair.class); // true
        // 无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>
    }
}
  1. 无法判断带泛型的类型,例如:x instanceof Pair<String>
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>) {
}
  • 并不存在Pair<String>.class,而是只有唯一的Pair.class
  1. 不能实例化T类型,例如:new T()
public class Pair<T> {
    private T first;
    private T last;

    public Pair() {
        // Compile error:
        first = new T();
        last = new T();
        // 擦除后会变成
        // first = new Object();
		// last = new Object();
        // 创建new Pair<String>()和创建new Pair<Integer>()就全部成了Object
    }
}
  • 如何实例化T类型
class Pair<T> {
    private T first;
    private T last;

    // 借助Class<T>参数并通过反射来实例化T类型,使用的时候,也必须传入Class<T>。
    // Pair<String> pair = new Pair<>(String.class);
    public Pair(Class<T> clazz) throws InstantiationException, IllegalAccessException {
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}
public static void main(String[] args) throws Exception {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(6);

    //反射机制实现
    Class<? extends ArrayList> clazz = list.getClass();
    Method add = clazz.getDeclaredMethod("add", Object.class);
    add.invoke(list, "哈哈哈");
    System.out.println("list = " + list);
}

泛型继承

  • 无法获取Pair<T>T类型,即给定一个变量Pair<Integer> p,无法从p中获取到Integer类型。
  • 但是,在父类是泛型类型的情况下,编译器就必须把类型T(对IntPair来说,也就是Integer类型)保存到子类的class文件中,不然编译器就不知道IntPair只能存取Integer这种类型。在继承了泛型类型的情况下,子类可以获取父类的泛型类型。
public class Test2 {
    public static void main(String[] args) {
        Class<IntPair> clazz = IntPair.class;
        Type t = clazz.getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) t;
            // 可能有多个泛型类型
            Type[] types = pt.getActualTypeArguments();
            // 取第一个泛型类型
            Type firstType = types[0];
            Class<?> typeClass = (Class<?>) firstType;
            // Integer
            System.out.println(typeClass);
        }

    }
}

class IntPair extends Pair<Integer> {
    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}

泛型通配符

常用通配符的含义

  • T (Type) 具体的Java类
  • E (Element)在集合中使用,因为集合中存放的是元素
  • K V (key value) 分别代表java键值中的Key Value
  • N (Number)数值类型
  • ? 表示不确定的 Java 类型

上界通配符< ? extends E>

public class Test2 {
    public static void main(String[] args) {
        // 传入的类型是Pair<Number>,实际参数类型是(Integer, Integer)
        int sum = add(new Pair<Number>(1, 2));

        // 既然实际参数是Integer类型,试试传入Pair<Integer>
        Pair<Integer> p = new Pair<>(123, 456);
        // 编译错误
        sum = add(p);
        // Pair<Integer>不是Pair<Number>的子类,因此,add(Pair<Number>)不接受参数类型Pair<Integer>。
        
    }

    static int add(Pair<Number> p) {
        // 从add()方法的代码可知,传入Pair<Integer>是完全符合内部代码的类型规范
        // 实际类型是Integer,引用类型是Number,没有问题。问题在于方法参数类型定死了只能传入Pair<Number>
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}
  • 修改后
// 把泛型类型T的上界限定在Number了
// 除了可以传入Pair<Integer>类型,我们还可以传入Pair<Double>类型,Pair<BigDecimal>类型等等,因为Double和BigDecimal都是Number的子类。
static int add(Pair<? extends Number> p) {
    // 实际的方法签名变成<? extends Number> getFirst();
    // 即返回值是Number或Number的子类,因此,可以安全赋值给Number类型的变量
    Number first = p.getFirst();
    Number last = p.getLast();
    return first.intValue() + last.intValue();
}
  • 之后就不能预测实际类型就是Integer,因为实际的返回类型可能是Integer,也可能是Double或者其他类型,编译器只能确定类型一定是Number的子类(包括Number类型本身),但具体类型无法确定。
// 报错
Integer x = p.getFirst();
public class Test2 {
    public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

    static int add(Pair<? extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        // 编译出错
        // 方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)。除了null
        p.setFirst(new Integer(first.intValue() + 100));
        p.setLast(new Integer(last.intValue() + 100));
        // 如果我们传入的p是Pair<Double>,显然它满足参数定义Pair<? extends Number>,然而,Pair<Double>的setFirst()显然无法接受Integer类型。
        return p.getFirst().intValue() + p.getFirst().intValue();
    }

}
  • java.util.List<T>接口
public interface List<T> {
    int size(); // 获取个数
    T get(int index); // 根据索引获取指定元素
    void add(T t); // 添加一个新元素
    void remove(T t); // 删除一个已有元素
}
// 从方法内部代码看,传入List<? extends Integer>或者List<Integer>是完全一样的
int sumOfList(List<? extends Integer> list) {
    int sum = 0;
    for (int i=0; i<list.size(); i++) {
        Integer n = list.get(i);
        sum = sum + n;
    }
    return sum;
}
  • List<? extends Integer>的限制
    • 允许调用get()方法获取Integer的引用;
    • 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。
  • 因此,方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)remove(? extends Integer)这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法(恶意调用set(null)除外)。

下界通配符< ? super E>

// 传入Pair<Integer>是允许的,但是传入Pair<Number>是不允许的
void set(Pair<Integer> p, Integer first, Integer last) {
    p.setFirst(first);
    p.setLast(last);
}
  • 这次,我们希望接受Pair<Integer>类型,以及Pair<Number>Pair<Object>,因为NumberObjectInteger的父类,setFirst(Number)setFirst(Object)实际上允许接受Integer类型。
// 使用super通配符来改写这个方法
// Pair<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型
void set(Pair<? super Integer> p, Integer first, Integer last) {
    p.setFirst(first);
    p.setLast(last);
}
  • 考察Pair<? super Integer>setFirst()方法,它的方法签名实际上是:
void setFirst(? super Integer);
  • 考察Pair<? super Integer>getFirst()方法,它的方法签名实际上是:
? super Integer getFirst();
// 无法通过编译
Integer x = p.getFirst();
// 唯一可以接收getFirst()方法返回值的是Object类型
Object obj = p.getFirst();
  • 使用<? super Integer>通配符表示:

    • 允许调用set(? super Integer)方法传入Integer的引用;

    • 不允许调用get()方法获得Integer的引用。除了Object o = p.getFirst()

​ 使用<? super Integer>通配符作为方法参数,表示方法内部代码对于参数只能写,不能读

  • 对比extends和super:

    • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);

    • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

无限定通配符

  • <?>通配符既没有extends,也没有super,因此:

    • 不允许调用set(T)方法并传入引用(null除外);

    • 不允许调用T get()方法并获取T引用(只能获取Object引用)。

    • 既不能读,也不能写,那只能做一些null判断

static boolean isNull(Pair<?> p) {
    return p.getFirst() == null || p.getLast() == null;
}
  • Pair<?>是所有Pair<T>的超类

PECS原则

  • 如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。
// PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super
class PESC {
    ArrayList<? extends Animal> exdentAnimal;
    ArrayList<? super Animal> superAnimal;
    Dog dog = new Dog("小黑", "黑色");

    private void test() {
        //正确
        Animal a1 = exdentAnimal.get(0);
        //错误
        // Animal a2 = superAnimal.get(0);

        //错误
        // exdentAnimal.add(dog);
        //正确
        superAnimal.add(dog);
    }
}
  • Collections类的copy
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        // 对于类型<? extends T>的变量src,我们可以安全地获取类型T的引用,而对于类型<? super T>的变量dest,我们可以安全地传入T的引用
        for (int i = 0; i < srcSize; i++)
            dest.set(i, src.get(i));
    } else {
        ListIterator<? super T> di = dest.listIterator();
        ListIterator<? extends T> si = src.listIterator();
        for (int i = 0; i < srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}
  • copy()方法内部不会读取dest,因为不能调用dest.get()来获取T的引用;
  • copy()方法内部也不会修改src,因为不能调用src.add(T)
  • 如果在方法代码中意外修改了src,或者意外读取了dest,就会导致一个编译错误

总结

  1. 使用类似<? extends Number>通配符作为方法参数时表示:

    • 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();

    • 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);

    • 即使用extends通配符表示可以读,不能写。

  2. 使用类似<T extends Number>定义泛型类时表示:

    • 泛型类型限定为Number以及Number的子类。
  3. 使用类似<? super Integer>通配符作为方法参数时表示:

    • 方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);

    • 方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst();

    • 即使用super通配符表示只能写不能读。

  • 只用于功能时,泛型结构使用<? extends T>
  • 只用于功能时,泛型结构使用<? super T>
  • 如果既用于,又用于操作,那么直接使用
  • 如果操作与泛型类型无关,那么使用<?>
  • ?类型不确定和Object作用差不多,好多场景下可以通用,但?可以缩小泛型的范围,如:List<? extends Animal>,指定了范围只能是Animal的子类,但是用List<Object>,没法做到缩小范围

标签:last,Pair,类型,泛型,Integer,public,first
From: https://www.cnblogs.com/sprinining/p/18300952

相关文章

  • 观《深入理解C#》有感---泛型五种约束
    一、引用类型约束classSample<T>whereT:class类型实参可以是:任何类: Sample<string>接口: Sample<IDisposable>数组: Sample<int[]>委托: Sample<Action>二、值类型约束classSample<T>whereT:struct类型实参可以是:值类型: Sample<int>枚举: Sample&l......
  • Java泛型的定义与运用
    泛型泛型的作用从使用层面上来说是统一数据类型,防止将来的数据转换异常。从定义层面上来说,定义带泛型的类,方法等,将来使用的时候给泛型确定什么类型,泛型就会变成什么类型,凡是涉及到泛型的都会变成确定的类型,代码更灵活。不使用泛型,少了限制,则在集合添加数据就不会类型异常......
  • Java 中的泛型 集合(List,Set) Map
    泛型集合(List,Set)Map泛型泛型的本质是参数化类型,即允许在编译时对集合进行类型检查,从而避免安全问题,提高代码的复用性泛型的具体定义与作用定义:泛型是一种在编译阶段进行类型检查的机制,它允许在类,方法,接口后通过<>来声明类型参数.这些参数在编译时会被具体的类......
  • DataTable 与 泛型集合List<T>相互转换
    List转DataTablepublicstaticDataTableToDataTable<T>(thisList<T>list){DataTableresult=newDataTable();List<PropertyInfo>pList=newList<PropertyInfo>();Typetype=typeof(T);Array......
  • java核心-泛型
    目录概述什么是泛型分类泛型类泛型接口泛型方法泛型通配符分类泛型类型擦除分类无限制类型擦除有限制类型擦除问题需求第一种第二种概述  了解泛型有利于学习jdk、中间件的源码,提升代码抽象能力,封装通用性更强的组件。什么是泛型在定义类、接口和方法时,......
  • 泛型
    泛型的本质就是泛型参数化,确保类型一致性和安全性泛型上限与泛型下限泛型上限和泛型下限都是用于限定参数范围的1.泛型上限(上限指的是上限范围,读取的上限范围,)通过extends关键字来限制参数上限,通过限制操作的顶层基类,来控制读取的类型,因为你读取的所有的对象,都是这个顶层基类的......
  • Java泛型
    Java泛型泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数据进行排序,我们就可以使用Java泛型。1......
  • 在构造方法里获取当前类的泛型
    定义publicclassMyClass<T>{privateClass<T>clazz;publicMyClass(){Typetype=this.getClass().getGenericSuperclass();if(typeinstanceofParameterizedTypeparameterizedType){if(parameterizedType.......
  • Golang面试:泛型
    Go语言在1.18版本中引入了泛型(Generics),这是Go语言发展中的一个重要里程碑。泛型允许你编写更通用和可复用的代码,而无需牺牲类型安全性。以下是对Go中泛型的详细介绍,包括其语法、使用场景和示例代码。1.泛型的基本概念泛型允许你定义可以处理多种数据类型的函数和数据结构,而无需......
  • 深入理解泛型(经典详解)
    深入理解泛型(经典详解):<T>T和T的使用以及public<E>List<E>get()泛型方法详解、类型擦除、通配符的使用、泛型类的应用、泛型之间的继承_泛型t-CSDN博客一、为什么要使用泛型?泛型俗称“标签”,使用<E>表示。泛型就是在允许定义类,接口时通过一个标识表示某个属性的类型或者......