深入理解java核心机制(虚拟机JVM类加载,反射)

一、java的核心机制

java有两种核心机制:java虚拟机(JavaVirtual Machine)与来及回收机制(Garbage collection)

Java虚拟机:是运行所有java程序的抽象计算机,是java语言的运行环境,在其上面运行Java代码编译后的字节码程序,java虚拟机实现了与平台无关性
java垃圾回收:自动释放不用对象内存空间,在java程序运行过程中自动进行,垃圾回收机制可以大大缩短编程时间,保护程序的完整性,是java语言安全策略的一个重要部分。

二、java虚拟机及其结构

java垃圾回收不需要程序员手动操作,我们需要关注的是java虚拟机。java虚拟机承载着程序从源码到运行的全部工作。

java虚拟机是可运行java代码的假想计算机,有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统,可以执行java字节码程序。Java语言的一个非常重要的特征就是平台的无关性。而使用Java虚拟机是实现这一特点的关键。java语言使用模式java虚拟机屏蔽了与平台相关的信息,使得java语言编译程序只需要生成在java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

对于JVM的基本结构,我们可以从下图大致了解:

这里写图片描述

三、程序运行过程

从源文件创建到程序运行,java程序要经过两大步骤:编译,运行
1、源文件由编译器编译成字节码(ByteCode)
2、字节码由虚拟机解释运行

第一步(编译):创建源文件之后,程序会被编译为.class文件。Java编译一个类时,如果这个类所依赖的类还没有被编译,编译器就会先编译这个被依赖的类,然后引用,否则直接引用。编译后的字节码文件格式主要分为两部分:常量池和方法字节码。
第二步(运行):Java类运行的过程大概分为两个过程:1、类的加载 2、执行

四、类的加载

类的加载过程

Java程序编译后形成.class文件,通过类加载器将字节码(*.class)加载入JVM内存中。JVM将类加载过程分加载、连接、初始化三个阶段。其中连接阶段又分为验证,准备,解析三个阶段。

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

这里写图片描述

(1)Bootstrap ClassLoader启动类加载器

负责加载$JAVA_HOME中jre/lib/里所有的 class(JDK 代表 JDK 的安装目录,下同),或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的java.*开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器由 C++ 实现,不是 ClassLoader 子类。无法被 Java 程序直接引用的。

(2)Extension ClassLoader扩展类加载器

该加载器由sun.misc.LauncherExtClassLoader实现,负责加载java平台中扩展功能的一些jar包,包括JAVA_HOME中jre/lib/.jar或-Djava.ext.dirs指定目录下的 jar 包。即JDK\jre\lib\ext目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器

(3)App ClassLoader应用程序类加载器

该类加载由sun.misc.Launcher$AppClassLoader 来实现,负责加载classpath中指定的jar包及目录中的class,开发者可以直接是使用该类的加载器,如果应用程序没有定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

启动类加载器:它是由C++实现(这里仅限于Hotspot,也就是jdk1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现),是虚拟机自身的一部分。
所有其他类加载器:这些类加载器是由java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader
,这些类加载器需要由启动类加载器加载到内存之后才能加载其他类。
应用程序都是由这三种类加载器互相配合进行加载的,我们还可以加入自定义的类加载器。

加载

加载时类加载过程的第一阶段,在加载阶段,虚拟机需要完成以下三件事:
1、通过一个类的全限定名来获取其定义的二进制字节流
2、将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.class对象,作为对方法区中这些数据的访问入口

注意:
这里第一条中的二进制字节流并不是单纯的从calss文件中获取的,比如他还可以从jar包中获取,从网络中获取(最典型的应用便是 Applet)、由其他文件生成(JSP 应用)等。
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

JVM主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,JVM并不是在一开始就把一个程序就所有的类都加载到内存中,而是到用的时候才把它加载进来,而且只加载一次。

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从 Custom ClassLoader 到 BootStrap ClassLoader 逐层检查,只要某个 Classloader 已加载就视为已加载此类,保证此类只所有 ClassLoade r加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
这几种类加载器的层次关系如下图所示:
这里写图片描述

