使用try-catch over条件来安全地设置值,并在java中以最小的性能影响

Using try-catch over if conditions to safely set values with minimum performance impact in java

在这里,我的主要目标是安全地设置值,而不会产生性能(速度,内存,CPU等)影响。

我有一个愚蠢的选择(坏的风格)也在下面提到。
那么,最好的方法是什么? 选项1? 选项2? 还是另一个?

选项1 :

1
2
3
4
5
6
7
8
if(
    animalData!=null &&
    animalData.getBreedData()!=null &&
    dogx.getBreed() != null && dogx.getBreed().getBreedCode() != null &&
    animalData.getBreedData().get(dogx.getBreed().getBreedCode()) != null
){
    dogx.getBreed().setBreedId(animalData.getBreedData().get(dogx.getBreed().getBreedCode()));
}

选项2:

1
try{dogx.getBreed().setBreedId(animalData.getBreedData().get(dogx.getBreed().getBreedCode()));}catch(Exception e){}

注意:这段代码是一个循环,有数千次迭代。


检查null是唯一与Java异常原理一致的选项。

NullPointerExceptionRuntimeException,这意味着它是为报告编程错误而设计的。这使得将它用于除终止程序之外的任何事情都是非常糟糕的做法。

您可以通过存储对null -checking的对象的引用来优化代码,以便以后可以重用它们:

1
2
3
4
5
6
7
8
9
10
11
12
BreedData breedData;
DogBreed dogBreed;
String breedCode;
String breedId;
if( animalData != null
&&  (breedData = animalData.getBreedData())!=null
&&  (dogBreed = dogx.getBreed()) != null
&&  (breedCode = dogx.getBreed().getBreedCode()) != null
&&  (breedId = breedData.get(breedCode)) != null
) {
    dogBreed.setBreedId(breedId);
}


选项3:

1
2
3
4
5
6
7
8
9
10
11
12
13
Optional.ofNullable(animalData)
    .map(animalData -> animalData.getBreedData())
    .ifPresent(breedData -> {
        Optional.ofNullable(dogx.getBreed())
            .map(breed -> breed.getBreedCode())
            .ifPresent(breedCode -> {
                thisBreedData = breedData.get(breedCode); // here we could use a third Optional call…
                if (thisBreedData != null) {
                    dogx.getBreed().setBreedId(thisBreedData));
                }
            })
    });
}


以上答案实际上没有回答你的问题。所以这是我的:

如果性能对您来说真的很重要 - 删除null检查可能是个好主意。许多高性能库使用这种方法,例如,这里是来自HikariCP(最快的java数据库连接池)的FastList类代码:

1
2
3
4
5
6
7
8
   public boolean add(T element)
   {
      try {
         elementData[size++] = element;
      } catch (ArrayIndexOutOfBoundsException e) {
          ...
      }
}

添加了此try catch作为范围检查的替换。删除边界检查实际上使这个代码更快。

以下是证明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
@Measurement(iterations = 20, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
public class BoundsCheck {

    @Param("1")
    int index;
    int[] ar;

    @Setup
    public void setup() {
        ar = new int[] {1, 2};
    }

    @Benchmark
    public int boundCheck() {
        if (index >= ar.length) {
           throw new IndexOutOfBoundsException();
        }
        return ar[index];
    }

    @Benchmark
    public int noCheck() {
        return ar[index];
    }

    @Benchmark
    public int noCheckWithTryCatch() {
        try {
            return ar[index];
        } catch (RuntimeException e) {
            return -1;
        }
    }
}

结果:

1
2
3
4
Benchmark                        (index)   Mode  Cnt       Score       Error  Units
BoundsCheck.boundCheck                 1  thrpt   20  334197.761 ±  2593.034  ops/s
BoundsCheck.noCheck                    1  thrpt   20  370148.527 ±  9016.179  ops/s
BoundsCheck.noCheckWithTryCatch        1  thrpt   20  371782.744 ± 17290.347  ops/s

我们在这里能看到什么?消除边界检查可使您获得+ 10%的性能提升。 try {} catch在抛出异常之前不会花费任何费用。

但是,这种方法仅适用于您可以保证数据中没有NPE的情况。因此异常实际上永远不会被抛出或抛出很少。否则,异常抛出可能会使您的代码更慢。

这里没有确切的答案。这实际上取决于您的数据,您需要知道您的数据才能得出任何进一步的结论。

另外,请记住,JIT可以在代码中尽可能地消除null的检查,因此这种优化可能不值得。只有真实数据的测试才能给你一个答案。