美文网首页
Java 类型与反射

Java 类型与反射

作者: BitterOutsider | 来源:发表于2020-12-06 21:16 被阅读0次

Java的类与Class

  • RTTI(Run-Time Type Identification)运行时类型识别
    任何对象在任何时刻都可以知道自己是什么类型的(不管你当前是什么类型)。我们可以用Object.getClass方法获取他的类型(他的Class 对象)。举一个例子:
public class Main {
    public static void main(String[] args) {
        Item obj = new RPC().getItemById(1);
        // class com.github.lazyben.Goods
        System.out.println(obj.getClass());
    }
}

interface Item { }

class Goods implements Item { }

class RPC {
    public Item getItemById(int id){
        return new Goods();
    }
}
  • Class对象是什么?从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。简单点来说Class对象就是一个类的说明书,JVM根据这个说明书创造类的实例。
    总结一下Class是什么:1.它是类的一种 2.对象内容是你创建类的类型信息 3.不能new生产,只有JVM创建。

有了以上的知识后我们就可以更好的理解 instanceof 和 强制类型转换 了。因为在运行时 JVM 清楚地知道该对象到底是什么类型的(它是由哪一份Class“说明书”装配出来的)。我们也可以更好地理解静态变量:它可以理解为属于这个Class对象的一个变量。

类加载与ClassLoader

假如我们有一堆的Class对象说明书CatDogObject,这些Class对象又是从哪里来的呢?

  • 一个类是什么时候被加载的?答案是在第一次被使用的时候,举一个例子:
package com.github.lazyben;

public class Main {
    public static void main(String[] args) {
        new WhiteCat();
    }
}

class Animal{}

class Cat extends Animal{}

class WhiteCat extends Cat{}

我们使用-verbose:class打印出类加载过程,并用grep lazyben过滤出自己的类(方便查看)。这里很容易看出类是从target目录下来的,且加载顺序符合类的加载顺序。java自带的包在不同的地方,同样的我们可以grep Object一探究竟。

java -verbose:class -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/......./target/classes com.github.lazyben.Main | grep lazyben

// 打印出以下的信息
[Loaded com.github.lazyben.Main from file:/Users/lazyben/..../target/classes/]
[Loaded com.github.lazyben.Animal from file:/Users/lazyben/..../target/classes/]
[Loaded com.github.lazyben.Cat from file:/Users/lazyben/..../target/classes/]
[Loaded com.github.lazyben.WhiteCat from file:/Users/lazyben/..../target/classes/]

下图是类的加载过程,经过下面几个过程,我们就将一个Class对象变到类内存里面,使得我们可以根据这份说明书去创造实例对象。


  • 那么Class对象是被谁加载出来的?
    Classloader负责从外部系统中加载⼀个类。Classloader是一个加载类的对象。加载一个类时,这个类对应的Java⽂件并不⼀定需要存在,这个字节码(.class文件)也不⼀定需要存在:文件的本质就是字节流,因此我们可以随便去哪获得这传字节流。而且我们可以动态生成。这是Java世界丰富多彩的应⽤的基⽯:整个Spring就是基于此。

  • ClassLoader的双亲委派加载模型
    如果一个人写了一个恶意的java.lang.String做一些坏事,如何避免呢?答案就是双亲委派加载模型。ClassLoader.loadClass方法加载一个类。我们打开其源代码,可以看到在加载一个类的时候,先会去看其父亲是否存在,如果存在则会拿到他的父亲加载的这个类。如果父亲没加载,它才会自己尝试去加载它。所以一些最基本的类都会在最开始由双亲委派加载模型最顶端的启动类加载器加载,不会轮到下面的类加载器加载。

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                }                 
            // .......
            return c;
        }
    }
  • 自定义类加载器
    一般来说,要实现自定义的类加载器,需要完成以下2个步骤:
    1. 如果类名对应的字节码文件存在,则将它读取成为字节数组
      1.1 调用ClassLoader.defineClass()方法将字节数组转化为Class对象
    2. 如果类名对应的字节码文件不存在,则抛出ClassNotFoundException
public class MyClassLoader extends ClassLoader {
    // 存放字节码文件的目录
    private final File bytecodeFileDirectory;