这种层次关系称为类加载器的双亲委派模型。双亲委派模型的工作流程是:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

验证

验证的目的是为了确保 Class 文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。

准备

准备阶段目的是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配,对于该阶段有以下几点需要准备:

这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
这里所设置的初始值通常情况下是数据类型默认的零值(如 0、0L、null、false 等),而不是被在 Java 代码中被显式地赋予的值。

解析

解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。

解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info 四种常量类型。

1、类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。

2、字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束

初始化

类初始化是类加载过程的最后一个阶段,到初始化阶段,才真正开始执行类中的java程序代码,虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化:

遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的java代码场景:
1、使用new关键字实例化对象时候,读取或设置一个静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的惊涛字段除外)、以及调用一个类的静态方法时’
2、使用java.lang.refect包的方法对类进行反射调用时候,如果类还没有被初始化,则需要先触发其初始化。
3、当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发父类的初始化。
4、当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会先执行该主类

虚拟机规定只有这四种情况才会触发类的初始化,称为对一个类进行主动引用除此之外所有的引用类的方式不会触发其初始化,称之为被动引用。

五、静态加载和动态加载

Java初始化一个类的时候可以用new操作符来初始化,也乐意通过Class.forName的方式来得到一个Class类型的实例,然后通过这个Class类型的实例的newInstance来初始化.我们把前者叫做JAVA的静态加载,把后者叫做动态加载.。

有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定(dynamic binding)、动态链接(dynamic linking)、动态加载(dynamic loading)等。然而“动态”一词其实没有绝对而普遍适用的严格定义,有时候甚至像面向对象当初被导入编程领域一样,一人一把号,各吹各的调。
一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

静态加载的时候如果在运行环境中找不到要初始化的类,抛出的是NoClassDefFoundError,它在JAVA的异常体系中是一个Error.

动态态加载的时候如果在运行环境中找不到要初始化的类,抛出的是ClassNotFoundException,它在JAVA的异常体系中是一个checked异常,在写代码的时候就需要catch.

六、反射

反射是框架设计的灵魂
(使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))
Java反射机制:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

java反射机制主要提供了以下功能:

在运行时判断任意一个对象所属的类;在运行时候构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

Java有个Object类,是所有Java类的继承根源,其内声明了数个应该在所有java类中被改写的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class对象。

Class类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时候的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。

如果您想借由“修改Java标准库源码”来观察Class 对象的实际生成时机(例如在Class的constructor内添加一个println()),这样是行不通的!因为Class并没有public constructor。

Class是Reflection故事起源。针对任何您想探勘的类,唯有先为它产生一个Class 对象,接下来才能经由后者唤起为数十多个的Reflection APIs。Reflection机制允许程序在正在执行的过程中,利用Reflection APIs取得任何已知名称的类的内部信息,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可以在执行的过程中,动态生成instances、变更fields内容或唤起methods。

从Class中获取信息

Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考JDK文档

获取类内信息

获取内容    方法签名
构造器 Constructor<T> getConstructor(Class<?>... parameterTypes)
包含的方法   Method getMethod(String name, Class<?>... parameterTypes)
包含的属性   Field getField(String name)
包含的Annotation   <A extends Annotation> A getAnnotation(Class<A> annotationClass)
内部类 Class<?>[] getDeclaredClasses()
外部类 Class<?> getDeclaringClass()
所实现的接口  Class<?>[] getInterfaces()
修饰符 int getModifiers()
所在包 Package getPackage()
类名  String getName()
简称  String getSimpleName()

一些判断类本身信息的方法

