用Class.forNamw通过类名获取字节码文件(Class类型的对象代表某一个类的字节码文件),然后通过newInstance创建对象,代替new创建对象。Class.forname()方法最早我们在使用jdbc驱动开发JSP程序的时候经常用,是不是很熟悉。
//目标类,没有实现set,get方法
public class Target {
public String name;
private int age;//私有属性
public void doSome(){
System.out.println("doSome");
}
}
//测试类
public class Test66 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class targetClass=Class.forName("demo.Target");//完整包名
Target target= (Target) targetClass.newInstance();//调用无参构造方法构造的对象
target.doSome();
}
结果:
可以看到不使用Target target = new Target(); 也可以实现对象创建。这样的好处是,让程序更加灵活,在不改变Java代码的情况下,可以通过读属性文件创建对象,做到不同对象的实例化(比如只需要修改properties文件中的value),其实就是遵循了OCP(开闭原则:对扩展开发,对修改关闭。)
反射机制的其他重要方法getDeclaredField获取属性、getDeclaredMethod获取方法、invoke进行方法调用
注意:牢记这几个方法,在日常开发中,一看到这几个方法就想到是反射,那么学习反射的目的就达到了(阅读Spring源码等)
//测试类
public class Test66 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
Class targetClass=Class.forName("demo.Target");//完整包名
Target target= (Target) targetClass.newInstance();//调用无参构造方法构造的对象
//反射机制获取类的公共属性
Field nameFiled = targetClass.getDeclaredField("name");
nameFiled.set(target,"反射");//给target对象的no属性赋值,语法有点诡异
String name = (String) nameFiled.get(target);//获取属性值
System.out.println("target对象的name属性的值为:"+name);
//反射机制获取累的私有属性
Field ageFiled = targetClass.getDeclaredField("age");
//age是Target类的私有属性,不能直接访问。
ageFiled.setAccessible(true);//打破封装,这也是反射机制的缺点,危险
ageFiled.set(target,18);
int age = (int) ageFiled.get(target);
System.out.println("target对象的age属性的值为:"+age);
//getDeclaredMethod获取类的方法
Method doSome = targetClass.getDeclaredMethod("doSome");
doSome.invoke(target);//获取到方法后再用invoke调用
}
对java类的私有属性和封装的理解,请参考Java类的私有属性和封装的理解_source code August的博客-CSDN博客
类对象类对象(Class类型的对象代表某一个类的字节码文件)
类对象的三种不同获取方式:
public class Test2222 {
public static void main(String[] args) throws ClassNotFoundException {
Class targetClass=Class.forName("demo.Target");//通过类名获取类对象
Class targetClass1 = Target.class;
Class targetClass2 = new Target().getClass();
System.out.println(targetClass==targetClass1);//true
System.out.println(targetClass==targetClass2);//true
}
}
补充:
Class.forname()方法的执行会导致:类加载,而JVM加载类时会执行静态代码块,所以如果只希望一个类的静态代码块执行,其他代码不执行,就可以用Class.forname()方法。实例代码如下:
//目标类
public class Target {
public String name;
private int age;
public static void doOther(){
System.out.println("静态方法");
}
public static int itemCapacity=8; //声明的时候 初始化
static{
//静态代码块中的属性
itemCapacity = 6;//静态初始化块 初始化
System.out.println("静态代码块执行");
}
}
//测试类
public class Test2222 {
public static void main(String[] args) throws ClassNotFoundException {
Class targetClass=Class.forName("demo.Target");//通过类名获取类对象
// System.out.println(Target.itemCapacity);//观察静态代码块执行后属性的值
}
运行结果:
所以只执行Class.forname()方法,静态代码块会执行。同时补充一点,实例化对象是在类加载之后的,所以如果你执行了new,那么类加载一定做了,如果类有静态代码块,那也一定执行了。还需注意,我们在属性itemCapacity定义的时候赋值为8了,但是在类加载之后(静态代码块执行之后),结果是6,不信可以试一试。
类加载器ClassLoaderClassLoader是专门负责加载类的命令/工具,代码在开始执行之前,会将所需要的类全部加载到JVM中。(简单理解)
类加载器的作用有类加载和类缓存,.java文件在经过编译之后会变成.class字节码文件,类加载器将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。这是类加载的作用。但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制(gc)可以回收这些Class对象,这是类缓存的作用。(复杂理解,来自网络)
JDK中有三个类加载器:启动类加载器,扩展类加载器,应用类加载器
假设有一段代码:String s="abc";
通过类加载器加载,看到以上代码,类加载器会找String.class文件,找到就加载
首先,通过”启动类加载器“专门加载:C:Program FilesJavajdk1.8jrelibrt.jar
注意rt.jar中都是JDK最核心的类库
如果通过”启动类加载器“找不到的时候,会通过”扩展类加载器“加载,专门加载:C:Program FilesJavajdk1.8jrelibext.jar
如果通过“扩展类加载器”加载不到的时候,会通过“应用类加载器”加载,专门加载classpath中的类。
双亲委派机制为了面试了解一下吧。双亲委派机制就是优先加载前两个类加载器,防止黑客植入后门程序,比如自己写个String,自己写的是在应用加载器加载,有双亲委派机制,就会调用启动类加载器优先加载JDK自己的String。



