JVM内存中的两大对象
在java中可以将对象分为两大体系:字节码对象和实例对象
反射应用的入口为字节码对象,任意的一个类在同一个JVM内部,字节码对象是唯一的
此字节码对象会在第一次类加载时创建,用于存储类的结构信息
基于字节码对象,我们可以获取如下对象:
- Constructor (构造方法对象类型,基于此对象构建类的实例对象)
- Field (属性对象类型)
- Method (方法对象类型)
- Annotation (注解对象类型)
1、字节码对象
每个类在加载(将类读到内存)时都会创建一个字节码对象
且这个对象在一个JVM内存中是唯一的
此对象中存储的是类的结构信息
字节码对象的获取方式(常用方式有三种)
- 类名.class
- Class.forName(“包名.类名”)
- 类的实例对象.getClass();
说明:字节码对象是获取类结构信息的入口
类加载时会做哪些事情
- 构建类的字节码对象,类型为Class类型
- 可能会初始化类中的静态变量(类变量)
- 可能会执行类中的静态代码块.(具体是否执行由加载方式决定)
谁负责将类加载(读)到内存中
类加载器(ClassLoader)
类加载器分三种(分别加载java自带的,加载自定义的,加载导入的)
谁提供类加载器
JDK官方或者第三方
如何保证类对象只有一个
类加载时采用了双亲委派模型
2、类的实例对象
如何理解类的实例对象(类的对象)
- 客观事务在内存中的呈现(堆内存中的一块区域)
- 类的实例对象在同一个JVM内存中可以有多份.
Java中对象的创建方式
- 通过new关键字创建
- 通过反射创建(首先要先获取字节码对象)
Java中对象的作用
-
存储数据(变量:类变量,实例变量,参数变量,局部变量)
a) Pojo (普通的java对象)
b) Vo (值对象) -
执行业务逻辑(方法):各司其职,各尽所能.
a) Ctroller
b) Service
c) Dao
建议:面向对象设计时不要设计一个大而全的对象.
java反射机制
- java反射机制是一个动态机制
- 允许我们在程序的运行过程中通过字符串来指挥程序实例化、操作属性、调用方法等
- 这使得代码提高了灵活度,
但是反射机制会带来更多的资源开销和较慢的运行效率(相较于硬编码-直接编写代码) - 程序不应当过于依赖反射,它应当只是起到画龙点睛的作用,在合适的时候使用
Class类(类对象)
Class类被称为类对象,它的每一个实例表示JVM加载的一个类
并且JVM内部每个被加载的类都有且只有唯一一个Class实例与之对应
通过类对象我们可以得到其表示的类的一切信息
便于我们在程序运行期间来通过它操作其表示的类
获取类对象的方式
获取一个类的类对象有三种方式:
1、通过每个对象的class属性获取
调用该类的静态属性class,每个类都有
获取的就是该类的类对象,基本类型也有
//方式1
//获取String这个类的类对象
Class cls1 = String.class;
//类对象可以通过getName()方法获取该类的名字(完全限定名)
String name = cls1.getName();
System.out.println(name);
//获取int的类对象
Class cls2 = int.class;
System.out.println(cls2.getName());
2、通过Class的静态方法forName
该方法可以指定要获取的类的名字(完全限定名),从而得到该类的类对象
这里可能找不到这个类,所以需要处理异常
基本类型的比较特别,因为基本类型实际没有属性方法等面向对象的特性的
所以方式2不适用基本类型
//方式2(注意这里要写完全限定名,包含包名),要处理异常(可能找不到这个类)
try {
Class cls1 = Class.forName("java.lang.String");
System.out.println(cls1.getName());
//不适用于基本类型
//Class cls2 = Class.forName("int");
//Class cls2 = Class.forName("java.lang.int");
//可以访问其引用类型
Class cls2 = Class.forName("java.lang.Integer");
//getMethods() 通过类对象获取类的方法集合
Method[] methods = cls2.getMethods();
for(Method method:methods){
System.out.println(method.getName());
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
动态获取类对象
//方式2,动态机制的体现
Scanner scan = new Scanner(System.in);
System.out.print("请输入一个类名:");
String className = scan.nextLine();
try {
Class cls = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
3、通过类加载器ClassLoader得到
4、类的实例对象.getClass()
String str = new String("abc");
Class cls = str.getClass();
常用API(Class与Method)
通过Class的API可以获取一个类的所有信息,详情见API
通过Class获取下面相应类型,再逐一解析每个结构的内容
- Constructor (构造方法对象类型,基于此对象构建类的实例对象)
- Field (属性对象类型)
- Method (方法对象类型)
- Annotation (注解对象类型)
注意:获取这些对象的方法如果含Declared,表示仅获取本类自己的,不获取继承的
1、getName() 获取类对象的类名
String getName()
Class cls = String.class;
String name = cls.getName();
System.out.println(name);
2、获得 Method 方法对象类型
通过Method类,来解析调用方法
1. getMethods()获取类对象数组的方法
Method[] getMethods() 获取这个类所有方法(包含继承的),返回一个方法数组
Method[] getDeclaredMethods() 仅获取本类自己定义的方法,返回一个方法数组
Scanner scan = new Scanner(System.in);
System.out.print("请输入一个类名:");
String className = scan.nextLine();
try {
Class cls = Class.forName(className);
//获取所有的方法,包含继承的父类中的
//Method[] methods = cls.getMethods();
//获取自己的方法,不包含父类中的
Method[] methods = cls.getDeclaredMethods();
for(Method method:methods){
System.out.println(method.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2. getMethod() 获取类对象的方法
Method getMethod(方法名, 该方法的参数类型的类对象)
Method getDeclaredMethod() 仅获取本类自己定义的方法,返回一个方法
获取这个类的指定方法(不写参数的话指无参方法)
参数类型注意要写我们指定方法需要传入的参数的类型所属的类的类对象(比如String的为String.class)
需要调用的方法有多少个参数,就传入多少个,但是要注意参数的类对象的顺序不能写反了
package reflect;
import java.lang.reflect.Method;
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
//先获取类对象
Class cls = Class.forName("reflect.Person");
//根据类对象,实例化一个对象
Object obj = cls.newInstance();
//获取我们要调用参数的类对象
Class classStr = Class.forName("java.lang.String");
//获取具体的方法,传入方法签名(方法名和方法参数)
Method method = cls.getMethod("sayHello", classStr);
//调用方法,传入调用的实例化对象和实际参数
method.invoke(obj, "爸爸");
//调用多参数的方法
Method method1 = cls.getMethod("sayHi", String.class, int.class);
method1.invoke(obj, "爸爸", 88);
//调用无参数方法
Method method2 = cls.getMethod("sayHello");
method2.invoke(obj, null);
}
}
ps.拓展-变长参数
JDK5之后推出一个特性:变长参数
可以参考Python的函数篇-不定长参数,两者概念相同
变长参数的实际类型为数组,而且变长参数只能是当前参数的最后一个参数,且只能定义一个
在参数列表里面写 参数类型… 参数名
public class Demo {
public static void main(String[] args) {
test(1,"a");
test(1,"a","b");
test(1,"a","b","c");
}
public static void test(int i, String... s){
System.out.println(s.length);
}
}
getMethod() 方法传入的就是一个字符串和一个变长参数
其源码为
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes, true);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
3. getDeclaredMethod()与getDeclaredMethods()
仅获取本类自己定义的方法,详情见上述
一般含Declared的都是表示仅限于自己
4. invoke() 通过Method对象调用方法
invoke(类对象, 传入的参数);
通过上面获取的method对象,调用方法,指定类对象,和传入的参数
invoke()方法传入的参数也是一个类对象,一个变长参数
其源码为
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
5. setAccessible(boolean b) 强制访问私有属性
该方法为Method继承自java.lang.reflect.AccessibleObject
通过Method的实例化对象调用
将此对象的 accessible
标志设置为指定的布尔值
这个标志表示私有权限,设置为true,将前行开放其权限
package reflect;
import java.lang.reflect.Method;
/**
* 在Person类中添加一个私有方法say
* @author Tian
*
*/
public class ReflectDemo6 {
public static void main(String[] args) throws Exception {
// Person p = new Person();
// p.say(); //编译不通过,不能调用私有方法
//通过无参构造器实例化对象
Class cls = Class.forName("reflect.Person");
Object obj = cls.newInstance();
//调用方法
Method m = cls.getDeclaredMethod("say");
//强制访问私有方法
m.setAccessible(true);
m.invoke(obj);
}
}
3、T newInstance() 创建实例化对象
创建由此类对象表示的类的新实例,使用该方法实例化的类要有无参构造器
参见:通过反射机制进行实例化对象
4、获得 Constructor 构造方法对象类型
getConstructor() 获取类对象中的Constructor对象
Constructor<T> getConstructor(Class<?>… parameterTypes)
返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共类函数
Constructor<?>[] getConstructors()
返回包含一个数组Constructor对象反射由此表示的类的所有公共构造类对象。
就是获得类对象的一个Constructor对象,注意获取类对象的类中构造方法要设置为公有(public)
应用于调用有参构造方法构造实例化对象
参见:通过反射机制进行实例化对象
5、获得 Annotation 注解对象类型
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null
Annotation[] getAnnotations() 返回此元素上存在的所有注释
注意:是获得注解,注解不是注释
注解例子:重写方法的注解是 @Override
6、获得 Field 属性对象类型
Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段
Field[] getFields()
返回一个包含全部 Field对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段
通过反射机制进行实例化对象
-
加载要实例化的类的类对象
-
通过类对象的 newInstance() 方法实例化
该方法是调用无参构造器实例化的对象
所以使用该方法实例化的类要有无参构造器
调用无参构造方式实例化对象
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//硬编码实例化对象
Person p = new Person();
System.out.println(p);
//反射机制动态实例化
Scanner scan = new Scanner(System.in);
System.out.print("请输入类名(完全限定名):");
String className = scan.nextLine();
//1.加载实例化类的类对象
Class cls = Class.forName(className);
//2.通过类方法newInstance()实例化对象
//注意:字符串实例化的对象默认为“”空串
Object obj = cls.newInstance();
System.out.println(obj);
}
}
调用有参构造方法实例化对象
public class ReflectDemo5 {
public static void main(String[] args) throws Exception {
//先加载需要实例化的类的类对象
Class cls = Class.forName("reflect.Person");
//再获取类对象中的Constructor对象,注意构造方法要设置为公有(public)
Constructor c = cls.getConstructor(String.class, int.class);
//再通过刚才获得的Constructor对象来实例化对象
Object obj = c.newInstance("爸爸", 88);
}
}
通过反射机制调用方法
- 加载要操作的类的类对象
- 利用类对象实例化其表示的类的实例对象
- 通过类对象获取要调用的方法
- 调用该方法
package reflect;
import java.lang.reflect.Method;
import java.util.Scanner;
/**
* 调用无参的方法
* @author Tian
*
*/
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(System.in);
System.out.print("请输入类名(完全限定名):");
String className = scan.nextLine();
//1.加载要操作的类的类对象
//
Class cls = Class.forName(className);
//2.实例化获取的类对象的实例对象
Object obj = cls.newInstance();
//3.通过类对象获取要调用的方法
System.out.print("请输入方法名:");
String methodName = scan.nextLine();
Method method = cls.getMethod("sayHello");
//4.调用该方法
//obj为第2步获取的类对象,null表示无参方法
method.invoke(obj, null);
}
}
调用有参方法,参见上述的 getMethod() 方法
通过反射破坏泛型
泛型只在编译时有效,运行时都是当做Object,泛型的底层实现还是Object
所以,我们可以通过反射破坏泛型
package reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectDemo7 {
public static void main(String[] args) throws Exception{
ArrayList<String> list = new ArrayList<>();
list.add("123");
list.add("abc");
//list.add(456); 不行,泛型限制
//使用反射破坏泛型,添加泛型不允许的元素
Class cls = list.getClass();
//获取传入参数的类对象,泛型的底层是Object
Class classStr = Object.class;
//通过类对象,获得添加元素的方法
Method m = cls.getMethod("add", classStr);
//调用方法
m.invoke(list, 456);
System.out.println(list);
}
}
通过泛型破坏私有属性
通过 setAccessible(boolean b) 方法可以强制访问私有属性
详情见setAccessible(boolean b) 方法示例