Java笔记(23)反射

这篇Java笔记详细介绍了反射机制,从类加载器开始,包括类加载的步骤,然后讲解了反射的概念,如何获取Class对象,以及如何通过Class对象访问构造方法、成员变量和方法。此外,还提到了反射如何越过泛型检查、动态代理的应用,以及枚举的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java笔记(23)反射

1.类加载器

当我们写出一个java类的时候,这个扩展名为".java"的文件经过编译器编译成一个扩展名为".class"的文件,class文件中存储的是经过编译后的可由java虚拟机执行的虚拟机指令。当我们需要使用某个类时,虚拟机会经历下面几个步骤:

  1. 加载这个类的class文件,并在内存中创建这个class文件的Class对象
  2. 验证这个文件中的信息和数据是否符合虚拟机需求
  3. 为类成员初始化(static修饰的字段)这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
  4. 解析,主要将常量池中的符号引用替换为直接引用的过程。
  5. 初始化:类加载最后阶段,若该类具有超类(父类),则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段进行赋值,成员变量也将在这个阶段被初始化为默认值)。

上面是类加载的几个步骤,而类加载器就是用来完成这几个步骤的,虚拟机提供了3种类加载器,来分别加载不同类型的类;

  • (Bootstrap)根类加载器:主要加载的是JVM自身需要的类,这个类加载器使用C++语言实现的,是虚拟机自身的一部分,它负责将java安装路径文件lib下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
  • 扩展(Extension)类加载器:扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
  • 系统(System)类加载器:也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

2.反射概述

当我们知道一个类是由类加载器加载到内存中的,并且创建了一个Class对象在内存中,那我们怎么使用这个Class对象呢?对此,Java就提供了一个反射机制,它可以在运行状态中,对于任意一个类都可以知道这个类的所有属性和方法,对于任意一个对象,都能调用它任意的方法或属性,包括各种权限修饰符所限制的(例如private)
可以看出反射机制的功能非常强大,它只要获取到一个类的字节码文件对象,就可以剖析获取到该类的所有信息,而想要使用这种机制,我们就先要得到class文件对象,其实也就是得到Class类的对象,所有字节码文件对象都是Class的对象;

3.获取class文件对象的方式

1.通过Object类中的getClass()方法

//先定义一个Person类做实验对象
public class Person {
	private String name;
	public int age;
	String address;
	
	public Person(){}
	
	private Pereson(String name,int age){
		this.name = name;
		this.age = age;
	}
	public Person(String name,int age,String address){
		this.name = name;
		this.age = age;
		this.address = address;
	}
	public void show(){
		System.out.println("show" + name);
	}
	private void method(){
		System.out.println("method" + name);
	}
}
//获取Person类的字节码文件对象
public class Demo {
	public static void main(String[] args) throws ClassNotFoundException {
		Person p = new Person();
		Person p2 = new Person();
		Class c1 = p.getClass();
		Class c2 = p2.getClass();
		//注意c1==c2,因为Class对象基于其class文件的,Person对象的字节码文件只有一个,字节码文件对象当然就相同
	}
}
2.类名.class的方式获取class对象
		Class c3 = Person.class;
3.通过Class类的forName静态方法获取Class对象
		Class c4 = Class.forName("类的全路径"); //带包名的路径,例如cn.test.Person

这三种获取Class文件对象的方式在开发中比较常用的是第三种方式,因为其需求的是一个类的路径的字符串,在反射中是比较灵活的;

4.通过Class对象获取其构造方法

有了一个class文件的Class对象后,我们就可以获得其的构造方法信息了,下面是案例:

//还是使用上面例子中的Person对象
	public static void main(String[] args){
		//获取Class对象,泛型里如果不知道获取的类是什么类型就使用?
		Class<?> c = Class.forName("cn.test.Person");
		//获得所有公共构造方法,获取所有构造方法请使用getDeclaredConstructors()方法
		Constructor[] cons = c.getConstructors();
		for(Constructor con : cons){
			System.out.println(con);
		}
		//获取单个带参构造方法
		Constructor con = c.getConstructor(String.class,int.class,String.class);
		//通过带参构造方法对象创建对象
		Object obj = con.newInstance("王一",20,"北京");
	}
		//获取私有构造方法
		Constructor con = c.getDeclaredConstructor(String.class,int.class);
		//由于访问的是私有,要先通过一个方法取消Java语言访问检查
		con.setAccessible(true);
		Object obj = con.newInstance("李四",20);