判断内容    方法签名
注解类型?   boolean isAnnotation()
使用了该Annotation修饰?   boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
匿名类?    boolean isAnonymousClass()
数组? boolean isArray()
枚举? boolean isEnum()
原始类型?   boolean isPrimitive()
接口? boolean isInterface()
obj是否是该Class的实例 boolean isInstance(Object obj)

1、获取class

这里写图片描述
主有三种获得class的途径,使用时要注意区别

a、类型.class 如: String.class使用类名加“.class”的方式即会返回与该类对应的Class对象。这个方法可以直接获得与指定类关联的Class对象,而并不需要有该类的对象存在。
b、Class.forName(“类名”);该方法可以根据字符串参数所指定的类名获取与该类关联的Class对象。如果该类还没有被装入,该方法会将该类装入JVM。forName方法的参数是类的完 整限定名(即包含包名)。通常用于在程序运行时根据类名动态的载入该类并获得与之对应的Class对象。
c、obj.getClass();所有Java对象都具备这个方法,该方法用于返回调用该方法的对象的所属类关联的Class对象

2、获取构造方法
Class类提供了四个public方法,用于获取某个类的构造方法:

Constructor getConstructor(Class[] params)根据构造函数的参数,返回一个具体的具有public属性的构造函数

Constructor getConstructors()     返回所有具有public属性的构造函数数组

Constructor getDeclaredConstructor(Class[] params)     根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)

Constructor getDeclaredConstructors()    返回该类中所有的构造函数数组(不分public和非public属性)


 * 1 反射出无参的构造方法并得到对象
  * 注意:
  * 1 在Class.forName()中应该传入含有包名的类全名
  * 2 newInstance()方法的本质是调用类的无参Public构造方法
  */
  String className1="cn.testreflect.Worker";
  Class clazz1=Class.forName(className1);
  Object object1=clazz1.newInstance();
  System.out.println("object1.toString()="+object1.toString());
  /**
  * 2 反射出带参数的构造方法并得到对象
  */
  String className2="cn.testreflect.Worker";
  Class clazz2=Class.forName(className2);
  Constructor constructor1=clazz2.getConstructor(int.class,String.class);
  Object object2=constructor1.newInstance(18,"小明");
  System.out.println("object2.toString()="+object2.toString());

3、获取类的成员方法

与获取构造方法的方式相同,存在四种获取成员的方法的方式。

Method getMethod(String name, Class[] params) 根据方法名和参数,返回一个具体的具有public属性的方法

Method[] getMethods() 返回所有具有public属性的方法数组

Method getDeclaredMethod(String name, Class[] params)  根据方法名和参数,返回一个具体的方法(不分public和非public属性)

Method[] getDeclaredMethods() 返回该类中的所有的方法数组(不分public和非public属性)
 * 调用对象的带参数的方法
  */
  String className5="cn.testreflect.Worker";
  Class clazz5=Class.forName(className5);
  Method method=clazz5.getMethod("printMessage", 
String.class,int.class,int.class);
  Object object5=clazz5.newInstance();
  method.invoke(object5, "周星星",50,9527);
  } catch (Exception e) {
  System.out.println(e.toString());
  }

4、获取类的成员变量(成员属性)

Field getField(String name)  根据变量名,返回一个具体的具有public属性的成员变量

Field[] getFields()  返回具有public属性的成员变量的数组

Field getDeclaredField(String name) 根据变量名,返回一个成员变量(不分public和非public属性)

Field[] getDelcaredFields() 返回所有成员变量组成的数组(不分public和非public属性)
* 1 获取类的私有字段
  * 注意:
  * 获取共有字段应调用clazz3.getField(name)方法
  */
  String className3="cn.testreflect.Worker";
  Class clazz3=Class.forName(className3);
  Field ageField1=clazz3.getDeclaredField("age");
  System.out.println("ageField1="+ageField1);
  /**
  * 2 获取和更改某个对象的私有字段
  * 即模拟get()和set()方法
  */
  String className4="cn.testreflect.Worker";
  Class clazz4=Class.forName(className4);
  Field ageField2=clazz4.getDeclaredField("age");
  Object object4=constructor1.newInstance(18,"小明");
  //取消访问私有字段的合法性检查
  ageField2.setAccessible(true);
  //获取对象的私有字段
  Object ageObject4=ageField2.get(object4);
  System.out.println("ageObject4="+ageObject4);
  //再更改对象的私有字段的值
  ageField2.set(object4, 9527);
  //重新获得
  Object ageObject5=ageField2.get(object4);
  System.out.println("ageObject5="+ageObject5);

