Java类型信息

一、RTTI

大白话:本来只能在编译期做有关类型的操作,有了RTTI(runtime type information),在运行时也可以做了。RTTI的最终目的:在运行时识别一个对象的类型。

我们有两种方式可以在运行时识别对象和类型信息

  1. 传统RTTI,即在编译时已经知道了类型
  2. 基于反射的RTTI

先看1 , 第2点的话看下一节
一个很简单的继承(其实是模板设计模式),对于shapes来说:放入Circle、Triangle对象会向上转型为Shape,但在转型为Shape的同时也丢失了Shape对象的具体类型(即不知道是Circle 还是Triangle了);更彻底地说,对数组而言,放入的对象其实都是Object,但是在拿出来的时候由Object转型成了 Shape,只是转型为Shape,并没有转型为具体的Circle、Triangle等类型。为啥拿出来的时候可以确认是Shape? 这就是RTTI的传统用法:自动将Object转型为Shape。

public class ShapeTest {

    public static void main(String[] args) {
        List<Shape> shapes = Arrays.asList(new Circle(), new Triangle());
        shapes.forEach(Shape::draw);
//        Circle.draw
//        Triangle.draw
    }

}

abstract class Shape{
    void draw() {
        System.out.println(this + ".draw");
    }

    public abstract String toString();
}

class Circle extends Shape {

    @Override
    public String toString() {
        return "Circle";
    }
}

class Triangle extends Shape{

    @Override
    public String toString() {
        return "Triangle";
    }
}

二、Class对象

要知道RTTI在运行时怎么玩的,首先要知道它是怎么表示的,这是由Class对象完成的。
每个类都对应一个Class对象,编译了一个新类就产生一个新的Class对象,更恰当说,是被保存在一个同名的 .class 对象中。为了生成这个对象,JVM将使用称之为类类加载器的子系统。

类加载器子系统,是一个类加载器链,但只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓可信类,含JavaAPI,它从本地磁盘加载类。其实JVM也可以从网络加载类,此时类是不可信的。

所有类都是在第一次使用时动态加载到JVM中的。程序创建第一个对类的静态成员的引用时就会加载这个类。构造器也是类静态方法的一种

Java类不是在运行之前就全部被加载,而是动态加载的。

  1. Class.forname\ Object.getClass
  2. 类字面常量加载,如int.class 。这种加载不会自动初始化Class对象。类字面常量的初始化被延迟到了对静态方法(构造器隐式是静态的)或非常数静态域首次引用时才执行

类加载的三个步骤:

  1. 加载
  2. 链接(为静态域分配内存,并可能会解析这类创建的对其他类的所有引用)
  3. 初始化(初始化)

2.1 泛化的Class引用

2.1.1 Class<?> 与Class

本质上,泛化的Class引用还是Java泛型机制的使用。
先来一个小例子e.g.1:

//e.g.1
public class GenericReferences {

    public static void main(String[] args) {
        Class integerClass = int.class;
        integerClass = double.class; //OK
        Class<Integer> intClass = int.class;
        intClass = double.class; // 类型转换异常
    }
}

而下面这个例子使用了泛型,在编译期就检查到了incompatible type
那假如我们希望放松限制:看上去 NumberInteger的父类,那么我们使用Class< Number >去接收int.class,应该没毛病啊!其实不然,虽然NumberInteger的父类,但是Class< Number>并不是Class< Integer>的父类。我们一定要准确地理解java的多态,不要生搬硬套:Java多态–>父类引用指向子类对象。

//e.g.2 
class WildCardReferences{
    public static void main(String[] args) {
        //        Class<Number> numberClass = int.class; //incompatible types
        Class<?> integerClass = int.class;
        integerClass = double.class; //OK
    }
}

从e.g.2,Class<?>Class有啥区别呢?
先说结论:Class<?>优于Class
再说原因:Class<?>告诉了编译器,我们就是要选择非具体的类应用,而不是疏忽中忘了给定泛型。Class则明显是没加泛型了。

2.1.2 Class<? extends T>

那我们想创建一个Class类型,它被限定为某种类型,或者某种类型的子类,则应使用 ? extends T 这样的通配符。

class WildCardReferences{
    public static void main(String[] args) {
        //        Class<Number> numberClass = int.class; //incompatible types
        Class<?> integerClass = int.class;
        integerClass = double.class; //OK
    }
}

注意下:向Class添加泛型语法仅仅是为了提供编译期类型检查,但是可能有些错误直到运行时才能暴露。

public class FillList2<T> {

    private Class<T> type;
    /*通过构造函数传递一个类型参数*/
    public FillList2(Class<T> type) {
        this.type = type;
    }

