我写了两种方法来检查性能
public class Test1 {
private String value;
public void notNull(){
if( value != null) {
//do something
}
}
public void nullNot(){
if( null != value) {
//do something
}
}
}
并在编译后检查它的字节码
public void notNull();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: getfield #2; //Field value:Ljava/lang/String;
4: ifnull 7
7: return
LineNumberTable:
line 6: 0
line 9: 7
StackMapTable: number_of_entries = 1
frame_type = 7 /* same */
public void nullNot();
Code:
Stack=2, Locals=1, Args_size=1
0: aconst_null
1: aload_0
2: getfield #2; //Field value:Ljava/lang/String;
5: if_acmpeq 8
8: return
LineNumberTable:
line 12: 0
line 15: 8
StackMapTable: number_of_entries = 1
frame_type = 8 /* same */
}
在这里,两个操作码用于实现if条件:在第一种情况下,它使用ifnull-检查堆栈的最高值为null-,在第二种情况下,它使用if_acmpeq-检查前两个值在堆栈中是相等的 -
那么,这会对性能产生影响吗? (这将有助于我证明null的首次实现在性能方面以及在可读性方面都很好:))
比较生成的字节码大多没有意义,因为大多数优化都是在运行时使用JIT编译器进行的。我猜想在这种情况下,表达式同样快。如果有任何差异,则可以忽略不计。
这不是你需要担心的事情。寻找大图优化。
哦,如果你要求最终表现,不要创建额外的类或方法。甚至静态方法也需要一些时间,因为Java类加载器需要JIT加载它。
因此,每当您需要检查变量是否为空时,您只需测试它
null = value
要么
if (x == null)
坦率地说,我认为选择其中一个的性能加值很容易被引入不必要方法的开销所抵消。
您可以在编码期间忽略这些非常微小的优化内容
正如您所看到的,性能差异非常小。不要担心小事情,最好更多地关注算法。显然,可读性是一个因素。
我会使用“新的”Java 8功能,我写了几个例子:
if (null == x)
}
在Java-8中,import java.util.Optional;
public class SillyExample {
public void processWithValidation(final String sampleStringParameter){
final String sampleString = Optional.ofNullable(sampleStringParameter).orElseThrow(() -> new IllegalArgumentException("String must not be null"));
//Do what you want with sampleString
}
public void processIfPressent(final String sampleStringParameter){
Optional.ofNullable(sampleStringParameter).ifPresent(sampleString -> {
//Do what you want with sampleString
});
}
public void processIfPressentWithFilter(final String sampleStringParameter){
Optional.ofNullable(sampleStringParameter).filter("hello"::equalsIgnoreCase).ifPresent(sampleString -> {
//Do what you want with sampleString
});
}
类引入了另外两种方法:Objects
和Objects#nonNull,可用于替换Objects#isNull检查。有趣的是,他们都首先使用对象:
null
和
public static boolean isNull(Object obj) {
return obj == null;
}
相应地。我想这意味着这是推荐的方式(至少核心jdk开发人员使用这种方法)public static boolean nonNull(Object obj) {
return obj != null;
}
我更喜欢Objects source code,因为它清楚地显示它只是用于空检查。
字节代码只是源代码的简单翻译。
如果速度(或存储器/无论情况如何)增益可以忽略不计,则不以牺牲可读性为代价进行优化。我认为!=null
通常更具可读性,所以使用它。
有了这样的问题,很难知道JVM会有多聪明(虽然答案是“如果可能的话通常很聪明”,在这种情况下看起来非常可能)。但只是为了确定,测试一下:
class Nullcheck {
public static class Fooble { }
Fooble[] foo = {null , new Fooble(), null , null,
new Fooble(), null, null, new Fooble() };
public int testFirst() {
int sum = 0;
for (int i=0 ; i<1000000000 ; i++) if (foo[i&0x7] != null) sum++;
return sum;
}
public int testSecond() {
int sum = 0;
for (int i=0 ; i<1000000000 ; i++) if (null != foo[i&0x7]) sum++;
return sum;
}
public void run() {
long t0 = System.nanoTime();
int s1 = testFirst();
long t1 = System.nanoTime();
int s2 = testSecond();
long t2 = System.nanoTime();
System.out.printf("Difference=%d; %.3f vs. %.3f ns/loop (diff=%.3f)\n",
s2-s1,(t1-t0)*1e-9,(t2-t1)*1e-9,(t0+t2-2*t1)*1e-9);
}
public static void main(String[] args) {
Nullcheck me = new Nullcheck();
for (int i=0 ; i<5 ; i++) me.run();
}
}
在我的机器上产生:
Difference=0; 2.574 vs. 2.583 ns/loop (diff=0.008)
Difference=0; 2.574 vs. 2.573 ns/loop (diff=-0.001)
Difference=0; 1.584 vs. 1.582 ns/loop (diff=-0.003)
Difference=0; 1.582 vs. 1.584 ns/loop (diff=0.002)
Difference=0; 1.582 vs. 1.582 ns/loop (diff=0.000)
所以答案是:不,没有任何有意义的差异。 (并且JIT编译器可以在相同次数的重复运行之后找到额外的技巧来加速每个。)
更新:上面的代码运行ad-hoc基准测试。使用JMH(现在它存在!)是帮助避免(某些)微基准测试陷阱的好方法。上面的代码避免了最严重的陷阱,但它没有给出明确的错误估计,而忽略了有时重要的其他各种事情。这些天:使用JMH!此外,如有疑问,请运行自己的基准测试。细节有时很重要 - 对于像这样简单的事情来说并不常见,但如果对你来说非常重要,你应该检查一个尽可能接近生产的条件。
除了在C中避免意外赋值的来之不易的智慧,这有利于将常量放在二元运算符的左边,我发现左边的常量更具可读性,因为它将关键值放在最显着的位置。
通常一个函数体只会使用几个变量,而且通常通过上下文显而易见的是哪个变量正在检查中。通过将常量放在左边,我们更接近地模仿switch
和case
:给定此变量,选择匹配值。看到左侧的值,一个侧重于所选的特定条件。
当我扫描
if (var == null)
我读到它,“我们在这里检查var
,我们正在比较它是否平等,反对......啊,null。”相反,当我扫描
if (null == var)
我想,“我们看到一个值是否为空,并且......是的,我们正在检查它是var
。”这是一个更强烈的认可
if (null != var)
我的眼睛立即接受了。
这种直觉来自于习惯的一致性,更喜欢阅读所写的内容,以及写出喜欢阅读的内容。人们可以从任何一种方式学习它,但它不是客观真实的,因为其他人在这里回答说将变量放在左边是更清楚的。这取决于首先想要最明确的表达方式。
看到字节码差异很有吸引力。谢谢你的分享。
差异可以忽略不计,所以选择最具可读性的(qazxsw poi imo)
为了便于阅读,我坚持使用(value!= null)。但是你总是可以使用断言。
像这样的分钟优化是编译器的工作,特别是在像Java这样的高级语言中。
虽然严格说来这里没有关系,但不要过早优化!
从观点来看,性能没有显着差异。
但是,首先编写null以捕获拼写错误很有用。
例如,如果您习惯于编写此代码:
!= null
可能被误写为:
if (obj == null)
从编译器的角度来看,这很好。
但是,如果您习惯将代码编写为:
if (obj = null)
写错了:
if (null == obj)
编译器会让你知道你在那一行犯了一个错误。
将if (null = obj)
放在首位似乎会产生额外的字节码,但除此之外可能没有性能差异。
就个人而言,在担心性能之前,我不会担心性能问题。
我会使用null
方法,只是因为如果你忘记了notNull()
并且不小心输入!
,你就不会抛出编译器错误。