Java 基础回顾-3:泛型和 Class 类

本文详细介绍了Java中的泛型,包括泛型类、泛型接口、泛型方法、泛型参数约束以及泛型的补偿,如创建实例、创建数组等。同时,讨论了类型擦除的概念和其在运行时的影响。此外,还探讨了Class类在泛型中的应用,通过示例展示了如何使用Class获取类信息和进行反射操作。

1、泛型

以 ArrayList 为例,在范型出现之前,ArrayList 的实现机制是内部管理一个 Object[] 类型的数组。比如add 方法以前是 add(Object obj),现在是 add(E e)。那么以前的时候显然如果你定义一个 String 类型的 ArrayList,传入 File 类型也是可以的,因为它也继承自 Object。这显然就会出现错误!但是有了泛型之后,传入的只能是E类型的,不然会报错。也就是说,泛型给我们提供了一种类型检查的机制。

Java泛型是使用类型擦除来实现的。它的好处只是提供了编译器类型检查机制。在泛型方法内部无法获取任何关于泛型参数类型的信息,泛型参数只是起到了占位的作用。如 List<String> 在运行时实际上是 List 类型,普通类型被擦除为 Object。因为泛型是擦除的,所以泛型不能用于显式地引用运行时类型的操作中,例如:转型、instanceof 操作或者 new 表达式。

另外,

  1. 使用泛类型的代码意味着可以被很多不同类型的对象重用;
  2. Java 编译器最终将泛型版本编译为无泛型版本;
  3. 使用 extends 的意义就在于指定了擦除的上界。

1.2 泛型类

泛型类的声明与一般类的声明语法一致,但需要在声明的泛型类名称后使用 <> 指定一个或多个类型参数,如:

    class MyClass <E> { }class MyClass <K,V> { }

1.3 泛型接口

泛型接口的声明与一般接口的声明语法一致,但需要在声明的泛型接口名称后使用 <> 指定一个或多个类型参数,如:

    interface IMyMap <E> 或 intetface IMyMap <K,V> 

1.4 泛型方法

从下面的示例中,我们可以看出当定义了一个泛型方法的时候,可以提高代码的复用性。如:

public void method(T e) { } 或 public void method(List<?> list)

不过通常我们需要为泛型指定一个擦除上界来对泛型的范围进行控制。

1.5 泛型参数的约束

    <T extends 基类或接口><T extends 基类或接口1 & 基类或接口2>

前面的形式表示 T 需要是指定的基类或者接口的子类,后面的形式表示 T 需要是指定的接口或者基类 1 的子类并且是基类或者接口 2 的子类。

1.6 泛型的补偿

1.6.1 创建实例

