在认识反射之前,我们先来回顾一下java程序的执行过程:
Java程序运行时,必须经过编译和运行两个步骤。首先将后缀名为.java的源文件进行编译,生成后缀名为.class的字节码文件。然后Java虚拟机将编译好的字节码文件加载到内存(这个过程被称为类加载,是由加载器完成的),最后虚拟机针对加载到内存的java类进行解释执行,显示结果。
Java的反射就是利用上面加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。】
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
反射的根源是Class类,在Java中对Class类的实例化提供三种操作方法:
1.利用getClass方法,通过实例化对象调用;(通常用于知道实例化对象,但不知道属于哪个类的情况)
2.利用“类.Class”方式获取;
3.通过Class对象的静态方法forName()获取;(最常用的方法,但是可能抛出异常)
观察这三种对象实例化方式:
//获取Class类实例化对象
class Book{
}
public class Test01{
public static void main(String[] args) {
//第一种,通过对象调用getClass()
Book book = new Book();
Class<?> classA = book.getClass();
System.out.println(classA.getName());
//第二种,通过类名.class方式
Class<?> classB = Book.class;
System.out.println(classB.getName());
//第三种,通过forName(),需要进行异常处理
try{
Class<?> classC = Class.forName("zhang.da.pao.Book");
System.out.println(classC.getName());
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=57037:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
zhang.da.pao.Book
zhang.da.pao.Book
zhang.da.pao.Book
Process finished with exit code 0
需要注意的是,使用Class内部的forName()方法时,利用此方法可以实现Class的实例化,在方法内传入类名称的时候要求使用类的完整名称“包.类”,如果该类不存在则会出现“ClassNotFoundException”异常,进行加载的类一定要在CLASSPATH可以识别的路径中。
Class作为反射操作的源头,在Class类中可以获取一些结构上的信息,例如类的包、类继承的父类、类实现的接口。方法定义如下:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Package getPackage() | 方法 | 获取类所在的包名称 |
2 | public ClassgetSuperclass() | 方法 | 获取类所继承的父类 |
3 | public Class>[] getInterfaces() | 方法 | 获取实现的所有接口,通过集合返回 |
使用上述方法,实现class的结构信息获取:
import java.util.Arrays;
//获取Class源头信息
interface IBook{}
interface IStudy{}
class People{}
class Student extends People implements IBook,IStudy{}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("zhang.da.pao.Student");
System.out.println("包名:"+clazz.getPackage());
System.out.println("接口:"+Arrays.toString(clazz.getInterfaces()));
System.out.println("父类名:"+clazz.getSuperclass());
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=62382:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
包名:package zhang.da.pao
接口:[interface zhang.da.pao.IBook, interface zhang.da.pao.IStudy]
父类名:class zhang.da.pao.People
Process finished with exit code 0
类中可能包含大量构造方法,构造方法是类实现初始化的必要方法,Class类中包含如下获取构造的方法:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Constructor>getContructor(Class>…parameterTypes)throws NoSuchMethodException,SecurityException | 方法 | 根据指定的参数类型获取指定的构造方法(public型) |
2 | public Constructor>getContructors()throws SecurityException | 方法 | 获取所有的构造方法(public型) |
3 | public Constructor>getDeclaredContructor(Class>…parameterTypes)throws NoSuchMethodException,SecurityException | 方法 | 获取指定的构造方法(public、protected、default型) |
4 | public Constructor>getDeclaredContructors()throws SecurityException | 方法 | 获取所有的构造方法(public、protected、default型) |
观察获取构造方法的使用过程:
import java.lang.reflect.Constructor;
//获取Class源头信息
class Book{
private String name;
private int price;
public Book() {
}
public Book(String name,int price){
this.name = name;
this.price = price;
}
}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class<?> clazz = Class.forName("zhang.da.pao.Book");
//获取所有构造方法
Constructor<?> con[] = clazz.getDeclaredConstructors();
for(Constructor cont: con){
System.out.println("所有构造方法:"+cont);
}
//获取指定构造方法
System.out.println("指定构造方法:"+clazz.getDeclaredConstructor(String.class,int.class));
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=65121:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
所有构造方法:public zhang.da.pao.Book()
所有构造方法:public zhang.da.pao.Book(java.lang.String,int)
指定构造方法:public zhang.da.pao.Book(java.lang.String,int)
Process finished with exit code 0
此时可以获取本类的所有构造方法,获取构造方法的意义在于进行反射的构造调用,方法如下:
public T newInstance(Object... initargs)
throws InstantiationException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException
Constructor类中提供了一个newInstance()方法,这个方法的功能是可以调用指定的构造进行对象实例化处理。简单来说就是把Class类获取的对象实例化,这个方法非常重要,可以说是反射功能实现的基础保证!!!下面使用newInstance()进行对象实例化操作:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//获取Class源头信息
class Book{
private String name;
private int price;
public Book(String name,int price){
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "书名:"+this.name+"价格:"+this.price;
}
}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Class.forName("zhang.da.pao.Book");
//获取指定构造方法
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class);
//暂时不考虑异常的处理问题,直接抛出
Book book = (Book)constructor.newInstance("java",26);
System.out.println(book);
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=57909:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
书名:java价格:26
Process finished with exit code 0
上述操作方法实现了对象的实例化操作,但这个操作是在JDK1.9之后推荐的做法,在JDK1.8及以前有两种操作方法:
1.Class类中:
@Deprecated(since="9")
public T newInstance()
throws InstantiationException,
IllegalAccessException
默认调用的是无参构造,如果没有无参构造则会抛出异常,所以在JDK1.9之后改为使用Constructor类调用该方法。
2.Constructor类:
public T newInstance(Object... initargs)
throws InstantiationException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException
类中的结构主要有三个组成部分:构造、方法、成员属性。前两种在前面的部分都已经介绍了怎样通过反射获取,成员属性也是可以获取的。
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Field[] getDeclaredFields()throws SecurityException | 方法 | 获取本类中定义的所有成员属性 |
2 | public Field getDeclaredField(String name)throws NoSuchMethodException,SecurityException | 方法 | 获取本类一个指定的成员属性 |
3 | public Field[] getFields()throws SecurityException | 方法 | 获取所有继承来的public成员属性 |
4 | public Field getFields(String name)throws NoSuchMethodException,SecurityException | 方法 | 获取一个本类定义的成员属性 |
使用上述方法进行属性的获取:
import java.lang.reflect.Field;
import java.util.Arrays;
//获取类中的属性
class Book{
public String name;
public int price;
}
class Student extends Book{
private String score;
}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("zhang.da.pao.Book");
//获取所有的继承的父类中的public属性
{
Field field[] = clazz.getFields();
System.out.println("继承的父类中的public属性:" + Arrays.toString(field));
}
//获取本类中定义的属性
{
Field field[] = clazz.getDeclaredFields();
System.out.println("本类中的所有属性:" + Arrays.toString(field));
}
}
}
通过以上方法实现了成员属性的获取,在Field类中有几个重要方法:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Class>getType() | 方法 | 获取属性类型 |
2 | public Object get(Object obj)throws IllegalArgumentException,IllegalAccessException | 方法 | 获取属性内容 |
3 | public void set(Object obj,Object value)throws IllegalArgumentException,IllegalAccessException | 方法 | 设置属性内容 |
4 | public void setAccessible(boolean flag) | 方法 | 取消封装 |
使用上述方法实现对属性的类型返回,以及对属性进行赋值操作:
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
class Book{
private String name;
private int price;
}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Class.forName("zhang.da.pao.Book");
//获取类对象
Object obj = clazz.getDeclaredConstructor().newInstance();
//获取本类中定义的属性
Field field = clazz.getDeclaredField("name");
//取消封装
field.setAccessible(true);
System.out.println("属性的类型:"+field.getType().getName());
//传入对象和属性值
field.set(obj,"编程思想");
System.out.println("书名:"+field.get(obj));
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=59103:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
属性的类型:java.lang.String
编程思想
Process finished with exit code 0
可以观察到上面的代码中使用了一个setAccessible()方法,该方法可以解除封装操作。
上面我们讲了类和构造方法的调用方式,在类中出了构造方法外,最多的就是类中的普通方法。Class类中定义了很多获取类中方法的操作:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Method[] getDeclaredMethods()throws SecurityException | 方法 | 获取本类中的所有方法 |
2 | public Method getDeclaredMethod(String name,Class>…parameterTypes)throws NoSuchMethodException,SecurityException | 方法 | 获取本类一个指定的方法 |
3 | public Method[] getMethods()throws SecurityException | 方法 | 获取所有的public方法 |
4 | public Method getMethod(String name,Class>…parameterTypes)throws NoSuchMethodException,SecurityException | 方法 | 获取一个公共的方法,包括父类的方法 |
//获取类中的方法
import java.lang.reflect.Method;
//获取Class源头信息
class Book{
public String toString() {
return null;
}
}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException{
Class<?> clazz = Class.forName("zhang.da.pao.Book");
//获取类中的所有方法
Method method[] = clazz.getDeclaredMethods();
for(Method mod: method){
System.out.println(mod);
}
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=59518:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
public java.lang.String zhang.da.pao.Book.toString()
Process finished with exit code 0
此时就获取了类中的方法,但是方法输出是依靠Method类默认的toString()方法,此时返回的方法内容并不能直接使用,如果想要返回一个标准格式的方法(标准格式的方法包括:访问修饰符、返回值类型、方法体、参数列表),需要进行以下操作:
//以标准格式获取类中的方法
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
class Book{
private String name;
private int price;
public Book(String name,int price){
this.name = name;
this.price = price;
}
public void buy(String name,int price){
System.out.println("购买书名:"+this.name+"购买价格:"+this.price);
}
public String toString() {
return null;
}
}
public class Test01{
public static void main(String[] args) throws ClassNotFoundException{
//获取反射对象
Class<?> clazz = Class.forName("zhang.da.pao.Book");
//获取类中的所有方法
Method method[] = clazz.getDeclaredMethods();
for(int x = 0; x < method.length;x++ ){
//1.输出方法的修饰符
//method[x].getModifiers()返回特定的字节码,
// Modifier.toString(int)来解释此字节码int,会返回修饰符的字符串形式。
System.out.print(Modifier.toString(method[x].getModifiers()));
//2.输出方法的返回值类型
System.out.print(method[x].getReturnType().getName());
//3.输出方法名称
System.out.print(method[x].getName());
System.out.print("(");
//4.输出参数列表
//获取所有参数类型
Class<?> params[] = method[x].getParameterTypes();
for(int y = 0 ; y < params.length ; y++) {
System.out.print(params[y].getSimpleName()+"arg"+y);
//","比参数少一个
if(y<params.length-1){
System.out.print(",");
}
}
System.out.println(")");
//5.输出异常情况
Class<?> [] exps = method[x].getExceptionTypes();
if(exps.length>0){
System.out.print("throws");
for(int z = 0 ; z < exps.length;z++ ){
System.out.println(exps[z].getSimpleName());
if(z<exps.length-1){
System.out.print(",");
}
}
}
System.out.println();
}
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=62298:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
publicjava.lang.StringtoString()
publicvoidbuy(Stringarg0,intarg1)
Process finished with exit code 0
在java中可以使用Method类中方法描述可以调用的方法结构,也可以输出全部的方法。
以上介绍了反射获取方法属性的方式,但对于反射对方法的操作来说,最重要的是Method类中提供的反射调用方法:Invoke()方法,先来看看方法的定义:
//Method类的invoke(Object obj,Object args[])方法接收的参数必须为对象;
//如果参数为基本类型数据,必须转换为相应的包装类型的对象。invoke()方法的返回值总是对象。
//如果实际被调用的方法的返回类型是基本类型数据,那么invoke()方法会把它转换为相应的包装类型的对象,再将其返回
public Object invoke(Object obj,
Object... args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException
传入的参数说明:(该方法所在类的一个对象,方法中的参数)
创建一个StringUtil功能类,用于首字母大写操作:
package zhang.da.util.Imp;
//定义一个首字母大写处理类
public class StringUtil {
public static String initcap(String str){
if(str == null || " ".equals(str)){
return str;
}
if(str.length() == 1){
return str.toUpperCase();
}
return str.substring(0,1).toUpperCase()+str.substring(1);
}
}
使用invoke()实现方法调用操作:
import zhang.da.util.Imp.StringUtil;
import java.lang.reflect.Method;
//使用invoke()调用类中的方法
class Book{
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test01{
public static void main(String[] args) throws Exception {
String fieldName = "name" ;
String fieldValue = "java";
Class<?> clazz = Book.class;
//通过反射类的构造方法,获取实例化对象
Object obj = clazz.getDeclaredConstructor().newInstance();
//获取Method类对象set,用来调用set方法
Method setMethod = clazz.getDeclaredMethod("set"+
//使用首字母大写方法接收获取到的变量名;获取变量的类型作为获取方法的参数传入
StringUtil.initcap(clazz.getDeclaredField(fieldName).getName()),clazz.getDeclaredField(fieldName).getType());
setMethod.invoke(obj,fieldValue);
//获取Method类对象get,用来调用get方法
Method getMethod = clazz.getDeclaredMethod("get"+
//使用首字母大写方法接收获取到的变量名;
StringUtil.initcap(clazz.getDeclaredField(fieldName).getName()));
//使用Object类型接收invoke()方法返回的对象
Object returnValue = getMethod.invoke(obj);
System.out.println(returnValue);
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=51863:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
java
Process finished with exit code 0
此时反射就实现了方法的调用,这也就是为什么在简单java类中要存在有setter、getter方法。
通过对反射机制的了解,现在对象实例化有两种操作方式:使用new关键字、使用反射机制,为什么类中原有new的操作方式还要增加反射机制呢?原因在于工厂类的使用:
//传统的工厂类设计
interface Book{
public void study(String str);
}
class Student implements Book{
@Override
public void study(String str) {
System.out.println("学习课程:"+str);
}
}
class Factory{
//设置静态方法,返回Student类型的对象
public static Student getInstance(String className){
if(className.equals("student")){
return new Student();
}
}
}
public class Test01{
public static void main(String[] args) {
Book book = Factory.getInstance("student");
book.study("java" );
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=62965:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
学习课程:java
Process finished with exit code 0
此时虽然实现了一个工厂类,但如果现在有一种情况,需要频繁向类中增加子类,修改方法为在工厂类的静态方法中增加判断,if(新类.equals(“类名称”)){return new 新类};如果要频繁增加或减少类,对工厂类的冲击很大。
//使用反射修改工厂类
interface Book{
public void study(String str);
}
class Student implements Book{
@Override
public void study(String str) {
System.out.println("学习课程:"+str);
}
}
class Factory{
public static Student getInstance(String className){
Student instance = null;
try{
//通过反射获取实例化对象
instance = (Student) Class.forName(className).getDeclaredConstructor().newInstance();
}catch(Exception e){}
return instance;
}
}
public class Test01{
public static void main(String[] args) {
Book book = Factory.getInstance("zhang.da.pao.Student");
if(book != null) {
book.study("java");
}
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=50682:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
学习课程:java
Process finished with exit code 0
使用反射实现工厂类,使工厂类内部有更高的内聚性。
单例设计模式的核心在于,一个类在一个JVM中只允许有一个实例化对象,对于单例模式本身提供有两种结构:懒汉式单例模式、饿汉式单例模式。其中懒汉式单例模式比较复杂。
首先观察传统的懒汉式单例设计:
//懒汉式单例设计
class Singleton{
private static Singleton instance;
private Singleton(){
System.out.println("构造,实例化Singleton对象");
}
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
public class Test01{
public static void main(String[] args) {
for(int x = 0 ; x < 6 ; x ++){
//使用多线程运行singleton对象实例化
new Thread(()->{
Singleton singleton = Singleton.getInstance();
}).start();
}
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=53819:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
构造,实例化Singleton对象
构造,实例化Singleton对象
构造,实例化Singleton对象
Process finished with exit code 0
上述代码显示,单例模式下使用多线程会导致单例失效(出现了对象的多次实例化),原因为在单例程序代码中的判断依据(if(instance == null));这条语句在多线程模式下可能会有多条线程进入此判断内,在执行结果产生之前多次实现对象实例化。
该怎样解决这样的问题呢?通过多线程的知识可以想到使用synchronized追加同步处理,修改代码段:
public synchronized static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
这样就避免了多个线程同时执行,但这样处理的效率又是怎样呢?假设现在有以万记位的线程数,那么这些线程就要以排队的方式进入判断,效率很低。
这样看来同步并不是随便添加的,在读取判断的时候增加同步会严重影响程序性能,那么如果在处理的过程中增加同步呢?看下面的的处理代码:
//优化后的懒汉式单例设计
class Singleton{
private static Singleton instance;
private Singleton(){
System.out.println("构造,实例化Singleton对象");
}
//先判断是否存在实例化对象
public static Singleton getInstance(){
if(instance==null){
//增加同步处理,进一步判断是否存在,此时只有少量线程排队
//使用Singleton.class类锁
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
public class Test01{
public static void main(String[] args) {
for(int x = 0 ; x < 6 ; x ++){
//使用多线程运行singleton对象实例化
new Thread(()->{
Singleton singleton = Singleton.getInstance();
}).start();
}
}
}
执行结果为:D:\java\jdk-11\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar=60043:D:\IDEA\IntelliJ IDEA 2018.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\JavaTest\out\production\Test01 zhang.da.pao.Test01
构造,实例化Singleton对象
Process finished with exit code 0
此时的懒汉式单例设计既保证了getInstance()方法的性能,同时满足了对象实例化的次数为一次。