当 JVM 运行起来的时候就会给内存划分空间,那么这块空间称之为运行时数据区。
(
当一个 Java 源程序编译成 class 字节码文件之后,字节码文件里存放的都是二进制的汇编命令,当程序运行的时候,JVM 会将这个二进制的命令逐行解释,交给 CPU 去执行)
- 备注:
(
- 备注:
由于局部变量都是存放在栈中,而每一个线程都对应自己的线程栈,因此局部变量是线程安全的,不会才产生资源共享的情况。)
- 备注:
Java 中除了 8 个基本数据类型以外的所有类型都是引用数据类型)
- 备注:
5) 方法区
- - `实例变量:`实例变量是随着对象的创建而创建,而实例是存放在堆中,所以实例变量自然也就跟实例一并保存在堆内存。只要创建多少个实例,就会有多少份实例变量。当实例被回收的时候,实例变量也随之而销毁。
- - `静态变量:`静态变量也叫类变量,它是在类加载的时候就已经初始化好,并存放在方法区,并且只有一份,所以它是被多个实例所共享的一个变量。
(代码中出现但没有解释的数字常量或字符串)(用来确定是否是一个 class 文件)、常量池(常量池在下面会有完整说明)、访问标志(当前的 class 是类还是接口,是否是抽象类, 是否是 public 修饰,是否使用了
- 魔数
修饰等描述信息…)、字段表集合信息(使用什么访问修饰符、是实例变量还是静态变量,是否用
- final
修饰等描述信息…)、 方法表集合信息(访问修饰符,是否静态方法,是否用 final 修饰,是否用了
- final
修饰,是否是
- synchronized
方法…)等内容。当一个类加载器加载一个 class 文件的时候, 会根据这个 class 文件的内容创建一个 Class 对象,而这个 Class 对象就包括了上述的这些内容。后续要创建这个类的所有实例,都是通过这个 Class 对象创建出来的。
- native
和
- Double
类。
- Float
在 JDK8 之后,方法区已经取消,方法区被一个叫 MetaSpace,它和堆合并到一起管理)
- 备注说明:
- 扯了好多Java虚拟机的内容,也没讲多深,因为这里主要的目的是为了大家方便理解Java反射机制,下面正式进入正题
当 ClassLoader 加载一个 class 文件到 JVM 的时候,会自动创建一个该类的 Class 对象,并且这个对象是唯一的,后续要创建这个类的任何实例,都会根据这个 Class 对象来创建。因此每当加载一个 class 文件的时候,都会创建一个与之对应的 Class 对象。
- String s = new String();
- private Class(ClassLoader loader){
- classLoader = loader;
- }
- // 类名.class 通过获取类的静态成员变量class得到(任何类都有一个隐含的静态成员变量class)
- Class<?> clazz = String.class;
- // 对象.getClass
- Class<?> clazz2 = new String().getClass();
- // Class.forName("全量限定名")
- Class<?> clazz3 = Class.forName("java.lang.String");
这三种方式都是利用反射获取的都是同一个 Class 对象,这也叫做 String 的类类型,也就是描述何为类,一个类都有哪些东西,所以可以通过类类型知道一个类的属性和方法,并可以调用一个类的属性和方法,这就是反射的基础。)
- 注意:
反射是指
(包括类信息、属性信息、方法信息等元素)。它可以让 Java 这种静态语言具备一定的动态性。目前大部分的开源框架实现都是基于反射的机制实现。
- 在程序的运行期间动态的去操作某个Class对象里面的成员
JVM → 类加载 → class 文件 → 创建 → Class 对象 → 构建类的实例 → instance(实例);
重点在运行时动态的操作 Class 对象。
为何要用反射机制?直接 new 对象不 ok 了吗,这就涉及到了动态与静态的概念
- 运行期类型的判断,动态类加载,动态代理就使用了反射
1. 对性能有影响。反射相当于一系列解释操作,通知 JVM 要做的事情。性能比直接的 java 代码执行相同的操作要慢很多。
2. 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
- // 在反射操作之前的第一步,就是要先获取Class对象
- Class<?> clazz = Class.forName("org.demo.bean.People");
- // 根据Class对象创建一个实例
- clazz.newInstance();
// 获取所有公开的属性字段(包括继承父类的公有属性)
- getField()
// 获取本类所有(包括公有和私有,但是不包括父类的)的属性字段(注意:如果要访问和操作私有属性,必须调用 setAccessible 方法,打开访问开关)
- getDeclaredField()
// 获取所有公有的属性(包括继承自父类的公有属性)
- getFields()
// 获取本类所有的属性(包括共有和私有的,但是不包括父类的)
- getDeclaredFields()
// 给属性赋值,需要传入两个参数,第一个参数是当前类的一个实例,第二个参数是具体要赋予的值
- set()
// 获取属性的值,需要传入一个当前类的实例作为参数
- get()
// 获取属性的名称
- getName()
// 获取属性的类型
- getType()
// 判断该属性上是否定义了指定的注解,需要传入一个注解的 Class 对象作为参数
- isAnnotationPresent()
// 获取当前属性上的注解对象,需要传入一个注解的 Class 对象作为参数
- getAnnotation()
- public static void main(String[] args)throwsException{
- // 在反射操作之前的第一步,就是要先获取Class对象
- Class<?> clazz = Class.forName("org.demo.bean.People");
- // 根据Class对象创建一个实例
- Object instance = clazz.newInstance();
- // 获取指定的属性
- Field f1 = clazz.getField("userName");
- // 获取属性的值,get方法需要传入一个当前类的实例
- Object value = f1.get(instance);
- System.out.println(value);
- // 通过反射给属性赋值
- // 第一个参数是当前类的实例,第二个参数是要赋予的值
- f1.set(instance, "godql");
- value = f1.get(instance);
- System.out.println(value);
- // 获取一个私有的属性
- // 如果需要访问和操作私有的成员,必须打开访问开关
- // 打开访问开关其实就是破坏封装
- Field f2 = clazz.getDeclaredField("age");
- // 强制打开访问权限
- f2.setAccessible(true);
- Object value2 = f2.get(instance);
- System.out.println(value2);
- f2.set(instance, 30);
- value2 = f2.get(instance);
- System.out.println(value2);
- // 获取属性的名称
- System.out.println(f1.getName());
- System.out.println(f2.getName());
- // 获取属性的类型
- System.out.println(f1.getType());
- System.out.println(f2.getType());
- // 获取所有公有的属性(包括继承自父类的公有属性)
- Field[] fs1 = clazz.getFields();
- // 获取本类所有的属性(包括共有和私有的,但是不包括父类的)
- Field[] fs2 = clazz.getDeclaredFields();
- // 判断当前属性上是否定义了注解
- System.out.println(f1.isAnnotationPresent(MyAnno.class));
- System.out.println(f2.isAnnotationPresent(MyAnno.class));
- // 获取属性上定义的注解
- MyAnno anno = f1.getAnnotation(MyAnno.class);
- // 获取注解上的属性值
- System.out.println(anno.name());
- }
// 获取指定的公共的方法(包括继承自父类公共的),需要传递两个参数,第一个参数是方法名称,第二个参数是一个可变参数,传递的是方法参数的类型
- getMethod()
// 获取所有的公共的方法(包括继承父类的公共方法)。
- getMethods()
// 获取本类中指定的方法(包括私有和共有的,不包括父类的),需要传递两个参数,第一个参数是方法名称,第二个参数是一个可变参数,传递的是方法参数的类型。如果是私有方法,同样需要先打开访问开关 (setAccessible(true))。
- getDeclaredMethod()
// 获取本地中所有的方法(包括私有和公共的,不包括父类)
- getDeclaredMethods()
// 获取方法名称
- getName()
// 获取方法的返回值类型
- getReturnType()
// 获取方法中所有的参数类型
- getParameterTypes()
// 获取方法中参数的总个数
- getParameterCount()
// (JDK1.8 新特性) 获取方法中所有的参数信息,每一个参数信息都是一个 Parameter 类的对象。可以通过这个对象获取各个参数的类型以及名称 (注意:如果要获取参数名,在编译的时候需要加上一个 parameters 参数,如:javac -parameters Xxx.java。或者是在开发环境中设置相应的编译选项)。
- getParameters()
// 回调当前方法, 需要传递两个参数,第一个是当前类的实例,第二个是一个可变参数,需要传入调用方法是所需的参数值。
- invoke()
- public static void main(String[] args)throwsException{
- Class<?> clazz = Class.forName("org.demo.bean.People");
- Object instance = clazz.newInstance();
- // 获取指定的Method
- Method m1 = clazz.getMethod("say", String.class, int.class);
- // 获取方法名
- System.out.println(m1.getName());
- // 获取方法的返回值类型
- System.out.println(m1.getReturnType());
- // 获取方法的所有参数类型
- Class<?>[] paramsType = m1.getParameterTypes();
- for (Class<?> c : paramsType) {
- System.out.println(c);
- }
- // 获取参数名称(JDK1.8开始支持)
- Parameter[] params = m1.getParameters();
- for (Parameter p : params) {
- System.out.println("参数类型:"+p.getType());
- System.out.println("参数名称:"+p.getName());
- }
- // 通过当前的方法,获取定义这个方法的类
- Class<?> c = m1.getDeclaringClass();
- System.out.println(c.getName());
- // 方法回调,目的就是通过反射去调用一个方法
- m1.invoke(instance, "godql", 21);
- }
// 获取无参并且公共的构造方法
- getConstructor()
// 获取一个构造方法可以是私有的也可以是公共的,需要传入一个可变参数,就是构造方法的参数类型(注意:如果是私有的,必须先打开访问开关)
- getDeclaredConstructor()
// 通过构造方法创建实例,也需要传入一个可变参数,传入的是具体的值
- newInstance()
// 获取所有公共的构造方法,返回的是一个 Constructor 数组
- getConstructors()
// 获取所有的构造方法 (包括私有和共有的), 同样返回的是一个数组
- getDeclaredConstructors()
// 获取所有的参数对象,和 Method 一样
- getParameters()
// 获取所有的参数类型,同 Method 一样
- getParameterTypes()
- public static void main(String[] args)throwsException{
- Class<?> clazz = People.class;
- // 获取无参的构造方法
- Constructor<?> c1 = clazz.getConstructor();
- // 获取构造方法的名称
- System.out.println(c1.getName());
- // 获取一个私有并且带参数的构造方法
- Constructor<?> c2 = clazz.getDeclaredConstructor(String.class);
- // 可以通过构造方法实例化一个对象
- //(注意:如果默认有一个无参并且是公共的构造方法,
- // 那么可以直接使用class.newInstance()方法创建实例,
- // 如果构造方法是私有的,或者是带参数的,就必须先获取
- // Constructor对象,在通过这个对象来创建类实例)
- // 1.适用于无参并且是公共的构造方法
- /*
- Object instance = clazz.newInstance();
- System.out.println(instance);
- */
- // 2.适用于带参数或是私有的构造方法
- // 由于构造方法也可以私有化,所以必须先打开访问开关
- c2.setAccessible(true);
- Object instance = c2.newInstance("godql");
- System.out.println(instance);
- // 获取所有public修饰的构造方法
- Constructor<?>[] cons = clazz.getConstructors();
- // 获取所有构造方法(包括私有的)
- Constructor<?>[] cons2 = clazz.getDeclaredConstructors();
- }
// 获取当前类所在的包,使用 Package 对象进行封装,可以从中获取包的信息,例如:包名
- getPackage()
// 获取当前类的简单类名(不包括包名)
- getSimpleName()
// 获取当前类的完整类名 (包括包名)
- getName()
// 获取当前类的父类,返回的也是一个 Class 对象
- getSuperclass()
// 获取当前类所实现的所有接口,返回的是一个 Class 数组
- getInterfaces()
// 判断当前类上是否定义了注解
- isAnnotationPresent()
// 获取类上定义的注解
- getAnnotation()
- public static void main(String[] args){
- List list = new ArrayList();
- List<String> list1 = new ArrayList<>();
- list.add("godql");
- // list1.add(20); 错误的
- Class c1 = list.getClass();
- Class c2 = list1.getClass();
- System.out.println(c1 == c2); // 结果:true,说明类类型完全相同
- // 反射的操作都是编译之后的操作(运行时)
- /*
- * 以上说明编译之后集合的泛型是泛型擦除的
- * Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了。
- * 验证: 通过方法的反射来操作,绕过编译
- */
- try {
- // 通过动态操作方法的反射得到add方法
- Method m = c2.getMethod("add", Object.class);
- // 方法回调 给list1添加一个int型的,这是在运行时的操作,所以编译器编译时没有泛型检查,所以不会报错
- // 绕过编译操作
- m.invoke(list1, 20);
- // 验证是否有添加进list集合里
- System.out.println(list1.size());
- // 这时候不能使用foreach遍历,否则集合会认为集合里边全是String类型的值
- // 且有类型转换错误,因为这个集合里面有int类型、String类
- System.out.println(list1);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
来源: http://www.tuicool.com/articles/FnYNvaj