Java复习(六)----反射(一)

现在我们开始来讲解一下Java中的反射,因为知识有点多,我打算分两三个章节发出来。
坚持自己的梦想,即使没有翅膀也能飞翔

什么是反射

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。

对反射更简单的解释就是:Java是个千年难遇的美女,但是美女有很多事情是规定不让你做的。而反射就是一把枪,有枪在手,你想让美女干嘛就干嘛。

为什么需要反射

一句话概括就是使用反射可以赋予JVM动态编译的能力,否则类的元数据信息只能用静态编译的方式实现,例如热加载,Tomcat的classloader等等都没法支持。

反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。通过Class实例获取Class信息的方法称为反射

Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。

反射有什么用

Java的反射用的最多的地方就是在框架里面举个例子,比如说有两个程序员,他们俩个都在创建两个类,第一个程序员现在需要第二个程序员创建的类,可是现在第二个程序员创建的类还没有完成。问:现在第一个程序员可以成功编译吗?

答案当然是不行的,可是现在第一个程序员又需要第二个程序员的类,这个时候,反射就可以完成这种想法。因为反射是在类运行的时候获取对象的各个信息,所以第一个程序员的类就可以完成编译了。

小总结:

  • 在写代码的时候,如果我们使用了未定义的类,编译就会报错,Java的反射机制就可以解决该问题
  • 反射使得代码更具通用性和灵活性。

Class类

除了基本类型之外,Java的其他类型全部都是class(包括interface)。如:

  • String
  • Object
  • Runnable
  • Exception

class(包括interface)的本质是数据类型(Type)。无继承关系的数据类型无法赋值:

Number n = new Double(123.456);//yes
String s = new Double(123.456);//error

前面我们说了通过Class实例获取Class信息的方法称为反射,那我们要如何获取一个class的Class实例呢?

  • 方法一:直接通过一个class的静态变量class获取:
Class c = String.class;
  • 方法二:如果我们有一个实例变量,可以通过该实例提供的getClass()方法获取:
String s = "Hello!";
Class c = s.getClass();
  • 方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class c = Class.forName("java.lang.String");

Class实例比较和instanceof的差别:

Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类

boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class

Integer是int的包装类,Java为每个原始类型都提供了包装类型,从Java5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。以后我会写一篇文章来具体介绍一下。

instanceof关键字的作用:是判断一个对象是否是一个具体类的实例,我们在重写equals方法中要先判断是否是同一对象,之后再判断一个对象是否是另一个的实例,如果是判断各个属性值以判断是否是同一对象,不是一定不是同一对象。

动态加载类

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。

在获得类类型中,有一种方法是 Class.forName(“类的全称”),有以下要点:

  • 不仅表示了类的类类型,还代表了动态加载类
  • 编译时刻加载类是静态加载类,运行时刻加载类是动态加载类

再说动态加载类之前,我们先了解一下静态加载类:

class Test1
{
    public static void main(String[] args) {
        
        // new 创建对象,是静态加载类,在编译时刻就需要加载所有的可能使用到的类
        if ("word".equals(args[0])) {
            Word w = new Word();
            w.start();
        }
        if("Excel".equals(args[0])) {
            Excel e = new Excel();
            e.start();
        }
    }
}

如果运行该程序,会报错,因为 new 创建对象,是静态加载类,在编译时刻就需要加载所有的可能使用到的类,但我们并没有 Word 和 Excel 类。

现在我们通过 Class.forName(“具体包名”) 来动态加载类。

class Test2{
    public static void main(String[] args) {
        try {
            // 动态加载类,在运行时刻加载
            // args 是使用命令行的参数列表 形如:  java  指定的程序名 参数 
            // args[0]即参数列表的第一个参数
            Class c = Class.forName(args[0]);
            //通过类类型,创建该类对象
            OfficeAble oa = (OfficeAble) c.newInstance();
            oa.start();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行 javac 后我们发现程序并没有报错,因为该类动态加载 OfficeAble ,编译过程不加载,只在运行时刻加载。

通过Class实例能干嘛呢,下面我们就来讲讲

通过Class实例获取字段信息

Class类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getField():获取所有的public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类所有的field(不包括父类)
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}

class Student extends Person {
    public int score;
    private int grade;
}

class Person {
    public String name;
}

上述代码首先获取Student的Class实例,然后分别获取public字段、继承的public字段以及private字段。

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,如:“name”;
  • getType():返回字段类型,也是一个Class实例,如:String.class;
  • getModifiers():返回字段的修饰符,他是一个int。

获取字段值

利用反射拿到字段的一个field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。

实例一:

public class Main {

    public static void main(String[] args) throws Exception {
        Object p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    public String name;//得是用public,要是使用private会报错
//除了public还可以在调用Object value = f.get(p);前,先写一句:f.setAccessible(true);
    public Person(String name) {
        this.name = name;
    }
}

结果为:

Xiao Ming

这里可能会有人说,使用f.setAccessible(true);可以直接调用private字段,那封装岂不是没意义了吗。

正常情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值

设置字段值

我们获取字段值之后,自然可以设置字段的值。设置字段值是通过Field.set(Object, Object)实现的。其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。

实例二:

public class Main {

    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        System.out.println(p.getName()); // "Xiao Ming"
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);
        f.set(p, "Xiao Hong");
        System.out.println(p.getName()); // "Xiao Hong"
    }
}

class Person {
    private String name;

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

    public String getName() {
        return this.name;
    }
}

结果为:

Xiao Ming
Xiao Hong
//将Xiao Ming变成了Xiao Hong Hong

大家要是对uni-app和Java的其他知识有兴趣可以去看看我主页喔,虽然发表的不多,但是我会努力的

反射二在这,点击跳转

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值