Java编程思想 第十四章 类型信息


(1) 运行时类型信息(RTTI,Runtime Type Information)将你从只能在编译器执行面向类型的操作的禁锢中解脱了出来,使得你可以在程序运行时发现和使用类型信息。
(2)Java如何让我们在运行时发现和使用类型信息? 传统的RTTI反射机制

14.1 为什么需要RTTI

通常我们会创建一个具体对象(Cirle,Square,Triangle),然后把它向上转型为Shape(丢失了具体类型)。并在后面的程序中使用匿名的Shape引用,Shape对象实际执行什么样的代码,是由引用所指向的具体对象所决定的。

14.2 Class对象

Class对象包含了与类有关的信息,事实上,Class对象就是用来创建类的所有常规对象的。

类是程序的一部分,每个类都有一个Class对象,每当编写并且编译一个新类,就会产生一个Class对象(存在一个同名的.class文件中)。通过JVM中被称为“类加载器”的子系统生成这个类的对象。(类加载器包含了一条类加载器链,原生类加载器通常是从本地加载类的,用户可以挂接额外的类加载器(如从数据库等途径加载类)。

所有的类都是在第一次使用时才动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这表明构造器也是类的静态成员,虽然它没有static关键字。使用new关键字构建对象也会被当作对类的静态成员的引用。

类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的加载器就会从本地查找类名.class文件,检验.class文件是否被破坏。
例1:

package com.fei.hanh;

public class AAA {
    static {
        System.out.println("AAA被加载");
    }
}
package com.fei.hanh;

public class BBB extends AAA {
    static {
        System.out.println("BBB被加载");   // 加载时会执行static语句
    }

    public static void main(String[] args) {
        try {
            Class aClass1 = Class.forName("com.fei.hanh.BBB");   // 根据包名返回一个Class对象
            Class aClass2 = Class.forName("com.fei.hanh.BBB");   // 已被加载
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

例2:

package com.fei.hanh;

public class AAA {
    void f() {
        System.out.println("f");
    }
}

package com.fei.hanh;

interface CCC {
}

public class BBB extends AAA implements CCC {

    public static void main(String[] args) {
        try {
            Class aClass1 = Class.forName("com.fei.hanh.BBB");   // AAA被加载 -> BBB被加载
            System.out.println(aClass1.getName() + "  " + aClass1.getSimpleName() + "  " + aClass1.getCanonicalName() + "  " + aClass1.isInterface());

            for (Class c : aClass1.getInterfaces()) {
                System.out.println(c.getName() + "  " + c.getSimpleName() + "  " + c.getCanonicalName() + "  " + c.isInterface());
            }

            Class superclass = aClass1.getSuperclass();
            System.out.println(superclass.getName() + "  " + superclass.getSimpleName() + "  " + superclass.getCanonicalName() + "  " + superclass.isInterface());

            AAA o = (AAA) superclass.newInstance();
            o.f();

            Class aClass = o.getClass();
            System.out.println(aClass.getName() + "  " + aClass.getSimpleName() + "  " + aClass.getCanonicalName() + "  " + aClass.isInterface());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

14.2.1 类字面常量

还可以通过类字面常量来生成对Class对象的引用,如AAA.class、int.class、char.class。它在编译时就会做检查,因此更安全、不需要置于try语句块,不需要调用forName()方法,因此更高效。

为了使用类而做的准备工作:
1、加载。由类加载器找到.class字节码文件,并从字节码文件中创建一个Class对象。
2、链接。为静态域分配空间。
3、初始化。执行超类和当前类静态初始化器和静态初始化块。

使用类字面常量不会初始化。调用static final 编译器常量不会初始化。调用forName()方法会初始化。

package com.fei.hanha;
import java.util.Random;
public class AAA {
    static final int a = 100;   // static final 编译器常量
    static final int b = new Random().nextInt(100);   // static final 非编译器常量
    static {
        System.out.println("AAA");
    }
}

package com.fei.hanha;
public class BBB {
    static final int a = 100;
    
    public static void main(String[] args) throws Exception {
        System.out.println(AAA.a);     // static final的编译器常量,不会对类进行初始化
        Class aaaClass1 = AAA.class;   // 不会对类进行初始化
        System.out.println("----------------------");
        Class aaaClass2 = Class.forName("com.fei.hanha.AAA");   // 会初始化
    }
}

14.2.2 泛化的Class引用

package com.fei.hanha;

public class AAA {
    public AAA() {}

    public String toString() {
        return "AAA";
    }
}
package com.fei.hanha;

import java.util.ArrayList;
import java.util.List;

public class FilledList<T> {
    private Class<T> type;
    public FilledList(Class<T> type) {
        this.type = type;
    }
    public List<T> create(int elment) {
        List<T> ts = new ArrayList<T>();
        try {
            for (int i = 0; i < elment; i++) {
                ts.add(type.newInstance());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return ts;
    }

    public static void main(String[] args) {
        FilledList<AAA> integerFilledList = new FilledList<AAA>(AAA.class);
        System.out.println(integerFilledList.create(20));
    }
}

getSuperclass获取超类时无法指定确切得到泛型类型

package com.fei.hanha;

public class BBB extends AAA {
    public BBB() {}

    public String toString() {
        return "BBB";
    }

    public static void main(String[] args) throws Exception {
        Class<BBB> bbbClass = BBB.class;   // 泛型指定为BBB
        BBB bbb = bbbClass.newInstance();

        Class<? super BBB> superclass = bbbClass.getSuperclass();   // 只能指定是BBB的超类
        Object object = superclass.newInstance();   // 用Object类型
    }
}

14.2.3 新的转型语法

Java SE5中增加了cast()方法用于转型,但是用的地方很少。
还有Class.asSubclass()。

package com.fei.hanha;

public class BBB extends AAA {
    public BBB() {

    }

    public String toString() {
        return "BBB";
    }

    public static void main(String[] args) throws Exception {
        AAA aaa = new BBB();
        BBB bbb = (BBB) aaa;   // 普通转型
        bbb = BBB.class.cast(aaa);   // cast()方法接受参数对象,并将其转型为Class引用的类型
    }
}

14.3 类型转换前先做检查

RTTI形式:
(1)传统的类型转换。在编译期,如果想向下转型,需要使用显式的类型转换,告诉编译器你拥有额外的信息知道该类型是某种特定类型。

(3)关键字instanceof。返回一个布尔值,告诉我们是不是某个特定类型的实例。在向下转型前,如果不能确认类型,最好使用instanceof判别,避免产生ClassCastException。

package com.fei.hanha;
class BBB {
    public void f() {
        System.out.println("BBB");
    }
}

public class AAA {
    public void f() {
        System.out.println("AAA");
    }

    public static void main(String[] args) {
        Object aaa = new AAA();
        if (aaa instanceof BBB) {
            ((BBB)aaa).f();
        }  else if (aaa instanceof AAA) {
            ((AAA)aaa).f();
        }
    }
}

14.3.1 动态的instanceof

不用instanceof一个个判断,可以将class对象放到列表中,循环调用Class.isInstance()判别。

package com.fei.hanha;

import java.util.ArrayList;
import java.util.Arrays;

public class AAA extends CCC {
    public void f() {
        System.out.println("AAA");
    }

    public static void main(String[] args) throws Exception {
        Object aaa = new AAA();
        ArrayList<Class<? extends CCC>> classes = new ArrayList<Class<? extends CCC>>(Arrays.asList(AAA.class,BBB.class));    // 使用类字面常量组成列表
        for (Class<? extends CCC> c : classes) {   
            if (c.isInstance(aaa)) {   // 判断是否为实例
                c.cast(aaa).f();   // 向下转型调用f()
            }
        }
    }
}
package com.fei.hanha;

public class BBB extends CCC {
    public void f() {
        System.out.println("BBB");
    }
}
package com.fei.hanha;

public class CCC {
    public void f() {
        System.out.println("CCC");
    }
}

14.4 注册工厂

14.4.1 实例

生产者接口
package com.fei.hanha;
public interface Factory<T> {
    T create();
}
生产者A
package com.fei.hanha;
public class A extends Part {
    public static class Factory implements com.fei.hanha.Factory<A> {
        public A create() {
            return new A();
        }
    }
}
生产者B
package com.fei.hanha;
public class B extends Part{
    public static class Factory implements com.fei.hanha.Factory<B> {
        public B create() {
            return new B();
        }
    }
}
生产者C
package com.fei.hanha;
public class C extends Part {
    public static class Factory implements com.fei.hanha.Factory<C> {
        public C create() {
            return new C();
        }
    }
}
工厂
package com.fei.hanha;

import java.util.HashMap;
import java.util.Map;

public class Part {
    static Map<String,Factory<? extends Part>> partFactories = new HashMap<String,Factory<? extends Part>>();
    static {
        partFactories.put("A",new A.Factory());
        partFactories.put("B",new B.Factory());
        partFactories.put("C",new C.Factory());
    }

    public static Part getProduct(String name) {
        if (partFactories.get(name) == null) {
            System.out.println("输入异常");
            return new Part();
        }
        return partFactories.get(name).create();
    }

    public String toString() {
        return getClass().getSimpleName();
    }
}
消费者
package com.fei.hanha;
public class GetProduct {
    public static void main(String[] args) {
        Part a = Part.getProduct("A");
        System.out.println(a);
        Part b = Part.getProduct("B");
        System.out.println(b);
        Part c = Part.getProduct("C1");
        System.out.println(c);
    }
}

14.5 instanceof与Class的等价性

instanceof / isInstance:你是这个类或是这个类的派生类么?
equals:你是这个类么?

package com.fei.hanhan;

public class BBB {
    public static void main(String[] args) {
        System.out.println(new AAA() instanceof BBB);
        System.out.println(BBB.class.isInstance(new AAA()));
        System.out.println("========================================");
//        System.out.println(new AAA().getClass() == AAA.class);
//        System.out.println(new BBB().getClass() == BBB.class);
//        System.out.println(new AAA().getClass() == BBB.class);   // 报错
//        System.out.println(new BBB().getClass() == AAA.class);   // 报错
        System.out.println("========================================");
        System.out.println(new AAA().getClass().equals(AAA.class));
        System.out.println(new AAA().getClass().equals(BBB.class));
        System.out.println(new BBB().getClass().equals(BBB.class));
        System.out.println(new BBB().getClass().equals(AAA.class));
    }
}

14.6 反射

反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

14.6.1 字段

获取字段
package com.fei.hanhan;

import java.lang.reflect.Field;

public class Student extends Person {
    private int grade;
    private String grade2;
    String name;

    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        Field[] declaredFields = studentClass.getDeclaredFields();
        Field[] fields = studentClass.getFields();
        for (Field declaredField : declaredFields) {    // 所有当前类的字段
            System.out.println(declaredField + "/" + declaredField.getName() + "/" + declaredField.getType() + "/" + declaredField.getModifiers());
        }
        System.out.println("=======================");
        for (Field field : fields) {   // 所有当前类和父类的public字段
            System.out.println(field + "/" + field.getName() + "/" + field.getType() + "/" + field.getModifiers());
        }
        System.out.println("=======================");
        System.out.println(studentClass.getDeclaredField("grade"));   // 指定当前类的字段
        System.out.println(studentClass.getField("age"));  // 指定当前类和父类的public字段
    }
}
获取字段值
package com.fei.hanhan;
import java.lang.reflect.Field;

public class Student extends Person {
    public static void main(String[] args) throws Exception {
        try {
            System.out.println(Person.class.getDeclaredField("name").get(new Person("Han")));  // 获取指定实例的字段值
        } catch (IllegalAccessException e) {
            Field name = Person.class.getDeclaredField("name");
            //  无法访问Person的private字段,除非用此行设置为一律允许访问
            // setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。
            // 例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全
            name.setAccessible(true);
            System.out.println(name.get(new Person("Han")));
        }
    }
}
设置字段值
package com.fei.hanhan;
import java.lang.reflect.Field;

public class Student extends Person {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Han");
        try {
            System.out.println(Person.class.getDeclaredField("name").get(p));
        } catch (IllegalAccessException e) {
            Field name = Person.class.getDeclaredField("name");
            name.setAccessible(true);
            System.out.println(name.get(p));
            name.set(p,"Fei");   // 设置值
            System.out.println(name.get(p));
        }
    }
}

14.6.2 方法

获取方法
package com.fei.hanhan;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Student extends Person {
    private int grade;
    private String grade2;
    String name;

    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        Method[] declaredMethods = studentClass.getDeclaredMethods();
        Method[] Methods = studentClass.getMethods();
        for (Method declaredMethod : declaredMethods) {    // 所有当前类的方法
            System.out.println(declaredMethod + "/" + declaredMethod.getName() + "/" + declaredMethod.getReturnType() + "/" + declaredMethod.getParameterTypes() + "/" + declaredMethod.getModifiers());
        }
        System.out.println("===================================");
        for (Method Method : Methods) {   // 所有当前类和父类的public方法
            System.out.println(Method + "/" + Method.getName() + "/" + Method.getReturnType() + "/" + Method.getParameterTypes() + "/" + Method.getModifiers());
        }
    }
}
调用方法
package com.fei.hanhan;

import java.lang.reflect.Method;

public class Student extends Person {
    private int grade;
    private String grade2;
    String name;

    public String f1(String a,String b) {
        return a + " " + b;
    }

    public static String f1(String a) {
        return a;
    }

    public String f1() {
        return "student";
    }

    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        Method method = studentClass.getDeclaredMethod("f1", String.class, String.class);  // 方法名 方法参数类型
        String s = (String) method.invoke(new Student(),"aaa","bbb");   // 填写实例 参数值
        System.out.println(s);

        Method method2 = studentClass.getDeclaredMethod("f1", String.class);
        String s2 = (String) method2.invoke(null,"aaa");   // 调用静态方法时,实例为null
        System.out.println(s2);

        Method method3 = studentClass.getDeclaredMethod("f1");
        method3.setAccessible(true);   // 非public方法
        String s3 = (String) method3.invoke(new Student());
        System.out.println(s3);

        Method method4 = Person.class.getDeclaredMethod("f1");
        String s4 = (String) method4.invoke(new Student());   // 仍支持多态
        System.out.println(s4);
    }
}

14.6.3 构造方法

package com.fei.hanhan;

import java.lang.reflect.Constructor;

public class Student extends Person {
    public Student(String name) {
        System.out.println(name);
    }

    private Student(int age) {
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
        Constructor<?>[] constructors = Student.class.getConstructors();   // public 的
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("===============================");
        Constructor<?>[] declaredConstructors = Student.class.getDeclaredConstructors();   // 所有
        for (Constructor constructor : declaredConstructors) {
            System.out.println(constructor);
        }
        System.out.println("===============================");
        Constructor<Student> constructor = Student.class.getConstructor(String.class);
        Student han = constructor.newInstance("Han");
    }
}

14.7 RMI

https://www.cnblogs.com/binarylei/p/14110008.html
人们想要在运行时获取类的信息(使用反射)的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力。这被称为远程方法调用(RMI)。

Java的RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。RMI是Remote Method Invocation的缩写。

package com.fei.hanhanh;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.time.LocalDateTime;

/**
 * Java提供了RMI实现远程方法调用:
 * RMI通过自动生成stub和skeleton实现网络调用,客户端只需要查找服务并获得接口实例,服务器端只需要编写实现类并注册为服务;
 * RMI的序列化和反序列化可能会造成安全漏洞,因此调用双方必须是内网互相信任的机器,不要把1099端口暴露在公网上作为对外服务。
 * Java的RMI规定此接口必须派生自java.rmi.Remote,并在每个方法声明抛出RemoteException。
 */
public interface WorldClock extends Remote {
    LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}

package com.fei.hanhanh;

import java.rmi.RemoteException;
import java.time.LocalDateTime;
import java.time.ZoneId;

public class WorldClockService implements WorldClock {
    @Override
    public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
        return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
    }
}

package com.fei.hanhanh;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class Server {
    public static void main(String[] args) throws RemoteException {
        System.out.println("create World clock remote service...");
        // 实例化一个WorldClock:
        WorldClock worldClock = new WorldClockService();
        // 将此服务转换为远程服务接口:
        WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
        // 将RMI服务注册到1099端口:
        Registry registry = LocateRegistry.createRegistry(1099);
        // 注册此服务,服务名为"WorldClock":
        registry.rebind("WorldClock", skeleton);
    }
}

package com.fei.hanhanh;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.time.LocalDateTime;

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        // 连接到服务器localhost,端口1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 查找名称为"WorldClock"的服务并强制转型为WorldClock接口:
        WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
        // 正常调用接口方法:
        LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
        // 打印调用结果:
        System.out.println(now);
    }
}

14.8 动态代理

14.8.1 静态代理

如果想要增强ReadImpl的read功能,但是不改变ReadImpl的代码,需要一个ReadProxy,通过获取ReadImpl的对象增强功能。

package com.fei.hanhanha;

public interface Read {
    public void read();
}
package com.fei.hanhanha;

public class ReadImpl implements Read {
    public void read() {
        System.out.println("read....");
    }
}
package com.fei.hanhanha;

public class ReadProxy implements Read {
    ReadImpl pi;

    public ReadProxy(ReadImpl pi) {
        this.pi = pi;
    }

    public void read() {
        System.out.println("eat....");
        pi.read();
        System.out.println("sleep....");
    }

    public static void main(String[] args) {
        ReadProxy readProxy = new ReadProxy(new ReadImpl());
        readProxy.read();
    }
}

14.8.2 动态代理

https://blog.csdn.net/mhmyqn/article/details/48474815
在静态代理中,ReadImpl和ReadProxy都实现了Read接口。如果修改Read接口,ReadProxy也需要修改。

// 接口
package com.fei.hanhanha;

public interface Read {
    String read(String bookName);
}

// 实现类
package com.fei.hanhanha;

public class ReadImpl implements Read {
    public String read() {
       return "B";
    }
}
// 处理器
package com.fei.hanhanha;

public class ReadProxy implements InvocationHandler {
    ReadImpl pi;

    public ReadProxy(ReadImpl pi) {
        this.pi = pi;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	    System.out.println("代理开始"); 
       	String re = (String) method.invoke(pi, args);   // method为代理类调用的方法,args为方法传入的参数
       	System.out.println("代理结束"); 
		return re;
    }
}
```java
package com.fei.hanhanha;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ReadProxy {
    
    public static void main(String[] args) {
        Read r = (Read) Proxy.newProxyInstance(Read.class.getClassLoader(),new Class[]{Read.class}, new ReadProxy(new ReadImpl()));
        String s = r.read("编程思想");
        System.out.println(s); 
    }
}

14.9 空对象

14.10 接口与类型信息

package com.fei.hanhanhan;
import com.fei.hanha.B;

public class BBB {
    public static void main(String[] args) {
    	A a = new B();
    	a.f();
    	a.g();
    	
    	if (a instanceof B) {
	    	B b = (B) a;   // 可以通过RTTI强转调用h()
	        a.f();
	        a.g();
	        a.h();
    	}
    }
}

如果不想让用户调用到h()

package com.fei.hanhanha;

public interface A {
    public void f();
    public void g();
}
package com.fei.hanhanha;

public class HiddenC {
    public static class C implements A {   // private的内部类
        public void f() {
            System.out.println("f");
        }
        public void g() {
            System.out.println("g");
        }
        public void h() {
            System.out.println("h");
        }
    }

    public static A getA() {
        return new C();
    }
}

但是还是可以通过反射调用到

package com.fei.hanhanha;
import java.lang.reflect.Method;

public class B {
    public static void main(String[] args) throws Exception {
        A a = HiddenC.getA();
        Method h = a.getClass().getDeclaredMethod("h");
        h.setAccessible(true);
        h.invoke(a);
    }
}

即使只给用户编译后的代码,也可以通过反编译知道有哪些private成员

javap -private HiddenC
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值