文章目录
(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