使用类型标签来获取指定类型的实例:

    try {
        Person person = Person.class.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

但是使用上面的方式,要求指定的类型标签必须有默认的构造器。

1.6.2 创建数组

可以使用类型标签+Array.newInstance()的方式实现:

    int[] arr = (int[]) Array.newInstance(int.class, 5);

下面的这种方式在运行时不会出错,但是在 main 方法中强制进行类型转换的时候会出错:

    private static  <T> T[] createArray(T t) {
        T[] arr = (T[]) new Object[5];
        arr[0] = t;
        return arr;
    }

    public static void main(String ...args) {
        Integer[] array = createArray(5); // ClassCastException
        System.out.println(Arrays.toString(array));
    }

这是因为它的运行时类型仍然是Object[],写成下面的形式就不会错了:

    public static void main(String ...args) {
        Object[] array = createArray(5); // 不会出错
        System.out.println(Arrays.toString(array));
        Integer integer = (Integer) array[0]; // 也不会错
        System.out.println(integer);
    }

因为有了擦除,数组的运行时类型只能是 Object[]

1.6.3 自限定的类型
    private static class SelfBounded<T extends SelfBounded<T>> {}

    private static class A extends SelfBounded<A> {}
    
    private static class B extends SelfBounded<A> {}

    private static class C extends SelfBounded {}

    // private static class D extends SelfBounded<C> {}  // 错误!

    // private static class E extends SelfBounded<B> {}  // 错误!

    // private static class F extends SelfBounded<D> {}  // 错误!

可以看出,当定义了 class M extends SelfBounded<N> 的时候,这里对N的要求是它的必须实现了 SelfBounded<N>

1.6.4 泛类与子类

虽然 Object obj = new Integer(123); 是可行的,但是 ArraylList<Object> ao = new ArrayList<Integer>(); 是错误的。因为 IntegerObject 的派生类,但是 ArrayList<Integer> 不是 ArraylList<Object> 的派生类。

1.6.5 通配符

根据上面的泛类与子类的关系,如果要实现一个函数,如

    void PrintArrayList(ArrayList<Object> c){
        for(Object obj:c){}
    }

那么 ArrayList<Integer>(10) 的实例是无法传入到该函数中的,因为 ArrayList<Integer>ArrayList<Object> 是没有继承关系的。在这种情况下就可以使用通配符解决这个问题。我们可以定义上面的函数为如下形式,这样就可以将泛型传入了。

    PrintArrayList(ArrayList<?>c) {
        for(Object obj:c){}
    }

当然,也可以指定通配符 ? 的约束,即将其写成下面的形式

    <? extends 基类>
    <? super 派生类>

就是在运行时指定擦除的边界。

2、Class 类

Class 类包含了与类某个类有关的信息,Class 也支持泛型。它们的效果是相同的,只是使用泛型具有编译器类型检查的效果,相对更加安全。

以下是使用Class类的一个测试例子。在这里我们可以通过反射来获取并修改 private方法private字段accessible 属性。当设置为 true 时,我们就可以对其进行调用或者修改。

此外,我们还可以进行自定义注解以及获取类、方法和字段的注解等信息。

示例 1:获取 Class 对象的属性信息,修改 private 类型的字段,调用 private 方法:

    public static void main(String ...args) {
        Class<SubClass> subClass = SubClass.class;

        try {
            // 如果SubClass是private类型的,那么会抛出以下异常:
            // java.lang.IllegalAccessException: Class me.shouheng.rtti.RttiTest can not access a member of class
            // me.shouheng.rtti.RttiTest$SubClass with modifiers "private"
            SubClass sub = subClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

输出 Class 类的方法,这里输出的结果中包含了超类的方法:

        System.out.print("\nMethods:\n");
        System.out.println(Arrays.toString(subClass.getMethods()));

输出 Class 内部定义的方法,它只输出了在 SubClass 中新加入的方法的定义:

        System.out.print("\nDeclaredMethods:\n");
        System.out.println(Arrays.toString(subClass.getDeclaredMethods()));

        System.out.print("\nMethodInformation:\n");
        printMethodInfo(subClass);

输出 Class 的字段,输出的结果为空:

        System.out.print("\nFields:\n");
        System.out.println(Arrays.toString(subClass.getFields()));

输出 Class 中定义的字段:

        System.out.print("\nDeclaredFields:\n");
        System.out.println(Arrays.toString(subClass.getDeclaredFields()));

        System.out.print("\nFieldInformation:\n");
        printFieldInfo(subClass);

        System.out.print("\nClassInformation:\n");
        printClassInfo(subClass);
    }

    private static void printMethodInfo(Class<?> c) {
        SubClass sub = new SubClass();
        sub.setName("My Simple Name");
        // 打印方法信息
        Method[] methods = c.getDeclaredMethods();
        Method method = methods[0];
        System.out.println(method.getName());
        System.out.println(method.getReturnType());

输出方法的注解信息,对于能够输出的注解信息是有要求的:

        // 输出注解信息
        Annotation[] annotations = method.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

可以在获取了方法的Method对象之后调用它的invoke方法进行触发:

        // 触发方法
        try {
            System.out.println(method.invoke(sub));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

修改方法的属性,这里我们是已经知道out方法是第二个方法的前提下进行的。还有直接使用Class对象的getMethod尝试使用方法名获取方法是不行的:

        try {
            // 设置out方法的accessible为true,这样可以从外部调用该方法
            method = methods[1];
            method.setAccessible(true);
            // 直接根据名称来获取out方法是不行的
    //      method = c.getMethod("out");
    //      method.setAccessible(true);
            method.invoke(sub);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private static void printFieldInfo(Class<?> c) {
        SubClass sub = new SubClass();
        sub.setName("My Simple Name");
        Field[] fields = c.getDeclaredFields();
        Field field = fields[0];
        try {
            System.out.println(field.get(sub));
        } catch (IllegalAccessException e) {
            System.out.println(e);
        }
        Annotation[] annotations = field.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        System.out.println(field.isAccessible());
        System.out.println(field.getName());

我们可以通过修改一个字段的访问性来修改这个字段的属性:

        field.setAccessible(true);
        try {
            field.set(sub, "Shit");
            System.out.println(field.get(sub));
        } catch (IllegalAccessException e) {
            System.out.println(e);
        }
    }

    private static void printClassInfo(Class<?> c) {
        System.out.println(Arrays.toString(c.getAnnotations()));
        System.out.println(c.getCanonicalName());
        System.out.println(c.getClass());
        System.out.println(c.getGenericSuperclass());
        System.out.println(Arrays.toString(c.getGenericInterfaces()));
        System.out.println(Arrays.toString(c.getConstructors()));
        System.out.println(c.getPackage());
    }

    private static class SuperClass {}

    // private 类型的不行
    @Deprecated
    public static class SubClass extends SuperClass implements Interface {
        @me.shouheng.rtti.Field
        @NotNull
        private String name;
        private SuperClass sup;

        @Deprecated
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public SuperClass getSup() {
            return sup;
        }

        public void setSup(SuperClass sup) {
            this.sup = sup;
        }

        private void out() {
            System.out.println("private method in SubClass");
        }
    }

    private interface Interface {}

输出结果:

	Methods:
	[public java.lang.String me.shouheng.rtti.RttiTest$SubClass.getName(), public void me.shouheng.rtti.RttiTest$SubClass.setName(java.lang.String), public me.shouheng.rtti.RttiTest$SuperClass me.shouheng.rtti.RttiTest$SubClass.getSup(), public void me.shouheng.rtti.RttiTest$SubClass.setSup(me.shouheng.rtti.RttiTest$SuperClass), public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
	
	DeclaredMethods:
	[public java.lang.String me.shouheng.rtti.RttiTest$SubClass.getName(), private void me.shouheng.rtti.RttiTest$SubClass.out(), public void me.shouheng.rtti.RttiTest$SubClass.setName(java.lang.String), public me.shouheng.rtti.RttiTest$SuperClass me.shouheng.rtti.RttiTest$SubClass.getSup(), public void me.shouheng.rtti.RttiTest$SubClass.setSup(me.shouheng.rtti.RttiTest$SuperClass)]
	
	MethodInformation:
	getName
	class java.lang.String
	@java.lang.Deprecated()
	My Simple Name
	private method in SubClass
	
	Fields:
	[]
	
	DeclaredFields:
	[private java.lang.String me.shouheng.rtti.RttiTest$SubClass.name, private me.shouheng.rtti.RttiTest$SuperClass me.shouheng.rtti.RttiTest$SubClass.sup]
	
	FieldInformation:
	java.lang.IllegalAccessException: Class me.shouheng.rtti.RttiTest can not access a member of class me.shouheng.rtti.RttiTest$SubClass with modifiers "private"
	@me.shouheng.rtti.Field()
	false
	name
	Shit
	
	ClassInformation:
	[@java.lang.Deprecated()]
	me.shouheng.rtti.RttiTest.SubClass
	class java.lang.Class
	class me.shouheng.rtti.RttiTest$SuperClass
	[interface me.shouheng.rtti.RttiTest$Interface]
	[public me.shouheng.rtti.RttiTest$SubClass()]
	package me.shouheng.rtti

示例2:泛型参数的获取:

下面是在实际的框架设计中会用到的一些方法,它尝试从类的泛型中获取泛型的名称:

    private static class Model { }

    private static class Product extends Model { }

    private static class Store<T extends Model> {

        public Store() {
            Class cls = this.getClass();
            ParameterizedType pt = (ParameterizedType) cls.getGenericSuperclass();
            Type[] args = pt.getActualTypeArguments();

            System.out.println(cls);
            System.out.println(pt);
            System.out.println(Arrays.toString(args));
            System.out.println(((Class) args[0]).getSimpleName());
        }
    }

    private static class ProductStore extends Store<Product> { }

    public static void main(String ...args) {
        ProductStore store = new ProductStore();
    }

输出结果:

	class me.shouheng.rtti.RttiTest$ProductStore
	me.shouheng.rtti.RttiTest.me.shouheng.rtti.RttiTest$Store<me.shouheng.rtti.RttiTest$Product>
	[class me.shouheng.rtti.RttiTest$Product]
	Product

Java 基础回顾系列文章,给你的 Java 查缺补漏:

本系列以及其他系列的文章均维护在 Github 上面:Github / Awesome-Java,欢迎 Star & Fork. 如果你喜欢这篇文章,愿意支持作者的工作,请为这篇文章点个赞?!

<think>嗯,用户现在问的是Java的上限下限,也就是的通配符中的extendssuper。之前用户问过printf的格式化输出,现在转向了基础知识。可能用户正在学习Java基础,特别是这一块,想深入理解类型参数的限制。 首先,我需要确定用户已经了解的基本概念,比如什么是方法,以及为什么需要。可能用户已经知道用于类型安全,避免强制类型转换,但上限下限可能对他们来说有点抽象。需要从基础讲起,但也要深入例子,帮助理解。 接下来,用户可能对上下限的应用场景不太清楚,比如什么时候用extends,什么时候用super。这时候需要举一些实际的例子,比如在集合中的应用,比如List<? extends Number>List<? super Integer>的区别。同时,PECS原则(Producer Extends, Consumer Super)可能需要解释清楚,这样用户能理解在方法参数中如何选择。 另外,用户可能会混淆类型参数的上限通配符的上限。比如,在定义时,<T extends Number>方法的参数List<? extends Number>是不同的。需要指出这两者的区别,避免混淆。 可能用户在实际编程中遇到了需要限制类型的情况,比如写一个方法,只能处理数字类型的列表,或者需要向列表中添加元素。这时候上限下限就显得很重要了。通过实际的代码示例,比如遍历一个extends的列表,或者向一个super的列表添加元素,能帮助用户更好地理解。 还要注意用户可能的误区,比如使用extends时不能添加元素,只能读取,而super可以添加特定类型的元素。这些限制需要明确说明,避免用户在实际使用中出现错误。 另外,用户可能想知道上下限在类型擦除后的影响,但这个问题比较复杂,可能暂时不需要深入,除非用户有更高级的问题。目前重点放在语法应用上。 最后,总结部分需要简明扼要,回顾上限下限的作用,以及它们的典应用场景,帮助用户巩固记忆。可能还需要提到常见的错误,比如错误地尝试添加元素到extends通配符的集合中,导致编译错误,提醒用户注意。 总之,回答需要结构清晰,从定义、语法、示例、应用场景到注意事项,逐步展开,确保用户能够逐步理解上下限的概念用法。同时,结合之前的printf问题,保持回答的一致性,使用用户已经适应的格式示例风格。</think>以下是关于Java**上限(Upper Bound)**与**下限(Lower Bound)**的详细解析: --- ### **一、核心概念** #### **1. 上限(`<? extends T>`)** - **作用**:限制类型的**最大范围**,表示类型必须是`T`或其**子**。 - **本质**:定义类型的**生产者(Producer)**,主要用于**安全读取数据**。 - **语法**:`Class<? extends T>` 或 `T<? extends U>` #### **2. 下限(`<? super T>`)** - **作用**:限制类型的**最小范围**,表示类型必须是`T`或其**父**。 - **本质**:定义类型的**消费者(Consumer)**,主要用于**安全写入数据**。 - **语法**:`Class<? super T>` 或 `T<? super U>` --- ### **二、代码示例与对比** #### **1. 上限(`extends`)** ```java // 定义方法:接收Number及其子的列表(如Integer、Double) public static void printNumbers(List<? extends Number> list) { for (Number num : list) { // 安全读取 System.out.print(num + " "); } } // 调用 List<Integer> intList = Arrays.asList(1, 2, 3); List<Double> doubleList = Arrays.asList(1.1, 2.2); printNumbers(intList); // 合法 ✅ printNumbers(doubleList); // 合法 ✅ ``` **限制**: - **不可写入**:`list.add(10)` 会编译报错 ❌ - **原因**:Java无法确定实际类型是否匹配(可能是`Integer`或`Double`) --- #### **2. 下限(`super`)** ```java // 定义方法:接收Integer及其父的列表(如Number、Object) public static void addIntegers(List<? super Integer> list) { list.add(10); // 安全写入 ✅ list.add(20); } // 调用 List<Number> numList = new ArrayList<>(); List<Object> objList = new ArrayList<>(); addIntegers(numList); // 合法 ✅ addIntegers(objList); // 合法 ✅ ``` **限制**: - **读取不安全**:`Integer i = list.get(0)` 需强制类型转换 ❌ - **原因**:实际类型可能是`Number`或`Object` --- ### **三、核心规则(PECS原则)** | **场景** | **选择** | **记忆口诀** | |-----------------------|------------------|---------------------------| | 需要**读取数据**(生产者) | `<? extends T>` | **Producer Extends** | | 需要**写入数据**(消费者) | `<? super T>` | **Consumer Super** | --- ### **四、应用场景** #### **1. 上限的典用途** - **集合的只读操作** ```java // 安全遍历任何Number子的集合 public static double sum(List<? extends Number> list) { double total = 0; for (Number num : list) { total += num.doubleValue(); } return total; } ``` - **API设计中的类型约束** ```java // 限制键类型必须实现Comparable class SortedMap<K extends Comparable<? super K>, V> { ... } ``` --- #### **2. 下限的典用途** - **写入集合** ```java // 将多个Integer添加到父类型集合 public static void fill(List<? super Integer> list, int count) { for (int i = 0; i < count; i++) { list.add(i); // 安全写入 } } ``` - **回调函数设计** ```java // 接收Consumer<? super T>,兼容父处理器 public void process(List<T> data, Consumer<? super T> consumer) { data.forEach(consumer); } ``` --- ### **五、类型参数与通配符的对比** | **特性** | **类型参数(`<T>`)** | **通配符(`<?>`)** | |---------------------|--------------------------|----------------------------| | 定义位置 | /方法声明时 | 方法参数或变量类型 | | 可操作性 | 可直接使用类型`T` | 需配合`extends`/`super` | | 典用途 | 定义或方法 | 灵活约束已有类型 | | 示例 | `class Box<T> { ... }` | `void print(List<?> list)` | --- ### **六、常见误区与避坑指南** 1. **混淆`extends`与`super`** - 错误:尝试向`List<? extends Number>`添加`Double` - 正确:改用`List<? super Double>` 2. **过度使用通配符** - 错误:`Map<?, ?> map = new HashMap<>();` - 正确:明确具体类型参数(除非确实需要完全未知类型3. **忽略PECS原则** - 错误:在需要写入数据的方法中使用`<? extends T>` - 正确:根据操作方向选择通配符类型 --- ### **七、底层原理** - **类型擦除**:信息在编译后被擦除,转换为原始类型(Raw Type) - **桥方法生成**:编译器自动生成方法保证类型安全 - **通配符捕获**:通过辅助方法捕获具体类型(如`helper(List<T> list)`) --- ### **总结** | **特性** | **上限(`<? extends T>`)** | **下限(`<? super T>`)** | |-----------------|------------------------------------|----------------------------------| | **方向** | 限制类型为`T`的子 | 限制类型为`T`的父 | | **操作权限** | 只读(安全读取) | 可写(安全写入) | | **典场景** | 数据生产者(如遍历集合) | 数据消费者(如填充集合) | 掌握上下限是写出**类型安全**且**灵活**的Java代码的关键,尤其在集合操作API设计中应用广
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值