动态代理
- 代理:本来应该自己做的事情,却请来了别人来做,被请的人就是代理对象。
- 动态代理:在程序运行过程中产生的这个对象。动态代理其实就是通过反射来生成一个代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;//用户操作接口
interface UserDao {public abstract void add();public abstract void delect();public abstract void update();public abstract void find();
}
interface StudentDao {public abstract void login();public abstract void regist();
}//接口实现类
class UserDaoImpl implements UserDao {@Overridepublic void add() {System.err.println("add");}@Overridepublic void delect() {System.err.println("delect");}@Overridepublic void update() {System.err.println("update");}@Overridepublic void find() {System.err.println("find");}
}
class StudentDaoImpl implements StudentDao {@Overridepublic void login() {System.err.println("login");}@Overridepublic void regist() {System.err.println("regist");}
}//实现InvocationHandler接口
class MyInvocationHandler implements InvocationHandler {private Object target;// 膜标对象public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("权限检验");Thread.sleep(1000);Object result = method.invoke(target, args);Thread.sleep(1000);System.out.println("日志记录");System.out.println("------------------");return result;}}//代理对象
public class inputStreamYH {public static void main(String[] args) throws InterruptedException {UserDao ud = new UserDaoImpl();System.out.println("---------1---------");ud.add();ud.delect();ud.update();ud.find();Thread.sleep(1000);System.out.println("--------2----------");MyInvocationHandler myInvocationHandler = new MyInvocationHandler(ud);UserDao u = (UserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(),ud.getClass().getInterfaces(),myInvocationHandler);u.add();u.delect();u.update();u.find();System.out.println("---------3---------");StudentDao s = new StudentDaoImpl();MyInvocationHandler my = new MyInvocationHandler(s);StudentDao u2 = (StudentDao) Proxy.newProxyInstance(s.getClass().getClassLoader(),s.getClass().getInterfaces(),my);u2.login();u2.regist();}
}
动态语言vs 静态语言
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
2、静态语言
- 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
- Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
- Java的动态性让编程的时候更加灵活!
反射Reflection技术
反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
Class类是 Java 反射的核心类,可以获取类的属性方法等内容。
Java 反射主要提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
- 重点:是运行时而不是编译时
反射的好处:
- 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
- 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
- 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
反射的缺点
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。
- 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
- 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
- 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
反射的主要用途
很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
举一个例子,在运用 Struts 2 框架的开发中我们一般会在 struts.xml
里去配置 Action
,比如:
<action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction"method="execute"><result>/shop/shop-index.jsp</result><result name="error">login.jsp</result>
</action>
配置文件与 Action
建立了一种映射关系,当 View 层发出请求时,请求会被 StrutsPrepareAndExecuteFilter
拦截,然后 StrutsPrepareAndExecuteFilter
会去动态地创建 Action 实例。比如我们请求 login.action
,那么 StrutsPrepareAndExecuteFilter
就会去解析struts.xml文件,检索action中name为login的Action,并根据class属性创建SimpleLoginAction实例,并用invoke方法来调用execute方法,这个过程离不开反射。
对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。而对于一般的开发者来说,不深入框架开发则用反射用的就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。
反射的基本步骤:
1、获得Class对象,就是获取到指定的名称的字节码文件对象。
2、实例化对象,获得类的属性、方法或构造函数。
3、访问属性、调用方法、调用构造函数创建对象。
反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关的主要API
java.lang.Class:代表一个类
接口 | 功能 |
Member | 该接口可以获取有关类成员(域或者方法)后者构造函数的信息。 |
AccessibleObject | 该类是域(field)对象、方法(method)对象、构造函数(constructor)对象的基础类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。 |
Array | 该类提供动态地生成和访问JAVA数组的方法。 |
Constructor | 提供一个类的构造函数的信息以及访问类的构造函数的接口。 |
Field | 提供一个类的 Field 的信息以及访问类的域的接口。代表类的成员变量 |
Method | 提供一个类的 Method 的信息以及访问类的方法的接口。 |
Modifier | 提供了 static 方法和常量,对类和成员访问修饰符进行解码。 |
Proxy | 提供动态地生成代理类和类实例的静态方法。 |
反射的基本运用
反射可以用于判断任意对象所属的类,获得 Class 对象,构造任意一个对象以及调用一个对象。这里我们介绍一下基本反射功能的使用和实现(反射相关的类一般都在 java.lang.relfect 包里)。
反射Class 类获得 Class 对象
在Object类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。Class类是Java 反射机制的入口,封装了一个类或接口的运行时信息,通过调用Class类的方法可以获取这些信息。
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
Class类的常用方法
// 手动输入类名
Scanner s = new Scanner(System.in);
System.out.println("获取类");
String className = s.next();
Class<?> clazz = Class.forName(className);
获取Class字节码文件对象,有4种方式:
1 获取Class对象 Object ——> getClass()
//对象都有了还要反射干什么?
Student stu = new Student();
Class<? extends Student> stuClass1 = stu.getClass();
System.out.println(stuClass1.getName());2 任何数据类型(包括基本数据类型)都有一个静态的class属性。
//需要导入类的包,依赖太强,不导包就抛编译错误
Class<Student> stuClass2 = Student.class;
System.out.println(stuClass2 == stuClass1);3 通过Class类的静态方法:forName(String className),
//一个字符串可以传入也可写在配置文件中等多种方法
Class<?> stuClass3;
try {stuClass3 = Class.forName("com.niu.Student");// student的路径System.out.println(stuClass3);
} catch (ClassNotFoundException e) {e.printStackTrace();
} 4 其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);
哪些类型可以有Class字节码文件对象?
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitivetype:基本数据类型
(7)void
Class c1= Object.class;
System.out.println("c1 :" + c1); // c1 :class java.lang.Object
Class c2= Comparable.class;
System.out.println("c2 :" + c2);// c2 :interface java.lang.Comparable
Class c3= String[].class;
System.out.println("c3 :" + c3); // c3 :class [Ljava.lang.String;
Class c4= int[][].class;
System.out.println("c4 :" + c4);// c4 :class [[I
Class c5= ElementType.class;
System.out.println("c5 :" + c5);// c5 :class java.lang.annotation.ElementType
Class c6= Override.class;
System.out.println("c6 :" + c6);// c6 :interface java.lang.Override
Class c7= int.class;
System.out.println("c7 :" + c7); // c7 :int
Class c8= void.class;
System.out.println("c8 :" + c8);// c8 :void
Class c9= Class.class;
System.out.println("c9 :" + c9);// c9 :class java.lang.Class
int[] a= new int[10];
int[] b= new int[100];
Class c10= a.getClass();
System.out.println("c10 :" + c10);// c10 :class [I
Class c11= b.getClass();
System.out.println("c11 :" + c11);// c11 :class [I
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10== c11); //true// 获取Class类实例,如果泛型不确定可以使用?号代替
// 通过Class类静态forName(“类包名.类名”)获取Class类实例,建议使用这种形式
Class<?> c111 = Class.forName("Text");
System.out.println("c111:" + c111);// c111:class Text
// 通过对象.getClass获取Class类实例
Class<?> c21 = new Text1().getClass();
System.out.println("c21:" + c21);// c21:class Text1
// 通过使用类名.class获取Class类实例。
Class<?> c31 = Text1.class;
System.out.println("c31:" + c31);// c31:class Text1
// 如果已创建了引用类型的对象,则可以通过调用对象中的 getClass( )方法获取Class类实例。
String name = new String("111");
Class<?> c41 = name.getClass();
System.out.println("c41:" + c41);// c41:class java.lang.String
// 通过调用某个类的Class实例的getSuperClass()方法可以获取该类超类的Class实例。
Class<?> c51 = name.getClass().getSuperclass();
System.out.println("c51:" + c51);// c51:class java.lang.Object
// 如果是基本数据类型的包装类,则可以通过包装类.TYPE获取Class类实例
Class<?> c61 = Boolean.TYPE;
System.out.println("c61:" + c61);// c61:Boolean
判断是否为某个类的实例
一般地,我们用 instanceof
关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的 isInstance()
方法来判断是否为某个类的实例,它是一个 native 方法:
public native boolean isInstance(Object obj);
class.inInstance(obj)
这个对象能不能被转化为这个类
1.一个对象是本身类的一个对象
2.一个对象能被转化为本身类所继承类(父类的父类等)和实现的接口(接口的父接口)强转
3.所有对象都能被Object的强转
4.凡是null有关的都是false class.inInstance(null)
类名.class和对象.getClass()几乎没有区别,因为一个类被类加载器加载后,就是唯一的一个类。
obj.instanceof(class)
也就是说这个对象是不是这种类型,
1.一个对象是本身类的一个对象
2.一个对象是本身类父类(父类的父类)和接口(接口的接口)的一个对象
3.所有对象都是Object
4.凡是null有关的都是false null.instanceof(class)
package cn.itcast.bean;class A {
}class B extends A {
}public class Test {public static void main(String[] args) {B b = new B();A a = new A();A ba = new B();System.out.println("1------------");System.out.println(b instanceof B);//trueSystem.out.println(b instanceof A);//trueSystem.out.println(b instanceof Object);//trueSystem.out.println(null instanceof Object);//trueSystem.out.println("2------------");System.out.println(b.getClass().isInstance(b));//trueSystem.out.println(b.getClass().isInstance(a));//falseSystem.out.println("3------------");System.out.println(a.getClass().isInstance(ba));//trueSystem.out.println(b.getClass().isInstance(ba));//trueSystem.out.println(b.getClass().isInstance(null));//falseSystem.out.println("4------------");System.out.println(A.class.isInstance(a));//trueSystem.out.println(A.class.isInstance(b));//trueSystem.out.println(A.class.isInstance(ba));//trueSystem.out.println("5------------");System.out.println(B.class.isInstance(a));//falseSystem.out.println(B.class.isInstance(b));//trueSystem.out.println(B.class.isInstance(ba));//trueSystem.out.println("6------------");System.out.println(Object.class.isInstance(b));//trueSystem.out.println(Object.class.isInstance(null));//false}
}
反射创建指定类的对象:
获取了字节码文件对象后,最终都需要创建指定类的对象:创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):
1,调用空参数的构造函数:
使用了Class类中的newInstance()方法。
Class<?> c = String.class;
Object str = c.newInstance();// 如何生成获取到字节码文件对象的实例对象。类加载
Class clazz = Class.forName("cn.itcast.bean.Person");
clazz = Person.class;// 根据对象获得类型
Person obj3 = (Person)clazz.newInstance();
System.out.println("年龄"+obj3.getAge()+"姓名"+obj3.getName());
2,调用带参数的构造函数:
先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
// 如何生成获取到字节码文件对象的实例对象。类加载
Class clazz = Class.forName("cn.itcast.bean.Person");
//既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
//获取一个带参数的构造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要对对象进行初始化,使用构造器的方法newInstance();
Person obj = (Person) constructor.newInstance("zhagnsan",30);
System.out.println("年龄"+obj.getAge()+"姓名"+obj.getName());// 直接获得指定的类型
clazz = Person.class;
Constructor constructor1 = clazz.getConstructor(String.class,int.class);
Person obj1 = (Person) constructor1.newInstance("zhagnsan1",3);
System.out.println("年龄"+obj1.getAge()+"姓名"+obj1.getName());
//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();
public static void method_2() throws Exception {Class clazz = Class.forName("cn.itcast.bean.Person");//既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。//获取一个带参数的构造器。Constructor constructor = clazz.getConstructor(String.class,int.class);//想要对对象进行初始化,使用构造器的方法newInstance();Person obj = (Person) constructor.newInstance("zhagnsan",30);System.out.println("年龄"+obj.getAge()+"姓名"+obj.getName());//获取所有构造器。Constructor[] constructors = clazz.getConstructors();//只包含公共的for(Constructor con : constructors) {System.out.println("只包含公共的"+con);//只包含公共的public cn.itcast.bean.Person(java.lang.String,int)//只包含公共的public cn.itcast.bean.Person()}constructors = clazz.getDeclaredConstructors();//包含私有的for(Constructor con : constructors) {System.out.println("包含私有的"+con);//包含私有的private cn.itcast.bean.Person(java.lang.String)//包含私有的public cn.itcast.bean.Person(java.lang.String,int)//包含私有的public cn.itcast.bean.Person()}
}package cn.itcast.bean;public class Person {String name;int age;public Person () {}public Person (String name, int age) {this.name = name;this.age = age;}private Person (String name) {this.age = age;}@Overridepublic String toString () {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}public String getName () {return name;}public void setName (String name) {this.name = name;}public void setAge (int age) {this.age = age;}public int getAge () {return age;}
}
反射指定类中的方法:
//获取类中所有的方法。public static void method_1() throws Exception {Class clazz = Class.forName("cn.itcast.bean.Person");Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。for(Method method : methods) {System.out.println(method);}}//获取指定方法;public static void method_2() throws Exception {Class clazz = Class.forName("cn.itcast.bean.Person");//获取指定名称的方法。Method method = clazz.getMethod("show", int.class,String.class);//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。Object obj = clazz.newInstance();method.invoke(obj, 39,"hehehe");//执行一个方法}//想要运行私有方法。public static void method_3() throws Exception {Class clazz = Class.forName("cn.itcast.bean.Person");//想要获取私有方法。必须用getDeclearMethod();Method method = clazz.getDeclaredMethod("method", null);// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。}//反射静态方法。public static void method_4() throws Exception {Class clazz = Class.forName("cn.itcast.bean.Person");Method method = clazz.getMethod("function",null);method.invoke(null,null);}
获取类的成员变量(字段)信息(Fields)
可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值。JAVA 的Class类提供了几个方法获取类的属性。
getField(String name)
- public Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口或父类的指定公共成员字段,name参数指定了属性的名称,返回该Class对象表示类或接口中与指定属性名(含继承的)相同的public 属性对应的Field对象。
getFields()
- public Field[] getFields()
返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段,返回一个该Class对象表示类或接口中所有public属性(含继承的)对应的Field对象数组。
getDeclaredField(String name)
- public Field getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段,name参数指定了属性的名称,返回一个与该Class对象表示类或接口内定义的所有属性名(不含继承的)相匹配的属性相对应的Field对象。
getDeclaredFields()
- public Field[] getDeclaredFields()
返回 Field 对象的一个数组,获取类中所有的属性(public、protected、default、private),但不包括继承的属性,返回 Field 对象的一个数组。返回一个该Class对象表示类或接口内定义的所有属性(不含继承的)对应的Field对象数组,。
getFields和getDeclaredFields区别:
getFields返回的是申明为public的属性,包括父类中定义,
getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。
Field 类对象常用方法
获取变量的类型:
Field.getType():返回这个变量的类型
Field.getGenericType():如果当前属性有签名属性类型就返回,否则就返回 Field.getType()
isEnumConstant() : 判断这个属性是否是枚举类
获取成员变量的修饰符
Field.getModifiers() 以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符
获取和修改成员变量的值
getName() : 获取属性的名字
get(Object obj) 返回指定对象obj上此 Field 表示的字段的值
set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值
主要是这几个方法,在此不再赘述:
import java.lang.reflect.Field;class A {public static String a1 ="as";private String a2;protected String a3;String a4;
}class B extends A {public static int b1 =99;private String b2;protected String b3;String b4;
}public class Test {public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {//第一步:获取操作类Class clazz = B.class;//第二步:获取此类的所有的公共(public)的字段,返回 Field 对象的一个数组System.out.println("getField "+clazz.getField("a1").get(clazz));Field[] fields = clazz.getFields();for (Field field : fields) {//第三步:获取字段的名称System.out.println("getFields field.getName() "+field.getName());System.out.println("getFields getType() "+field.getType());System.out.println("getFields field.isEnumConstant() "+field.isEnumConstant());System.out.println("getFields field.getGenericType() "+field.getGenericType());System.out.println("getFields field.getModifiers() "+field.getModifiers());
// field.set(clazz,21);System.out.println("getFields field.get() "+field.get(clazz));}//第二步:获取此类的所有的公共(public)的字段,返回 Field 对象的一个数组System.out.println("getDeclaredField "+clazz.getDeclaredField("b1").get(clazz));Field[] fields1 = clazz.getDeclaredFields();for (Field field : fields1) {//第三步:获取字段的名称String fieldValue = field.getName();System.out.println("getDeclaredFields "+fieldValue);}}
}
getField as
getFields field.getName() b1
getFields getType() int
getFields field.isEnumConstant() false
getFields field.getGenericType() int
getFields field.getModifiers() 9
getFields field.get() 99
getFields field.getName() a1
getFields getType() class java.lang.String
getFields field.isEnumConstant() false
getFields field.getGenericType() class java.lang.String
getFields field.getModifiers() 9
getFields field.get() as
getDeclaredField 99
getDeclaredFields b1
getDeclaredFields b2
getDeclaredFields b3
getDeclaredFields b4
常见错误
set(Object obj, Object value) 时,新value和原value的类型不一致导致,如下:无法转换类型导致的 java.lang.IllegalArgumentException(注意:反射获取或者修改一个变量的值时,编译器不会进行自动装/拆箱,所以int 和Integer需手动修改)
set(Object obj, Object value) 时,修改 final类型的变量导致的 IllegalAccessException。由于 Field 继承自 AccessibleObject , 我们可以使用 AccessibleObject.setAccessible() 方法告诉安全机制,这个变量可以访问即可解决,如field.setAccessible(true)。
getField(String name) 或getFields() 获取非 public 的变量,编译器会报 java.lang.NoSuchFieldException 错
获取方法
获取某个Class对象的方法集合,主要有以下几个方法:
Method[] getMethods():返回一个该Class对象表示类或接口中所有public方法(含继承的)对应的Method对象数组。 | Method[] array = clazz.getMethods(); for (Method a : array) { System.out.println(a.toString()); } Thread.sleep(1000); |
Method getMethod(String methodName, Class<?>... parameterTypes):返回与该Class对象表示类或接口中方法名和方法形参类型相匹配的public方法(含继承的)的Method对象。其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象 | Method array2 = clazz.getMethod("Text_1", String.class); Object obj1 = clazz.newInstance(); array2.invoke(obj1, "hehehe"); |
Method[] getDeclaredMethods():返回一个该Class对象表示类或接口内声明定义的所有访问权限的方法,包括公共、保护、默认(包)访问和私有方法,(不含继承的)对应的Method对象数组 | Method array4 = clazz.getDeclaredMethod("Text_1", String.class); Object obj1 = clazz.newInstance(); array2.invoke(obj1, "hehehe"); |
Method getDeclaredMethod(String methodName,Class<?>... parameterTypes) 返回与该Class对象表示类或接口中方法名和方法形参类型相匹配的所有访问权限的方法,包括公共、保护、默认(包)访问和私有方法,(不含继承的)对应的Method对象。其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象 | Method[] array1 = clazz.getDeclaredMethods(); for (Method a : array1) { System.out.println(a.toString()); } |
只是这样描述的话可能难以理解,我们用例子来理解这三个方法:
例子
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public static void test() throws Exception {Class<?> c = methodClass.class;Object object = c.newInstance();Method[] methods = c.getMethods();//getMethods()方法获取的所有方法System.out.println("getMethods获取的方法:开始");for(Method m:methods){System.out.println(m);}Thread.sleep(1000);System.out.println("getMethods获取的方法:结束");Method[] declaredMethods = c.getDeclaredMethods();//getDeclaredMethods()方法获取的所有方法System.out.println("getDeclaredMethods获取的方法::开始");for(Method m:declaredMethods) {System.out.println(m);}Thread.sleep(1000);System.out.println("getDeclaredMethods获取的方法:结束");//获取methodClass类的add方法Method method = c.getMethod("add", int.class, int.class);}
}
class methodClass extends methodCl{public final int fuck = 3;public int add(int a,int b) throws InterruptedException {return a+b;}public int sub(int a,int b) {return a+b;}
}
class methodCl {public final int fuck1 = 3;public int add1(int a,int b) {return a+b;}private int sub2(int a,int b) {return a+b;}
}
程序运行的结果如下:
getMethods获取的方法:开始
public int cn.itcast.bean.methodClass.add(int,int)
public int cn.itcast.bean.methodClass.sub(int,int)
public int cn.itcast.bean.methodCl.add1(int,int)
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) 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()
getMethods获取的方法:结束
getDeclaredMethods获取的方法::开始
public int cn.itcast.bean.methodClass.add(int,int)
public int cn.itcast.bean.methodClass.sub(int,int)
getDeclaredMethods获取的方法:结束
可以看到,通过 getMethods()
获取的方法可以获取到父类的方法,比如 java.lang.Object 下定义的各个方法。
通过调用Class对象的getMethods、getMethod、getDeclaredMethods和getDeclaredMethod方法可以获取到Class对象表示类中某些或某个具体方法所对应Method对象,
Method对象有一下常用方法:
Class<?> getDeclaringClass():返回声明Method对象表示方法的类或接口的 Class 对象。
int getModifiers():以整数形式返回此Method对象所表示方法的修饰符。应该使用Modifier类对修饰符进行解码。
Class<?> getReturnType():返回Method对象所表示的方法的返回值类型所对应的Class对象。
String getName():返回方法名。
Class<?>[] getParameterTypes():返回由Method对象代表方法的形参类型对应Class对象组成的数组。如果方法没有参数,则数组长度为 0。
Class<?>[] getExceptionTypes():返回由Method对象表示方法抛出异常类型对应Class对象组成的数组。如果此方法没有在其 throws子句中声明异常,则返回长度为 0 的数组。
例子
Class<?> cl = methodClass.class;
//获取methodClass类的add方法
Method c = cl.getMethod("add", int.class, int.class);
// Class<?> getDeclaringClass():返回声明Method对象表示方法的类或接口的 Class 对象。
Class<?> cla = c.getDeclaringClass();
System.out.println("方法的类或接口: " + cla.getName());
// int getModifiers():以整数形式返回此Method对象所表示方法的修饰符。应该使用Modifier类对修饰符进行解码。
int a = c.getModifiers();
System.out.println("方法的修饰符解码: " + a);
System.out.println("方法的修饰符: " + Modifier.toString(a));
// Class<?> getReturnType():返回Method对象所表示的方法的返回值类型所对应的Class对象。
Class<?> aa = c.getReturnType();
System.out.println("返回值类型: " + aa.toString());
// String getName():返回方法名。
System.out.println("方法名: " + c.getName());
// Class<?>[] getParameterTypes():返回由Method对象代表方法的形参类型对应Class对象组成的数组。
// 如果方法没有参数,则数组长度为 0。
Class<?>[] par = c.getParameterTypes();
for (Class<?> a1 : par) {System.out.println("参数: " + a1.getName());
}
// Class<?>[] getExceptionTypes():返回由Method对象表示方法抛出异常类型对应Class对象组成的数组。
// 如果此方法没有在其 throws子句中声明异常,则返回长度为 0 的数组。
Class<?>[] ex = c.getExceptionTypes();
for (Class<?> a2 : ex) {System.out.println("异常: " + a2.getName());
}
返回结果
方法的类或接口: cn.itcast.bean.methodClass
方法的修饰符解码: 1
方法的修饰符: public
返回值类型: int
方法名: add
参数: int
参数: int
异常: java.lang.InterruptedException
返回Class对象表示类构造方法
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
此方法可以根据传入的参数来调用对应的Constructor创建对象实例。
Constructor[] getConstrutors():
Constructor[] getConstrutors()返回该Class对象表示类包含的所有public构造方法(不含继承)所对应Constructor对象数组。如果没有构造方法,则返回一个默认的无参构造方法:
|
Constructor getConstrutor(Class<?>... parameterTypes):
Constructor getConstrutor(Class<?>... parameterTypes):返回与该Class对象表示类中参数列表相匹配的public构造函数(不含继承)对应的Constructor对象。
|
Constructor<?>[] getDeclaredConstructors()
Constructor<?>[] getDeclaredConstructors():返回一个该Class对象表示类中声明的所有构造方法(不区分访问权限)对应的Constructor对象数组。
Constructor<?>[] con2 = clazz.getDeclaredConstructors(); for (Constructor a : con2) { System.out.println(a.toString()); } |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):返回与该Class对象表示类中定义的形参类型相匹配的构造方法(不区分访问权限)的Constructor对象。
Constructor<Text2> con3 = clazz.getConstructor(int.class); System.out.println(con3); |
public class Test {public static void main(String[] args) throws Exception{//根据指定的文件路径返回一个Class对象Class cls = User.class;Constructor[] con = cls.getConstructors();for (Constructor a : con) {System.out.println(a.toString());}Thread.sleep(100);System.out.println("--------------------");Constructor[] con1 = cls.getDeclaredConstructors();for (Constructor a : con1) {System.out.println(a.toString());}Thread.sleep(100);System.out.println("--------------------");//指定获取带有两个参数的构造器,要指定参类型、参数的位置、参数的个数Constructor cons = cls.getConstructor(int.class,String.class);// 通过这个构造器对象创建实体类对象User user = (User)cons.newInstance(15,"“嘻嘻”");System.out.println(user.getAge());Thread.sleep(100);System.out.println("--------------------");Constructor<User> cons1 = cls.getDeclaredConstructor(int.class);// 通过这个构造器对象创建实体类对象User user1 = cons1.newInstance(1599);System.out.println(user1.getAge());}
}
class User {private int age;private String name;public User(int age, String name) {super();this.age = age;this.name = name;}private User(int age) {super();this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public User() {super();}
}
通过调用Class对象的getConstrutors()、getConstrutor、getDeclaredConstructors和getDeclaredConstructor方法可以获取到Class对象表示类中某些或某个具体构造方法所对应Constructor对象,Constructor对象有一下常用方法:
Class<T> getDeclaringClass():返回声明Constructor对象对应构造方法的类的Class对象。
int getModifiers():以整数形式返回Constructor对象表示的构造函数的修饰符。
String getName() :以字符串形式返回Constructor对象所表示得构造方法的名称。
Class<?>[] getParameterTypes():返回由Constructor对象所表示的构造方法的形参类型对应Class对象组成的数组此 。如果构造方法没有参数,则数组长度为0。
Constructor<?> con = clazz.getConstructor(int.class, int.class, int.class); System.out.println("构造方法的类名称: " + con.getName()); // 以字符串形式返回Constructor对象所表示得构造方法的名称。 Class<?> dec = con.getDeclaringClass(); System.out.println("构造方法的类或接口: " + dec.getName()); // 返回声明Constructor对象对应构造方法的类的Class对象。构造方法的类或接口: Text2 int cll = con.getModifiers(); System.out.println("构造方法的整数形式返回字段的修饰符: " + cll); // 以整数形式返回Constructor对象表示的字段的修饰符。构造方法的整数形式返回字段的修饰符: 1 System.err.println("构造方法的tostring返回字符串的修饰符: " +Modifier.toString(cll)); // 返回Constructor对象表示的字段的修饰符。构造方法的tostring返回字符串的修饰符: public Class<?>[] par = con.getParameterTypes(); for (Class<?> p : par) { System.out.println("构造方法的参数: " + p); System.out.println("构造方法的参数名称字符串: " + p.getName()); // 返回由Constructor对象所表示的构造方法的形参类型对应Class对象组成的数组此 。 // 如果构造方法没有参数,则数组长度为0。 构造方法的参数: int 构造方法的参数名称字符串: int } |
通过反射调用方法
当我们从类中获取了一个方法后,我们就可以用 invoke()
方法来调用这个方法。
通过调用Method对象的invoke方法可以实现对方法的调用:
Object invoke(Object obj, Object... args):调用Method对象指代的方法并返回Object类型结果。obj表示该方法所在类实例,如果方法时静态的则obj可以指定为null; args表示传入该方法的参数,如果方法没有参数,则args数组长度可以为 0 或 null 。
通过调用Field对象的如下方法可以获取或设置属性的值:
Object get(Object obj):返回Field表示字段的Object类型的值。obj为该属性所在类创建的对象,如果该属性是静态的,则可设置为null。
void set(Object obj, Object value):为Field对象表示属性设置新值。obj为该属性所在类创建的对象,如果该属性为静态的则课设置为null;value为该属性新值。
invoke 方法的原型为:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
下面是一个实例:
Class<?> klass = methodClass.class;
//创建methodClass的实例
Object obj = klass.newInstance();
//获取methodClass类的add方法
Method method = klass.getMethod("add",int.class,int.class);
//调用method对应的方法 => add(1,4)
Object result = method.invoke(obj,1,4);
System.out.println(result);
关于 invoke
方法的详解,后面我会专门写一篇文章来深入解析 invoke 的过程。
利用反射创建数组
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。下面我们看一看利用反射创建数组的例子:
public static void testArray() throws ClassNotFoundException {Class<?> cls = Class.forName("java.lang.String");Object array = Array.newInstance(cls,25);//往数组里添加内容Array.set(array,0,"hello");Array.set(array,1,"Java");Array.set(array,2,"fuck");Array.set(array,3,"Scala");Array.set(array,4,"Clojure");//获取某一项的内容System.out.println(Array.get(array,3));
}
其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {return newArray(componentType, length);
}
而 newArray 方法是一个 native 方法,它在 HotSpot JVM 里的具体实现我们后边再研究,这里先把源码贴出来:
private static native Object newArray(Class<?> componentType, int length)
throws NegativeArraySizeException;
源码目录:openjdk\hotspot\src\share\vm\runtime\reflection.cpp
arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {if (element_mirror == NULL) {THROW_0(vmSymbols::java_lang_NullPointerException());}if (length < 0) {THROW_0(vmSymbols::java_lang_NegativeArraySizeException());}if (java_lang_Class::is_primitive(element_mirror)) {Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);return TypeArrayKlass::cast(tak)->allocate(length, THREAD);} else {Klass* k = java_lang_Class::as_Klass(element_mirror);if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {THROW_0(vmSymbols::java_lang_IllegalArgumentException());}return oopFactory::new_objArray(k, length, THREAD);}
}
另外,Array 类的 set
和 get
方法都为 native 方法,在 HotSpot JVM 里分别对应 Reflection::array_set
和 Reflection::array_get
方法,这里就不详细解析了。
反射的一些注意事项
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
Java反射机制六问
JVM加载class文件的原理机制?
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
利用反射和重载完成以下功能
1)创建Student类,类中有属性name和age并封装属性
2)重载Student的构造函数,一个是无参构造并,另一个是带两个参数的有参构造,要求在构造函数打印提示信息
3)创建带main函数的NewInstanceTest类,利用Class类得到Student对象
4)通过上述获取的Class对象分别调用Student有参函数和无参函数
利用反射的知识完成下面的操作
创建Mytxt类,创建myCreate()方法完成创建文件D:\myhello.txt文件的功能。创建带main方法的NewInstanceTest类,通过Class类获取Mytxt对象,调用myCreat()
利用Annotation和反射知识完成操作
1)自定义一个有效的Annotation注释名为MyAnnotation,其中有属性myname创建Student类并重写toString(),toString()要求使用三个基本的Annotation和自定义的MyAnnotation注释
2)创建TestGetAnno类,打印出Student类的toString方法的所有注释
利用通过反射修改私有成员变量
- 定义PrivateTest类,有私有name属性,并且属性值为hellokitty,只提供name的getName的公有方法
- 创建带有main方法ReflectTest的类,利用Class类得到私有的name属性
- 修改私有的name属性值,并调用getName()的方法打印name属性值
利用反射和File完成以下功能
- 利用Class类的forName方法得到File类
- 在控制台打印File类的所有构造器
- 通过newInstance的方法创建File对象,并创建D:\mynew.txt文件