1.了解反射
什么是反射,反射有什么作用
1.在Java中,反射是一种机制,允许程序在运行时动态地获取、使用和修改类的信息。通过反射,可以在编译时不知道类的具体信息的情况下,操作和查看类的属性、方法和构造函数等。
2.反射有以下几个主要的作用:
- 动态加载类:使用反射可以在运行时动态地加载需要使用的类,而不需要在编译时将类写死在代码中。这样可以实现更加灵活的代码结构和功能。
- 获取类的信息:通过反射,可以获取类的名称、父类、接口、方法、字段等详细信息。这对于编写通用框架、调试工具和JavaBean的序列化等场景非常有用。
- 创建对象和执行方法:使用反射可以动态地创建对象,即使在编译时无法确定具体的类。同时,还可以在运行时调用任意对象的方法,甚至是私有方法。
- 修改私有字段:反射允许程序访问和修改类的私有字段的值。这在某些特定的应用场景中可能是必要的,但需要小心使用,遵循权限和安全性的原则。
需要注意的是,反射是一种强大而复杂的机制,在普通的业务代码中并不常用。滥用反射可能会导致性能下降,代码可读性降低,并增加出错的可能性。因此,在使用反射时,需要谨慎权衡利弊,并确保了解其使用方式和限制。
面试题:什么是反射?
- 所谓反射,是java在运行时进行自我观察的能力,通过class、constructor、field、method四个方法获取一个类的各个组成部分。
- 在Java运行时环境中,对任意一个类,可以知道类有哪些属性和方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于反射机制。
2.Class类的三种实例化方法
1.通过实例化对象调用getclass()方法实现
2.通过直接调用类名的.Class
3.通过调用Class.forname("包+类名") throws ClassNoFoundException
实例化案例:
packageExample1701; classMember{ privateStringname; privateintage; publicvoidsetName(Stringname) { this.name=name; } publicvoidsetAge(intage) { this.age=age; } publicStringgetName() { returnname; } publicintgetAge() { returnage; } } publicclassjavaDemo { publicstaticvoidmain(String[] args)throwsException { Membermem=newMember(); // 通过实例化getClass()方法得到类Class<?>claszz1=mem.getClass(); System.out.println(claszz1); // 通过直接获取类名实例化ClassClass<?>claszz2=Member.class; System.out.println(claszz2); // 通过调用Class里的forname方法实现实例化Class<?>claszz3=Class.forName("Example1701.Member"); System.out.println(claszz3); } }
3.反射机制与对象实例化
通过反射机制进行对象实例化就能替代new的关键词的使用
案例代码:实例化对象
package Example1702; class Member{ Member(){ System.out.println("自动调用构造函数"); } @Override public String toString() { return "实现Member对象的创建"; } } public class javaDemo { public static void main(String[] args)throws Exception { Class<?> claszz = Class.forName("Example1702.Member"); // 创建实例化对象并用Object类进行接收 Object obj = claszz.getDeclaredConstructor().newInstance(); System.out.println(obj); // 对比区别 Object obj2 = claszz.newInstance(); System.out.println(obj2); } }
问: 可以发现Object接收两个对象输出后都是一样的,那么
Object obj = claszz.getDeclaredConstructor().newInstance();与
Object obj2 = claszz.newInstance();有什么区别吗
答:
在Java 9及之前,我们可以使用
Class.newInstance()
方法来创建一个类的实例对象。这个方法是通过调用类的默认构造函数来创建对象的。例如,claszz.newInstance()
会调用Member
类的默认构造函数创建对象。然而,从Java 9开始,
Class.newInstance()
方法被标记为过时了,因为它在处理异常和对私有构造函数的访问上存在一些限制。取而代之的是,推荐使用getDeclaredConstructor().newInstance()
方法来创建对象。这个方法更为灵活,可以处理带参数的构造函数并且可以处理私有构造函数。所以,
Object obj = claszz.getDeclaredConstructor().newInstance()
与Object obj2 = claszz.newInstance()
的区别在于:
- 创建实例对象的方式不同:
claszz.getDeclaredConstructor().newInstance()
可以处理带参数的构造函数,而claszz.newInstance()
只能调用无参构造函数。- 访问权限不同:
claszz.getDeclaredConstructor().newInstance()
可以处理私有构造函数,而claszz.newInstance()
无法处理私有构造函数。- 兼容性不同:
claszz.getDeclaredConstructor().newInstance()
是在Java 9及之后引入的,而claszz.newInstance()
是在Java 8及之前推荐使用的方法。因此,在现代的Java版本中,建议使用
getDeclaredConstructor().newInstance()
方法来创建类的实例对象,它更加通用和灵活。
4.反射与单例设计模式
单例设计模式都不陌生,在以前的文章中有提到过,其实现的方法就是通过私有化构造方法实现外部无法实例化对象,并且在类的内部就定义一个唯一的对象,最后通过函数调用的形式将对象输出出去,并且设计模式有两种一种饿汉模式,一种懒汉模式,分别是饿汉:一开始就定义一个唯一对象,懒汉模式:当需要用到的时候才进行创建对象。
那么懒汉模式下就有可能出现问题,什么问题呢?关于多线程问题,懒汉是通过if判断是否对象为空,但是多线程可能出现并发问题,大家都同时执行了if判断语句发现对象为空则大家都一起创建一个对象,造成对象不唯一,不符合单例设计模式
那么如何解决呢?通过反射就能实现多线程下单例设计模式的唯一性
案例代码:
package Example1703; class Only { // 实现单例化设计模式 private Only(){ } private static volatile Only onlyOne = null; // 创建对象或者获取对象 public static Only getOnly(){ if (onlyOne == null){ synchronized (Only.class){ if (onlyOne == null){ try { // 通过反射创建对象 onlyOne = Only.class.getDeclaredConstructor().newInstance(); }catch (Exception e){ e.printStackTrace(); } return onlyOne; } } } return onlyOne; } public void print(){ System.out.println("Hello"); } } public class javaDemo { public static void main(String[] args) { // 创建多线程 for (int i = 0;i<3;i++){ new Thread(()->{ Only.getOnly().print(); System.out.println(Thread.currentThread().getName()); },i+"的线程").start(); } } }
问1:synchronized (Only.class)的作用,为什么要写Only.class而不是this?
synchronized (Only.class)
使用类级别的锁来实现同步。在Java中,每个类都有一个对应的Class对象,可以通过类名后面加上
.class
来获取该类的Class对象。而this
关键字代表当前实例对象,它是指向当前对象的引用。在单例模式中,我们希望通过
synchronized
来保证只有一个线程能够创建实例对象。使用synchronized (Only.class)
,即使用类级别的锁,意味着多个线程在访问这段代码时会竞争同一个锁,即类级别的锁。而如果使用
synchronized (this)
,则表示使用实例级别的锁。在这个例子中,我们不希望通过实例级别的锁来控制多个线程对实例的访问,因为还没有创建实例对象,所以也不存在实例对象来获得锁。因此,为了确保在多线程环境下只创建一个实例对象,需要使用类级别的锁,即
synchronized (Only.class)
,而不是实例级别的锁。
问2:为什么要进行两次判断是否为空?
答: 问2:进行两次判断是否为空的目的是为了提高代码执行的效率和性能。在双重检查锁定机制中,第一次判断
onlyOne
是否为空是为了避免重复进行同步块的加锁和解锁操作,从而提高了代码的执行效率。如果只有一次判断是否为空,那么每次调用getOnly()
方法时都会进入同步块,性能开销会增加。同时,第二次判断是为了在多个线程同时通过了第一次判断后,只有第一个获得锁的线程才会进入同步块创建实例。其他线程在获取到锁后,再次判断
onlyOne
是否为空,如果已经不为空,就直接返回已经创建好的实例,避免了重复创建对象。如果只有一次判断是否为空,那么即使已经有线程创建了实例,其他线程也会通过第一次判断,进入同步块再次创建实例,这样会造成多个实例的创建,不符合单例模式的要求。
因此,为了确保只有一个线程创建实例,并提高代码的执行效率和性能,需要进行两次判断是否为空。
5.通过反射获取类结构的信息
常用通过反射获取类结构的方法有这几个
方法名 |
作用 |
getName() | 获取类的完整名称(包括包路径) |
getPackage() | 获取类所在的包信息 |
getSuperclass() | 获取类的父类 |
getInterfaces() | 获取类实现的接口列表 |
案例代码:
packageExample1704; interfaceInterface1 {} interfaceInterface2 {} abstractclassFather {} classTestextendsFatherimplementsInterface1, Interface2 { privateStringname; privateintage; Test(Stringname, intage) { this.age=age; this.name=name; } publicvoidprint() { System.out.println("输出信息Test类"); } } publicclassjavaDemo { publicstaticvoidmain(String[] args) { // 获取指定类的class对象Class<?>clazz=Test.class; // 获取指定类的包的名称Packagepag=clazz.getPackage(); System.out.println(pag.getName()); // 获取类的父类的信息Class<?>superClass=clazz.getSuperclass(); System.out.println(superClass.getName()); // 获取类的接口的信息Class<?>[] interfaces=clazz.getInterfaces(); for (Class<?>temp : interfaces) { System.out.println(temp.getName()); } } }
编辑