一、创建对象方式
- new
- 反射
- class.newInstance:调用无参构造函数创建对象(内部调用constructor.newInstance)
- constructor.newInstance:调用有参和私有的构造函数
- clone
- jvm会新建一个对象,把所有内容拷贝过去,不会调用任何构造函数
- 先实现Cloneable,重写clone方法(不重写默认调用Object类的clone方法)
- 反序列化
- 基于反射,序列化和反序列化jvm会创建一个单独的对象
- 方法句柄(谨慎)
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
public class MethodHandleExample {
public static void main(String[] args) throws Throwable {
Class<MyClass> clazz = MyClass.class;
MethodHandle constructor = MethodHandles.lookup().findConstructor(
clazz,
MethodType.methodType(void.class)
);
MyClass instance = (MyClass) constructor.invokeExact();
instance.doSomething();
}
static class MyClass {
public void doSomething() {
System.out.println("Doing something...");
}
public MyClass() {
}
}
}
- unsafe分配内存(不建议)
- sun.misc.Unsafe类进行内存操作:内存分配和对象实例化
- 不可移植性
- 安全性问题:绕过了安全机制,可能导致内存泄漏、非法访问、数据损坏
- 不符合面向对象原则
二、动态代理
- JDK
- Java.lang.reflect包中的Proxy类和InvocationHandler接口提供
- 有限制:使用动态代理的对象必须实现一个或多个接口
- Cglib
- 第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展
- 想代理没有实现接口类,用Cglib实现,达到代理类无侵入
静态代理 vs 动态代理
- 静态代理
- 编译期确定的
- 需要手写很多代码,代理类比较多或同时代理多个对象会很复杂
- 动态代理
- 运行期确定的
- 反射是动态代理的实现方式之一
- 使用动态代理可以方便地在运行期生成代理类:AOP、过滤器、拦截器
- servlet的filter、spring的aop、struts拦截器、mybatis分页插件、日志拦截、事务拦截、权限拦截
Spring AOP实现方式
三、 注解
- 为java代码提供元数据,标识一个类或字段,常和反射、AOP结合。符合条件,执行某种能力
- 元注解
- @Target(该注解用于何地,即元素类型:类、方法、字段)、@Retention(在什么级别保存该注解,指定被修饰的注解的生命周期:在源代码、编译时、运行时保存【SOURCE、CLASS(默认)、RUNTIME】)、@Documented(将此注解包含在javadoc中)@Inherited(允许子类继承父类中的注解)
四、 序列化 & 反序列化
- 对象序列化:对象持久化方式,把对象状态保存为字节数组,在需要时通过反序列化再转成对象
- 类实现了java.io.Serializable接口就可以被序列化
- 通过ObjectOutputStream、ObjectInputStream对对象进行序列化和反序列化
- 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还有就是两个类的序列化ID是否一致
- 序列化并不保存静态变量
- 想让父类对象也序列化,就让父类也实现Serializable接口
- transient 关键字:控制变量的序列化,在变量生命前加上,可以阻止该变量被序列化到文件中
- 反序列化后,transient变量被设为初始值
- 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的:密码字符串之类。希望在序列化时进行加密。客户端如果有解密的密钥,只有在客户端反序列化时,才可以对密码读取
五、枚举
- enum定义一个枚举类型
- valueOf可以自动对入参进行非法参数的校验
- 调用枚举中的方法,相对普通常量来说操作性更强
- 枚举实现接口,很容易实现策略模式
- 枚举自带属性,扩展性更强
- enum定义一个枚举类型,编译器自动帮我们创建一个final类型继承Enum类,所以枚举类型不能被继承
- 枚举的equals底层还是用了==,所以两个都可以