JAVA Reflection(反射) 笔记+他人博客

1 反射Reflection

1.1 Java反射机制概述

1.1.1 静态 VS 动态语言

动态语言

​ 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

​ 主要动态语言:Object-C、C#、JavaScript、PHP、Python等

静态语言

​ 与动态语言相对应的,运行时结构不可改变的语言就是静态语言。如Java、C、C++

​ Java不是动态语言,但是Java可以称之为“准动态语言”。即java有一定的动态性,可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活。

1.1.2 Java Reflection

Reflection(反射)是java被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息(包括private),并能直接操作任意对象的内部属性及方法。

Class C = Class.forName("java.lang.String");

加载完类之后,在堆内存的方法区中就产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。可以通过这个对象看到类的结构。这个对象就像是一面镜子,透过这个镜子可以看到类的结构,多以将只称为:反射

在这里插入图片描述

1.1.3 Java 反射机制研究及应用

Java反射机制提供的功能

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时获取泛型信息

在运行时调用任意一个对象的成员变量和方法

在运行时处理注解

生产动态代理

1.1.4 Java反射优点和缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么,并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。

1.1.5 反射相关的API

java.lang.Class:代表一个类

java.lang.reflect.Method:代表类的方法

java.lang.reflect.Field:代表类的成员变量

java.lang.reflect.Constructor:代表类的构造器

1.2 理解Class类并获取Class实例

1.2.1 Class类

在Object类中定义了以下的方法,此方法将被所有子类继承

public final Class getClass()

以上的方法返回值类型是一个Class类,此类是java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了那些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void[])的有关信息。

​ Class本身也是一个类

​ Class对象只能由系统建立对象

​ 一个加载的类在JVM中只会有一个Class实例

​ 一个Class对象对应的是一个加载到JVM中的一个.class文件

​ 每个类的实例都会记得自己是由哪个Class实例所生成

​ 通过Class可以完整得到一个类中的所有被加载的结构

​ Class类是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象

1.2.2 Class类的常用方法

static Class<?> forName(String className) 		//返回与给定字符串名称的类或接口相关联的类对象。 
T newInstance() // 创建由此类对象表示的类的新实例。  
String getName()  // 返回由类对象表示的实体(类,接口,数组类,原始类型或空白)的名称,作为 String 。  
Class<? super T> getSuperclass() // 返回类表示此所表示的实体(类,接口,基本类型或void)的超类类 。
public Class<?>[] getInterfaces() //确定由该对象表示的类或接口实现的接口。 
ClassLoader getClassLoader() // 返回类的类加载器。 
Constructor<?>[] getConstructors() // 返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。  
Method getMethod(String name, Class<?>... parameterTypes) // 返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。      
public Field[] getDeclaredFields() throws SecurityException // 返回的数组Field对象反映此表示的类或接口声明的所有字段类对象。 

1.2.3 获取Class类的实例

  1. 若已知具体的类,通过类的Class属性获取,该方法最为安全可靠,程序性能最高

    Class clazz = Person.calss();
    
  2. 已知某个类的实例,调用该实例的getClass()方法获取Class对象

    Class calzz = Person.getClass();
    
  3. 已知一个类的全类名,且在该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

    Class clazz = Class.forName("demo01.Student");
    
  4. 内置基本数据类型可以直接用类名.Type

  5. 可以使用ClassLoader

package com.kuang.reflection;

public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        System.out.println("名字是:" + person.name);   // 名字是:null

        // 方式1:通过对象获得
        Class<? extends Person> c1 = person.getClass();
        System.out.println(c1.hashCode());  // 764977973

        // 方式2:forName获得
        Class<?> c2 = Class.forName("com.kuang.reflection.Student");
        System.out.println(c2.hashCode());  // 824909230

        // 方式3:通过类名.class获得
        Class<Student> c3 = Student.class;
        System.out.println(c3.hashCode());  // 824909230

        // 方式4:基本内置类型的包装类都有一个Type属性
        Class<Integer> c4 = Integer.TYPE;
        System.out.println(c4.hashCode());  // 589431969
        System.out.println(c4); // int

        // 获得父类类型
        Class<?> sc1 = c2.getSuperclass();
        System.out.println(sc1);    // class com.kuang.reflection.Person
        
    }
}
class Person{
    public String name;

