人与人交流要用语言
,人与机器人的交互同样需要语言,从计算机诞生至今,计算机语言经历了机器语言
、汇编语言
和高级语言
。在所有的程序设计语言中,只有机器语言能够被计算机直接理解和执行,而其他程序语言都必须先翻译成与机器语言,才能和计算机交互。
静态语言和动态语言
对于我们来说,接触做多的就是高级语言
,包括C、C++、Java、python、JavaScript
等。这些高级语言可以大概分为两大类,即动态语言
和静态语言
通俗来讲,如果
在编译时
就知道变量的类型,该可认为该语言是静态的,如我们所熟知的Java、C、C++等,它们在写代码时必须指定每个变量的类型。
动态语言一般指的是脚本语言,如python、JavaScript等,这类语在编写代码是
不必指定类型
。
从直观上看,静态语言在代码编译时需要指定变量类型;而动态语言则是在运行期间才会检查变量类型。所以,针对动态语言来说,我们可以在运行时改变其结构
,即运行时的代码可以根据某些条件改变自身的结构。
按照划分,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类型。
其中验证
、准备
和解析
统称为连接
,下面我们详细分析下类的加载过程
Note:对于常量池中的符号引用解析
,要结合具体的实际情况自行判断,到底是在类加载器加载时就对常量池中的符号引用解析,还是等到一个符号引用将要被使用前采取解析它。
初始化阶段
是类加载过程的最后一个阶段,在这个阶段时,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。初始化步骤如下:
这时,我们可能会有疑惑,什么时候会发生类的初始化呢?事实上,只要当类主动引用时才会发生初始化。
那么,是不是可以理解为,类的被动引用就不会发生初始化了,是的,下面列出的这几种情况就不会发生类的初始化
上面我们详细分析了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()方法
类必须有一个无参构造器,因为当操作时,若没有明确调用类中的构造器,则会调用无参构造器,若要实例化对象,需要使用构造器 类的构造器访问权限需要足够
测试代码
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
山腰太拥挤了,我想去山顶看看