Android prevents null pointer exception

null pointer exception

Review the exception first.

Exceptions are divided into Exceptionand Error, Exception and Error classes are inherited from the Throwableclass.

  • 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/catchstatement and is not throwsthrown, the compilation will fail.

NullPointerException (NullPointerException) is a runtime exception that is thrown nullwhen . 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/catchstatement, 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:

  1. Java annotation assistance
  2. Introducing the Optional type in Java 8
  3. 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 NullPointerExceptionpossible .

Android provides nullable @NonNulland 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
insert image description here
insert image description here
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

OptionalThe 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"

ofNullableThe method mainly wraps the object into an Optionalobject (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);
    }

mapThe method will check whether the current value is empty, and return an empty Optionalobject if it is empty, otherwise wrap the return value of Functionthe callback method of the interface applyinto a new Optionalobject.

    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));
        }
    }

orElseThe 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 Optionalthe class is mainly to wrap the object into Optionalan object to prevent it NullPointerException.

OptionalThe 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 Optionalthe class to wrap any object encountered in the chain into Optionala 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 Optionalit 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 nullreferences :

    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:
insert image description here
non-null type, no question mark added after the type indicates that variables of this type cannot be stored nullReference :

    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:
insert image description here
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.

Guess you like

Origin blog.csdn.net/wangjiang_qianmo/article/details/97624882