考虑这种方法(仅用于说明):
boolean isSmallNumber(String s) {
return (n in ["one", "two", "three", "four"]);
}
当然,这不是 Java ,但它可能是您最喜欢的支持集合文字的替代语言,例如 Groovy 或 Kotlin 。表达式非常简洁,就像字符串文字一样,允许编译器将集合文字放在某个静态存储区域(甚至可能是"intern()"
)。
现在输入 Java 9 :
boolean isSmallNumber(String s) {
return Set.of("one", "two", "three", "four").contains(s);
}
这也很简洁,但遗憾的是,每次调用它时,它都会在堆上分配一个新的Set,然后立即将其用于垃圾回收。
当然,您可以定义一个集合常量:
private static final Set<String> SMALL_NUMBERS = Set.of(...);
但是,这个定义可能距离大类中的方法定义一千行,并且您可能无法想到一个好的描述性名称,而文字可能更清晰(在这个假设的情况下)。
所以,如果我在方法中使用Set.of(...)
,那么 JIT 编译器会在每次调用方法时优化新对象的创建吗?
答案 0 :(得分:15)
我制作了一个简单的JMH基准:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Temp {
private Object value;
@Setup
public void setUp() {
value = 50;
}
@Benchmark
public boolean list1() {
return List.of("one").contains(value);
}
@Benchmark
public boolean list2() {
return List.of("one", "two").contains(value);
}
@Benchmark
public boolean list3() {
return List.of("one", "two", "three").contains(value);
}
@Benchmark
public boolean list4() {
return List.of("one", "two", "three", "four").contains(value);
}
@Benchmark
public boolean set1() {
return Set.of("one").contains(value);
}
@Benchmark
public boolean set2() {
return Set.of("one", "two").contains(value);
}
@Benchmark
public boolean set3() {
return Set.of("one", "two", "three").contains(value);
}
@Benchmark
public boolean set4() {
return Set.of("one", "two", "three", "four").contains(value);
}
}
在使用-prof gc
运行基准测试后,我可以得出以下结论:JIT优化list1
,list2
,set1
,set2
,但不是{ {1}},list3
,list4
,set3
[1]
这似乎完全合理,因为set4
N >= 3
/ listN
创建了比setN
更复杂的List
/ Set
实现。
N <= 2
实现2个元素:
List
static final class List2<E> extends AbstractImmutableList<E> {
private final E e0;
private final E e1;
...
}
实现3个或更多元素:
List
static final class ListN<E> extends AbstractImmutableList<E> {
private final E[] elements;
...
}
包含另一个间接层(数组),这显然使得逃逸分析变得更加困难。
JMH输出(略微改为适合页面):
ListN
[1] Java HotSpot(TM)64位服务器VM(内置9 + 181,混合模式)