目录描述
- 1.反射机制概念
- 2.获取Class的三种方式
- 2.1 第一种 Class.forName()
- 2.2 第二种 getClass()
- 2.3 第三种 .class
- 3.通过反射实例化对象
- 4.通过读属性文件实例化对象
- 5.只让静态代码块执行:forName()
- 6.获取类路径下文件的绝对路径
- 7.以流的形式返回绝对路径
- 8.资源绑定器 ResourceBundle
- 9.JDK中自带的类加载器概述
- 10.双亲委派机制
1.反射机制概念
- 作用:通过Java语言中的反射机制可以操作字节码文件,优点类似于(可以读和修改字节码文件);通过反射机制可以操作代码片段(class文件)
- 反射机制的相关类在java.lang.reflect.*;包下
- 反射机制相关重要类
反射机制相关重要类 | 作用 |
java.lang.Class | 代表整个字节码,代码一个类型,代表整个类 |
java.lang.reflect.Method | 代表字节码中的方法字节码 ,代表类中的方法 |
java.lang.reflect.Constructor | 代表字节码中的构造方法字节码 ,代表类中的构造方法 |
java.lang.reflect.Field | 代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量) |
举例:
//java.lang.Class:
public class User{
//Field
int no;
//Constructor
public User(){
}
public User(int no){
this.no = no;
}
//Method
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
}
2.获取Class的三种方式
2.1 第一种 Class.forName()
Class c = Class.forName(“完整类名带包名”);
- 静态方法
- 方法的参数是一个字符串
- 字符串需要的是一个完整类名
- 完整类名必须带有包名,java.lang包也不能省略
package Reflect;
public class ReflectTest01 {
public static void main(String[] args) {
try {
Class c1 = Class.forName("java.lang.String");//c1代表String.class文件,或者说c1代表String类型
Class c2 = Class.forName("java.util.Date");//c2代表Date类型
Class c3 = Class.forName("java.lang.Integer");//c3代表Integer类型
Class c4 = Class.forName("java.lang.System");//c4代表System类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.2 第二种 getClass()
Class c = 对象.getClass();
package Reflect;
public class ReflectTest01 {
public static void main(String[] args) {
Class c1 = null;
try {
c1 = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//java中任何一个对象都有一个方法:getClass()
String s = "abc";
Class x = s.getClass();//x代表String.class字节码文件,s代表String类型
System.out.println(c1 == x);//true (== 判断的是对象的内存地址)
}
}
内存演示图:
2.3 第三种 .class
Java语言中任何一种类型,包括基本数据类型,它都有.class属性
Class c = 任何类型.class;
package Reflect;
import java.util.Date;
public class ReflectTest01 {
public static void main(String[] args) {
String s = "abc";
Class x = s.getClass();
Class z = String.class;//z代表String类型
//Class k = Date.class;//k代表Date类型
//Class f = int.class;//f代表int类型
//Class e = double.class;//e代表double类型
System.out.println(x == z);// true
}
}
3.通过反射实例化对象
问题:获取到Class,能干什么?
答:通过Class的newInstance()方法来实例化对象
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造存在才可以
用户类:
package bean;
public class User {
public User(){
System.out.println("无参数构造方法!");
}
//定义了有参数的构造方法,也要保证无参数构造方法存在
public User(String s){
}
}
测试类:
package Reflect;
import bean.User;
public class ReflectTest02 {
public static void main(String[] args) {
//这是不使用反射机制,创建对象
User user = new User();
System.out.println(user);//输出为:无参数构造方法!bean.User@119d7047
//下面这段代码是以反射机制的方式创建对象
try {
//通过反射机制,获取Class,通过Class来实例化对象
Class c = Class.forName("bean.User");//c代表User类型
//newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建
//重点:newInstance()调用的是无参构造,必须保证无参构造是存在的!
Object obj = c.newInstance();
System.out.println(obj);//输出为:无参数构造方法!bean.User@119d7047
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
4.通过读属性文件实例化对象
- 验证反射机制的灵活性
- Java代码写一遍,在不改变Java源代码的基础之上,可以做到不同对象的实例化,非常之灵活【符合OCP开闭原则:对扩展开发,对修改关闭】
- 后期需要学习高级框架,而在工作过程中,也都是使用高级框架,包括:ssh【Spring,Struts,Hibernate】,ssm【Spring,SpringMVC,MyBatis】
- 高级框架底层实现原理:都采用了反射机制,所以反射机制还是重要的,学会了反射机制有利于理解剖析框架底层的源代码
属性配置文件:
className=bean.User
测试代码类:
package Reflect;
import bean.User;
import java.io.FileReader;
import java.util.Properties;
public class ReflectTest03 {
public static void main(String[] args) throws Exception{
//这种方式代码就写死了,只能创建一个User类型的对象
User user = new User();
//以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象
//通过IO流读取classinfo.properties文件
FileReader reader = new FileReader("reflect/classinfo.properties");
//创建属性类对象Map
Properties pro = new Properties();//key value 都是String
//加载
pro.load(reader);
//关闭流
reader.close();
//通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
//通过反射机制实例化对象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
运行结果:
5.只让静态代码块执行:forName()
- 如果只希望一个类的静态代码块执行,其它代码一律不执行,可以使用:Class.forName(“完整类名”);
- 这个方法的执行会导致类加载,类加载时,静态代码块执行
- JDBC技术中需要使用
package Reflect;
public class ReflectTest04 {
public static void main(String[] args) {
try {
//Class.forName()这个方法的执行会导致:类加载
Class.forName("Reflect.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
//静态代码块在类加载时执行,并且只执行一次
static {
System.out.println("MyClass类的静态代码块执行了!");
}
}
运行结果:
6.获取类路径下文件的绝对路径
以下讲解的这种方式是通用的,但前提是:文件需要在类路径下,才能用这种方式
配置文件:
className=bean.User
测试类代码:
//解析:
//Thread.currentThread() 当前线程对象
//getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象
//getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
String path = Thread.currentThread().getContextClassLoader()
.getResource("默认从类的根路径下作为起点").getPath();
package Reflect;
import java.io.FileReader;
public class AboutPath {
public static void main(String[] args) throws Exception{
//这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根
//这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了
//FileReader reader = new FileReader("reflect/classinfo2.properties");
//接下来说一种比较通用的一种路径,即使代码换位置了,但这样编写仍然时通用的
//注意:使用一下通用方式的前提:这个文件必须在类路径下
//类路径:放置在src下的都是类路径【src是类的根路径】
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo2.properties").getPath();//这种方式获取文件绝对路径是通用的
//采用以上代码可以拿到一个文件的绝对路径
System.out.println(path);
}
}
运行结果:
7.以流的形式返回绝对路径
当不以流的形式返回:
package Reflect;
import java.io.FileReader;
import java.util.Properties;
public class IoPropertiesTest {
public static void main(String[] args) throws Exception {
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo2.properties").getPath();
FileReader reader = new FileReader(path);
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String className = pro.getProperty("className");
System.out.println(className);//输出为:bean.User
}
}
当以流的形式返回:
package Reflect;
import java.io.InputStream;
import java.util.Properties;
public class IoPropertiesTest {
public static void main(String[] args) throws Exception {
InputStream reader = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("classinfo2.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String className = pro.getProperty("className");
System.out.println(className);//输出为:bean.User
}
}
8.资源绑定器 ResourceBundle
- java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
- 使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下
package Reflect;
import java.util.ResourceBundle;
public class ResourceBundleTest {
public static void main(String[] args) {
//资源绑定器,只能绑定xxx.properties文件,并且这个文件必须在类路径下
//文件扩展名也必须是properties
//并且在写路径的时候,路径后面的扩展名不能写
ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");
String className = bundle.getString("className");
System.out.println(className);//输出为:bean.User
}
}
9.JDK中自带的类加载器概述
- 类加载器:专门负责加载类的命令/工具 ClassLoader
- JDK中自带了3个类加载器:启动类加载器、扩展类加载器、应用类加载器
- 举例:假设有这样一段代码:String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中
通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载。
加载步骤:
1.首先通过"启动类加载器"加载
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库
2.如果通过"启动类加载器"加载不到的时候,会通过"扩展类加载器"加载
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\*.jar
3.如果"扩展类加载器"没有加载到,那么会通过"应用类加载器"加载
注意:应用类加载器专门加载:classpath中的类
10.双亲委派机制
java中为了保证类加载的安全,使用了双亲委派机制
- 优先从启动类加载器中加载,这个称为“父”
- “父”无法加载到,再从扩展类加载器中加载,这个称为“母”
- 双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止