类型信息

欢迎访问配色更好看的个人站

开始吧

运行时类型信息使得你可以在程序运行时发现和使用类型信息

java运行时识别对象和类的信息主要有两种方式:
- RTTI
- 反射

RTTI

接口与父类都是一种窗口,透过他们只能看到具体实现的一部分

RTTI(Run-Time Type Identification), 在运行时识别一个对象的类型。
有了RTTI才有多态, 而多态是面向对象编程的基本目标。
举个栗子

List<String> lizi = new ArrayList<>();

这种写法的好处是方便替换具体实现,可能开始的时候觉得随机读取比较多就用ArrayList, 之后发现增删改比较多,可以简单的将实现替换为LinkedList即可。除非要用到具体实现的具体特性,否则一直建议使用更宽泛的类型引用。

Class

获得Class对象的两种方式
  • Class.forName(name) 获得Class对象的同时会加载类
  • XXXX.class 只获得Class对象,不会加载类。学名是类字面常量。
// Class.forName 方法, 找到合适的类加载器调用本地方法forName0
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    Class<?> caller = null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // Reflective call to get caller class is only needed if a security manager
        // is present.  Avoid the overhead of making this call otherwise.
        caller = Reflection.getCallerClass();
        if (sun.misc.VM.isSystemDomainLoader(loader)) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}
// 从参数上就能看出来这货是要加载类了
private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;
值得注意的点

编译器已知的static final修饰的常量,这个值不需要进行类初始化就能访问到。为啥呢?

class Initable {
  static final int staticFinal = 57;
  static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
  static {
    System.out.println("类初始化啦");
  }
}
class ClassInitialization {
  public static Random rand = new Random(57);
  public static void main(String[] args) {
    // 没有触发类的初始化
    Class initable = Initable.class;
    // 没有触发类的初始化
    System.out.println(Initable.staticFinal);
    // 触发类的初始化
    System.out.println(Initable.staticFinal2)
  }
}

// 输出:
// 57
// 类初始化啦
// 241
判断对象与类型的关系

判断一个对象是否是一个类型或者其类型是这个类型的子类,有两种方式:

Class type = ...;
Object obj = ...;
// 方法1
if (obj instanceOf type) {
  ...
}
// 方法2
if(type.isInstance(obj)) {
  ...
}

反射

世界是平衡的

正如英文单词reflection的含义一样,使用反射API的时候就好像在看一个Java类在水中的倒影一样。知道了Java类的内部 结构之后,就可以与它进行交互,包括创建新的对象和调用对象中的方法等。这种交互方式与直接在源代码中使用的效果是相同的,但是又额外提供了运行时刻的灵活性。使用反射的一个最大的弊端是性能比较差。相同的操作,用反射API所需的时间大概比直接的使用要慢一两个数量级。不过现在的JVM实现中,反射操作的性能已经有了很大的提升。在灵活性与性能之间,总是需要进行权衡的。应用可以在适当的时机来使用反射API。

基本用法

Java反射API的第一个主要作用是获取程序在运行时刻的内部结构。只要有了java.lang.Class类的对象,就可以通过其中的方法来获取到该类中的构造方法、域和方法。对应的方法分别是getConstructor、getField和getMethod。这三个方法还有相应的getDeclaredXXX版本,区别在于getXXX只会获得“允许”访问的内容(即public修饰的), 而getDeclaredXXX会获得所有修饰的内容;

使用Java反射API的时候可以绕过Java默认的访问控制检查,只需要在获取到Constructor、Field和Method类的对象之后,调用setAccessible方法并设为true即可。有了这种机制,就可以很方便的在运行时刻获取到程序的内部状态。

反射API的另外一个作用是在运行时刻对一个Java对象进行操作。 这些操作包括动态创建一个Java类的对象,获取某个域的值以及调用某个方法。在Java源代码中编写的对类和对象的操作,都可以在运行时刻通过反射API来实现。
举个栗子

