Java的泛型与反射

泛型

什么是泛型,泛型的优点是什么

  • Java泛型机制时从JDK1.5才开始加入的,因此为了兼容之前的jdk版本,java泛型的实现采取了“伪泛型”策略,即java在语法上支持泛型,但在编译阶段会进行“类型擦除”然后虚拟机会帮我们进行标记,然后把它转为我们指定的类型

  • 泛型可以被理解为一种不确定的数据类型。它允许类或方法适配多个类型,我们可以将它想象成一个容器,这个容器中可以装不同类型的数据。

  • 泛型的优点主要体现在类型安全,在Java编译器编译时就能检查泛型类型的正确性,避免了在运行时可能遇到的类型转换异常。还有能够避免多余的类型检查和转换,泛型代码在编译时会被擦除,不会引入额外的运行时开销

如何声明一个泛型类?

  • 声明一个泛型类的语法格式是在类名后面使用尖括号<>,并在括号内指定泛型参数,泛型参数可以是任意合法的标识符,我们通常使用大写字母T表示,我们来看一下下面这个例子
public class Generics<T>{

    //变量类型声明为泛型变量名字
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

Generics类拥有一个泛型参数T,通过使用泛型参数,我们可以在类中定义成员变量和方法,并在需要时使用这个泛型参数来表示特定类型的数据。通过声明泛型类,我们可以在创建对象时指定具体的类型,比如Generics或Generics。这样一来,我们就可以使用这个泛型类处理不同类型的数据,不用为每种类型单独写一个类

泛型方法是什么

  • 泛型方法是在方法中使用泛型参数的方法。通过在方法中声明泛型类型参数,可以使得方法能够适用于多种数据类型,提高代码的灵活性和复用性。
public class GenericMethod {

    public static <T> void print(T value) {
        System.out.println(value);
    }

    public static void main(String[] args) {
        Integer intValue = 10;
        Double doubleValue = 3.1415926;
        String stringValue = "今天天气真好";

        // 调用泛型方法,并传递不同类型的参数
        print(intValue);    // 输出:10
        print(doubleValue); // 输出:3.1415926
        print(stringValue); // 输出:今天天气真好
    }
}

在上述示例中,print方法是一个泛型方法。它有一个泛型类型的参数,并打印出该参数的值,然后我们调用了print方法三次,传入了三个不同类型的值,因为print方法是泛型方法,可以接受任意类型的参数,所以我们不用重载方法来处理不同类型的数据。

泛型通配符是什么

  泛型通配符是Java中用来表示未知类型的特殊符号。通配符使用"?"来表示,它可以用于泛型类、接口、方法的定义中。通配符有两种形式:无界通配符和有界通配符。我们来看一下

  • 无界通配符:

我们可以看到,List<?>表示一个未知的元素类型列表,可以存储任意类型的元素。但是,我们无法直接向这样的列表中添加元素,因为我们无法确定要添加的元素的确切类型。

List<?> list = new ArrayList<>();
  • 有界通配符:

有界通配符使用? extends类型或者?super类型表示,表示可以匹配指定类型或其子类型或其父类型。

  • ? extends 类型:表示匹配指定类型的子类型。
List<? extends Number> list = new ArrayList<>();

在这个示例中,List<? extends Number>表示一个元素类型是Number或其子类的列表,比如Integer、Double等。我们可以从这样的列表中读取元素,因为我们知道元素是Number或其子类,并且可以进行相应的操作。但是,我们无法往这样的列表中添加元素,因为我们不知道要添加的元素的确切类型。

  • ? super 类型:表示匹配指定类型的父类型。
List<? super Integer> list = new ArrayList<>();

在这个示例中,List<? super Integer>表示一个元素类型是Integer或其父类的列表,比如Number、Object等。我们可以向这样的列表中添加Integer或Integer的子类元素,因为它们都是Integer的父类。

类型擦除是什么