5.通过Class对象获取成员变量和方法

//依旧使用上面那个例子
		//获取class文件对象
		Class<?> c = Class.forName("cn.protest.Person");
		
		Constructor<?> con = c.getConstructor();	
		//通过无参构造创建对象
		Object obj = con.newInstance();
		
		//获取单个成员变量对象
		Field nameField = c.getDeclaredField("name");
		//取消访问检查
		nameField.setAccessible(true);
		
		//将指定对象上的成员变量赋值
		nameField.set(obj, "王二");
		System.out.println(obj);

获取方法案例

//获取字节码文件对象
		Class<?> c = Class.forName("cn.protest.Person");
		
//		获取所有方法,获取的是自己的和父类的公共方法
//		Method[] methods = c.getMethods();
//		
//		获取自己的所有方法
//		Method[] methods2 = c.getDeclaredMethods();
		
		Constructor<?> con = c.getConstructor();
		Object obj = con.newInstance();
		
		//获取单个方法的方法有两个参数,第一个是方法名称,第二个是一个可变参数,接收的是方法的参数
		Method m1 = c.getMethod("show");	//这里show方法没有参数,后面的参数就不传了
		
		//调用obj对象的m1方法
		m1.invoke(obj);	//有两个参数,后面的参数是一个可变参数,传递的是方法的参数
		如果要访问私有方法,要先取消检查

6.通过反射越过泛型检查

		ArrayList<Integer> list = new ArrayList<Integer>();
		
		//获取集合的Class对象
		Class<?> c = list.getClass();
		
		//获取该对象的add方法,泛型默认类型是Object类型
		Method m = c.getMethod("add", Object.class);
		
		m.invoke(list, "hello");
		
		System.out.println(list);
//可以看到通过该方式越过了集合的泛型检查

7.动态代理

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

案例:

//用户操作接口
public interface UserDao {
	//有两个方法,注册和登录
	public abstract void register();
	public abstract void login();
}

public class UserDaoImpl implements UserDao {

	@Override
	public void register() {
		System.out.println("注册功能");
	}

	@Override
	public void login() {
		System.out.println("登录功能");
	}

}

//代理实例的处理类,负责代理对象的具体执行
public class MyInvocationHandle implements InvocationHandler {
	//目标对象
	private Object target;
		
	public MyInvocationHandle(Object target) {
		super();
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("权限校验");
		Object result = method.invoke(target, args);
		System.out.println("日志记录");
		return result;
	}

}
//测试
public class Test {
	public static void main(String[] args) {
		//创建用户操作对象
		UserDao ud = new UserDaoImpl();
		
		//创建代理对象
		MyInvocationHandle handle = new MyInvocationHandle(ud);
		//这个方法返回的是一个代理后的UserDao对象
		UserDao proxy = (UserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(), handle);
		
		proxy.login();
		//通过代理对象再不修改原有类的源码上增强了对象的方法功能
	}
}

8.枚举

枚举类似于单例模式中的单例类,不可以在外部直接创建对象,但其内部提供了一些自己已经造好的对象,供外部访问;与单例不同的是,枚举可以给出多个对象给外界,但必须是有限数量的,枚举出来的对象一般是用静态成员变量的方式存在的。

public class MeiJu {
	private String name;
	//创建枚举对象
	public static final MeiJu LEFT = new MeiJu("left");
	public static final MeiJu RIGHT = new MeiJu("right");
	
	//私有构造方法,使得外界不能随意创建对象
	private MeiJu(String name){
		this.name = name;
	}
}
//第二种实现枚举类的方式
public enum MeiJu {
	LEFT,RIGHT;

	//私有构造方法,使得外界不能随意创建对象
	private MeiJu(String name){
		this.name = name;
	}
}
//实现原理与第一个相同,只不过编译器自动帮我们做了枚举的声明
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值