    public MyClassLoader(File bytecodeFileDirectory) {
        this.bytecodeFileDirectory = bytecodeFileDirectory;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        final File file = new File(bytecodeFileDirectory, name + ".class");
        if (file.exists()) {
            try {
                final byte[] bytes = Files.readAllBytes(file.toPath());
                return defineClass(name, bytes, 0, bytes.length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        throw new ClassNotFoundException(name);
    }

    public static void main(String[] args) throws Exception {
        File projectRoot = new File(System.getProperty(...);
        MyClassLoader myClassLoader = new MyClassLoader(projectRoot);

        Class testClass = myClassLoader.loadClass(...);
        Object testClassInstance = testClass.getConstructor().newInstance();
        String message = (String) testClass.getMethod(...).invoke(testClassInstance);
    }
}

反射

用一句话概括反射:它是运行时行为,动态调用。如何根据参数动态创建⼀个对象?如何根据参数动态调⽤⼀个⽅法?如何根据参数动态获取⼀个属性?答案都是反射。这意味着我们有了无与伦比的灵活性。但是一般来说反射的性能比较差,因为JDK无法预测被调用的方法,无法实施优化。
根据参数动态创建⼀个对象,其中forName根据一个类名(全限定类名)返回一个Class对象。

Class klass = Class.forName(args[0]);
Object obj = klass.getConstructor().newInstance();

根据参数动态调⽤⼀个⽅法

Cat cat = new Cat();
cat.getClass().getMethod(args[0]).invoke(cat);

根据参数动态获取⼀个属性

Cat cat = new Cat();
cat.getClass().getField(args[0]).get(cat);
  • 一个小实战:使用反射实现一个Java Bean到Map的转换器,传入一个遵守Java Bean约定的对象,读取它的所有属性,存储成为一个Map。基本思想:
    1. 遍历map中的所有键值对,寻找klass中名为setXXX,且参数为对应值类型的方法(即setter方法)
    2. 使用反射创建klass对象的一个实例
    3. 使用反射调用setter方法对该实例的字段进行设值
public class MapBeanConverter {
    public static Map<String, Object> beanToMap(Object bean) {
        return Stream.of(bean.getClass().getDeclaredMethods())
                .filter(MapBeanConverter::isJavaBeanMethod)
                .collect(Collectors.toMap(MapBeanConverter::getMethodName, method -> getMethodReturnValue(method, bean)));
    }

    private static Object getMethodReturnValue(Method method, Object bean) {
        try {
            return method.invoke(bean);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String getMethodName(Method method) {
        String methodName = method.getName();
        String name = methodName.startsWith("get") ? methodName.substring(3) : methodName.substring(2);
        return Character.toLowerCase(name.charAt(0)) + name.substring(1);
    }

    private static boolean isJavaBeanMethod(Method method) {
        final String methodName = method.getName();
        boolean isStartWithGet = methodName.startsWith("get") && methodName.length() > 3;
        boolean isStartWithIs = methodName.startsWith("is") && methodName.length() > 2;
        return (isStartWithGet || isStartWithIs)
                && (method.getParameterCount() == 0)
                && Character.isUpperCase(methodName.charAt(isStartWithGet ? 3 : 2));
    }

    public static <T> T mapToBean(Class<T> klass, Map<String, Object> map) {
        try {
            final T instance = klass.getConstructor().newInstance();
            map.forEach((key, value) -> {
                String methodName = "set" + Character.toUpperCase(key.charAt(0)) + key.substring(1);
                try {
                    klass.getMethod(methodName, value.getClass()).invoke(instance, value);
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            });
            return instance;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) {
            e.printStackTrace();
        }
        return null;
    }

    public static class DemoJavaBean {
        private Integer id;
        private String name;
        private final String privateField = "privateField";

        public int isolate() {
            System.out.println(privateField);
            return 0;
        }

        public String is() { return ""; }

        public Integer getId() { return id; }

        public void setId(Integer id) { this.id = id; }

        public String getName() { return name; }

        public String getName(int i) { return name + i; }

        public void setName(String name) { this.name = name; }

        public boolean isLongName() { return name.length() > 10; }
    }

    public static void main(String[] args) {
        DemoJavaBean bean = new DemoJavaBean();
        bean.setId(100);
        bean.setName("AAAAAAAAAAAAAAAAAAA");
        System.out.println(beanToMap(bean));

        Map<String, Object> map = new HashMap<>();
        map.put("id", 123);
        map.put("name", "ABCDEFG");
        System.out.println(mapToBean(DemoJavaBean.class, map));
    }
}

相关文章

  • Java 类型与反射

    Java的类与Class RTTI(Run-Time Type Identification)运行时类型识别任何对...

  • CoreJava笔记 - 范型程序设计(5)

    反射与范型 由于类型擦除,反射无法得到关于范型类型参数的信息。 范型的Class类在Java的反射库中,Class...

  • Java 反射机制

    [1]. java反射详解[2]. Java Reflection(反射机制)详解[3]. 深入理解Java类型...

  • java 的反射

    参考文档:深入理解Java类型信息(Class对象)与反射机制

  • Java反射理解

    Java反射理解 Java类型信息 RTTI(运行时类型识别)源于《Thinking in Java》一书,其作用...

  • Java 类型 & 反射

    类 & Class RTTI => Run-Time Type Identification => 运行时类型识别...

  • Java基础之反射

    Java基础之—反射(非常重要)Java中反射机制详解Java进阶之reflection(反射机制)——反射概念与...

  • Java基础--反射

    什么是Java反射 概念 java反射是指java能够在运行时确定类的类型信息,包括其方法、字段、构造函数等,并能...

  • Java面试总结:Java基础篇(2)

    1.反射与动态代理 反射:反射是java提供的一种机制,可以让我们在运行时期获得某个对象的类型,获取类声明的属性和...

  • Java 高级基础——反射

    Java 高级基础——反射 反射的意义:Java 强类型语言,但是我们在运行时有了解、修改信息的需求,包括类信息、...

网友评论

      本文标题:Java 类型与反射

      本文链接:https://www.haomeiwen.com/subject/utpowktx.html