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
好处是它更靠近数据,因为对象标识的是问题空间的实体
( 感觉没啥用,增加了编程的复杂性 )