Java反射机制快速入门与通配符

1. Java 反射的原理

​ 在 Java 中,每个类在编译后都会生成一个 .class 文件,JVM 会为每个加载的类创建一个 Class 对象,这个对象包含了类的全部结构信息,包括类名、方法、字段、构造函数等。Class 对象存储了类的元数据,这些元数据可以在运行时被访问。通过 Class 对象,程序可以动态地获取类的信息和操作类的内容。反射的核心在于通过 Class 对象动态加载类,获取类的各种结构信息。

Java 反射机制基于以下几种核心类:

  1. Class:表示类的对象,提供了类的名称、字段、方法等信息。
  2. Field:表示类的成员变量,提供了访问和修改字段值的方法。
  3. Method:表示类的方法,提供了调用方法的功能。
  4. Constructor:表示类的构造函数,提供了创建实例的方法。

这些类都位于 java.lang.reflect 包中。

2. 获取类的Class对象

2.1 Class.forName()

Class<?> classForName = Class.forName("java.lang.String");
System.out.println("Class.forName: " + classForName.getName()); // 输出 java.lang.String

解释:

Class.forName() 是一种动态加载类的方法,通过类的全限定名(包名+类名)来获取类的 Class 对象。

2.2 .class

Class<?> _class = String.class;
System.out.println(".class: " + _class.getName()); // 输出 java.lang.String

解释:

.class 是编译时获取 Class 对象的方式,JVM 在编译期就知道该类,并为其生成相应的 Class 对象。这是静态类型安全的方式。

2.3 getClass()

 String sayHello = "Hello World";
 Class<?> classForInstance = sayHello.getClass();
 System.out.println("getClass(): " + classForInstance.getName()); // 输出 java.lang.String

解释:getClass() 是通过对象实例来获取该实例所属类的 Class 对象。这种方式适用于已经有了对象实例的情况。

2.4 ClassLoader

ClassLoader classLoader = ClassReflectDemo.class.getClassLoader();
Class<?> classForLoader = classLoader.loadClass("java.lang.Integer");
System.out.println("ClassLoader: " + classForLoader.getName()); // 输出 java.lang.Integer

解释:每个类都是由类加载器加载的,ClassLoader 提供了一种通过类加载器加载类并返回其 Class 对象的方式。这是一种更灵活的方式,可以自定义类加载器。

2.5 通过子类对象获取父类 Class对象

Class<?> superClass = String.class.getSuperclass();
System.out.println("Superclass: " + superClass.getName()); // 输出 java.lang.Object

解释:每个对象不仅知道它自己的类,还知道它的父类。通过 getSuperclass() 方法,可以获取该类的直接父类的 Class 对象。

2.6 通过接口类型

Class<?> interfaceClass = String.class.getInterfaces()[0];
System.out.println("Interface Class: " + interfaceClass.getName()); // 输出 java.io.Serializable

解释:对象不仅属于某个类,还可能实现一个或多个接口。通过 getInterfaces() 方法,可以获取一个对象实现的接口的 Class 对象。

2.7 通过注解

Annotation annotation = A_Class.class.getAnnotation(Name.class);
System.out.println("Annotation: " + annotation); // 输出 @jdk.jfr.Name(value="123")

解释:类可以被注解标记,通过 getAnnotation() 获取注解实例,再通过 annotationType() 获取注解的 Class 对象。

2.8 使用动态代理

InvocationHandler handler = (_, _, _) -> null; // 使用 Lambda 表达式简化实现,不做任何处理。
Runnable proxyInstance = (Runnable) Proxy.newProxyInstance(
          Runnable.class.getClassLoader(),
          new Class[]{Runnable.class},
          handler
);
Class<?> proxyClass = proxyInstance.getClass();
System.out.println("Proxy Class: " + proxyClass.getName()); // 输出类似 jdk.proxy1.$Proxy0 的类名
proxyInstance.run(); 

解释:动态代理允许在运行时创建代理类。通过 Proxy.newProxyInstance() 可以创建代理实例,代理实例有自己的 Class 对象。

