一、什么是反射?
反射是Java中的一种底层技术,可以动态(在运行时)的获得一个类的信息。
二、反射的核心
反射的核心是类对象。当代码被编译成.class文件后,每个类的信息都会产生一个.class文件,JVM到classpath中把class文件中所保存的类的信息加载到JVM内部的过程,叫做类加载。
由于JVM虚拟机中只能加载基本数据类型和对象,所以类加载后便成为类对象(将一个类的信息封装到一个对象中),每个类只有唯一的一个类对象。(类对象是类加载的结果,保存一个类的信息)
类的对象和类对象的区别:类的对象:一个学生对象保存一个学生的信息
类对象:保存了学生的类的信息,类对象也是对象,属于Class类
三、获取类对象的方式
(1)使用类的对象获得类对象
(2)使用类获得类对象
(3)使用类的完全限定名获得类对象
Student s1 = new Student("zhangsan", 20);
// 1使用类的对象获得类对象
Class c1 = s1.getClass();
// 2 使用类获得类对象
// Class c4=int.class;//基本类型也有类对象
Class c2 = Student.class;
// 3使用类的完全限定名获得类对象
Class c3 = Class.forName("day1.reflect.Student");
c1 c2 c3都是Student类的类对象,所以c1==c2==c3,为同一个类对象。
四、通过类对象操作类的相关信息
(1)通过类对象(Class类的对象)获取类名、父类名、实现的接口名
// 使用类对象获得类名
System.out.println(c1.getName());
System.out.println(c1.getSimpleName());
// 获得父类
System.out.println(c1.getSuperclass().getName());
// 获得实现的接口
for (Class c : c1.getInterfaces()) {
System.out.println(c.getName());
}
(2)通过类对象获取包名
Package p = c1.getPackage();
System.out.println(p.getName());
(3)通过类对象获取该类的方法
// 获得类的方法
// 获得所有可以调用的方法
for (Method m : c1.getMethods()) {
System.out.println(m.getName());
}
// 获得本类中所有声明的方法
System.out.println("------------------");
for (Method m : c1.getDeclaredMethods()) {
System.out.println(m.getName());
}
①getMethods()方法:获取到的是本类所有的非私有方法和从父类继承过来的所有方法
②getDeclaredMethods()方法:获取的是本类的方法(包括私有方法),而不存在从父类继承来的方法。
获得特定的一个方法
// 获得一个方法,需要提供:方法名,形参类型
Method m1 = c1.getMethod("getName");
System.out.println(m1);
Method m2 = c1.getDeclaredMethod("study",String.class);
System.out.println(m2);
getMethod(方法名,需获取方法的形参列表的每个元素类型所对应的类对象);
(4)获得、设置类的属性和属性值
// 获得类的属性
for (Field f : c1.getDeclaredFields()) {
System.out.println(f);
}
获取私有的属性,需要设置可见性。
// 获得私有的属性name
Field f1 = c1.getDeclaredField("name");
// 把私有的属性设置为可访问
f1.setAccessible(true);
// 访问属性
// 设置属性值,第一个参数是给谁设置属性,第二个参数是要设置的属性值
f1.set(s1, "zhanglaosan");
// 获得属性值,参数为获得谁的属性值
System.out.println(f1.get(s1));
如果需要获取静态属性,则只需要把形参中的对象填为null
(5)获取类的构造方法
// 获得类的构造方法
for (Constructor cc : c1.getConstructors()) {
System.out.println(cc);
}
System.out.println("-----------------");
for (Constructor cc : c1.getDeclaredConstructors()) {
System.out.println(cc);
}
// 获得有两个参数的构造方法:String,int
Constructor con1 = c1.getConstructor(String.class, int.class);
(6)通过类对象进行方法调用
// 使用反射的方式进行方法调用
// 获得getName方法对应的方法对象
Method m1 = c1.getMethod("getName");
// 调用
// 两个参数,第一个参数是调谁的方法,第二个参数是方法调用时传的参数,可变长
// 返回值是方法本调用时的返回值
Object result = m1.invoke(s1);// Object result = s1.getName();
System.out.println(result);
// 获得要调用的方法对象
Method m2 = c1.getMethod("setAge", int.class);
// 使用反射进行方法调用
m2.invoke(s1, 21);
但当需要调用一些私有方法时,需要设置可访问。
Class c1 = s1.getClass();
Method m1 = c1.getDeclaredMethod("study");
// 把私有的方法设置为可访问的
m1.setAccessible(true);
m1.invoke(s1);
(7)通过类对象创建对象
①通过类对象直接调用newInstance()方法获得一个对象,返回值为Object类型的对象,但要求该类必须存在公共无参构造器,不然无法创建。
②通过类对象获取构造方法类的对象,调用构造方法类的newInstance()方法,此方法无论有参无参,无论公共还是私有,都可以进行对象的创建。
// 获得类对象
Class c2 = Teacher.class;
// 获得私有的无参构造方法
Constructor con1 = c2.getDeclaredConstructor();
// 设置可访问性为true
con1.setAccessible(true);
// 调用,创建对象
Object o1 = con1.newInstance();
System.out.println(o1);
五、单例设计模式
单例设计模式:一个类只允许创建一个对象
(1)饿汉式单例:如果不考虑反射等其他技术的影响,可以使用,可靠
abstract class Student{
//饿汉式单例
//abstract类无法创建对象,即保证了即使通过反射这种技术,也无法通过类的构造器去创建对象
//new Student(){}实则为一个子类的匿名内部类对象,因为此abstract类无任何抽象方法
//保证了线程安全且效率高,只是随着类的加载对象就已经被创建(即使不使用)占内存空间。
public static final Student stu = new Student() {};
private Student() {
}
}
(2)懒汉式单例:第一次使用对象时创建,需要考虑并发问题,效率低
class Teacher{
//饿汉式单例
//用volatile关键字修饰保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
//解决了共享数据的内存可见性问题
private static volatile Teacher t;
private Teacher() {
}
//如不用volatile关键字修饰,则必须把synchronized锁加在方法上
//(相当于对本方法体加了synchronized(this),又因为此方法为静态方法,故相当于synchronized(Teacher.class))
public static Object getTeacher() {
//此if主要判断对象是否被创建
if(t == null) {
//t1,t2,t3线程如因争抢资源在此等待,只能有一个线程进入synchronized
synchronized(Teacher.class){
//主要是判断在并发时是否创建了对象
if(t == null) {
t = new Teacher();
}
}
}
return t;
}
}