  • 类型擦除是指在编译时期对泛型类型的实际类型信息进行擦除的过程,例如List擦除后的类型是,List也擦除后的类型是List,在编译后的字节码中,泛型类型的类型参数被替换成它们的上限。需要注意的是泛型的擦除和翻译是在编译时进行的,而不是在运行时,Java虚拟机并不知道泛型的存在,所有的泛型类型都被擦除为它们的原始类型。这也是为什么在使用泛型时,无法获取泛型的具体类型信息的原因。

泛型边界是什么

  • 泛型边界是指在泛型类型参数的声明中,用于限制该参数可以接受的具体类型范围。通过使用泛型边界,我们可以在编写泛型代码时对类型参数做出更精确的限制,我们上面其实也有提到,使用 extends 关键字可以来限定泛型类型参数的上界,使用super关键字来限定泛型类型参数的下界。指定了上界后,表示泛型类型参数必须是指定的类或其子类,比如 表示 T 必须是 Number 类或 Number 的子类。指定了下界后,表示泛型类型参数必须是指定类的超类,包括指定类本身。比如表示T必须是Integer的超类可以是Integer本身或Object类

反射

什么是反射

  • 反射是指程序在运行时通过字节码和对象,可以获取虚拟机中任意对象的所有信息,包括属性、方法等等。利用反射,我们可以轻松地获取并操作对象的各种信息。

反射机制的功能

  反射机制是指在程序运行时动态的获取检查和修改类的信息以及调用对象的方法,它提供了一系列功能

  • 获取类的信息:可以在运行时获取一个类的相关信息,比如类名、父类、实现的接口、构造函数、方法和字段等

  • 创建对象实例:可以在运行时动态的创建一个类的对象实例。无论是否有无参构造函数,通过反射的newInstance()方法都可以创建类的实例

  • 调用方法:可以在运行时动态的调用一个类的方法。通过获取Method对象,可以灵活调用公共或私有方法,并传递相应的参数

  • 操作字段:可以在运行时动态的获取和修改类的字段的值。通过获取Field对象,可以获取和设置字段的值,不管是公共的还是私有的

  • 动态代理:反射机制可以实现动态代理,在运行时动态生成一个代理类,并通过代理类来调用被代理对象的方法。通过动态代理,我们可以在不修改原有代码的情况下增加额外的逻辑

  • 加载外部类和资源:可以在程序运行时动态加载外部的类和资源文件,实现了更好的灵活性和可扩展性。

反正简单来说反射机制提供了一种在程序运行时获取和操作类的能力,让我们可以在运行时动态的创建对象、调用方法、修改属性等。使程序具有更好的扩展性和适应性。

通过反射操作运行时类

我们先来看下面这个案例,再来进行介绍

  MyClass:

public class MyClass {

    private String name;

    public MyClass() {
    }

    public MyClass(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println("Hello, " + name);
    }
}

  ReflectionExample:

public class ReflectionExample {

    public static void main(String[] args) {
        // 获取类的信息
        Class<?> clazz = MyClass.class;
        String className = clazz.getName();
        System.out.println("Class Name: " + className);

        Constructor<?>[] constructors = clazz.getConstructors();
        System.out.println("Constructors:");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        Method[] methods = clazz.getMethods();
        System.out.println("Methods:");
        for (Method method : methods) {
            System.out.println(method);
        }

        Field[] fields = clazz.getDeclaredFields();
        System.out.println("Fields:");
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

我们来看上面的例子,首先我们通过MyClass.class来获取MyClass类的Class对象。然后使用getName()方法获取类的名称,并将其打印出来

接着使用getConstructors()方法获取类的所有公共构造函数,并将它们打印出来。通过遍历构造函数数组,可以获取到每个构造函数的详细信息

继续用getMethods()方法获取类的所有公共方法,并将它们打印出来。同样地,通过遍历数组,可以获取到每个方法的详细信息

最后,使用getDeclaredFields()方法获取类的所有字段并将它们打印出来。然后通过遍历字段数组可以获取到每个字段的详细信息

反射的基本原理

在Java中,每个类在编译时都会生成对应的字节码文件,里面包含了类的结构信息,当程序运行时,JVM会将这些字节码文件加载到内存中,并创建一个Class对象来表示该类。而Class对象是反射机制的核心,它保存了类的所有信息,并且提供了一些API来访问和操作这些信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值