Article Directory
null pointer exception
Review the exception first.
Exceptions are divided into Exception
and Error
, Exception and Error classes are inherited from the Throwable
class.
- Exception (program recoverable): Indicates an exception that the program can handle, which can be caught and recovered. When encountering such an exception, the exception should be handled as much as possible to restore the program, and the exception should not be arbitrarily terminated.
- Error (program is unrecoverable): generally refers to problems related to virtual machines, such as system crashes, virtual machine errors, insufficient memory space, method call stack overflow, etc. For this type of error, if the Java compiler does not check it, the application program will be interrupted, and the program itself cannot recover and prevent it. When encountering such an error, it is recommended to terminate the program.
Exception is further divided into runtime exceptions and checked exceptions :
- Runtime exceptions: such as null pointers , parameter errors, etc.
- Checked exception: If this type of exception has no
try/catch
statement and is notthrows
thrown, the compilation will fail.
NullPointerException (NullPointerException) is a runtime exception that is thrown null
when . Such situations include:
- Invokes an instance method of a null object.
- Access or modify fields of a null object.
- Treat null as an array and get its length.
- Treat null as an array, access or modify its timeslice.
- Throws null as a Throwable value.
Encountering this exception, if there is no try/catch
statement, will cause the application to terminate. From the statistics of online application problems, it is found that null pointer exceptions are relatively common. So it is very necessary to prevent null pointer exceptions.
Prevent Null Pointer Exceptions
To prevent null pointer exceptions, you can start from three aspects:
- Java annotation assistance
- Introducing the Optional type in Java 8
- Using Kotlin to distinguish between nullable and non-nullable types
Java annotations
Java annotations can be used to express the nullability of values, helping static scanning tools find NullPointerException
possible .
Android provides nullable @NonNull
and non-nullable annotations @NonNull
:
@Nullable
public Fragment findFragment(@NonNull FragmentActivity activity, @NonNull String tag) {
FragmentManager fragmentManager = activity.getSupportFragmentManager();
return fragmentManager.findFragmentByTag(tag);
}
When users use it, if they do not use nullable and non-nullable types correctly, they will get a prompt from the compiler: so annotations can only be used to remind
users whether they are nullable or non-nullable types when using them. But it does not limit whether the user passes a nullable type or a non-nullable type.
Optional types in Java 8
Optional
The type introduced in Java 8 , which is a container object that may or may not contain non-null values, prevents this in a more elegant way NullPointerException
.
The main methods provided by the Optional class:
method name | express meaning |
---|---|
of | Returns an Optional with the Optional's current non-null value. |
ofNullable | Returns an Optional with the specified value, or an empty Optional if non-null. |
isPresent | Returns true if a value exists, otherwise false. |
get | Returns the value if present in the Optional, otherwise throws NoSuchElementException. |
map | Applies the provided mapping function if a value is present, returning an Optional of the result if the result is not empty. Otherwise an empty Optional is returned. |
flatMap | If a value exists, apply the provided Optional mapping function to it and return the result, otherwise return an empty Optional. This method is similar to map(Function), but the provided mapper is a mapper whose result is already an Optional, and flatMap will not use the additional Optional if called. |
orElse | Returns the value if it exists, otherwise returns other. |
Test example:
Create a User class:
public class User {
private String nickname;
private Address address;
public User() {
}
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
public void setAddress(Address address) {
this.address = address;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
Get Nickname:
private String getUserNickname(User user) {
return Optional.ofNullable(user).map(new Function<User, String>() {
@Override
public String apply(User t) {
return t.getNickname();
}
}).orElse("default");
}
String nickname = getUserNickname(null);
//运行结果:nickname="default"
ofNullable
The method mainly wraps the object into an Optional
object (combination is used):
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
map
The method will check whether the current value is empty, and return an empty Optional
object if it is empty, otherwise wrap the return value of Function
the callback method of the interface apply
into a new Optional
object.
public boolean isPresent() {
return value != null;
}
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}
orElse
The method checks whether the current value is empty, and if it is empty, returns the default value passed in:
public T orElse(T other) {
return value != null ? value : other;
}
As can be seen from the above, using Optional
the class is mainly to wrap the object into Optional
an object to prevent it NullPointerException
.
Optional
The class can also solve some chain calls that are often encountered in the actual development process NullPointerException
.
Next, through the User class, the attributes of the Country class are obtained, which is generally written like this:
String countryName = null;
Address address = user.getAddress();
if(address!=null) {
Country country = address.getCountry();
if(country !=null) {
countryName = country.getName();
}
}
In the above code, every object encountered in the process has to be nullified. If the call chain is long, the code will be less friendly. Modify the return value of user.getAddress()
the and address.getCountry()
methods :
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
public Optional<Country> getCountry() {
return Optional.ofNullable(country);
}
Use Optional
the class to wrap any object encountered in the chain into Optional
a object :
private String getCountryName(User user) {
return Optional.ofNullable(user).flatMap(new Function<User, Optional<Address>>() {
@Override
public Optional<Address> apply(User t) {
return t.getAddress();
}
}).flatMap(new Function<Address, Optional<Country>>() {
@Override
public Optional<Country> apply(Address t) {
return t.getCountry();
}
}).map(new Function<Country, String>() {
@Override
public String apply(Country t) {
return t.getName();
}
}).orElse("default");
}
String countryName = getCountryName(user);
//运行结果:countryName = "default"
In the above chain method call, if any object in the middle is null, it is impossible to throw a null pointer exception.
Although Optional
it can be prevented NullPointerException
, the code is still very verbose, and the additional wrapper interface will also affect the performance of the runtime.
Kotlin
Kotlin distinguishes between nullable and non-nullable types, and checks for both nullable and non-nullable types at compile time.
Nullable type, a question mark is added after the type to indicate that variables of this type can store null
references :
fun findFragment(activity: FragmentActivity?, tag: String?): Fragment? {
val fragmentManager = activity?.supportFragmentManager
return fragmentManager?.findFragmentByTag(tag)
}
If it is not used correctly, you will get an error message from the compiler and cannot compile:
non-null type, no question mark added after the type indicates that variables of this type cannot be stored null
Reference :
fun findFragment(activity: FragmentActivity, tag: String): Fragment? {
val fragmentManager = activity.supportFragmentManager
return fragmentManager?.findFragmentByTag(tag)
}
Also, if not used correctly, you will get a compiler error and fail to compile:
there is no difference between nullable and non-nullable types at runtime, only the types are checked during compilation. So, using Kotlin's nullable types does not incur additional overhead at runtime.
For the judgment of nullable types, Kotlin also provides safe call operators?.
, Elvis operators?:
, safe conversion operatorsas?
, and non-null assertions!!
to prevent null pointer exceptions, which will not be introduced here.
Summarize
There are three ways to prevent null pointer exceptions: use Java annotations to assist, introduce the Optional type in Java 8, and use Kotlin to distinguish between nullable and non-nullable types. The use of Java annotations can only prompt the user whether it is a nullable type or a non-nullable type when using it, and it does not limit whether the user passes a nullable type or a non-nullable type; the introduction of the Optional type in Java 8 can prevent empty Pointer exception, but the code is still very verbose, and the additional wrapper interface will also affect the performance at runtime; using Kotlin to distinguish between nullable and non-nullable types, the type will be checked during compilation, and will not bring additional overhead. In conclusion, using Kotlin is the best way to prevent null pointer exceptions.