Java OO设计中的常见问题
这个与OOP编程的哲学有关, 形而上
属于一部分,形而下
属于一部分。
抽象类与实现类的变量初始化顺序问题
public abstract class Widget {
private final int cachedWidth;
private final int cachedHeight;
public Widget() {
this.cachedWidth = width();
this.cachedHeight = height();
}
protected abstract int width();
protected abstract int height();
}
public class SquareWidget extends Widget {
private final int size;
public SquareWidget(int size) {
this.size = size;
}
@Override
protected int width() {
return size;
}
@Override
protected int height() {
return size;
}
}
上述实现过程的输出都是 0
潜在问题
- 子类的构造发生在父类构造函数之后,在jvm字节码的实现也反应了这个问题。 在子类的实例化过程,先调用父类constructor,再调用子类的构造函数。
Java中的枚举类型的效率问题
- 问题
枚举类型与final 整形字段的效率比较
class Test {
// 定义两个静态final类型的字段
static final int ONE = 1;
static final int TWO = 2;
// 两个枚举类型
enum TestEnum {ONE, TWO}
public static void main(String[] args) {
testEnum();
testInteger();
time("enum", new Runnable() {
public void run() {
testEnum();
}
});
time("integer", new Runnable() {
public void run() {
testInteger();
}
});
}
// 循环 1G次 enum取值
private static void testEnum() {
TestEnum value = TestEnum.ONE;
for (int i = 0; i < 1000000000; i++) {
if (value == TestEnum.TWO) {
System.err.println("impossible");
}
}
}
// 循环 1G次 final字段
private static void testInteger() {
int value = ONE;
for (int i = 0; i < 1000000000; i++) {
if (value == TWO) {
System.err.println("impossible");
}
}
}
private static void time(String name, Runnable runnable) {
long startTime = System.currentTimeMillis();
runnable.run();
System.err.println(name + ": " + (System.currentTimeMillis() - startTime) + " ms");
}
}
输出结果
enum: 11 ms
integer: 2 ms
效率差距的原因在于编译后的指令效率上
testEnum 的字节码
getstatic #11 <com/baeldung/concurrent/callable/Test$TestEnum.ONE>
3 astore_0
4 iconst_0
5 istore_1
6 iload_1
7 ldc #12 <1000000000>
9 if_icmpge 33 (+24)
12 aload_0
13 getstatic #13 <com/baeldung/concurrent/callable/Test$TestEnum.TWO>
16 if_acmpne 27 (+11)
19 getstatic #14 <java/lang/System.err>
22 ldc #15 <impossible>
24 invokevirtual #16 <java/io/PrintStream.println>
27 iinc 1 by 1
30 goto 6 (-24)
33 return
testInteger 函数的字节码
iconst_1
1 istore_0
2 iconst_0
3 istore_1
4 iload_1
5 ldc #12 <1000000000>
7 if_icmpge 29 (+22)
10 iload_0
11 iconst_2
12 if_icmpne 23 (+11)
15 getstatic #14 <java/lang/System.err>
18 ldc #15 <impossible>
20 invokevirtual #16 <java/io/PrintStream.println>
23 iinc 1 by 1
26 goto 4 (-22)
29 return
enum中编译后的值存储在常量池中,使用getstatic取出
getstatic #13 <com/baeldung/concurrent/callable/Test$TestEnum.TWO>
final staic 直接作为coast常量存在代码区, 从上述运行结果看 这两个指令是5倍的效率差距。
字符串的应用效率问题
𝟣 String label = “TEST”;
𝟤 String label = new String(“Test”);
以上两种获取label的方式有什么区别?
- 编译后的字节码对比
0 ldc #2 <TEST> # String label = "TEST"; 直接从常量池中加载
2 astore_1
3 new #3 <java/lang/String> # 第二中方式new一个对象
6 dup
7 ldc #4 <Test> # 也从常量池中加载同一个数据
9 invokespecial #5 <java/lang/String.<init>> # 调用构造函数
12 astore_2
13 return
可见,第一种方式明显比第一种快。
- 第一种方式 直接从常量池中加载常量。
- 第二种方式 先new一个对象,然后从常量池中加载构造函数用到的字符串,最后调用构造函数。