JAVA 反射有感

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA 反射有感相关的知识,希望对你有一定的参考价值。

#类加载器 和 反射
##类的加载,连接和初始化
###JVM和类
当一个jave程序被运行时,不管程序有多复杂,最终都会被装进java 虚拟机器(JVM)的进程里。同一个JVM的所有线程,所有变量都处于同一个进程中,都是用该JVM进程的内存区 (注意进程和线程的区别).

JVM进程被终止的情况

1.程序运行到正常结束

2.程序运行System.exit()或者Runtime.getRuntime().exit() 方法

3.程序执行过程中遇到未捕获的一场或错误 而结束

4.程序所在平台强制结束了JVM

###类的加载
当程序主动使用某个类,该类还没有被加载到内存中,系统会执行下面的三个步骤对类进行初始化

1.加载

是指将类的Class 文件读入内存,并创建一个java.lang.class 对象,当程序中使用任何的类的时候,系统都会创建一个java.lang.class对象;类的加载由类加载器完成,类加载器由JVM提供,类加载器是所有程序运行的基础;由JVM提供的类加载器叫做系统类加载器

不同的类加载器,可以从不同来源把类转换成二进制数据,通常有几种来源

  • 从本地文件中加载class文件(最常用)
  • 从JAR包中加载class文件(也常见,通过用于JDBC连接数据库)
  • 通过网络加载class文件
  • 把一个java源文件动态编译,并执行加载

2.连接

当类的加载之后,系统会生成一个Class 对象,后面进行连接,连接负责把类的二进制数据合并到JRE中

(JRE是Java Runtime Environment缩写,指Java运行环境,是Sun的产品。运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库)

3.初始化

JAVA中类的初始化两种方式(同时使用这两种方式,系统会按照在代码中的位置依次从上到下执行)

1.声明类变量

static int a=5

2.使用静态初始化块为类变量指定初始值

static int b;

static{

b=6

}

JVM初始化一个类包含下面几个步骤

1.假如这个类还没被加载和连接,则程序先 加载连接该类

2.如果该类的直接父类还没有被初始化,则应该先初始化其直接父类

3.假如类中有初始化语句,则系统依次执行这些初始化语句

在2中,系统对直接父类的初始化也遵循这三个步骤;所以java.lang.Object(超类) ,这个类被最先初始化

类初始化的时机

java程序使用某个类或者接口的时候,系统就会初始化该类或接口;

1 创建类的实例(包括new创建实例,反射,反序列化)

2调用某个类的方法(static 方法)

3 访问某个类或者接口的类变量,或者为该类变量赋值

4使用反射方式强制创建某个类或者接口对应的java.lang.Class对象;例如Class.forName("Person"),会直接初始化该类,并且返回该类的java.lang.Class对象,

5初始化该类的子类,子类的父类也会被初始化

6 直接运行java.exe 运行某个主类,程序会初始化该主类

初始化例外的情况

以下的程序是个例外,当出现final形的类变量,在类的编译的时候该变量的值就已经被确定(是加载的步骤的时候就已经被确定),这个被final修饰的变量就变成了一个宏变量,所以当主程序调用testCalss.contantVlaue时候,TestCalss这个类并不会被初始化

package com.test;

class testCalss{
static{
System.out.println("初始化该类");
}
static final String contantVlaue="final bianliang";
}

public class Test1 {
public static void main(String[] args) {
System.out.println(testCalss.contantVlaue);
}

}

如果当final 修饰的类变量不能在程序编译的时候确认下来,则必须等运行时才可以确定该类变量的值,如果通过该类访问类变量的方式,会导致该类被初始化

例如把 static final String contantVlaue=System.currentTimeMillis()+"";

##类加载器
类加载器负责将.class 文件(可以存储在磁盘上, 也可能在网络上)加载到内存中,并生成对应的java.lang.class对象(开发中无须过分关心类加载器,了解工作机制)

###初始化类加载器的组成
JVM启动的时候,会由三个类加载器组成初始化类加载器层次结构

  • Bootstrap ClassLoader:根类加载器 --负责加载java的核心类(比如String等基本变量),不是java.lang.ClassLoader的子类,而是有JVM自身实现
  • Extension ClassLoader:扩展类加载器--负责加载JRE的扩展目录(%JAVA_HOME%jre/lib/ext)中的JAR包的类,以这种方式,可以为java扩展核心类以外的新功能,只要把自己开发的类打包成JAR文件,放入%JAVA_HOME%jre/lib/ext 路径下即可
  • System ClassLoader:系统类加载器 --负责在JVM启动的时候加载来自java 的-classpath选项、java.call.path系统属性,或者classpath环境变量所指定的JAR包和类路径

###类加载机制

1.全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和应用的其他Class也将由该类加载器负责载入。(全由一个类加载器负责)

2.父类委托:先由Parent(父)类加载器试图加载该Class,只有在付类加载器无法加载该类时才尝试从自己的类路径中加载该类。

3.缓存机制:保证所有家长齐国的Class都会被缓存,当程序需要使用某个Class的时候直接加载就行

获取系统类加载器代码

ClassLoader systemLoader=ClassLoader.getSystemClassLoader();

获取系统类加载器的父类加载器,得到扩展类加载器

ClassLoader extensionLader=systemLoader.getParent();

JVM中除了根类加载器之外的所有类加载器都是ClassLoader子类的实例,ClassLoader的常用实现类,该类也是系统类加载器和扩展类加载器的父类;URLClassLoader功能强大,既可以从本地文件系统获取二进制文件加载,也可以从远程主机获取二进制文件来加载类

##通过反射查看类的信息