七、反射的使用

先写一个Student类。

1、获取Class对象的三种方式

1.1 Object ——> getClass();
1.2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
1.3 通过Class类的静态方法:forName(String className)(常用)

其中1.1是因为Object类中的getClass方法、因为所有类都继承Object类。从而调用Object类来获取

/** 
 * 获取Class对象的三种方式 
 * 1 Object ——> getClass(); 
 * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性 
 * 3 通过Class类的静态方法:forName(String  className)(常用) 
 * 
 */  
public class Fanshe {  
    public static void main(String[] args) {  
        //第一种方式获取Class对象    
        Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。  
        Class stuClass = stu1.getClass();//获取Class对象  
        System.out.println(stuClass.getName());  

        //第二种方式获取Class对象  
        Class stuClass2 = Student.class;  
        System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个  

        //第三种方式获取Class对象  
        try {  
            Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名  
            System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }  

    }  
}

注意:在运行期间,一个类,只有一个Class对象产生。

三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。

2、通过反射获取构造方法并使用

student类:

package fanshe;  

public class Student {  

    //---------------构造方法-------------------  
    //(默认的构造方法)  
    Student(String str){  
        System.out.println("(默认)的构造方法 s = " + str);  
    }  

    //无参构造方法  
    public Student(){  
        System.out.println("调用了公有、无参构造方法执行了。。。");  
    }  

    //有一个参数的构造方法  
    public Student(char name){  
        System.out.println("姓名:" + name);  
    }  

    //有多个参数的构造方法  
    public Student(String name ,int age){  
        System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。  
    }  

    //受保护的构造方法  
    protected Student(boolean n){  
        System.out.println("受保护的构造方法 n = " + n);  
    }  

    //私有构造方法  
    private Student(int age){  
        System.out.println("私有的构造方法   年龄:"+ age);  
    }  

}  

共有6个构造方法;
测试类:

package fanshe;  

import java.lang.reflect.Constructor;  


/* 
 * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员; 
 *  
 * 1.获取构造方法: 
 *      1).批量的方法: 
 *          public Constructor[] getConstructors():所有"公有的"构造方法 
            public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有) 

 *      2).获取单个的方法,并调用: 
 *          public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法: 
 *          public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有; 
 *       
 *          调用构造方法: 
 *          Constructor-->newInstance(Object... initargs) 
 */  
public class Constructors {  

    public static void main(String[] args) throws Exception {  
        //1.加载Class对象  
        Class clazz = Class.forName("fanshe.Student");  


        //2.获取所有公有构造方法  
        System.out.println("**********************所有公有构造方法*********************************");  
        Constructor[] conArray = clazz.getConstructors();  
        for(Constructor c : conArray){  
            System.out.println(c);  
        }  


        System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");  
        conArray = clazz.getDeclaredConstructors();  
        for(Constructor c : conArray){  
            System.out.println(c);  
        }  

        System.out.println("*****************获取公有、无参的构造方法*******************************");  
        Constructor con = clazz.getConstructor(null);  
        //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型  
        //2>、返回的是描述这个无参构造函数的类对象。  

        System.out.println("con = " + con);  
        //调用构造方法  
        Object obj = con.newInstance();  
    //  System.out.println("obj = " + obj);  
    //  Student stu = (Student)obj;  

        System.out.println("******************获取私有构造方法,并调用*******************************");  
        con = clazz.getDeclaredConstructor(char.class);  
        System.out.println(con);  
        //调用构造方法  
        con.setAccessible(true);//暴力访问(忽略掉访问修饰符)  
        obj = con.newInstance('男');  
    }  

}  

