人与人交流要用语言
,人与机器人的交互一样须要语言,从计算机诞生至今,计算机语言经历了机器语言
、汇编语言
和高级语言
。在全部的程序设计语言中,只有机器语言可以被计算机直接理解和执行,而其余程序语言都必须先翻译成与机器语言,才能和计算机交互。java
静态语言和动态语言python
对于咱们来讲,接触作多的就是高级语言
,包括C、C++、Java、python、JavaScript
等。这些高级语言能够大概分为两大类,即动态语言
和静态语言
编程
通俗来说,若是
在编译时
就知道变量的类型,该可认为该语言是静态的,如咱们所熟知的Java、C、C++等,它们在写代码时必须指定每一个变量的类型。数组
动态语言通常指的是脚本语言,如python、JavaScript等,这类语在编写代码是
没必要指定类型
。安全
从直观上看,静态语言在代码编译时须要指定变量类型;而动态语言则是在运行期间才会检查变量类型。因此,针对动态语言来讲,咱们能够在运行时改变其结构
,即运行时的代码能够根据某些条件改变自身的结构。markdown
按照划分,Java是属于静态语言的,可是因为Java提供了反射机制,使得Java成为了一种准动态语言
,利用反射能够得到相似动态语言的特性
,使得编程更加的灵活。数据结构
下面,咱们就认真学习下Java反射是什么
,怎么使用
,为什这么使用
?多线程
官方解释:JAVA反射机制是在运行状态中
,对于任意一个实体类
,都可以知道这个类的全部属性和方法
;对于任意一个对象
,都可以调用它的任意方法和属性
;这种动态获取信息以及动态调用
对象方法的功能称为java语言的反射机制。学习
看到官方解释,你们也许会有点懵,不要着急,咱们想一下在平常的开发过程当中,咱们常常会遇到某个类中的成员变量、方法是私有的,这些成员、方法是不对外开发的,可是咱们能够经过Java的反射机制在运行期间动态的获取。因此,咱们对Java反射能够从新理解以下:反射
就是程序在运行时,能够根据类的全限定名称
,动态地加载
该类,建立对象
,并能够调用该对象中地任意属性
和方法
。测试
那么,问题来了,为何要学习反射呢?
咱们想象这样一个场景,当咱们在程序中须要一些功能的时候,咱们通常采用的方式就是先new一个对象
,而后从对象中获取咱们所需功能的方法,可是咱们有没有想过,若是一个咱们的程序支持插件,可是咱们并不知道这个插件都有哪些类,这种状况下该怎么办呢?还好反射能够解决这个问题,使用反射能够在程序运行期间从配置文件中读取类名
,而后动态的获取对象类型的信息
。因此,反射的最大好处就是在运行期间动态的构建组
件,使得代码更具备灵活性和通用性。
正常方式:①引入须要的“包类”名称②经过new实例化③得到实例化对象
反射方式:①实例化对象②getClass方法③获得完整的“包类”名称
既然咱们要使用反射建立对象,那么咱们是如何建立Class呢?针对不一样的实例对象反射出的对象是不是同一个呢?
获取Class类三种方式
全限定类名
,可经过Class类的静态方法forName
获取Class c=Class.forName("java.Lang.String")
复制代码
class
属性获取Class c=Person.class;
复制代码
实例
,调用该实例的getClass()
方法获取Class对象Class c=person.getClass();
复制代码
实例代码(以第一种方法为例)
class People{
private int id;
private int age;
private String name;
....
}
复制代码
public class TestReflrction01 {
public static void main(String[] args) throws ClassNotFoundException {
//经过反射获取类的Class对象
Class c1=Class.forName("reflection.People");
System.out.println(c1);
}
}
复制代码
测试类的输出为:class reflection.People
那么,问题又来了,对于不一样的实例对象获取的Class类时否是同一个呢?咱们采用这样的方式,咱们获取不一样实例对象获取的Class的hashCode
,若是hashCode相同,则可证实他们是同一个Class
public class TestReflrction01 {
public static void main(String[] args) throws ClassNotFoundException {
//经过反射获取类的Class对象
Class c1=Class.forName("reflection.People");
System.out.println(c1);
//内存中只存在一个类的Class对象
//一个类被加载后,类的整个结构都会封装在Class对象中
Class c2=Class.forName("reflection.People");
Class c3=Class.forName("reflection.People");
Class c4=Class.forName("reflection.People");
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
复制代码
测试类的输出以下:
class reflection.People 460141958 460141958 460141958 复制代码
所以,咱们能够判定,同一个类的不一样的实例对象反射出class对象是同一个,是惟一的。
上面咱们学习了如何建立Class类,可是咱们确定会有这样的疑惑,为何能够动态建立Class类呢,它的原理是什么呢?要想了解它的原理,咱们必须先了解下JVM内存
。
咱们先看这样一张流程图
这张图详细的描述了咱们编写的Java文件的执行流程
,由于这里涉及了不少JVM
的知识点,感兴趣的同窗能够先看看我之前的一篇文章一文入门JVM虚拟机
,后面还会继续补充相关知识点,在这里,咱们主要分析类加载过程
。
咱们都了解java程序都是放在虚拟机上执行的,Java虚拟机把描述类的数据从Class文件加载到内存
,并对数据进行校验
、转换解析
和初始化
,最终造成能够被虚拟机直接使用的Java类型。
其中验证
、准备
和解析
统称为链接
,下面咱们详细分析下类的加载过程
- 经过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所表明的静态存储结构转化为方法区运行时数据结构
- 在内存中生成一个表明这个类的java.lang.Class对象,做为这个方法区这个类的各类数据的访问入口
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为了类变量(static)分配内存并设置类变量默认初始值阶段,这些内存都将在方法区中分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
Note:对于常量池中的符号引用解析
,要结合具体的实际状况自行判断,究竟是在类加载器加载时就对常量池中的符号引用解析,仍是等到一个符号引用将要被使用前采起解析它。
初始化阶段
是类加载过程的最后一个阶段,在这个阶段时,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。初始化步骤以下:
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中的全部类变量的赋值动做和静态代码块中的语句合并产生。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,若是发现其父类尚未进行初始化,则须要先触发其父类的初始化
- 虚拟机会保证一个类的()方法在多线程环境下被正确加锁和同步。
这时,咱们可能会有疑惑,何时会发生类的初始化呢?事实上,只要当类主动引用时才会发生初始化。
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,若是其父类没有被初始化,则先会初始化它的父类
那么,是否是能够理解为,类的被动引用就不会发生初始化了,是的,下面列出的这几种状况就不会发生类的初始化
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当经过子类引用父类的静态变量,不会致使子类的初始化
- 经过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在连接阶段就存入调用类的常量池中)
上面咱们详细分析了Java的内存分布和类的加载流程
,此时,咱们编写的代码已经处于在运行期
了,咱们知道利用反射能够在运行期动态的建立对象
,那么它的工做机制究竟是什么样的呢?在下面的文章中,咱们详细分析
上图是咱们类加载过程结束后的内存分布,每一个类都在堆中建立了表明本身本类的Class类
。记住,这个Class类是用于建立Class对象的,咱们继续向下分析。
当咱们在栈中new A时,它首先会找到堆中的Class类,由于Class类是访问方法区类A中各类数据的访问入口
。而后将相应的类信息带到堆中完成实例化。
这也就不难理解为为何能够反射能够在运行时期动态的获取的对象。在下面的文章中,咱们将详细讲解如何使用反射,即怎样利用反射建立运行时类对象,怎么获取运行时类的完整结构,如何调用运行时类的指定结构。
反射相关的API
反射机制提供的主要功能
在程序运行期间,Java运行时系统始终为全部对象维护一个被称为运行时的类型标识
。这个信息跟踪着每一个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。Java中提供了专门的类访问这些信息,保存这些信息的类称为Class。这个名称很容易让人产生混淆,由于在Object类中有一个方法用获取Class实例,此方法能够被全部的子类继承
public final Class getClass
复制代码
在Java API中,提供了获取Class类对象的三种方式
使用这种的方法的前提是要知道类的全路径名
//方式一:经过类的全类名获取,可经过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class c1= Class.forName("reflection.People");
复制代码
该方法适用于在编译前就已经明确要操做的Class
//方式二:若一致具体的类,经过类的class属性获取
Class c3=People.class;
复制代码
该方法适用于有对象实例的状况下
//方式二:调用该实例的getClass()获取
People people=new People();
复制代码
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:经过类的全类名获取,可经过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class c1= Class.forName("reflection.People");
System.out.println(c1);
//方式二:调用该实例的getClass()获取
People people=new People();
Class c2=people.getClass();
System.out.println(c2);
//方式三:若一致具体的类,经过类的class属性获取
Class c3=People.class;
System.out.println(c3);
}
}
复制代码
实际上,对于每一个类而言,JRE都为其保留一个不变的Class类型的对象
。一个Class对象包含特定某个结构的有关信息。Class总结以下:
在上面的文章中,咱们讲解了如何使用反射机制来建立Class类对象,当有了实际的对象后,咱们能够作哪些事情呢?反射机制为咱们提供了哪些具体的操做方法呢?
在java.lang.reflect
包中有三个类Field
、Method
和Constructor
分别用于描述类的属性
、方法
和构造器
。这三个类都有一个叫作getName
的方法,
Class类中的getFields
、getMethods
和getConstructord
方法将分别返回类提供的public
(属性、方法和构造器的数组),其中也包括了父类的public成员。
Class类中的getDeclaredFields
、getDeclaredMethods
和getDeclaredConstructors
方法将分别返回类中声明的(所有)
属性、方法和构造器,其中包括了私有成员和受保护成员,但不包括父类的成员
Field[] getFields()
Filed[] getDeclaredFields()
复制代码
getFileds
方法将返回一个包含Field对象的数组,这些对象记录这个类或其父类的public属性;getDeclaredFileds
也将返回一个包含Field对象的数组,这些对象记录这个类的所有属性。
Note:若是类中没有定义属性,或者Class对象描述的是基本类型或者数组类型,这些方法将返回一个长度为0的数组
public class TestReflection04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1=Class.forName("reflection.People");
//得到类的名字
System.out.println(c1.getName());//包名+类名
System.out.println(c1.getSimpleName());//类名
//得到类的属性
Field[] fields=c1.getFields(); //只能找到public
fields=c1.getDeclaredFields(); //能够找到所有的属性
for (Field field:fields){
System.out.println(field);
}
//得到指定属性的值
Field name=c1.getDeclaredField("name");
System.out.println(name);
}
}
复制代码
Method[] = new getMethods[]
Method[] =new getDeclaredMethods[]
复制代码
getMethods
方法将返回一个包含Method对象的数组,这些对象记录这个类或其父类的public方法;getDeclaredMethods[]
也将返回一个包含Method对象的数组,这些对象记录这个类或接口的所有方法。
/** * 获取类的运行时结构 * 包括方法、属性、构造器 */
public class TestReflection04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1=Class.forName("reflection.People");
//得到类的方法
Method[] methods=c1.getMethods();//得到本类及其父类的所有的public方法
for(Method method:methods){
System.out.println(method);
}
//得到本类的全部方法
methods=c1.getDeclaredMethods();
for(Method method:methods){
System.out.println(method);
}
//得到指定的方法
Method getName=c1.getMethod("getName",null);
System.out.println(getName);
}
}
复制代码
Constructor[] getConstructors()
Constructor[] getDeclaredConstructors()
复制代码
getConstructors
方法将返回一个包含Constructor对象的数组,这些对象记录这个类或其父类的public公有构造器;getDeclaredConstructors
也将返回一个包含Constructor对象的数组,这些对象记录这个类的全部构造器。
public class TestReflection04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1=Class.forName("reflection.People");
Constructor[] constructors=c1.getConstructors();
for (Constructor constructor:constructors){
System.out.println(constructor);
}
constructors=c1.getDeclaredConstructors();
for (Constructor constructor:constructors){
System.out.println(constructor);
}
//得到指定的构造器
Constructor declareConstructor=c1.getDeclaredConstructor(int.class,int.class,String.class);
System.out.println(declareConstructor);
}
}
复制代码
上面的文章中,咱们讲解了如何获取类的运行时结构,若是咱们要使用,必须建立类的对象
建立类的对象:调用Class对象newInstance()方法
类必须有一个无参构造器,由于当操做时,若没有明确调用类中的构造器,则会调用无参构造器,若要实例化对象,须要使用构造器
类的构造器访问权限须要足够
- 经过Class类的getDeclaredConstructor(Class.... parameTypes)得到本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组中去,里面包含了构造器中所需的各个参数
- 经过Constructor实例化对象
测试代码
public class TestRelection05 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c1=Class.forName("reflection.People");
//构造一个对象,本质上调用了无参构造器
People people=(People) c1.newInstance();
System.out.println(people);
//经过构造器建立对象
Constructor constructor=c1.getDeclaredConstructor(int.class,int.class,String.class);
People people2=(People) constructor.newInstance(23,25,"Simon");
System.out.println(people2);
//经过反射调用普通方法
People people3=(People) c1.newInstance();
Method setName=c1.getDeclaredMethod("setName", String.class);
//invoke:激活的意思(对象,"方法的值")
setName.invoke(people3,"Simon Lang");
System.out.println(people3.getName());
//经过反射调用属性
People people4=(People) c1.newInstance();
Field name=c1.getDeclaredField("name");
//不能直接操做私有属性,咱们须要关闭程序的安全监测,属性或者方法的setAccessible(true)
name.setAccessible(true);
name.set(people4,"Simon snow");
System.out.println(people4.getName());
}
}
复制代码
setAccessible
- 使得本来没法访问的私有成员也能够访问
参数值为false则指示反射的对象应该实施Java语言的访问检查
山腰太拥挤了,我想去山顶看看