2.9 通过泛型类型

B_Class<String> bClass = new B_Class<>(){};
Class<?> genericClass = bClass.getGenericTypeClass();
System.out.println("Generic Class: " + genericClass.getName()); // 输出 java.lang.String

解释:泛型在编译时会被类型擦除,但可以通过 ParameterizedType 获取泛型的实际类型,并转换为 Class 对象,由于 Java 泛型的擦除机制,直接实例化 B_Class<String> 时,T 的实际类型在运行时丢失,导致 getGenericTypeClass() 方法返回 Object.class。通过使用匿名子类或显式子类,可以保留泛型类型信息,从而正确获取泛型的实际类型。

案例完整代码:

import jdk.jfr.Name;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

public class ClassReflectDemo {
    public static void main(String[] args) {
        try {
            // 1. 使用 Class.forName() 动态加载类
            Class<?> classForName = Class.forName("java.lang.String");
            System.out.println("Class.forName: " + classForName.getName());// 输出 java.lang.String

            // 2. 使用 .class 语法获取类对象
            Class<?> _class = String.class;
            System.out.println(".class: " + _class.getName()); // 输出 java.lang.String

            // 3. 使用 getClass() 通过实例获取类对象
            String sayHello = "Hello World";
            Class<?> classForInstance = sayHello.getClass();
            System.out.println("getClass(): " + classForInstance.getName()); // 输出 java.lang.String

            // 4. 使用 ClassLoader 加载类
            ClassLoader classLoader = ClassReflectDemo.class.getClassLoader();
            Class<?> classForLoader = classLoader.loadClass("java.lang.Integer");
            System.out.println("ClassLoader: " + classForLoader.getName()); // 输出 java.lang.Integer

            // 5. 获取类的父类
            Class<?> superClass = String.class.getSuperclass();
            System.out.println("Superclass: " + superClass.getName()); // 输出 java.lang.Object

            // 6. 获取类实现的接口
            Class<?> interfaceClass = String.class.getInterfaces()[0];
            System.out.println("Interface Class: " + interfaceClass.getName()); // 输出 java.io.Serializable

            // 7. 通过匿名子类保留泛型类型信息并获取泛型参数的 Class 对象
            B_Class<String> bClass = new B_Class<>() {
            };
            Class<?> genericClass = bClass.getGenericTypeClass();
            System.out.println("Generic Class: " + genericClass.getName()); // 输出 java.lang.String

            // 8. 获取类上的注解并输出
            Annotation annotation = A_Class.class.getAnnotation(Name.class);
            System.out.println("Annotation: " + annotation); // 输出 @jdk.jfr.Name(value="123")

            // 9. 使用匿名内部类创建 InvocationHandler
            InvocationHandler handler = (_, _, _) -> null; // 使用 Lambda 表达式简化实现,不做任何处理。

            // 10. 使用动态代理创建一个实现 Runnable 接口的代理对象
            Runnable proxyInstance = (Runnable) Proxy.newProxyInstance(
                    Runnable.class.getClassLoader(),
                    new Class[]{Runnable.class},
                    handler
            );

            // 11. 获取代理对象的类名
            Class<?> proxyClass = proxyInstance.getClass();
            System.out.println("Proxy Class: " + proxyClass.getName()); // 输出类似 jdk.proxy1.$Proxy0 的类名

            // 12. 调用代理对象的方法
            proxyInstance.run(); // 由于 handler 不处理方法调用,因此这里不会有输出或实际操作。

        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

// 使用 @Name 注解标记 A_Class 类,Name 注解的值为 "123"
@Name("123")
class A_Class {

}

// 泛型类 B_Class,用于演示如何通过反射获取泛型参数的类型
class B_Class<T> {
    public Class<?> getGenericTypeClass() {
        // 获取当前类的直接超类的类型信息
        Type superclass = getClass().getGenericSuperclass();

        // 检查是否为 ParameterizedType,即泛型类型
        if (superclass instanceof ParameterizedType) {
            // 获取实际的泛型类型参数
            Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments();

            // 返回泛型参数的 Class 对象,如果存在泛型参数
            if (actualTypeArguments != null && actualTypeArguments.length > 0) {
                return (Class<?>) actualTypeArguments[0];
            }
        }

        // 如果无法确定泛型类型,默认返回 Object.class
        return Object.class;
    }
}

运行结果:

Class.forName: java.lang.String
.class: java.lang.String
getClass(): java.lang.String
ClassLoader: java.lang.Integer
Superclass: java.lang.Object
Interface Class: java.io.Serializable
Generic Class: java.lang.String
Annotation: @jdk.jfr.Name(“123”)
Proxy Class: jdk.proxy1.$Proxy2

小结:反射机制可以操作几乎所有的 Java 对象,包括普通对象、数组、枚举、匿名类等。

3. 通过反射创建实例

3.1 Class.newInstance()(已废弃)

class A_Class {
    public A_Class() {
        System.out.println("调用了无参构造!");
    }
}
A_Class.class.newInstance(); // 等同于 new A_Class(); 输出 "调用了无参构造!"

解释:Class.newInstance() 方法调用无参构造函数来创建实例。但自版本 9 起已弃用 ,原因是它无法处理带有私有构造函数的类,并且对异常的处理方式不够灵活。推荐使用 Constructor.newInstance() 方法。

3.2 Constructor.newInstance()

 Constructor<?> constructor = String.class.getConstructor(String.class);
 Object constructorInstance = constructor.newInstance("Hello World");
 System.out.println("Constructor instance:" + constructorInstance); // 输出 "Constructor instance:Hello World"

解释:通过获取 Constructor 对象并调用 newInstance() 方法,可以使用指定的构造函数创建实例。这个方法比 Class.newInstance() 更加灵活,支持带参数的构造函数,并且可以处理私有构造函数。

3.3 使用私有构造函数创建实例

class B_Class {
    private B_Class() {
        System.out.println("调用了私有无参构造!");
    }

    private B_Class(String name, Integer age) {
        System.out.println("私有有参构造:" + name + "今年" + age + "岁");
    }
}

Constructor<?> bClassConstructor1 = B_Class.class.getDeclaredConstructor();
bClassConstructor1.setAccessible(true); // 绕过访问控制检查
bClassConstructor1.newInstance(); // 输出 "调用了私有无参构造!"

Constructor<?> bClassConstructor2 = B_Class.class.getDeclaredConstructor(String.class, Integer.class);
bClassConstructor2.setAccessible(true); // 绕过访问控制检查
bClassConstructor2.newInstance("张三", 20); // 输出 "私有有参构造:张三今年20岁"

解释:通过 setAccessible(true),可以访问并使用私有构造函数来创建实例。getDeclaredConstructor(Class<?>... parameterTypes): 通过指定参数类型列表获取对应的构造函数。这里 String.classInteger.class 对应于构造函数的参数类型。

3.4 使用数组类的反射创建实例

Class<?> clazz = int[].class;
Object arrayInstance = java.lang.reflect.Array.newInstance(clazz.getComponentType(), 5);
System.out.println(java.util.Arrays.toString((int[]) arrayInstance)); // 输出 "[0, 0, 0, 0, 0]"

解释:使用 java.lang.reflect.Array.newInstance() 可以创建数组实例。

clazz.getComponentType():这个方法返回数组的组件类型,即 int[].class 的组件类型是 int.class。这是为了告诉反射我们要创建的数组的元素类型是什么。

Array.newInstance(Class<?> componentType, int length)Array 类是 Java 反射包中用于操作数组的类。newInstance 方法用于创建一个新数组,componentType 参数指定数组的元素类型,length 参数指定数组的长度。这里我们创建了一个长度为 5 的 int 数组。

arrayInstance:这是通过反射创建的数组对象,类型为 Object。但实际上它是一个 int[] 类型的数组。

3.5 通过反序列化创建实例

class C_Class implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    public C_Class() {
        System.out.println("反序列化创建实例");
    }
}


String filename = "example.ser";
File file = new File(filename);
   if (file.exists()) {
                // 文件存在,直接反序列化
                try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
                    C_Class instance = (C_Class) ois.readObject(); // 从文件中读取并反序列化对象
                    System.out.println("反序列化对象: " + instance); // 输出 "反序列化对象: C_Class@hashcode"
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
     } else {
                // 文件不存在,先序列化对象到文件
                try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
                    C_Class example = new C_Class(); // 输出 "反序列化创建实例"
                    oos.writeObject(example); // 将对象写入文件
                    System.out.println("对象已序列化到文件 " + filename);
                } catch (IOException e) {
                    e.printStackTrace();
                }
     }

解释:通过反序列化,将已经序列化的对象从字节流恢复为一个对象实例。如果文件存在,则执行反序列化操作,从文件中读取对象。如果文件不存在,则执行序列化操作,将对象保存到文件中。

3.6 使用工厂方法创建实例

class D_Class {
    public static D_Class createInstance() {
        System.out.println("通过工厂创建方法实例");
        return new D_Class();
    }
}
Method method = D_Class.class.getMethod("createInstance");
method.invoke(null); // 输出 "通过工厂创建方法实例"

解释:通过反射调用类的工厂方法来创建实例。

案例完整代码:

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ClassReflectDemo {
    public static void main(String[] args) {
        try {
            // 1. 通过反射调用无参构造函数创建 A_Class 的实例
            A_Class.class.newInstance(); // 等同于 new A_Class(); 输出 "调用了无参构造!"

            // 2. 通过反射调用 String 类的带参构造函数创建实例
            Constructor<?> constructor = String.class.getConstructor(String.class);
            Object constructorInstance = constructor.newInstance("Hello World");
            System.out.println("Constructor instance:" + constructorInstance); // 输出 "Constructor instance:Hello World"

            // 3. 通过反射调用 B_Class 的私有无参构造函数
            Constructor<?> bClassConstructor1 = B_Class.class.getDeclaredConstructor();
            bClassConstructor1.setAccessible(true); // 绕过访问控制检查
            bClassConstructor1.newInstance(); // 输出 "调用了私有无参构造!"

            // 4. 通过反射调用 B_Class 的私有带参构造函数
            Constructor<?> bClassConstructor2 = B_Class.class.getDeclaredConstructor(String.class, Integer.class);
            bClassConstructor2.setAccessible(true); // 绕过访问控制检查
            bClassConstructor2.newInstance("张三", 20); // 输出 "私有有参构造:张三今年20岁"

            // 5. 通过反射创建一个 int 数组实例,并初始化为长度为 5 的数组
            Class<?> clazz = int[].class;
            Object arrayInstance = java.lang.reflect.Array.newInstance(clazz.getComponentType(), 5);
            System.out.println(java.util.Arrays.toString((int[]) arrayInstance)); // 输出 "[0, 0, 0, 0, 0]"

            // 6. 判断文件是否存在,如果存在则反序列化 C_Class 对象,否则先序列化再反序列化
            String filename = "example.ser";
            File file = new File(filename);
            if (file.exists()) {
                // 文件存在,直接反序列化
                try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
                    C_Class instance = (C_Class) ois.readObject(); // 从文件中读取并反序列化对象
                    System.out.println("反序列化对象: " + instance); // 输出 "反序列化对象: C_Class@hashcode"
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                // 文件不存在,先序列化对象到文件
                try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
                    C_Class example = new C_Class(); // 输出 "反序列化创建实例"
                    oos.writeObject(example); // 将对象写入文件
                    System.out.println("对象已序列化到文件 " + filename);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // 7. 通过反射调用 D_Class 的静态工厂方法创建实例
            Method method = D_Class.class.getMethod("createInstance");
            method.invoke(null); // 输出 "通过工厂创建方法实例"

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

class A_Class {
    public A_Class() {
        System.out.println("调用了无参构造!");
    }
}

class B_Class {
    private B_Class() {
        System.out.println("调用了私有无参构造!");
    }

    private B_Class(String name, Integer age) {
        System.out.println("私有有参构造:" + name + "今年" + age + "岁");
    }
}

class C_Class implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    public C_Class() {
        System.out.println("反序列化创建实例");
    }
}

class D_Class {
    public static D_Class createInstance() {
        System.out.println("通过工厂创建方法实例");
        return new D_Class();
    }
}

运行结果:

调用了无参构造!
Constructor instance:Hello World
调用了私有无参构造!
私有有参构造:张三今年20岁
[0, 0, 0, 0, 0]
反序列化对象: C_Class@46f7f36a
通过工厂创建方法实例

4. 访问私有字段

访问私有字段通常通过公共的getter和setter方法实现,但在某些情况下,你可能需要使用反射直接访问和修改私有字段。使用反射可以绕过Java的访问控制机制,直接操作私有字段。

案例完整代码:

import java.lang.reflect.Field;

public class ClassReflectDemo {
    public static void main(String[] args) {
        try {
            // 1. 创建 A_Class 的实例
            A_Class instance = new A_Class();

            // 2. 获取 A_Class 的 Class 对象
            Class<?> aClass = A_Class.class;
            
            // 3. 获取私有字段 'name'
            Field nameField = aClass.getDeclaredField("name");
            // getDeclaredField(String name) 返回一个 Field 对象,表示类中声明的名为 'name' 的字段。
            // 这个字段是私有的,需要后续步骤来访问它。

            // 4. 绕过访问控制
            nameField.setAccessible(true);
            // setAccessible(true) 是关键一步,它绕过了 Java 的访问控制机制,使得可以访问和修改私有字段。

            // 5. 获取私有字段 'name' 的值
            Object name = nameField.get(instance);
            // 使用 Field 对象的 get() 方法来读取指定实例的字段值。
            // 这里读取了 'name' 字段的值,即 "joke"。

            // 6. 打印读取到的私有字段的值
            System.out.println("读取初始化值:"+name); // 输出: 读取初始化值:joke

            // 7. 修改私有字段 'name' 的值
            nameField.set(instance, "jokes");
            // 使用 Field 对象的 set() 方法来修改指定实例的字段值。
            // 这里将 'name' 字段的值从 "joke" 修改为 "jokes"。

            // 8. 再次读取并打印私有字段 'name' 的值
            name = nameField.get(instance);
            System.out.println("读取修改后的值:"+name); // 输出: 读取修改后的值:jokes
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

class A_Class {
    private String name = "joke";
}

运行结果:

读取初始化值:joke
读取修改后的值:jokes

解释:通过 getDeclaredField() 方法获取私有字段,使用 setAccessible(true) 绕过访问控制,然后通过 get()set() 方法读取和修改字段值。

5. 调用方法

案例完整代码:

import java.lang.reflect.Method;

public class ClassReflectDemo {
    public static void main(String[] args) {
        try {
            // 1. 创建 A_Class 的实例
            A_Class instance = new A_Class();
            // 2. 获取公共方法 'publicMethod' 的 Method 对象
            Method publicMethod = A_Class.class.getMethod("publicMethod");
            // 3. 调用公共方法
            publicMethod.invoke(instance);
            // 4. 获取私有方法 'privateMethod' 的 Method 对象
            Method privateMethod = A_Class.class.getDeclaredMethod("privateMethod");
            // 5. 设置方法可访问
            privateMethod.setAccessible(true);
            // 6. 调用私有方法
            privateMethod.invoke(instance);
            // 7. 获取带参数的公共方法 'methodWithArgs'
            Method methodWithArgs = A_Class.class.getMethod("methodWithArgs", String.class, Integer.class);
            // 8. 调用带参数的方法
            methodWithArgs.invoke(instance, "Joke", 42);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

class A_Class {
    // 公共方法
    public void publicMethod() {
        System.out.println("调用了公共方法!");
    }

    // 私有方法
    private void privateMethod() {
        System.out.println("调用了私有方法!");
    }

    // 带参数的公共方法
    public void methodWithArgs(String message, Integer number) {
        System.out.println("调用了带参数的方法:" + message + ", " + number);
    }
}

运行结果:

调用了公共方法!
调用了私有方法!
调用了带参数的方法:Joke, 42

解释:

​ 使用反射获取 Method 对象,这个对象表示要调用的方法。可以使用 getMethod()getDeclaredMethod() 获取 Method 对象。如果方法是私有的,需要通过 setAccessible(true) 来绕过访问控制。使用 Method.invoke(Object obj, Object... args) 来调用方法。obj 是要调用方法的对象,args 是传递给方法的参数。

6.通配符?

? 是 Java 泛型中的通配符,用于表示未知类型。它可以在泛型类或方法中使用,以实现更灵活的类型匹配。? 通配符常与 extendssuper 关键字结合使用,形成类型边界。

6.1 无界通配符 ?

? 可以单独使用,表示任意类型,但不能对其进行任何假设和操作。

import java.util.ArrayList;
import java.util.List;

public class ClassReflectDemo {
    public static void main(String[] args) {
        // 创建不同类型的 ArrayList
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);

        List<Double> doubleList = new ArrayList<>();
        doubleList.add(3.14);
        doubleList.add(2.718);

        // 使用无界通配符的泛型方法来打印列表内容
        printList(stringList);  // 输出: [Hello, World]
        printList(intList);     // 输出: [1, 2]
        printList(doubleList);  // 输出: [3.14, 2.718]
    }
    // 定义一个可以接受任意类型列表的泛型方法
    public static void printList(List<?> list) {
        System.out.println(list);
    }
}

运行结果:

[Hello, World]
[1, 2]
[3.14, 2.718]

6.2 上界通配符 ? extends T

? extends T 表示通配符类型可以是 TT 的子类,用于读取但不写入。

import java.util.ArrayList;
import java.util.List;

public class ClassReflectDemo {
    public static void main(String[] args) {
        // 创建不同类型的列表
        List<Integer> integerList = new ArrayList<>();
        integerList.add(10);
        integerList.add(20);

        List<Double> doubleList = new ArrayList<>();
        doubleList.add(3.14);
        doubleList.add(2.718);

        // 调用使用上界通配符的方法
        printList(integerList);  // 输出: [10, 20]
        printList(doubleList);   // 输出: [3.14, 2.718]
    }
    // 定义一个可以接受 Number 及其子类的泛型方法
    public static void printList(List<? extends Number> list) {
        System.out.println(list);
    }
}

运行结果:

[10, 20]
[3.14, 2.718]

6.3 下界通配符 ? super T

? super T 表示通配符类型可以是 TT 的父类,用于写入但不读取。

import java.util.ArrayList;
import java.util.List;

public class ClassReflectDemo {
    public static void main(String[] args) {
        // 创建一个可以接受 Integer 类型及其父类的 List
        List<Number> numberList = new ArrayList<>();
        numberList.add(1);   // 添加 Integer 类型的对象
        numberList.add(2.5); // 添加 Double 类型的对象

        // 使用下界通配符的方法
        printList(numberList);

        // 打印结果
        for (Number number : numberList) {
            System.out.println(number);
        }
    }
    // 使用下界通配符来接受 Integer 类型及其父类的集合
    public static void printList(List<? super Integer> list) {
        list.add(10);   // 可以安全地添加 Integer 类型的对象
        list.add(20);
        
        // 注意:由于使用了下界通配符,读取 list 中的元素时只能保证类型是 Object 或其子类型
        // 因此不能执行例如:Integer num = list.get(0); 这样具体类型的操作
        // list.get(0) 返回的类型被视为 Object,
        // 因为 list 可能是 List<Number> 或 List<Object>。
        // 编译器无法确定 list.get(0) 实际上是 Integer,因此直接将返回值赋给 Integer 变量是不安全的。
    }
}

运行结果:

1
2.5
10
20

小结:

上界通配符 ? extends T:允许你读取 TT 的子类型的对象,但通常不允许写入,因为你无法确定具体的子类型。

下界通配符 ? super T:允许你写入 TT 的子类型对象,因为容器至少是 T 类型的父类,但读取时可能只能获取 Object 类型。

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值