后台输出:

**********************所有公有构造方法*********************************  
public fanshe.Student(java.lang.String,int)  
public fanshe.Student(char)  
public fanshe.Student()  
************所有的构造方法(包括:私有、受保护、默认、公有)***************  
private fanshe.Student(int)  
protected fanshe.Student(boolean)  
public fanshe.Student(java.lang.String,int)  
public fanshe.Student(char)  
public fanshe.Student()  
fanshe.Student(java.lang.String)  
*****************获取公有、无参的构造方法*******************************  
con = public fanshe.Student()  
调用了公有、无参构造方法执行了。。。  
******************获取私有构造方法,并调用*******************************  
public fanshe.Student(char)  
姓名:男  

调用方法:
1.获取构造方法:
1).批量的方法:
public Constructor[] getConstructors():所有”公有的”构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

2).获取单个的方法,并调用:
public Constructor getConstructor(Class… parameterTypes):获取单个的”公有的”构造方法:
public Constructor getDeclaredConstructor(Class… parameterTypes):获取”某个构造方法”可以是私有的,或受保护、默认、公有;

调用构造方法:
Constructor–>newInstance(Object… initargs)

2、newInstance是 Constructor类的方法(管理构造函数的类)
api的解释为:
newInstance(Object… initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用

3、获取成员变量并调用

student类:

public class Student {  
    public Student(){  

    }  
    //**********字段*************//  
    public String name;  
    protected int age;  
    char sex;  
    private String phoneNum;  

    @Override  
    public String toString() {  
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex  
                + ", phoneNum=" + phoneNum + "]";  
    }  


}

测试类:

import java.lang.reflect.Field;  
/* 
 * 获取成员变量并调用: 
 *  
 * 1.批量的 
 *      1).Field[] getFields():获取所有的"公有字段" 
 *      2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有; 
 * 2.获取单个的: 
 *      1).public Field getField(String fieldName):获取某个"公有的"字段; 
 *      2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的) 
 *  
 *   设置字段的值: 
 *      Field --> public void set(Object obj,Object value): 
 *                  参数说明: 
 *                  1.obj:要设置的字段所在的对象; 
 *                  2.value:要为字段设置的值; 
 *  
 */  
public class Fields {  

        public static void main(String[] args) throws Exception {  
            //1.获取Class对象  
            Class stuClass = Class.forName("fanshe.field.Student");  
            //2.获取字段  
            System.out.println("************获取所有公有的字段********************");  
            Field[] fieldArray = stuClass.getFields();  
            for(Field f : fieldArray){  
                System.out.println(f);  
            }  
            System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");  
            fieldArray = stuClass.getDeclaredFields();  
            for(Field f : fieldArray){  
                System.out.println(f);  
            }  
            System.out.println("*************获取公有字段**并调用***********************************");  
            Field f = stuClass.getField("name");  
            System.out.println(f);  
            //获取一个对象  
            Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();  
            //为字段设置值  
            f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"  
            //验证  
            Student stu = (Student)obj;  
            System.out.println("验证姓名:" + stu.name);  


            System.out.println("**************获取私有字段****并调用********************************");  
            f = stuClass.getDeclaredField("phoneNum");  
            System.out.println(f);  
            f.setAccessible(true);//暴力反射,解除私有限定  
            f.set(obj, "18888889999");  
            System.out.println("验证电话:" + stu);  

        }  
    }

后台输出:

************获取所有公有的字段********************  
public java.lang.String fanshe.field.Student.name  
************获取所有的字段(包括私有、受保护、默认的)********************  
public java.lang.String fanshe.field.Student.name  
protected int fanshe.field.Student.age  
char fanshe.field.Student.sex  
private java.lang.String fanshe.field.Student.phoneNum  
*************获取公有字段**并调用***********************************  
public java.lang.String fanshe.field.Student.name  
验证姓名:刘德华  
**************获取私有字段****并调用********************************  
private java.lang.String fanshe.field.Student.phoneNum  
验证电话:Student [name=刘德华, age=0, sex=  

由此可见
调用字段时:需要传递两个参数:
Object obj = stuClass.getConstructor().newInstance();//产生Student对象–》Student stu = new Student();
//为字段设置值
f.set(obj, “刘德华”);//为Student对象中的name属性赋值–》stu.name = “刘德华”
第一个参数:要传入设置的对象,第二个参数:要传入实参

4、获取成员方法并调用

student类:

public class Student {  
    //**************成员方法***************//  
    public void show1(String s){  
        System.out.println("调用了:公有的,String参数的show1(): s = " + s);  
    }  
    protected void show2(){  
        System.out.println("调用了:受保护的,无参的show2()");  
    }  
    void show3(){  
        System.out.println("调用了:默认的,无参的show3()");  
    }  
    private String show4(int age){  
        System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);  
        return "abcd";  
    }  
}  

测试类:

import java.lang.reflect.Method;  

/* 
 * 获取成员方法并调用: 
 *  
 * 1.批量的: 
 *      public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类) 
 *      public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的) 
 * 2.获取单个的: 
 *      public Method getMethod(String name,Class<?>... parameterTypes): 
 *                  参数: 
 *                      name : 方法名; 
 *                      Class ... : 形参的Class类型对象 
 *      public Method getDeclaredMethod(String name,Class<?>... parameterTypes) 
 *  
 *   调用方法: 
 *      Method --> public Object invoke(Object obj,Object... args): 
 *                  参数说明: 
 *                  obj : 要调用方法的对象; 
 *                  args:调用方式时所传递的实参; 

): 
 */  
public class MethodClass {  

    public static void main(String[] args) throws Exception {  
        //1.获取Class对象  
        Class stuClass = Class.forName("fanshe.method.Student");  
        //2.获取所有公有方法  
        System.out.println("***************获取所有的”公有“方法*******************");  
        stuClass.getMethods();  
        Method[] methodArray = stuClass.getMethods();  
        for(Method m : methodArray){  
            System.out.println(m);  
        }  
        System.out.println("***************获取所有的方法,包括私有的*******************");  
        methodArray = stuClass.getDeclaredMethods();  
        for(Method m : methodArray){  
            System.out.println(m);  
        }  
        System.out.println("***************获取公有的show1()方法*******************");  
        Method m = stuClass.getMethod("show1", String.class);  
        System.out.println(m);  
        //实例化一个Student对象  
        Object obj = stuClass.getConstructor().newInstance();  
        m.invoke(obj, "刘德华");  

        System.out.println("***************获取私有的show4()方法******************");  
        m = stuClass.getDeclaredMethod("show4", int.class);  
        System.out.println(m);  
        m.setAccessible(true);//解除私有限定  
        Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参  
        System.out.println("返回值:" + result);  


    }  
}  

控制台输出:

***************获取所有的”公有“方法*******************  
public void fanshe.method.Student.show1(java.lang.String)  
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException  
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException  
public final void java.lang.Object.wait() throws java.lang.InterruptedException  
public boolean java.lang.Object.equals(java.lang.Object)  
public java.lang.String java.lang.Object.toString()  
public native int java.lang.Object.hashCode()  
public final native java.lang.Class java.lang.Object.getClass()  
public final native void java.lang.Object.notify()  
public final native void java.lang.Object.notifyAll()  
***************获取所有的方法,包括私有的*******************  
public void fanshe.method.Student.show1(java.lang.String)  
private java.lang.String fanshe.method.Student.show4(int)  
protected void fanshe.method.Student.show2()  
void fanshe.method.Student.show3()  
***************获取公有的show1()方法*******************  
public void fanshe.method.Student.show1(java.lang.String)  
调用了:公有的,String参数的show1(): s = 刘德华  
***************获取私有的show4()方法******************  
private java.lang.String fanshe.method.Student.show4(int)  
调用了,私有的,并且有返回值的,int参数的show4(): age = 20  
返回值:abcd  

由此可见:
m = stuClass.getDeclaredMethod(“show4”, int.class);//调用制定方法(所有包括私有的),需要传入两个参数,第一个是调用的方法名称,第二个是方法的形参类型,切记是类型。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println(“返回值:” + result);//

其实这里的成员方法:在模型中有属性一词,就是那些setter()方法和getter()方法。还有字段组成,这些内容在内省中详解

5、反射main方法
student类:

public class Student {  

    public static void main(String[] args) {  
        System.out.println("main方法执行了。。。");  
    }  
}  

测试类:

import java.lang.reflect.Method;  

/** 
 * 获取Student类的main方法、不要与当前的main方法搞混了 
 */  
public class Main {  

    public static void main(String[] args) {  
        try {  
            //1、获取Student对象的字节码  
            Class clazz = Class.forName("fanshe.main.Student");  

            //2、获取main方法  
             Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,  
            //3、调用main方法  
            // methodMain.invoke(null, new String[]{"a","b","c"});  
             //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数  
             //这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。  
             methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一  
            // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二  

        } catch (Exception e) {  
            e.printStackTrace();  
        }  


    }  
}

控制台输出:
main方法执行了。。。

6、反射方法的其它使用之—通过反射运行配置文件内容

student类:

public class Student {  
    public void show(){  
        System.out.println("is show()");  
    }  
}  

配置文件以txt文件为例子(pro.txt):

className = cn.fanshe.Student  
methodName = show  

测试类:

import java.io.FileNotFoundException;  
import java.io.FileReader;  
import java.io.IOException;  
import java.lang.reflect.Method;  
import java.util.Properties;  

/* 
 * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改 
 * 我们只需要将新类发送给客户端,并修改配置文件即可 
 */  
public class Demo {  
    public static void main(String[] args) throws Exception {  
        //通过反射获取Class对象  
        Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"  
        //2获取show()方法  
        Method m = stuClass.getMethod(getValue("methodName"));//show  
        //3.调用show()方法  
        m.invoke(stuClass.getConstructor().newInstance());  

    }  

    //此方法接收一个key,在配置文件中获取相应的value  
    public static String getValue(String key) throws IOException{  
        Properties pro = new Properties();//获取配置文件的对象  
        FileReader in = new FileReader("pro.txt");//获取输入流  
        pro.load(in);//将流加载到配置文件对象中  
        in.close();  
        return pro.getProperty(key);//返回根据key获取的value值  
    }  
}  

控制台输出:
is show()

需求:
当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动

要替换的student2类:

public class Student2 {  
    public void show2(){  
        System.out.println("is show2()");  
    }  
}  

配置文件更改为:

className = cn.fanshe.Student2  
methodName = show2  

控制台输出:
is show2();

7、反射方法的其它使用之—通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
测试类:

import java.lang.reflect.Method;  
import java.util.ArrayList;  

/* 
 * 通过反射越过泛型检查 
 *  
 * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值? 
 */  
public class Demo {  
    public static void main(String[] args) throws Exception{  
        ArrayList<String> strList = new ArrayList<>();  
        strList.add("aaa");  
        strList.add("bbb");  

    //  strList.add(100);  
        //获取ArrayList的Class对象,反向的调用add()方法,添加数据  
        Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象  
        //获取add()方法  
        Method m = listClass.getMethod("add", Object.class);  
        //调用add()方法  
        m.invoke(strList, 100);  

        //遍历集合  
        for(Object obj : strList){  
            System.out.println(obj);  
        }  
    }  
}  

控制台输出:
aaa
bbb
100

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值