Java 9将String底层实现从char[]改成byte[]的原因主要有以下几点:
节省内存空间:在Java 9之前,String的底层实现使用char[]来存储字符数据。每个char占用2个字节(16位),这意味着一个包含n个字符的字符串需要占用2n个字节的内存空间。而在Java 9中,String的底层实现改为使用byte[]来存储字符数据。对于ASCII字符(占1个字节),这种改变可以节省一半的内存空间。对于非ASCII字符,虽然仍然需要2个字节来表示,但在某些情况下,使用byte[]仍然可以节省内存空间。
提高性能:使用byte[]作为底层实现可以提高字符串操作的性能。因为byte[]数组的大小可以根据实际字符数据的需要动态调整,而不是固定为每个字符2个字节。这样可以减少内存分配和复制的次数,从而提高性能。
支持更多的字符集:Java 9引入了一种新的字符编码标准,即UTF-8。UTF-8是一种变长编码,可以使用1到4个字节来表示一个字符。通过使用byte[]作为底层实现,String可以更好地支持UTF-8编码,从而支持更多的字符集。
简化代码实现:在Java 9之前,String类的实现涉及到许多与char[]相关的复杂操作,例如字符数组的扩容、字符数组的复制等。通过将底层实现改为byte[],可以简化这些操作的实现,使代码更加简洁和易于维护。
总之,Java 9将String底层实现从char[]改成byte[]主要是为了节省内存空间、提高性能、支持更多的字符集以及简化代码实现。
String str = "aaa"
和 String str = new String("aaa")
在功能上是相同的,它们都创建了一个内容为 “aaa” 的字符串对象。但是,它们的创建方式和内存分配有所不同。
String str = "aaa"
: 这种方式是通过字符串字面量创建字符串对象。Java编译器会在编译时将字符串字面量放入常量池(interned strings pool)中。当执行这行代码时,JVM会检查常量池中是否已经存在内容为 “aaa” 的字符串对象,如果存在,则直接引用该对象;如果不存在,则在常量池中创建一个新的字符串对象,并将其引用赋值给变量str。
String str = new String("aaa")
: 这种方式是通过new关键字创建字符串对象。首先,JVM会检查常量池中是否已经存在内容为 “aaa” 的字符串对象,如果存在,则在堆内存中创建一个新的字符串对象,并将其引用赋值给变量str;如果不存在,则在常量池中创建一个新的字符串对象,并在堆内存中再创建一个新的字符串对象,将其引用赋值给变量str。
关于 new String("aaa")
创建了几个字符串对象的问题:
常量折叠是一种编译器优化技术,目的是减少运行时的计算开销。
在Java中,常量折叠主要指的是编译期间对常量表达式的预处理和优化。具体来说,就是在编译阶段对程序中的某些常量表达式进行求值,将计算结果直接嵌入到生成的字节码中,从而避免了在程序运行时对这些表达式的重复计算。
例如,对于代码int a = 1 + 2;
,经过常量折叠后,编译器会直接将其转换为int a = 3;
。同样地,对于字符串字面量的相加操作,如String s1 = "a" + "b";
,在编译期也会被优化为String s1 = "ab";
。这样,在运行时就不需要再进行字符串连接操作,提高了代码的执行效率。
需要注意的是,常量折叠只适用于编译时常量,也就是那些在编译时就能确定其值的表达式。这包括数字字面量、字符串字面量以及被final
修饰的字段。如果一个表达式中的变量或字段的值在编译时无法确定,那么这个表达式就无法进行常量折叠。
总的来说,常量折叠是提高程序运行效率的有效手段之一,它通过在编译阶段计算和简化表达式,减少了程序运行时的计算量和资源消耗。
代理模式是一种常用的设计模式,它通过代理类来控制对被代理类的访问。在代理模式中,代理类和被代理类通常实现相同的接口,以便客户端可以无差别地使用它们。
以下是代理模式的一些关键点:
接口实现:
引用持有:
方法调用:
代理目的:
结构:
客户端交互:
实现方式:
优点:
缺点:
在实际应用中,代理模式广泛应用于各种场景,如框架中的ORM代理、远程服务调用的代理、Spring框架的AOP代理等。通过代理模式,可以有效地分离关注点,提高系统的灵活性和可维护性。
JDK动态代理是Java中的一种代理机制,它允许在运行时动态地创建一个代理对象,用于拦截和增强目标对象的方法调用。以下是对JDK动态代理的详细分析:
代理类的结构:
java.lang.reflect.Proxy
类。InvocationHandler
的invoke
方法。代理对象的创建:
InvocationHandler
实例。java.lang.reflect.Proxy
类的静态方法newProxyInstance
来创建代理对象。方法调用的处理:
InvocationHandler
的invoke
方法。invoke
方法接收到调用请求后,可以根据需要执行一些额外的逻辑,然后决定是否调用实际的目标方法。局限性与限制:
与CGLIB动态代理的比较:
总的来说,JDK动态代理是一种基于接口的代理机制,它可以在运行时动态地创建代理对象,并允许在方法调用前后执行额外的逻辑。然而,它的局限性在于只能代理接口,并且无法直接代理私有方法。相比之下,CGLIB动态代理提供了更灵活的代理能力,但可能会引入额外的性能开销。
CGLIB(Code Generation Library)是一个强大的、高性能、高质量的Code生成类库,它广泛的被许多AOP框架使用,如Spring AOP和dynaop,为他们提供方法的拦截。
CGLIB动态代理的工作原理是通过继承被代理类来创建新的子类,在子类中实现对方法调用的拦截和增强。CGLIB通过使用一个字节码生成器来动态生成新的子类,这个子类实现了被代理类的所有方法,并提供了一个调用处理器(MethodInterceptor
),用于在方法调用前后执行自定义的逻辑。
实现方式:
代理限制:
性能:
使用场景:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBProxyExample {
public static void main(String[] args) {
// 创建目标对象
SomeClass target = new SomeClass();
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method call: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method call: " + method.getName());
return result;
}
});
// 创建代理对象
SomeClass proxy = (SomeClass) enhancer.create();
// 调用代理对象的方法
proxy.someMethod();
}
}
class SomeClass {
public void someMethod() {
System.out.println("Executing someMethod...");
}
}
在Spring框架中,可以通过配置来选择使用CGLIB动态代理:
<bean id="someBean" class="com.example.SomeClass">
<aop:scoped-proxy/>
bean>
CGLIB动态代理适用于以下场景:
性能问题:
无法代理final方法:
版本兼容性问题:
CGLIB 能够代理没有实现接口的目标对象,以及需要代理非 public 方法(包括 private、protected 和默认包访问权限的方法)的目标对象。对于 public 方法,使用 CGLIB 进行代理也是可行的,但关键在于 CGLIB 的代理机制提供了一种能力去代理那些在 JDK 动态代理中无法代理的方法。
在 CGLIB 中,当目标类没有实现任何接口时,或者你想代理的是类的非 public 方法时,CGLIB 通过生成目标类的子类来实现代理。这种代理方式允许你拦截并增强目标类的所有非 final 方法,无论这些方法是 public、protected、还是 private。
然而,对于 public 方法,如果你的目标类实现了一个或多个接口,并且你只想代理这些接口中定义的 public 方法,那么使用 JDK 动态代理通常是更好的选择,因为它更简单、性能也通常更好一些。JDK 动态代理只能代理接口中声明的方法,而不能代理类中新增的非接口方法。
总的来说,public 方法当然可以被 CGLIB 代理,但选择 CGLIB 来代理 public 方法通常是出于以下原因:
MethodInterceptor
或 CallbackFilter
等高级特性可能更加适合。因此,说 CGLIB 适用于代理非 public 方法,并不是指它不能代理 public 方法,而是强调其独特的能力在于代理那些通常无法通过标准的 JDK 动态代理来处理的方法。
参考文章—JDK动态代理和CGLIB动态代理