首页投稿(暂停使用,暂停投稿)

深入Java核心

2017-07-08  本文已影响36人  DrLester

类加载

类加载负责加载编译后的class文件(字节码文件)到JVM当中。

举例说明

    public class Test {
    
        String name;
    
        public void say(){
            System.out.println("Hello Word");
        }
        
        // 程序的入口的方法,和具体类无关
        public static void main(String[] args) {
            Test t = new Test();    
            t.say();
    
            // 得到当前的类加载器ApplicationClassLoader
            ClassLoader cl = Test.class.getClassLoader();
            System.out.println(cl);
    
            // 得到ApplicationClassLoader的父类加载器ExtensionClassLoader
            ClassLoader extCl = cl.getParent();
            System.out.println(extCl);
    
            // 得到ExtensionClassLoader的父类加载器BootstrapClassLoader
            // 由于BootstrapClassLoader是用C/C++语言编写的,在java中无法直接使用
            // 所以才会返回一个null
            ClassLoader bootCl = extCl.getParent();
            System.out.println(bootCl);
        }
    }

内存分配

当JVM运行起来的时候就会给内存划分空间,那么这块空间称之为运行时数据区。
(备注:当一个Java源程序编译成class字节码文件之后,字节码文件里存放的都是二进制的汇编命令,当程序运行的时候,JVM会将这个二进制的命令逐行解释,交给CPU去执行)

Class对象

当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对象里面的成员(包括类信息、属性信息、方法信息等元素)。它可以让Java这种静态语言具备一定的动态性。目前大部分的开源框架实现都是基于反射的机制实现。
JVM → 类加载 → class文件 → 创建 → Class对象 → 构建类的实例 → instance(实例);
重点在运行时动态的操作Class对象。

反射机制的利与弊

为何要用反射机制?直接new对象不ok了吗,这就涉及到了动态与静态的概念

反射机制的相关操作

创建实例

// 在反射操作之前的第一步,就是要先获取Class对象
Class<?> clazz = Class.forName("org.demo.reflect.bean.People");
// 根据Class对象创建一个实例
clazz.newInstance();

动态操作属性

    public static void main(String[] args) throws Exception {
         
        // 在反射操作之前的第一步,就是要先获取Class对象
        Class<?> clazz = Class.forName("org.demo.reflect.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));
        ystem.out.println(f2.isAnnotationPresent(MyAnno.class));
    
        // 获取属性上定义的注解
        MyAnno anno = f1.getAnnotation(MyAnno.class);
        // 获取注解上的属性值
        System.out.println(anno.name());
    }

动态操作方法

    public static void main(String[] args) throws Exception {
         
        Class<?> clazz = Class.forName("org.demo.reflect.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);
    }

动态操作构造方法

    public static void main(String[] args) throws Exception {
          
        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();
    }

Class中的一些API

通过反射了解集合泛型的本质

    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();
        }
    }

使用场景

源码下载

GitHub

I was within and without.

原文:http://www.godql.com/blog/2017/07/07/Core-Java/
作者:Dr.Lester

上一篇下一篇

猜你喜欢

热点阅读