让我敬而远之的Java反射机制原来没有那么难

news/2024/5/19 3:31:07 标签: java, 开发语言, 后端, 类加载, 反射

文章目录

前言

反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类得内部信息,并能够直接操作任意对象得内部属性和方法(包括私有)。

加载完类之后,在方法区中就会产生一个Class类型对象,这个对象就包含了完整得类得结构信息。我们可以通过这个对象看到类得结构。这个对象就像就像一面镜子,透过这个镜子可以看到类得结构,所以,我们形象得称之为:反射

所以我们首先简单的讲一下类加载,然后在讲反射机制。

类加载

类加载步骤

分为三步:加载、链接、初始化

加载Loading
作用:在内存中生成一个代表这个类的Class对象,作为方法区中这和类的各种数据的访问入口。

什么时候加载?
- new
- Class.forName(“包名.类名”)

怎么加载?

  • Bootstrap(根加载器,加载$JAVA_HOME/jre/lib/rt.jar包内的class文件,包含java运行环境所需的基础类)
  • ExtClass(加载$JAVA_HOME/jre/lib/ext/.jar目录下的class文件)
  • AppClass(用于`加载当前应用的classpath的所有类)

链接 Linking
该过程分三个阶段:验证、准备、解析

  • 验证:验证阶段用于确保加载的Class文件的字节流包含的信息是否符合虚拟机要求,保证其合法性。
  • 准备阶段: 为类变量(静态变量)分配内存并跟根据对象类型赋对应的默认值。
  • 解析阶段:用于将符号引用转换为直接引用。

初始化 initialization

执行类的构造方法的过程

反射技术

运行时动态访问对象的属性和方法。

Class对象

上面我们简单的了解了以下类加载机制。
那我们如何得到Class对象呢?

通常我们又三种方法:

  • 第一种,使用 Class.forName 静态方法。 前提:已明确类的全路径名。

  • 第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class

  • 第三种,使用类对象的 getClass() 方法。 适合有对象示例的情况下

代码:

java">package com.dyit.clazz;

public class GetClass {
	
	//定义一个内部类
	static class A {
		
		public A() {
			
		}	
		public void print() {
			System.out.println("内部类");
		}
	}
	
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		
		//得到class对象
		System.out.println(A.class);
		A b = new A();
		System.out.println(b.getClass());
		System.out.println(Class.forName("com.dyit.clazz.GetClass$A"));
	}

}

结果截图:
在这里插入图片描述
那我们拿到Class对象可以干什么呢?

之前我们类的实例化有三种方式

  • new 关键字
  • 克隆
  • 反序列化

现在我们可以通过类的Class对象创建类的实例

通过Class.newInstance()可以得到类的实例;
可以通过Class.getSimpleName()得到类的名称

代码如下:

java">package com.dyit.clazz;

public class ClassDemo {
	
	//定义一个内部类
	static class A {
		
		public A() {
			
		}	
		public void print() {
			System.out.println("内部类");
		}
	}
	
	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		//得到Class对象
		Class<A> a = A.class;
		//得到类的名称
		System.out.println(a.getName());
		System.out.println(a.getSimpleName());
		//创建实例
		A b = a.newInstance();
		//调用方法
		 b.print();
	}

}

结果:
在这里插入图片描述

Field类

描述类中的属性,(数值)域

首先通过Class对象得到所有的属性
Class.getDeclaredFields()

通过设置field.setAccessible(true)可以访问私有属性.
field.get(c) 得到c关于这和属性的值
field.getName()得到属性的名称
field.getType()得到属性的类型
field.getType().getSimpleName()得到属性的简单名称

可以通过set方法设置对象属性的值:field.set(c, 999.99)

代码:

java">package com.dyit.field;

import java.lang.reflect.Field;

public class FieldDemo {
	
	static class Car {
		private  String brand;
		private String color;
		private double price;
		
		public void setBrand(String brand) {
			this.brand = brand;
		}
		
		public void setColor(String color) {
			this.color = color;
		}
		
		public void setPrice(double price) {
			this.price = price;
		}

		@Override
		public String toString() {
			return "Car [brand=" + brand + ", color=" + color + ", price=" + price + "]";
		}
		
		
		
	}
	
	public static void main(String[] args) throws Exception, IllegalAccessException {
		
		Car c = new Car();
		c.setBrand("bwm");
		c.setColor("白色");
		c.setPrice(666.66);
		
		System.out.println(c);
		
		//得到类对象
		Class<Car> clazz = Car.class;
		//得到全部的属性
		Field[] fields = clazz.getDeclaredFields();
		
		for (Field field : fields) {
			field.setAccessible(true);//设置为可以访问
			System.out.println(field.get(c));//得到对象c的属性
			System.out.println(field.getName());//得到属性的名称
			System.out.println(field.getType());//类型
			System.out.println(field.getType().getSimpleName());
			
			if (field.getType().getSimpleName().equals("double")) {
				field.set(c, 999.99);
			}
		}
		
		System.out.println(c);
		
	}
}

结果截图:
在这里插入图片描述

Method类

通过Class对象得到关于类的方法
clazz.getDeclaredMethods()
通过getMrthod方法得到类的方法,参数为方法名和方法的参数类型
比如: clazz.getMethod("run");
clazz.getMethod("setColor", String.class);

然后通过invoke()方法调用方法 参数实例。

代码:

java">package com.dyit.method;

import java.lang.reflect.Method;

public class MethodDemo {
	static class Car {
		private String brand;
		private String color;
		private double price;

		public void setBrand(String brand) {
			this.brand = brand;
		}

		public void setColor(String color) {
			this.color = color;
		}

		public void setPrice(double price) {
			this.price = price;
		}

		@Override
		public String toString() {
			return "Car [brand=" + brand + ", color=" + color + ", price=" + price + "]";
		}

		public String getBrand() {
			return brand;
		}

		public String getColor() {
			return color;
		}

		public double getPrice() {
			return price;
		}

		public void run() {
			System.out.println("跑起来的");
		}

	}

	public static void main(String[] args) throws Exception, SecurityException {
		Car c = new Car();
		c.setBrand("bwm");
		c.setColor("red");
		c.setPrice(666.666);

		System.out.println(c);

		Class<Car> clazz = Car.class;

		Method[] Methods = clazz.getDeclaredMethods();

		Method method = clazz.getMethod("run");
		method.invoke(c);

		Method setMethod = clazz.getMethod("setColor", String.class);
		setMethod.invoke(c, "黑色");
		System.out.println(c);

		
		Method getMethod = clazz.getMethod("getColor");
		Object value = getMethod.invoke(c);
		System.out.println(value);

		
		Method setMethod1 = clazz.getMethod("setPrice", double.class);
		setMethod1.invoke(c, 99.6666);
		System.out.println(c);

	}

}

运行结果:
在这里插入图片描述

关于双亲委派机制

所谓的 双亲委派机制就是:当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此。只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
实例:
在src/main/java目录下新建java.lang包,然后在该包下新建一个String类
在这里插入图片描述
代码如下:

java">package java.lang; 

public class String { 
	public static void main(String[] args) { 
			System.out.println("helo"); 
	} 
}

程序输出结果:

错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public staticvoid main(String[] args) 否则 JavaFX
应用程序类必须扩展javafx.application.Application

什么意思呢?明明在类中我们有main方法为什么说找不到呢?

所以上面的例子中,AppClassLoader委派给它的父类ExtClassLoader去加载,ExtClassLoader又委托给它的父类BootstrapClassLoader去加载。BootstrapClassLoader从它的加载路径$JAVA_HOME/jre/lib/rt.jar 下找到了 java.lang.String 类,即rt.jar包下的String类,而该类里并没有main方法,所以便抛出了如上异常。

优点:

采用双亲委派的好处是:不管那个加载器加载这个类,最终都是委托给顶层的启动类(根)加载器进行加载。这样就保证了使用不同的类加载器最终得到的都是同一个string对象,所以我们自定义的java类并不会去污染jdk自带的类,这种保护机制也叫做沙箱安全机制

关于更多关于JVM的知识请看:JVM详解


http://www.niftyadmin.cn/n/1440859.html

相关文章

分库分表使用场景及设计方式

一. 分表 场景&#xff1a;对于大型的互联网应用来说&#xff0c;数据库单表的记录行数可能达到千万级甚至是亿级&#xff0c;并且数据库面临着极高的并发访问。采用Master-Slave复制模式的MySQL架构&#xff0c;只能够对数据库的读进行扩展&#xff0c;而对数据库的写入操作还…

ORA-01502 state unusable错误成因和解决方法(转)

ORA-01502 state unusable错误成因和解决方法(一),sql,sql教程,Oracle基础接到开发人员和业务人员的通知&#xff0c;说一个登陆页面不能用了&#xff0c;报错&#xff1a;2005-01-31 13:59:02,721 [com.aspire.common.dao.OamUserDAO]- -214:select错误java.sql.SQLException:…

Python中利用原始套接字进行网络编程的示例

Python中利用原始套接字进行网络编程的示例 在实验中需要自己构造单独的HTTP数据报文&#xff0c;而使用SOCK_STREAM进行发送数据包&#xff0c;需要进行完整的TCP交互。 因此想使用原始套接字进行编程&#xff0c;直接构造数据包&#xff0c;并在IP层进行发送&#xff0c;即采…

阿里的开源连接池框架druid的使用

文章目录Jdbc 的不足数据连接池&#xff08;DBCP&#xff09;数据源框架&#xff08;druid&#xff09;使用流程完整代码Jdbc 的不足 jdbc&#xff1a;java和数据库的桥梁 步骤&#xff1a; 注册驱动 Class.forName()—>一次建立连接 Connection —>每一次预处理对象Pre…

百度搜索引擎使用指南(转)

百度搜索使用了高性能的“网络蜘蛛”程序(Spider)自动的在互联网中搜索信息&#xff0c;可定制、高扩展性的调度算法使得搜索器能在极短的时间内收集到最大数量的互联网信息。百度搜索在中国和美国均设有服务器&#xff0c;搜索范围涵盖了中国大陆、香港、台湾、澳门、新加坡等…

如何计算 N叉树的最大深度

文章目录题目简述TreeNode代码DFSBFSLeetCode-559 题目简述 给定一个N叉树&#xff0c;找到其最大深度 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 TreeNode代码 class Node {//值public int val;//孩子结点 使用List集合存储public List<Node> child…

springboot09 事务 H2数据库

一、事务 1. 事务介绍 事务可以包含多个操作步骤 &#xff0c; 如果有一个步骤失败&#xff0c;那么这一组都以失败告终。 事务是指包含多个微小逻辑单元的一组操作&#xff0c; 只要其中有一个逻辑失败了&#xff0c;那么这一组操作就全部以失败告终&#xff0c;不存在一半成功…

保护SQL Server数据库的十大绝招(转)

1. 安装最新的服务包    为了提高服务器安全性&#xff0c;最有效的一个方法就是升级到SQL Server 2000 Service Pack 3a (SP3a)。另外&#xff0c;您还应该安装所有已发布的安全更新。   2. 使用Microsoft基线安全性分析器&#xff08;MBSA&#xff09;来评估服务器的安全…