Java Class
Outline
- RTTI
- Class对象
- 类型转换前先做检查
- 反射
- 动态代理
- 空对象
ref : Thinking in Java
RTTI
- Java使用Class对象执行其RTTI
- Java类在必须时才加载
- 当程序创建第一个对类的静态成员的引用时,就会加载这个类
- 因此构造器也是静态成员
- 当程序创建第一个对类的静态成员的引用时,就会加载这个类
类加载
- 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
- 类加载只有一次
初始化
- 静态变量初始化
static final变量不会初始化,直接赋值
- 静态变量赋值
- 执行静态代码块
1 | public class TestClassLoader |
上述程序输出为:
1 | 1: j i=0 n=0 |
Class loader:
先检查这个类的
Class对象是否被加载- 若未加载
一旦
Class对象加载入内润,就被用来创建该类的所有对象
Class对象
生成类对象的引用:
`
Class.forName(String className)静态方法: 返回该类的Class引用,具有副作用,如果该类还没有加载就会加载该类。该方法会抛出异常。1
Class t = Class.forName("java.lang.Thread")- Params: className – the fully qualified name of the desired class.
- Returns: the Class object for the class with the specified name.
- Throws: LinkageError – if the linkage fails ExceptionInInitializerError – if the initialization provoked by this method fails ClassNotFoundException – if the class cannot be located
Class.class( 类字面常量 ): 返回类对象的引用,没有副作用object.getClass(): 获得对象的确切类型的Class引用
类名:
object.getName(): 返回全限定名
object.getSimpleName(): 返回不含包名的类名
object.getCanonicalName(): 返回全限定名
创建对象:
Class.newInstance()实例方法:“虚拟构造器”,能且仅能调用该类的public无参数构造方法
类型转换前先做检查
Class引用可以指向别的Class对象,这个错误在编译期不会发现。 使用泛型语法可以在编译期执行类型检查:
1 | Class intClass = int.class; |
判断类型是否兼容:
- ```java if( Object obj_a instanceof Class class_b){ }
1
2
3
* ```java
public boolean isInstance(Object o);
判断类型是否相等:
equals()==
Reflection
利用反射来查看类
getDeclaredXX() 方法可以无视访问权限
得到方法:
Method getMethod(name, Class...):获取某个public的Method(包括父类)Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)Method[] getMethods():获取所有public的Method(包括父类)Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
得到字段:
Field getField(name):根据字段名获取某个public的field(包括父类)Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)Field[] getFields():获取所有public的field(包括父类)Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
访问构造方法:
getConstructor(Class...):获取某个public的Constructor;getDeclaredConstructor(Class...):获取某个Constructor;getConstructors():获取所有public的Constructor;getDeclaredConstructors():获取所有Constructor。
得到继承关系:
Class getSuperclass():获取父类类型;Class[] getInterfaces():获取当前类实现的所有接口。
通过
Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
例子: 打印类的方法和构造方法:
1 | public class ShowMethods { |
利用反射来修改类
反射可以绕过几乎所有障碍(访问权限)来得到类的信息、修改类,
即使只发布字节码文件,依然可以通过javap这样的反编译工具查看源代码
javap -p: 显示所有成员,包括私有成员
下面给出两种防御措施,并给出破解方案, 基本思路是:
- 只要得到对象,就可以调用
getClass()得到其实际类型(破解了类访问权限), - 再用
getDeclaredXXX()得到其所有字段/方法(破解了字段/方法访问权限) - 对非public字段/方法/构造方法的访问、修改,都要先
setAccessible(true)- 有些jvm有security manager,可能会拒绝
setAccessible(true)
- 有些jvm有security manager,可能会拒绝
- 接下来就可以设置字段:
Field.set(Object, Object),其中第一个Object参数是指定的实例,第二个Object参数是待修改的值final字段在被修改时是安全的, 运行时系统会在不抛任何异常的情况下接受修改尝试,但实际上不会发生任何修改
隐瞒类访问权限
防御措施
接口A只有方法f():
1 | package typeinfo.packageaccess; |
只暴露HiddenC一个类,它产生A类型(实际是C类型)的对象, 但是调用者无法调用A接口之外的方法, 因为类名C是不可见的, 因此理论上g(),u()等不应该能被调用
1 | class C implements A { |
破解方案
得到a的实际类型,然后进行方法调用
1 | static void callHiddenMethod( Object a, String method_name ) throws Exception |
主程序
1 | package typeinfo.HiddenImplementation; |
隐瞒字段访问权限
破解方法:
通过getDeclaredField(field_name) 就可以得到私有字段
后续可以setAccessible(true),set(Object, Object)对字段进行修改( final字段不可被修改,因此是安全的 )
1 | WithPrivateFinalField pf = new WithPrivateFinalField(); |
示例:
防御措施
1 | class WithPrivateFinalField{ |
破解方案
1 | public class ModifyingPrivateFields { |
javap查看源代码
可以看到,我们能看到private字段,因此保护是没有用的
1 | ❯ javap -p /home/lyk/Projects/java_learning/out/production/java_learning/WithPrivateFinalField.class |
动态代理
1 | class DynamicProxyHandler implements InvocationHandler{ |
在运行期动态创建一个interface实例的方法如下:
定义一个
InvocationHandler实例,它负责实现接口的方法调用;通过
1
Proxy.newProxyInstance()
创建
1
interface
实例,它需要3个参数:
- 使用的
ClassLoader,通常就是接口类的ClassLoader; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler实例。
- 使用的
将返回的
Object强制转型为接口。
空对象
空对象,相比null好处是它更靠近数据,因为对象标识的是问题空间的实体
( 感觉没啥用,增加了编程的复杂性 )