    public List<T> create(int numOfElements) {
        return IntStream.range(0, numOfElements).mapToObj(u -> {
            try {
                return type.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
    }

    public List<T> create2(int num) {
        List<T> list = new ArrayList<>();
        IntStream.range(0, num).forEach(u -> {
            try {
                list.add(type.newInstance()); // 本质上是使用了无参构造器
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return list;
    }

    public static void main(String[] args) {
        FillList2<Counter> list2 = new FillList2<>(Counter.class);
        System.out.println(list2.create2(10));
        System.out.println(list2.create(10));
    }
}

/*a simple and useful counter */
class Counter{
    private static int counter;
    public  final int id = counter++;

    @Override
    public String toString() {
        return id+"";
    }
}

“暗坑”在于:::假如Class< T> type对应的类型并没有无参构造器,那在运行时会抛出异常,而在编译时是检查不出端倪。

2.1.3 newInstance()的返回值类型
public class GenericToyTest {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<Son> sonClass = Son.class;
//        返回 准确的类型
        Son son = sonClass.newInstance();

//       getSuperclass() 只能接受 ? super Son 这样的通配类型,却不能用真正的父类来接收
//        这看起来确实有点怪,因为 getSuperclass()原本就返回了 父类(而不是父接口)啊!
//        而且 利用getSuperclass()返回的Class引用最终只能用Object来接收,而不是用Father!
        Class<? super Son> superclass = sonClass.getSuperclass();
//        Class<Father> superClass1 = sonClass.getSuperclass();
        Object object = superclass.newInstance();
    }
}

class Father{}
class Son extends Father{}

–> 仔细看看 代码上的注释啦!

2.1.4 cast()

cast()方法,在无法使用普通转型时会比比较有用,这在泛型中时有用到。

public class ClassCasts {

    public static void main(String[] args) {
        Building building = new House();
        Class<House> houseClass = House.class;
        //cast:::Casts an object to the class or interface represented by this Class object.
        House house = houseClass.cast(building);
        // 实际与cast 是相同作用,但是 cast()方法,在无法使用普通转型时会比比较有用,这在泛型中时有用到
        house = ((House) building);
    }
}
class Building{}
class House extends Building{}
2.1.5 Class.asSubClass()

public <U> Class<? extends U> asSubclass(@NotNull Class<U> clazz)

关于这个API的解释:

  • Casts this Class object to represent a subclass of the class represented by the specified class object. Checks that the cast is valid, and throws a ClassCastException if it is not. If this method succeeds, it always returns a reference to this class object.
  • This method is useful when a client needs to “narrow” the type of a Class object to pass it to an API that restricts the Class objects that it is willing to accept. A cast would generate a compile-time warning, as the correctness of the cast could not be checked at runtime (because generic types are implemented by erasure).

说人话:就是把调用方Class对象 narrow 成更具体的 子类的Class对象。

三、类型转换前检查

小结一下:目前的RTTI类型有:
1) 强转、多态
2)Class类
其实还有关键字 instanceOf

四、反射

关于反射的API不再赘述。

现在来讲下动态代理。窃以为,《编程思想》用最简单的话把代理给说明白了。
就是一个对象,你希望它有额外的或者不同的操作,但是你又不想把多的操作赋予到这个对象本身(以免污染这个类),所以干脆(动态)创建一个满足要求的对象来“替代”(代理)它
再简单地说:就是想要将额外的操作从一个对象分离出去

七、JDK Dynamic Proxy

这个也是面试说烂了的东西,网上的各种demo,各种教程铺天盖地.但本质其实并不复杂, 就是利用反射 + 动态生成.class文件(你没看错就是 文件)得到代理类.

/** this illustrates a dynamic proxy*/
public interface Subject {
    void operate();
}

// 被代理的类
class RealSubject implements Subject {

    @Override
    public void operate() {
        System.out.println("I am operating:" + this.getClass().getSimpleName());
    }
}

class TestInvocationHandler implements InvocationHandler {
    private Object target; //被代理对象,也就是 RealSubject 对象

    public TestInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 执行业务代码之前的处理逻辑
        // ...
        System.out.println("I am:" + this.getClass().getSimpleName() + ", pre-doing something");
        // 注意这里就是调用了被代理类的方法
        Object result = method.invoke(target, args);

        // 执行业务代码之后的处理逻辑
        //...

        return result;
    }
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }
}
class MainFunc{
    public static void main(String[] args) {
        Object proxy = new TestInvocationHandler(new RealSubject()).getProxy();
        ((Subject) proxy).operate();
    }
}

八、空对象

九、接口与类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值