What does the Java assert keyword do, and when should it be used?

前端 未结 19 1487
眼角桃花
眼角桃花 2020-11-22 15:58

What are some real life examples to understand the key role of assertions?

相关标签:
19条回答
  • 2020-11-22 16:28

    Here's an assertion I wrote in a server for a Hibernate/SQL project. An entity bean had two effectively-boolean properties, called isActive and isDefault. Each could have a value of "Y" or "N" or null, which was treated as "N". We want to make sure the browser client is limited to these three values. So, in my setters for these two properties, I added this assertion:

    assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;
    

    Notice the following.

    1. This assertion is for the development phase only. If the client sends a bad value, we will catch that early and fix it, long before we reach production. Assertions are for defects that you can catch early.

    2. This assertion is slow and inefficient. That's okay. Assertions are free to be slow. We don't care because they're development-only tools. This won't slow down the production code because assertions will be disabled. (There's some disagreement on this point, which I'll get to later.) This leads to my next point.

    3. This assertion has no side effects. I could have tested my value against an unmodifiable static final Set, but that set would have stayed around in production, where it would never get used.

    4. This assertion exists to verify the proper operation of the client. So by the time we reach production, we will be sure that the client is operating properly, so we can safely turn the assertion off.

    5. Some people ask this: If the assertion isn't needed in production, why not just take them out when you're done? Because you'll still need them when you start working on the next version.

    Some people have argued that you should never use assertions, because you can never be sure that all the bugs are gone, so you need to keep them around even in production. And so there's no point in using the assert statement, since the only advantage to asserts is that you can turn them off. Hence, according to this thinking, you should (almost) never use asserts. I disagree. It's certainly true that if a test belongs in production, you should not use an assert. But this test does not belong in production. This one is for catching a bug that's not likely to ever reach production, so it may safely be turned off when you're done.

    BTW, I could have written it like this:

    assert value == null || value.equals("Y") || value.equals("N") : value;
    

    This is fine for only three values, but if the number of possible values gets bigger, the HashSet version becomes more convenient. I chose the HashSet version to make my point about efficiency.

    0 讨论(0)
  • 2020-11-22 16:29

    Here's another example. I wrote a method that finds the median of the values in two sorted arrays. The method assumes the arrays are already sorted. For performance reasons, it should NOT sort the arrays first, or even check to ensure they're sorted. However, it's a serious bug to call this method with unsorted data, and we want those bugs to get caught early, in the development phase. So here's how I handled those seemingly conflicting goals:

    public static int medianOf(int[] a, int[] b) {
      assert assertionOnlyIsSorted(a);      // Assertion is order n
      assert assertionOnlyIsSorted(b);
      ... // rest of implementation goes here. Algorithm is order log(n)
    }
    
    public static boolean assertionOnlyIsSorted(int[] array) {
      for (int i=1; i<array.length; ++i) {
        if (array[i] < array[i-1]) {
          return false;
        }
        return true;
      }
    }
    

    This way, the test, which is slow, is only performed during the development phase, where speed is less important than catching bugs. You want the medianOf() method to have log(n) performance, but the "is sorted" test is order n. So I put it inside an assertion, to limit its use to the development phase, and I give it a name that makes it clear it's not suitable for production.

    This way I have the best of both worlds. In development, I know that any method that calls this incorrectly will get caught and fixed. And I know that the slow test to do so won't affect performance in production. (It's also a good illustration of why you want to leave assertions off in production, but turn them on in development.)

    0 讨论(0)
  • 2020-11-22 16:30

    A lot of good answers explaining what the assert keyword does, but few answering the real question, "when should the assert keyword be used in real life?"

    The answer: almost never.

    Assertions, as a concept, are wonderful. Good code has lots of if (...) throw ... statements (and their relatives like Objects.requireNonNull and Math.addExact). However, certain design decisions have greatly limited the utility of the assert keyword itself.

    The driving idea behind the assert keyword is premature optimization, and the main feature is being able to easily turn off all checks. In fact, the assert checks are turned off by default.

    However, it is critically important that invariant checks continue to be done in production. This is because perfect test coverage is impossible, and all production code will have bugs which assertions should help to diagnose and mitigate.

    Therefore, the use of if (...) throw ... should be preferred, just as it is required for checking parameter values of public methods and for throwing IllegalArgumentException.

    Occasionally, one might be tempted to write an invariant check that does take an undesirably long time to process (and is called often enough for it to matter). However, such checks will slow down testing which is also undesirable. Such time-consuming checks are usually written as unit tests. Nevertheless, it may sometimes make sense to use assert for this reason.

    Do not use assert simply because it is cleaner and prettier than if (...) throw ... (and I say that with great pain, because I like clean and pretty). If you just cannot help yourself, and can control how your application is launched, then feel free to use assert but always enable assertions in production. Admittedly, this is what I tend to do. I am pushing for a lombok annotation that will cause assert to act more like if (...) throw .... Vote for it here.

    (Rant: the JVM devs were a bunch of awful, prematurely optimizing coders. That is why you hear about so many security issues in the Java plugin and JVM. They refused to include basic checks and assertions in production code, and we are continuing to pay the price.)

    0 讨论(0)
  • 2020-11-22 16:30

    Assertions are disabled by default. To enable them we must run the program with -ea options (granularity can be varied). For example, java -ea AssertionsDemo.

    There are two formats for using assertions:

    1. Simple: eg. assert 1==2; // This will raise an AssertionError.
    2. Better: assert 1==2: "no way.. 1 is not equal to 2"; This will raise an AssertionError with the message given displayed too and is thus better. Although the actual syntax is assert expr1:expr2 where expr2 can be any expression returning a value, I have used it more often just to print a message.
    0 讨论(0)
  • 2020-11-22 16:32

    assert is a keyword. It was introduced in JDK 1.4. The are two types of asserts

    1. Very simple assert statements
    2. Simple assert statements.

    By default all assert statements will not be executed. If an assert statement receives false, then it will automatically raise an assertion error.

    0 讨论(0)
  • 2020-11-22 16:34

    What does the assert keyword in Java do?

    Let's look at the compiled bytecode.

    We will conclude that:

    public class Assert {
        public static void main(String[] args) {
            assert System.currentTimeMillis() == 0L;
        }
    }
    

    generates almost the exact same bytecode as:

    public class Assert {
        static final boolean $assertionsDisabled =
            !Assert.class.desiredAssertionStatus();
        public static void main(String[] args) {
            if (!$assertionsDisabled) {
                if (System.currentTimeMillis() != 0L) {
                    throw new AssertionError();
                }
            }
        }
    }
    

    where Assert.class.desiredAssertionStatus() is true when -ea is passed on the command line, and false otherwise.

    We use System.currentTimeMillis() to ensure that it won't get optimized away (assert true; did).

    The synthetic field is generated so that Java only needs to call Assert.class.desiredAssertionStatus() once at load time, and it then caches the result there. See also: What is the meaning of "static synthetic"?

    We can verify that with:

    javac Assert.java
    javap -c -constants -private -verbose Assert.class
    

    With Oracle JDK 1.8.0_45, a synthetic static field was generated (see also: What is the meaning of "static synthetic"?):

    static final boolean $assertionsDisabled;
      descriptor: Z
      flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
    

    together with a static initializer:

     0: ldc           #6                  // class Assert
     2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
     5: ifne          12
     8: iconst_1
     9: goto          13
    12: iconst_0
    13: putstatic     #2                  // Field $assertionsDisabled:Z
    16: return
    

    and the main method is:

     0: getstatic     #2                  // Field $assertionsDisabled:Z
     3: ifne          22
     6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
     9: lconst_0
    10: lcmp
    11: ifeq          22
    14: new           #4                  // class java/lang/AssertionError
    17: dup
    18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
    21: athrow
    22: return
    

    We conclude that:

    • there is no bytecode level support for assert: it is a Java language concept
    • assert could be emulated pretty well with system properties -Pcom.me.assert=true to replace -ea on the command line, and a throw new AssertionError().
    0 讨论(0)
提交回复
热议问题