How to avoid null pointers at the root?
Scenarios where null pointers may be generated
public class WhatIsNpe { public static class User { private String name; private String[] address; public void print() { System.out.println("This is User Class."); } public String readBook() { System.out.println("User read book."); return null; } } /** * <p> * Customize a runtime exception * </p> */ public static class CustomException extends RuntimeException { } public static void main(String[] args) { // Scenario 1: the instance method of an empty object is called // User user = null; // user.print(); // Scenario 2: accessing the properties of an empty object // User user = null; // System.out.println(user.name); // Scenario 3: when the array is an empty object, take its length // User user = new User(); // System.out.println(user.address); // Null pointer exception generated // System.out.println(user.address.length); // Scenario 4: null is regarded as the Throwable value, that is, an exception with empty memory address cannot be thrown when an exception is thrown, and the exception needs to be initialized // CustomException exception = null; // throw exception; // Scenario 5: the return value of the method is null, and the caller can use it directly User user = new User(); System.out.println(user.readBook()); // Null pointer exception generated System.out.println(user.readBook().contains("MySQL")); } }
Solution
Null pointer in automatic unpacking during assignment
Basic type and packaging type
Null pointer problem caused by automatic unpacking
Scene reproduction
@SuppressWarnings("all") public class UnboxingNpe { public static void main(String[] args) { // Scenario 1: null pointer appearing in automatic unpacking during variable assignment // Why does automatic unpacking cause null pointers? // Use this scenario to analyze why null pointers appear during automatic unpacking: // 1. javac UnboxingNpe.java // 2. javap -c UnboxingNpe.class /* Compiled from "UnboxingNpe.java" public class cn.xilikeli.java.escape.UnboxingNpe { public cn.xilikeli.java.escape.UnboxingNpe(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: aconst_null 1: astore_1 2: aload_1 3: invokevirtual #2 // Method java/lang/Long.longValue:()J 6: lstore_2 7: return } */ // From the above decompilation results, you can know that long. Com will be called when unpacking Longvalue method, and since count is null, a null pointer exception will be thrown Long count = null; long countA = count; // Scenario 2: null pointer appearing in automatic unpacking during method parameter transfer // Integer left = null; // Integer right = null; // System.out.println(add(left, right)); // Scenario 3: scenario for size comparison // Long x = 10L; // Long y = null; // System.out.println(compare(x, y)); } private static int add(int x, int y) { return x + y; } private static boolean compare(long x, long y) { return x >= y; } }
Suggestions on avoiding null pointer caused by automatic unpacking
- The basic type is better than the wrapper type, and the basic type is preferred.
- For uncertain wrapper types, be sure to check whether it is NULL.
- For wrapper types with a NULL value, the value is assigned to 0.
What if a null pointer appears when a string, array or collection is used?
Scene reproduction
@SuppressWarnings("all") public class BasicUsageNpe { private static boolean stringEquals(String x, String y) { return x.equals(y); } public static class User { private String name; } public static void main(String[] args) { // Scenario 1: null pointer error may be reported when using equals method for string // false // System.out.println(stringEquals("xyz", null)); // Null pointer exception, reason: null equals("xyz") // System.out.println(stringEquals(null, "xyz")); // Scenario 2: Although the object array is new, if the element is not initialized, a null pointer error will also be reported // User[] users = new User[10]; // for (int i = 0; i != 10; ++i) { // The null pointer will not be reported until the element is initialized. users[i] = new User(); // users[i].name = "abc-" + i; // } // Scenario 3: the List object add null does not report an error, but addAll cannot add null, otherwise a null pointer error will be reported List<User> userList = new ArrayList<>(); User user = null; // No error will be reported userList.add(user); List<User> userListA = null; // Null pointer userList.addAll(userListA); /* addAll Source code: public boolean addAll(Collection<? extends E> c) { // Since the userListA passed in is null, it will be null here toArray(); Null pointer exception thrown Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } */ } }
Precautions when using Optional to avoid null pointers
What is Optional
common method
/** * Returns an {@code Optional} with the specified present non-null value. * * Encapsulate the specified value with Optional and return it. If the value is null, a null pointer exception will be thrown * * @param <T> the class of the value * @param value the value to be present, which must be non-null * @return an {@code Optional} with the value present * @throws NullPointerException if value is null */ public static <T> Optional<T> of(T value) { return new Optional<>(value); } /** * Returns an empty {@code Optional} instance. No value is present for this * Optional. * * Used to create an empty Optional instance * * @apiNote Though it may be tempting to do so, avoid testing if an object * is empty by comparing with {@code ==} against instances returned by * {@code Option.empty()}. There is no guarantee that it is a singleton. * Instead, use {@link #isPresent()}. * * @param <T> Type of the non-existent value * @return an empty {@code Optional} */ public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } /** * Returns an empty {@code Optional} instance. No value is present for this * Optional. * * Used to create an empty Optional instance * * @apiNote Though it may be tempting to do so, avoid testing if an object * is empty by comparing with {@code ==} against instances returned by * {@code Option.empty()}. There is no guarantee that it is a singleton. * Instead, use {@link #isPresent()}. * * @param <T> Type of the non-existent value * @return an empty {@code Optional} */ public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } /** * Returns an {@code Optional} describing the specified value, if non-null, * otherwise returns an empty {@code Optional}. * * Similar to the of method, the specified value is returned after being encapsulated with Optional, but a null pointer exception will not be thrown because the passed value is null. * If the value passed in is null, an empty Optional instance is returned * * @param <T> the class of the value * @param value the possibly-null value to describe * @return an {@code Optional} with a present value if the specified value * is non-null, otherwise an empty {@code Optional} */ public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); } /** * If a value is present in this {@code Optional}, returns the value, * otherwise throws {@code NoSuchElementException}. * * If the value of Optional value exists, this value will be returned; otherwise, a NoSuchElementException exception will be thrown * * @return the non-null value held by this {@code Optional} * @throws NoSuchElementException if there is no value present * * @see Optional#isPresent() */ public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; } /** * Return {@code true} if there is a value present, otherwise {@code false}. * * Judge whether the Optional value exists. If it exists, it returns true. If it does not exist, it returns false * * @return {@code true} if there is a value present, otherwise {@code false} */ public boolean isPresent() { return value != null; }
Daily use reproduction of Optional
@SuppressWarnings("all") public class OptionalUsage { public static class User { private String name; public String getName() { return name; } } private static void isUserEqualNull() { // Empty judgment without Optional User user = null; if (user != null) { System.out.println("User is not null."); } else { System.out.println("User is null."); } // Get Optional instance Optional<User> optional = Optional.empty(); // Judge whether the instance is empty. If it is empty, false will be returned. If not, true will be returned if (optional.isPresent()) { System.out.println("User is not null."); } else { System.out.println("User is null."); } // The above two codes are almost the same, and there is no difference in essence. The isPresent source code is the same as the blank judgment in the first paragraph. If you use optional in this way, it is worthless // We should recognize the wonderful use of orElse, orElseGet, map and other methods } private static User anoymos() { return new User(); } public static void main(String[] args) { // Meaningless use isUserEqualNull(); // Meaningful usage User user = null; // Get Optional instance Optional<User> optionalUser = Optional.ofNullable(user); // Use of orElse: returns if it exists; if it is empty, the default value is provided User userA = optionalUser.orElse(new User()); // Use of orElseGet: if it exists, it will be returned. If it is empty, it will be generated by the function. It is more reusable than orElse, because it is more flexible to generate values through functions User userB = optionalUser.orElseGet(() -> anoymos()); // Use of orelsethlow: return if it exists, otherwise an exception will be thrown optionalUser.orElseThrow(() -> new RuntimeException("Throw custom exception.")); // Use of ifPresent: do the corresponding processing only when it exists, otherwise do nothing optionalUser.ifPresent(u -> System.out.println(u.getName())); // Use of map: you can perform some operations on the objects in Optional, and an Optional object will be returned // Obtain the user name. After obtaining it, judge whether it exists. If it does not exist, return the default value String name = optionalUser.map(u -> u.getName()).orElse("Default name"); System.out.println("name: " + name); // Maps can be cascaded infinitely. You need to pay attention to the types of options returned by each map operation // Get the user name, get the length of the name after getting it, and return 0 if it is empty Integer length = optionalUser.map(u -> u.getName()) .map(n -> n.length()) .orElse(0); System.out.println("length: " + length); } }
Is there any problem with the daily use method?
Some methods are meaningless, and meaningful methods should be used more. Some meaningless methods should be regarded as Optional private methods.
Try to catch but not really solve the exception?
What is an exception?
Java exception handling class
● Error
The Error class object is generated and thrown by the Java virtual machine. Most errors have nothing to do with the operations performed by the coder. For example, an Error exception will be thrown when the Java virtual machine runs.
● RuntimeException
Runtime exceptions, which define many exceptions for programs written by programmers, such as null pointer exceptions, array subscript out of bounds exceptions, and so on. These exceptions are non check exceptions. The program can choose to capture and handle them or not.
● CheckedException
The occurrence of this kind of exception can be predicted, and once this kind of exception occurs, some measures must be taken to deal with it. This type of exception can be caught to deal with or thrown, and the upper layer caller can continue to deal with it. If it is not handled, the code cannot be translated.
case
@SuppressWarnings("all") public class ExceptionProcess { public static class User { } /** * <p> * Java Exception essence: throw an exception * </p> */ private void throwException() { User user = null; // Some related processing of user... // After processing if (null == user) { // When you want to perform an operation, but the current situation does not allow downward execution, you can throw an exception and let the caller handle the exception throw new NullPointerException(); } } /** * <p> * Examples of exceptions that cannot be caught * <p> * If no catch null pointer exception is specified, the exception will continue to be thrown up. If there is no corresponding catcher, it will be thrown all the way to the main method, * If there is no corresponding processing, it will lead to the termination of the program * </P> */ private void canNotCatchNpeException() { try { throwException(); } catch (ClassCastException cce) { System.out.println("Catch type conversion exception: "); System.out.println(cce.getMessage()); System.out.println(cce.getClass().getName()); } } /** * <p> * Examples of exceptions that can be caught * </P> */ private void canCatchNpeException() { try { throwException(); } catch (ClassCastException cce) { System.out.println("Catch type conversion exception: "); System.out.println(cce.getMessage()); System.out.println(cce.getClass().getName()); } catch (NullPointerException npe) { System.out.println("Catch null pointer exception: "); System.out.println(npe.getMessage()); System.out.println(npe.getClass().getName()); } } public static void main(String[] args) { ExceptionProcess process = new ExceptionProcess(); process.canCatchNpeException(); System.out.println("Null pointer exception caught successfully..."); process.canNotCatchNpeException(); System.out.println("The exception thrown was not handled, This sentence will not be printed out..."); } }
Solve the hidden danger of resource leakage using try finally
Resources and resource leakage
Scene reproduction
The traditional try finally method closes resources
import java.io.*; /** * <p> * Solve the hidden danger of resource leakage using try finally * </p> */ public class Main { /** * The traditional try finally method releases resources */ private String traditionalTryCatch() throws IOException { // 1. Closure of a single resource // String line = null; // BufferedReader br = new BufferedReader(new FileReader("/Users/destroyer/Documents / document / hello.txt")); // try { // Read a row of resources // Note that exceptions may occur in try // return br.readLine(); // line = br.readLine(); // } finally { // The operation is completed and resources are released // This method can ensure that resources are closed correctly, and it is a better method // Even if the program throws an exception or returns directly, it can correctly complete the work of closing resources // Therefore, it is feasible to close a single resource in this way // It should be noted that exceptions may occur in finally. If exceptions occur here and also in try, the exceptions here will overwrite the exceptions in try and make debugging complex // br.close(); // } // return line; // 2. Closing multiple resources // The code is verbose and error prone File file; // First resource InputStream in = new FileInputStream("/Users/destroyer/Documents/file/hello.txt"); try { // Second resource OutputStream out = new FileOutputStream("/Users/destroyer/Documents/file/hello2.txt"); try { byte[] buf = new byte[100]; int n; while ((n = in.read(buf)) >= 0) { out.write(buf, 0, n); } } finally { out.close(); } } finally { in.close(); } return null; } public static void main(String[] args) { Main main = new Main(); try { main.traditionalTryCatch(); } catch (IOException e) { e.printStackTrace(); } } }
Problems and improvement schemes of try finally
Improvement scheme Code:
import java.io.*; /** * <p> * Solve the hidden danger of resource leakage using try finally * </p> */ public class Main { /** * The traditional try finally method releases resources */ private String traditionalTryCatch() throws IOException { // 1. Closure of a single resource // String line = null; // BufferedReader br = new BufferedReader(new FileReader("/Users/destroyer/Documents / document / hello.txt")); // try { // Read a row of resources // Note that exceptions may occur in try // return br.readLine(); // line = br.readLine(); // } finally { // The operation is completed and resources are released // This method can ensure that resources are closed correctly, and it is a better method // Even if the program throws an exception or returns directly, it can correctly complete the work of closing resources // Therefore, it is feasible to close a single resource in this way // It should be noted that exceptions may occur in finally. If exceptions occur here and also in try, the exceptions here will overwrite the exceptions in try and make debugging complex // br.close(); // } // return line; // 2. Closing multiple resources // The code is verbose and error prone, and you may forget to close resources File file; // First resource InputStream in = new FileInputStream("/Users/destroyer/Documents/file/hello.txt"); try { // Second resource OutputStream out = new FileOutputStream("/Users/destroyer/Documents/file/hello2.txt"); try { byte[] buf = new byte[100]; int n; while ((n = in.read(buf)) >= 0) { out.write(buf, 0, n); } } finally { out.close(); } } finally { in.close(); } return null; } /** * Improvement scheme: try with resources introduced in Java 7 realizes automatic resource shutdown * It is easy to use and the possibility of error is very low * BufferedReader This interface has implemented the autoclosable interface since Java 7. No matter whether the try statement ends normally or abnormally, the resources can be closed correctly */ private String newTryWithResources() throws IOException { // 1. Use and closing of individual resources // Similar to the syntax of the for loop, put resources in parentheses and operations on resources in curly braces. After the operation ends and exits the curly braces, try with resources can close the resources in parentheses // try (BufferedReader br = new BufferedReader(new FileReader("/Users/destroyer/Documents / document / hello.txt")){ // return br.readLine(); // } // 2. Use and shutdown of multiple resources try (FileInputStream in = new FileInputStream("/Users/destroyer/Documents/file/hello.txt"); FileOutputStream out = new FileOutputStream("/Users/destroyer/Documents/file/hello2.txt") ) { byte[] buffer = new byte[100]; int n = 0; while ((n = in.read(buffer)) != -1 ) { out.write(buffer, 0, n); } } return null; } public static void main(String[] args) { Main main = new Main(); try { // String s1 = main.traditionalTryCatch(); // System.out.println(s1); String s2 = main.newTryWithResources(); System.out.println(s2); } catch (IOException e) { e.printStackTrace(); } } }
finally, the exception in try suppresses the recurrence of the exception in try
Custom exception class:
public class MyException extends Exception { private static final long serialVersionUID = 5165206067888442631L; public MyException() { super(); } public MyException(String message) { super(message); } }
AutoClose class:
/** * <p> * This class implements the autoclosable interface. The class that implements this interface can be actively closed by try with resources after use * </p> */ public class AutoClose implements AutoCloseable { @Override public void close() throws Exception { // This close method is called when the resource is closed System.out.println(">>> close()"); throw new RuntimeException("Exception in close()"); } public void work() throws MyException { System.out.println(">>> work()"); throw new MyException("Exception in work()"); } }
Main method:
public static void main(String[] args) throws Exception { // Exceptions in close will override exceptions in work // AutoClose autoClose = new AutoClose(); // try { // autoClose.work(); // } finally { // autoClose.close(); // } // try with resources will not have the above problem, and the information of both exceptions will be displayed try (AutoClose autoClose = new AutoClose()) { autoClose.work(); } }
Common exceptions: concurrent modification, type conversion, enumeration lookup
Common cases
Code reproduction
/** * <p> * Employee type enumeration class * </p> */ public enum StaffTypeEnum { RD, QA, PM, OP; }
Scene reproduction Code:
import com.google.common.base.Enums; import java.util.*; /** * <p> * Common exceptions in coding: concurrent modification, type conversion, enumeration and lookup * </p> */ @SuppressWarnings("all") public class GeneralException { public static class User { private String name; public User() { } public User(String name) { this.name = name; } public String getName() { return name; } } public static class Manager extends User { } public static class Worker extends User { } /** * Concurrent modification exception * Iteratable objects can be modified while traversing */ private static void concurrentModificationException(ArrayList<User> userList) { // Cause of exception: fast failure mechanism // The iterator works in a separate thread and has a mutex // After the iterator is created, it will create a single chain index table pointing to the original object. When the number of original objects changes, the contents of the index table will not change synchronously // Therefore, when the index pointer moves backward, the object to be iterated cannot be found. Therefore, according to the principle of fast failure mechanism, the iterator will immediately throw ConcurrentModificationException // Therefore, the correct way to delete elements during traversal should be to use iterators instead of loops // for (User user : userList) { // if ("sky".equals(user.getName())) { // userList.remove(user); // } // } // There is no problem using iterators directly Iterator<User> iterator = userList.iterator(); while (iterator.hasNext()) { // next must be before remove // If you call remove and then next during the iteration process, concurrent modification exceptions will also occur, and the reason is the same as the previous loop User user = iterator.next(); if ("sky".equals(user.getName())) { iterator.remove(); } } // Although using iterators can delete elements during traversal, it is best not to do so, // A better implementation is to use the Stream in Java 8 to do filter operation and realize filtering, rather than deleting in traversal } private static final Map<String, StaffTypeEnum> typeIndex = new HashMap<>(StaffTypeEnum.values().length); // typeIndex initialization population static { // You only need to traverse once for (StaffTypeEnum value : StaffTypeEnum.values()) { typeIndex.put(value.name(), value); } } private static StaffTypeEnum enumFind(String type) { // Exceptions may occur // return StaffTypeEnum.valueOf(type); // Solution: // 1. The most common and simplest implementation // try { // return StaffTypeEnum.valueOf(type); // } catch (IllegalArgumentException e) { // return null; // } // 2. Improved implementation, but the efficiency is not high. If there are too many enumeration values, a full loop must be carried out every time // for (StaffTypeEnum value : StaffTypeEnum.values()) { // if (value.name().equals(type)) { // return value; // } // } // return null; // 3. Using the static map index, there is only one circular enumeration process. Defect: when the map cannot obtain the corresponding value, it will return a null // return typeIndex.get(type); // 4. Using Google Guava's Enums requires relevant dependencies // If the corresponding value is obtained, the corresponding value is returned; otherwise, null is returned return Enums.getIfPresent(StaffTypeEnum.class, type).orNull(); } public static void main(String[] args) { // 1. Concurrent modification exception // ArrayList<User> userList = new ArrayList<>( // Arrays.asList(new User("wade"), new User("sky")) // ); // java.util.ConcurrentModificationException // concurrentModificationException(userList); // 2. Type conversion exception: if the type conversion does not conform to the inheritance relationship of Java, a type conversion exception will be reported User user1 = new Manager(); User user2 = new Worker(); // Correct transformation // Manager m1 = (Manager) user1; // Wrong transformation, type conversion exception Java lang.ClassCastException // Manager m2 = (Manager) user2; // How to solve it? If you know the specific type of the object to be accessed, you can convert this type directly. // If you don't know the specific type, you can use the following two methods // 1. getClass.getName gets the specific type and performs a specific processing process according to the type // cn.xilikeli.java.escape.GeneralException$Worker System.out.println(user2.getClass().getName()); // 2. Use the instanceof keyword to determine whether this type is the type we want, and then do the corresponding operation // false System.out.println(user2 instanceof Manager); // The above two methods can avoid type conversion exceptions // 3. Enumeration search exception: when enumerating, if the enumeration value does not exist, it will not return null, but directly throw an exception System.out.println(enumFind("RD")); System.out.println(enumFind("abc")); } }