通过反射是可以访问类的私有方法和私有属性的,那么是不是封装性就没有意义了呢?
固然反射可以打破封装性,其实封装性更多的像是一种约定,约定类的私有方法和私有属性在外部就正常情况下最好不要调用,可以通过公共的方法简介调用私有属性和方法。就像是一台手机,类似于cpu调度等等是私有的,已经封装好了,在使用的时候通过APP可以实现对cpu的调度,反射就像是root,可以访问内部的cpu调度,一般情况下没有必要这么做,但是如果某些特殊情况则可以这么做。
@Test
public void test2() throws ClassNotFoundException {
// 方式一:次奥用运行时类的属性:.class
Class clazz1 = User.class;
System.out.println(clazz1);
// 方式二:通过运行时类对象,调用getClass()方法
User user = new User();
Class clazz2 = user.getClass();
System.out.println(clazz2);
// 方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("pojo.User");
System.out.println(clazz3);
// 方式四:使用类加载器ClassLoader
ClassLoader classLoader = System.class.getClassLoader();
Class clazz4 = classLoader.loadClass("pojo.User");
System.out.println(clazz4);
// 加载到内存的运行时类对象,只会存有一份,且会缓存一段时间,在此时间内,我们可以通过不同的方式获取此运行时类。
System.out.println(clazz1 == clazz2); //ture
System.out.println(clazz1 == clazz3); //ture
System.out.println(clazz1 == clazz4); //ture
}
@Test
public void test3(){
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
int[][] c = new int[10][10];
Class c10 = a.getClass();
Class c11 = b.getClass();
Class c12 = c.getClass();
// 对于相同类型相同维度的数组,运行时类是一样的
// 不同类型或者不同维度的数组,运行时类不一样
System.out.println(c10 == c11); // true
System.out.println(c10 == c12); // false
}
关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行 强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》 则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之 前开始):
编译:经过javac.exe编译后Person.java 会生成一个Person.class文件。
加载:Person.class经过java.exe加载后,会把类的相关信息加载到JVM内存中,这些信息会转化成数据结构保存到方法区的元空间中,同时在堆中生成一个代表这个类的java.lang.Class的运行时类对象,作为元空间中这个类的数据的访问入口。
连接:连接阶段会对静态变量的值进行默认赋值,此时Person类的静态变量赋值为0;
初始化:首先会对Person类的静态变量进行真正的赋值的操作。
对象创建:当我们new一个对象的时候JVM首先会去找到对应的类元信息,如果找不到意味着类信息还没有被加载,所以在对象创建的时候也可能会触发类的加载操作。当类元信息被加载之后,我们就可以通过类元信息来确定对象信息和需要申请的内存大小。
当我们new一个对象的时候,对象就开始创建了,执行流程大致分为三步:
类加载器的作用是将.class字节码文件的内容加载到内存中,并将这些景泰数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
双亲委派机制:如果一个类加载器收到了类加载的请求,这个类加载器不会先尝试加载这个类,而是会先委派给自己的父类加载器去完成,在每个层次的类加载器都是依此类推的。因此所有的类加载器请求其最后都应该被委派到顶层的启动类加载器中(Bootstrap),只有当父类的加载器在自己管辖范围内没有找到所需要的类的时候,子类加载器才会尝试自己去加载的,如果自己管理范围内也找不到需要加载的就会抛出:ClassNotFoundException 这个异常了。这就是为什么如果我们自己写java.lang.string类的时候,是不行的。
public void test4() throws Exception {
Class clazz = Class.forName("pojo.User");
User user = (User) clazz.getDeclaredConstructor().newInstance();
}
Java 9之前建议调用clazz.newInstance()
来创建对象,Java 9之后,推荐使用clazz.getDeclaredConstructor().newInstance()
来创建对象。
因为clazz.getDeclaredConstructor().newInstance()
底层是通过调用类的空参构造器进行对象的创建。为了代码的健壮性,方便以后可能通过反射获取对象,推荐在所写的类中提供空参构造器。
当然也可以使用带参构造器进行对象的创建。例如clazz.getDeclaredConstructor().newInstance(String str, Integer num)
,但带参构造器的参数千差万别,为了代码的通用性,建议使用空参构造器进行对象的创建。
public Class>[] getInterfaces()
:返回此对象所表示的类或接口实现的接口。public Class Super T> getSuperclass()
:返回表示此 Class
所表示的实体(类、接口、基本类型)的父类的 Class
。public Constructor[] getConstructors()
:返回此 Class
对象所表示的类的所有 public
构造方法。public Constructor[] getDeclaredConstructors()
:返回此Class对象表示的类声明的所有构造方法。在Constructor类中:
public int getModifiers();
public String getName();
public Class> getParameterTypes();
public Method[] getDeclaredMethods()
:返回此Class对象所表示的类或接口的全部方法public Method[] getMethods()
:返回此 Class
对象所表示的类或接口的 public
的方法Method
类中:
public Class> getReturnType()
:取得全部的返回值public Class>[] getParameterTypes()
:取得全部的参数public int getModifiers()
:取得修饰符public Class> [] getEXceptionTypes()
:取得异常信息public Field[] getFields()
:返回此 Class
对象所表示的类或接口的 public
的 Field
。public Field[] getDeclaredFields()
:返回此 Class
对象所表示的类或接口的全部 Field
Field
方法中
public int getModifiers()
:以整数形式返回此 Field
的修饰符public Class> getType()
:得到 Field
的属性类型public String getName()
:返回 Field
的名称。get Annotation(Class annotationClass)
:获取某个注解信息getDeclaredAnnotations()
:获取全部的注解信息Type getGenericSuperclass()
:获取带泛型的父类getActualTypeArguments()
:获取实际的泛型类型参数数组实际代码:
@Test
public void test4(){
// 获取带泛型的父类的泛型
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
// System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());
}
在反射机制中,可以直接通过 Field
类操作类中的属性,通过 Field
类提供的 set()
和get()
方法就可以完成设置和取得属性内容的操作。
public Field getField(String name)
:返回此 Class
对象表示的类或接口的指定的 public
的 Field
。
public Field getDeclaredField(String name)
: 返回此 Class
对象表示的类或接口的指定的 Field
。
在 Field
中:
public Object get(object obj)
: 取得指定对象 obj
上此 Field
的属性内容
public void set(Object obj,Object value)
: 设置指定对象 obj
上此 Field
的属性内容
通过反射,调用类中的方法,通过 Method
类完成。步骤:
Class
类的 getMethod(String name,Class… parameterTypes)
方法取得一个 Method
对象,并设置此方法操作时所需要的参数类型。Object invoke(Object obj, Object[] args)
进行调用,并向方法中传递要设置的 obj
对象的参数信息。Object invoke(object obj,Object… args)方法:
Object
对应原方法的返回值,若原方法无返回值,此时返回 null
Object obj
可为 null
Object[] args
为 null
private
,则需要在调用此 invoke()
方法前,显式调用方法对象的 setAccessible(true)
方法,将可访问 private
的方法。关于 setAccessible 方法的使用:
Method
和 Field
、Constructor
对象都有 setAccessible()
方法。setAccessible
是启动和禁用访问安全检查的开关true
则指示反射的对象在使用时应该取消 Java 语言访问检査。false
则指示反射的对象应该实施 Java 语言访问检査。@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}
概念:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理的缺点
需要解决的两个主要问题:
如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象:通过 Proxy.newProxyInstance()
实现
当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法:通过 InvocationHandler
接口的实现类及其方法 invoke()
动态代理相关的API:
Proxy:专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。提供用于创建动态代理类和动态代理对象的静态方法。
static Class> getProxyClass(ClassLoader loader, Class>...interface)
创建一个动态代理类所对应的 Class
对象static Object newProxyInstance(ClassLoader loader, Class>...interface, InvocationHandler h)
直接创建一个动态代理对象动态代理实现步骤:
InvocationHandler
的类,它必须实现invoke方法,以完成代理的具体操作。newProxyInstance(ClassLoader loader, Class>...interface, InvocationHandler h)
创建一个代理对象动态代理代码:
public interface Human {
public void getBelief();
public void eat(String str);
}
public class SuperMan implements Human{
@Override
public void getBelief() {
System.out.println("我是超人我会飞");
}
@Override
public void eat(String str) {
System.out.println("我喜欢吃" + str);
}
}
public class TestProxy {
@Test
public void testProxy(){
SuperMan superMan = new SuperMan();
ProxyFactory proxyFactory = new ProxyFactory(superMan);
Human human = (Human) proxyFactory.getInstance();
human.getBelief();
human.eat("四川麻辣烫");
}
}
class ProxyFactory{
Object object;
public ProxyFactory(Object object) {
this.object = object;
}
public Object getInstance(){
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(object, args);
}
});
}
}