// 简单类
class MyClass {
    public int count;
    public MyClass(int start) {
        count = start;
    }
    public void increase(int step) {
        count = count + step;
    }
}

// 一般操作
MyClass myClass = new MyClass(0);
myClass.increase(2);
System.out.println("Normal -> " + myClass.count);

// 反射操作
try {
    // 获取构造方法
    Constructor constructor = MyClass.class.getConstructor(int.class);
    // 创建对象
    MyClass myClassReflect = constructor.newInstance(10);
    // 获取方法
    Method method = MyClass.class.getMethod("increase", int.class);  
    // 调用方法
    method.invoke(myClassReflect, 5);
    // 获取域
    Field field = MyClass.class.getField("count");
    // 获取域的值
    System.out.println("Reflect -> " + field.getInt(myClassReflect));
} catch (Exception e) {
    e.printStackTrace();
}
处理泛型

比如在代码中声明了一个域是List类型的,虽然在运行时刻其类型会变成原始类型List,但是仍然可以通过反射来获取到所用的实际的类型参数。

Field field = Pair.class.getDeclaredField("myList"); //myList的类型是List
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {     
    ParameterizedType paramType = (ParameterizedType) type;     
    Type[] actualTypes = paramType.getActualTypeArguments();     
    for (Type aType : actualTypes) {         
        if (aType instanceof Class) {         
            Class clz = (Class) aType;             
            System.out.println(clz.getName()); //输出java.lang.String         
        }     
    }
}
动态代理

下面的代码用来代理一个实现了List接口的对象。所实现的功能也非常简单,那就是禁止使用List接口中的add方法。如果在getList中传入一个实现List接口的对象,那么返回的实际就是一个代理对象,尝试在该对象上调用add方法就会抛出来异常。

public List getList(final List list) {
    return (List) Proxy.newProxyInstance(DummyProxy.class.getClassLoader(), new Class[] { List.class },
        new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("add".equals(method.getName())) {
                    throw new UnsupportedOperationException();
                }
                else {
                    return method.invoke(list, args);
                }
            }
        });
 }

类加载器

概念

顾名思义,类加载器(Class Loader)用来加载类到Java虚拟机(JVM)中。一般来说,JVM使用Java类的方式如下:Java源程序(.java文件)在经过编译器编译之后就被转换成Java字节代码(.class文件)。类加载器负责读取Java字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个Java类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如Java字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

java.lang.ClassLoader

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。
为了完成加载类的这个职责,ClassLoader提供了一系列的方法。

方法说明
getParent()返回该类加载器的父类加载器。
loadClass(String name)加载名称为name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name)查找名称为name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name)查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len)把字节数组b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class c)链接指定的Java类。
组织结构

Java中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由Java应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载Java的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

// 演示类加载组织结构
public class ClassLoaderTree {

   public static void main(String[] args) {
       ClassLoader loader = ClassLoaderTree.class.getClassLoader();
       while (loader != null) {
           System.out.println(loader.toString());
           loader = loader.getParent();
       }
   }
}
// 输出
// sun.misc.Launcher$AppClassLoader@7adf9f5f
// sun.misc.Launcher$ExtClassLoader@5b2133b1

第一个输出的是 ClassLoaderTree类的类加载器,即系统类加载器。它是 sun.misc.Launcher AppClassLoadersun.misc.Launcher ExtClassLoader类的实例。需要注意的是这里并没有输出引导类加载器,这是由于有些JDK的实现对于父类加载器是引导类加载器的情况,getParent()方法返回null。

加载类的过程

类加载器首先检查这个类的Class是否已经加载,如果未加载,默认的类加载器会找到.class字节码,加载、链接、初始化;
- 加载 由类加载器执行, 查找字节码, 并根据字节码创建Class对象
- 链接 验证类中的字节码,为静态域分配存储空间,并且如果必要将解析这个类对其他类的引用并创建
- 初始化 如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值