    public Person() {
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Student extends Person{
    public Student() {
        this.name = "学生";
    }
}

class Teacher extends Person{
    public Teacher() {
        this.name = "老师";
    }
}

1.2.4 那些类型可以有Class对象

class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

interface:接口

[]:数组

enum:枚举

annotation:注解@interface

primitive type:基本数据类型

void

package com.kuang.reflection;

import java.lang.annotation.ElementType;

// 测试所有类型的class
public class Test03 {
    public static void main(String[] args) {
        Class<Object>       c1 = Object.class;    // 类
        Class<Comparable>   c2 = Comparable.class;    //接口
        Class<String[]>     c3 = String[].class;    //一维数组
        Class<int[][]>      c4 = int[][].class;  //二维数组
        Class<Override>     c5 = Override.class;    //注解
        Class<ElementType>  c6 = ElementType.class;  //枚举
        Class<Integer>      c7 = Integer.class;  //基本数据类型
        Class<Void>         c8 = void.class;    //void
        Class<Class>        c9 = Class.class;  //Class
        System.out.println(c1);     // class java.lang.Object
        System.out.println(c2);     // interface java.lang.Comparable
        System.out.println(c3);     // class [Ljava.lang.String;
        System.out.println(c4);     // class [[I
        System.out.println(c5);     // interface java.lang.Override
        System.out.println(c6);     // class java.lang.annotation.ElementType
        System.out.println(c7);     // class java.lang.Integer
        System.out.println(c8);     // void
        System.out.println(c9);     // class java.lang.Class

        // 只要元素类型与维度一样,就是同行一个class
        int[] a = new int[10];
        int[] b = new int[1000];
        System.out.println(a.getClass().hashCode());    // 764977973
        System.out.println(b.getClass().hashCode());    // 764977973
    }
}

1.3 类的加载与ClassLocader

1.3.1 类的加载

内存分析
在这里插入图片描述在这里插入图片描述

类的加载过程

​ 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化

加载:将class文件字节码内容加载到内存中,并将这静态数据转发能吃方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象

链接:将java类的二进制代码合并到JVM的运行状态之中的过程

​ 验证:确保加载的类信息符合JVM规范,没有安全方面的考虑

​ 准备:正式为类变量(static)分配内存,并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配

​ 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程

初始化

​ 执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而成的。(类构造器是构造类信息的,不是构造该类对象的构造器)

​ 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化

​ 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步

2 百度

2.1 动态语言与静态语言的区别

Static typing when possible, dynamic typing when needed.

(1)动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python 和 Ruby 就是一种典型的动态类型语言,其他的各种脚本语言如 JavaScript 也属于动态类型语言。

  • 动态类型语言的优点
    编写的代码数量更少,看起来更加简洁,可以把精力更多地放在业务逻辑上。虽然不区分类型在某些情况下会让程序变得难以理解,但整体而言,代码量越少,越专注于逻辑表达,对阅读程序越有帮助。
  • 动态类型语言的缺点
    无法保证变量的类型,从而在程序的运行期有可能发生跟类型相关的错误。

动态类型语言对变量类型的宽容给实际编码带来了很大的灵活性。由于无需进行类型检测,我们可以尝试调用任何对象的任意方法,而无需去考虑它原本是否被设计为拥有该方法。

(2)静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++ 是静态类型语言的典型代表,其他的静态类型语言还有 C#、JAVA 等。

  • 静态类型语言的优点
    首先是在编译时就能发现类型不匹配的错误,编译器可以帮助我们提前避免程序在运行期间有可能发生的一些错误。其次,如果在程序中明确规定了数据类型,编译器还可以针对这些信息对程序进行一些优化工作,提高程序执行速度。
  • 静态类型语言的缺点
    首先是迫使程序员依照强契约来编写程序,为每个变量规定数据类型,归根结底只是辅助我们编写可靠性高程序的一种手段,而不是编写程序的目的,毕竟大部分人编写程序的目的是为了完成需求交付生产。其次,类型的声明也会增加更多的代码,在程序编写过程中,这些细节会让程序员的精力从思考业务逻辑上分散开来。

一般静态类型语言更适合用于描述数据结构。

面向接口编程

在动态类型语言的面向对象设计中,我们不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:面向接口编程,而不是面向实现编程。例如,一个对象若有 push 和 pop 方法,并且这些方法提供了正确的实现,它就可以被当作栈来使用。一个对象如果有 length 属性,也可以依照下标来存取属性,这个对象就可以被当作数组来使用。

在静态类型语言中,要实现“面向接口编程”并不是一件容易的事情,往往要通过抽象类或者接口等将对象进行向上转型。当对象的真正类型被隐藏在它的超类型身后,这些对象才能在类型检查系统的“监视”之下互相被替换使用。只有当对象能够被互相替换使用,才能体现出多态性的价值。

多态

多态的含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象根据这个消息分别给出不同的反馈。

静态类型语言编译时会进行类型匹配检查,所以不能给变量赋予不同类型的值。为了解决这一问题,静态类型的面向对象语言通常通过向上转型的技术来取得多态的效果。

而动态类型语言的变量类型在运行期是可变的,这意味着对象的多态性是与生俱来的。一个对象能否执行某个操作,只取决于有没有对应的方法,而不取决于它是否是某种类型的对象。

2.2 Java Reflection是什么?

2.2.1 JAVA API:Oracle

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

反射通常由需要检查或修改Java虚拟机中运行的应用程序的运行时行为的程序使用。 这是一个相对高级的功能,只应由对语言基础有很深了解的开发人员使用。 考虑到这一警告,反射是一种强大的技术,可以使应用程序执行原本不可能的操作。

简单的讲:

  • 反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作他们。

再简单一点的讲:

  • 我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。

java 类

在这里插入图片描述

2.2.2 Java反射(Reflection)框架主要提供以下功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  4. 在运行时调用任意一个对象的方法

2.2.3 Java反射(Reflection)的主要用途

  1. 工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了
  2. 数据库JDBC中通过Class.forName(Driver).来获得数据库连接驱动
  3. 分析类文件:毕竟能得到类中的方法等等
  4. 访问一些不能访问的变量或属性:破解别人代码

2.2.4 Java反射(Reflection)的基本运用

获取Class有一下三种方法:

  1. 使用Class类的forName静态方法
//在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机),
//这通过java.lang.Class类的静态方法forName(String  className)实现。
//例如:
try{
    //加载MySql的驱动类
    Class.forName("com.mysql.jdbc.Driver") ;
}catch(ClassNotFoundException e){
    //System.out.println("找不到驱动程序类 ,加载驱动失败!");
    e.printStackTrace() ;
}
//成功加载后,会将Driver类的实例注册到DriverManager类中。
  1. 直接获取某一个对象的class
Class<?> klass = int.class; 
Class<?> classInt = Integer.TYPE;
  1. 调用某个对象的getClass()方法
public class Test{
    public static void main(String[] args) {
        Demo demo=new Demo();
        System.out.println(demo.getClass().getName());
    }
}

2.2.5 说说工厂模式和Java 反射(Reflection)机制

如果在工厂模式下面,我们不使用Java 反射(Reflection)机制,会是什么样子呢?

package com.b510.hongten.test.reflex;

/**
 * @author hongten
 * @created Apr 18, 2018
 */
public class FactoryTest {

    public static void main(String[] args) {
        Cats cats = FactoryTest.getInstance("Tiger");
        cats.eatMeat();

        /*
         * The Result : The tiger eat meat.
         */
    }

    public static Cats getInstance(String name) {
        Cats cats = null;
        if ("Tiger".equals(name)) {
            cats = new Tiger();
        }
        if ("Lion".equals(name)) {
            cats = new Lion();
        }
        return cats;
    }
}

interface Cats {
    public abstract void eatMeat();
}

class Tiger implements Cats {
    public void eatMeat() {
        System.out.println("The tiger eat meat.");
    }
}

class Lion implements Cats {
    public void eatMeat() {
        System.out.println("The lion eat meat.");
    }
}

我们会发现:

当我们在添加一个子类(Puma-美洲狮)的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改的就会很多。

我们对程序进行修改,加入反射机制。

package com.b510.hongten.test.reflex;

/**
 * @author hongten
 * @created Apr 18, 2018
 */
public class FactoryTest {

    public static void main(String[] args) {
        //Cats cats = FactoryTest.getInstance("com.b510.hongten.test.reflex.Lion");
        Cats cats = FactoryTest.getInstance("com.b510.hongten.test.reflex.Tiger");
        cats.eatMeat();

        /*
         * The Result : The tiger eat meat.
         */
    }

    public static Cats getInstance(String name) {
        Cats cats = null;
        try {
            try {
                //use Class.forName() with java reflection
                cats = (Cats) Class.forName(name).newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return cats;
    }
}

interface Cats {
    public abstract void eatMeat();
}

class Tiger implements Cats {
    public void eatMeat() {
        System.out.println("The tiger eat meat.");
    }
}

class Lion implements Cats {
    public void eatMeat() {
        System.out.println("The lion eat meat.");
    }
}

我们会发现利用了Java Reflection以后,现在就算我们添加任意多个子类的时候,工厂类就不需要修改。我们只需要传递工厂类的实现类的名称即可。

然而,在这样的情况下面,如果我们需要运行Lion实例的时候,我们还需要去修改java代码。

Cats cats = FactoryTest.getInstance("com.b510.hongten.test.reflex.Lion");

上面的代码虽然可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类。

能不能不需要修改java代码,同样可以完成相同的操作呢?是可以的。

我们可以让工厂模式结合属性文件(如:*.properties, *.xml文件)

属性文件:/reflex/cats.properties

lion=com.b510.hongten.test.reflex.Lion
tiger=com.b510.hongten.test.reflex.Tiger

测试文件:/reflex/FactoryTest.java

package com.b510.hongten.test.reflex;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * @author hongten
 * @created Apr 18, 2018
 */
public class FactoryTest {
    public static void main(String[] args) {
        Cats cats;
        try {
            cats = FactoryTest.getInstance(getPro().getProperty("tiger"));
            if (cats != null) {
                cats.eatMeat();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        /*
         * The Result : The tiger eat meat.
         */
    }

    public static Cats getInstance(String name) {
        Cats cats = null;
        try {
            try {
                // use Class.forName() with java reflection
                cats = (Cats) Class.forName(name).newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return cats;
    }

    public static Properties getPro() throws FileNotFoundException, IOException {
        Properties pro = new Properties();
        File f = new File("cats.properties");
        if (f.exists()) {
            pro.load(new FileInputStream(f));
        } else {
            pro.setProperty("lion", "com.b510.hongten.test.reflex.Lion");
            pro.setProperty("tiger", "com.b510.hongten.test.reflex.Tiger");
            pro.store(new FileOutputStream(f), "FRUIT CLASS");
        }
        return pro;
    }
}

interface Cats {
    public abstract void eatMeat();
}

class Tiger implements Cats {
    public void eatMeat() {
        System.out.println("The tiger eat meat.");
    }
}

class Lion implements Cats {
    public void eatMeat() {
        System.out.println("The lion eat meat.");
    }
}

作为开发者,我们只需要配置cats.properties属性文件即可。极大的提高了程序的扩展性能。

2.2.6 Java反射(Reflection)的一些注意事项

  1. 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
  2. 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。


.setProperty("tiger", "com.b510.hongten.test.reflex.Tiger");
            pro.store(new FileOutputStream(f), "FRUIT CLASS");
        }
        return pro;
    }
}

interface Cats {
    public abstract void eatMeat();
}

class Tiger implements Cats {
    public void eatMeat() {
        System.out.println("The tiger eat meat.");
    }
}

class Lion implements Cats {
    public void eatMeat() {
        System.out.println("The lion eat meat.");
    }
}

作为开发者,我们只需要配置cats.properties属性文件即可。极大的提高了程序的扩展性能。

2.2.6 Java反射(Reflection)的一些注意事项

  1. 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
  2. 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值