This chapter discusses several aspects of method design: how to handle parameters and return values, how to design method signatures, and how to document methods. Much of this chapter applies to constructors as well as to ordinary methods. Like Chapter 4, the focus of this chapter is on usability, robustness, and flexibility.
Most methods and constructors have certain restrictions on the parameter values ??passed to them. For example, the index value must be non-negative, the object reference cannot be null, etc. These are all very common. You should clearly indicate these restrictions in the documentation and enforce these restrictions by checking parameters at the beginning of the method body. It is a special case of the general principle that errors should be detected as soon as possible after they occur. If this is not done, it is less likely that an error will be detected, and even if an error is detected, it will be more difficult to determine the source of the error.
If you pass invalid parameter values ??to a method that checks the parameters before executing, it will fail quickly and clearly with an appropriate exception. If this method does not check its parameters, several things can happen. The method may fail during processing and produce a puzzling exception. Even worse, the method returns normally but quietly computes the wrong result. The worst thing is that the method can return normally, but it leaves an object in a destroyed state, which will cause an error at an unrelated point in the future at an uncertain time. In other words, failure to verify the validity of parameters may lead to a violation of failure atomicity, see Item 76 for details.
For public and protected methods, use the Javadoc @throws tag to document the exceptions that will be thrown when parameter value restrictions are violated. Such exceptions are usually IllegalArgumentException, IndexOutOfBoundsException, or NullPointerException (see Item 72 for details). Once the restrictions on method parameters are documented, and the exceptions that will be thrown if these restrictions are violated, it is a simple matter to enforce these restrictions. Here is a typical example:
Note that the documentation comment does not say that a NullPointerException is thrown if m is null, but as a by-product of calling m.signum(), even though the method does exactly that. of. The documentation for the NullPointerException exception is based on the class-level documentation comments of the surrounding BigInteger class. Class-level annotations apply to all parameters in all public methods of the class. This can avoid the confusion caused by documenting each NullPointerException in each method separately. It can be used in conjunction with @Nullable or similar annotations to indicate that a special parameter can be null, but this practice is not standard and there are multiple annotations that can accomplish this function.
The Objects.requireNonNull method added in Java7 is more flexible and convenient, so there is no need to manually check for null. If you wish, you can also specify your own exception details. This method returns its input, so you can use a value while performing a null check:
You can also ignore the return value and use Objects.requireNonNull as a standalone null check where necessary.
In Java 9, a facility for checking scope was added: java.util.Objects. This facility contains three methods: checkFromIndexSize, checkFromToIndex and checkIndex. This facility is not as flexible as the null-checking method. It does not allow you to specify your own exception details, but is designed specifically for use with list and array indexes. It does not handle closed ranges (including both of its endpoints). But if it does exactly what you need, then it's a useful tool.
For unexported methods, as the package creator, you have control over the circumstances under which this method will be called, so you can, and should, ensure that only valid parameter values ??are passed in. Therefore, non-public methods should generally use assertions to check their parameters, as follows:
Essentially, these assertions are claiming that the asserted condition will be true , boring peripheral package clients how to use it. Unlike general validity checks, if an assertion fails, an AssertionError will be thrown. Unlike normal validity checks, there is essentially no cost if they do not work unless they are enabled by passing the -ea (or -enableassertions) flag to the Java interpreter. For more information on assertions, please refer to Sun's tutorial.
For some parameters, the method itself is not used, but is saved for later use. It is particularly important to check the validity of such parameters. For example, take the static factory method in Item 20 as an example. Its parameter is an int array and returns a List view of the array. If the client of this method were to pass null, the method would throw a NullPointerException because the method contains an explicit condition check (calling Objects.requireNonNull). If this conditional check is omitted, it will return a reference to the newly created List instance. Once the client attempts to use this reference, a NullPointerException will be thrown immediately. At that point, it may be very difficult to find the source of the List instance, making debugging more complicated.
As mentioned earlier, some parameters are saved by methods for later use, and the constructor represents a special case of this principle. It is very important to check the validity of constructor parameters to avoid constructing objects that violate the constraints of this class.
There are exceptions to this rule that a method should check its arguments before performing its calculations. An important exception is that in some cases, or simply impractical, And the validity check is implicitly done in the calculation process. For example, taking the method Collections.sort(List) to sort a list of objects, all objects in the list must be comparable to each other. During the process of sorting a list, each object in the list is compared to some other object. If these objects are not comparable to each other, one of the comparison operations will throw a ClassException, which is exactly what the sort method is supposed to do. So it doesn't make much sense to check ahead of time whether the elements in the list are comparable to each other. However, please note that indiscriminate use of this method will result in the loss of failure atomicity, see Item 76 for details.
Sometimes, certain calculations will implicitly perform necessary validity checks, but if the checks are unsuccessful, an incorrect exception will be thrown. In other words, the exception thrown by the calculation process due to invalid parameter values ??does not match the exception that the documentation indicates that this method will throw. In this case, the exception conversion technique described in Item 73 should be used to convert the exception thrown during the calculation into the correct exception.
Please do not conclude from the contents of this article that any restrictions on parameters are a good thing. Instead, when designing methods, you should make them as general as possible and consistent with practical needs. If a method can do a reasonable job for all parameter values ??it accepts, the number of parameters should be as few as possible. Often, however, there are limitations inherent to the abstraction being implemented.
In short, whenever you write a method or constructor, you should consider the restrictions on its parameters. These restrictions should be documented and enforced through explicit checks at the beginning of the method body. It is very important to develop this habit. As long as the validity check fails once, the efforts you put in to perform the necessary validity checks will be repaid with interest.