java对象在运行时候都会出现两种类型,1.编译时类型和运行时类型。例如Person p=new Student();这行代码会生成一个P变量,变量的编译时类型为Person,运行时类型为Student;程序在运行时接受外部传入的一个对象,编译的时类型是Object,但程序又调用该对象运行时类型的方法。为了在程序运行的时候发现对象和类的真实信息,有两种做法

  1. 假设在编译时和运行时都完全知道对象和类的具体信息,可以先使用instanceof运算符进行判断,再利用强制类型转换将其转换成运行时类型的变量即可
  2. 编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,就必须用到反射的思想

###获得Class对象
每个类被加载之后,系统会为该类生成一个对应的Class对象,通过该Class对象可以访问JVM中的这个类。
1.使用Class类的forName(String className)静态方法,传入类的全限定类名(完整包名)

2.使用类的Class属性来获取该类对应的Class对象,例如Person.class将会返回Person类对应的Class对象

3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法。所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象

对于第一种方式和第二种方式都是直接根据类来取得该类的Class对象,相比之下,第二种方式有以下两种优势。
1.代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在
2.程序性能更好。这种方式无法调用方法,性能更好

###可以从Class中获取哪些信息?

  1. Constructor 构造器 getConstructor()
  2. Menthod 获取Class对应类所包含的方法 getMethod()
  3. 访问Class对应类所包含的成员变量 getFields()
  4. 获取几个方法用户访问Class对应类上所包含的Annotation getAnnotation()
  5. 还可以判断方法来判断该类是否为借口、枚举、注解类型等

Tips:重载:方法名相同,但是参数列表不同。在java中确定一个方法,有方法名是不够的,应该由方法名和形参列表(形参名是没有任何意义的,必须要靠形参类型来)来确定。
例如有三个方法

1.  public void info()
2.  public void info(String str)
3.  public void info(String str,Integer num)

要指定是第二个info 方法,因此在程序中获得该方法代码如下

class.getMethod("info",String.class)

指定第三个info,代码如下

class.getMehtod("info",String.class,Integer.class)

##使用反射生成操作对象
Class对象可以获得该类里的方法(method),构造器(Constructor),成员变量(Field)

###利用反射创建对象
两种方式

  1. 使用Class对象的newInstance()方法创建该Class对象对应类的实例,但是要求Class对象的对应类有默认构造器,执行newInstance()方法时,实际上利用默认构造器来创建该类的实例 -----最常用的方法
  2. 使用Class对象指定Constructor 对象,再调用Constructor对象的newInstance()方法来创建该Class对象的对应类的实例。这种方式可以选择使用指定的构造器来创建实例。

下面的例子就是通过反射的思想 使用第一种方式newInstance,来生成对象,形成效果类似与Spring 的生成对象

package com.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import java.util.Set;

public class ObjectPoolFactory {

private Map<String,Object> objectPool=new HashMap<String, Object>();

private Object createObject(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
    Class<?> clazz=Class.forName(className);
    return clazz.newInstance();
}
//传入文件存储的位置
public void initPool() throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException{
    try {
        //d:/test/a.properties
        InputStream fis=new FileInputStream(new File("d:/test/a.txt"));
        //读取properties文件中的信息
        {
        Properties props=new Properties(); 
        props.load(fis);
        //
        System.out.println("initPool1");
        //遍历properties文件中的信息,并根据props文件中的类的全限定名称创建对象,并放入对象池中
        Set<String> stringPropertyNames = props.stringPropertyNames();
        System.out.println(stringPropertyNames.isEmpty());
        for (String name : stringPropertyNames) {
            //存储的形式是类的权限定名称 ,对象 的形式存储在对象池中
            System.out.println("initPool2"+props.getProperty(name));
            objectPool.put(name, createObject(props.getProperty(name)));
        }
    }
    } catch (FileNotFoundException e) {
        System.out.println("没有加载到文件");
        e.printStackTrace();
    }
}
//返回对象
public Object getObject(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException  {
    //System.out.println(objectPool.get(className));
    return objectPool.get(className);
}

public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
    ObjectPoolFactory pool= new ObjectPoolFactory();
    pool.initPool();

    System.out.println(pool.getObject("a"));
    System.out.println(pool.getObject("b"));

}

}

如果不想用默认构造器的方式来生成对象,可以使用Constructor的方式来指定构造器,从而生成对象。步骤如下

  1. 获取该类的Class对象
  2. 利用Class对象的getConstructor()方法来获取指定的构造器
  3. 调用Constructor的newInstance()方法来创建Java对象

    `public class Test2 {

    public static void main(String[] args) throws Exception {
    //获取JFrams的Class对象
    Class<?> JFrame = Class.forName("javax.swing.JFrame");
    //获取JFrams Class对象中的带参数的构造器
    Constructor ctor=JFrame.getConstructor(String.class);
    //生成一个JFrams的对象,因为构造器中带参数,所以生成对象的时候就要带一个String类型的参数
    Object object = ctor.newInstance("123");
    System.out.println(object);
    }

反射总结:通常没有必要通过反射来创建对象,因为性能低,实际上,只有当程序需要动态创建某个类的对象的时候才考虑使用反射,通常再通用性比较广的框架上,可能大量使用反射(Spring)。

###调用方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMehtod()方法获取全部方法或者指定方法
getMethods() 获得全部方法,返回值为一个数组
getMethod(),获取指定方法,返回Method对象

Method 里面包含一个invoke()方法,签名如下
Object invoke(Object obj,Object...args):该方法中obj是执行该方法的主调,后面的args是执行该方法的实参

以上是关于JAVA 反射有感的主要内容,如果未能解决你的问题,请参考以下文章

反射的真正用法-有感于网易云课堂传智播客方立勋老师反射视频的小失误

查看发票组代码后的总结和有感

反射机制

反射机制入门

反射机制入门

反射机制入门