1. Excessive use of Null
Avoiding excessive use of null values is a best practice. For example, a better approach would be to have the method return an empty array or collection instead of a null value, as this would prevent the program from throwing NullPointerException. The following code snippet obtains a collection from another method:
</>List<String> accountIds = person.getAccountIds();for (String accountId : accountIds) { processAccount(accountId);}
When a person does not have an account, Getaccountids () returns a null value, and the program throws a NullPointerException exception. Therefore, it is necessary to add an empty check to solve this problem. If you replace the returned null value with an empty list, then NullPointerException will not appear. And, because we no longer need to check the variable accountId for short, the code becomes more concise.
When you want to avoid null values, different scenarios may take different approaches. One method is to use the Optional type, which can be either an empty object or a wrapper for some value.
</>Optional<String> optionalString = Optional.ofNullable(nullableString);if(optionalString.isPresent()) { System.out.println(optionalString.get());}
In fact, JAVA8 provides a more concise approach:
</>Optional<String> optionalString = Optional.ofNullable(nullableString);optionalString.ifPresent(System.out::println);
Java is supported by the Optional type from the JAVA8 version, but it has long been well known in the functional programming world. Prior to this, it was already in use in Google guava for earlier versions of Java.
2. Ignoring exceptions
We often ignore exceptions. However, for beginners and experienced Java programmers, best practices still deal with them. Exception throwing is usually purposeful, so in most cases it is necessary to record the event that caused the exception. Don't underestimate it, if necessary, you can re-throw it, display the error message to the user in a dialog box, or record the error message in the log. At the very least, you should explain why you did not handle the exception in order for other developers to be aware of the cause.
</>selfie = person.shootASelfie();try { selfie.show();} catch (NullPointerException e) { // Maybe, invisible man. Who cares, anyway?}
An easy way to emphasize that an exception is unimportant is to use this information as the variable name for the exception, like this:
</>try { selfie.delete(); } catch (NullPointerException unimportant) { }
3. Concurrency modification Exceptions
This exception occurs when the collection object is modified without using the method provided by the iterator object to update the contents of the collection. For example, here is a hats list, and you want to delete all of the values that contain the ear flaps:
new ArrayList<>();hats.add(new Ushanka()); // that one has ear flapshats.add(new Fedora());hats.add(new Sombrero());for (IHat hat : hats) { if (hat.hasEarFlaps()) { hats.remove(hat); }}
If you run this code, Concurrentmodificationexception will be thrown because the code is going through the collection and modifying it. The same exception may occur when multiple processes are acting on the same list, while one process is traversing the list and another process attempts to modify the contents of the list.
It is very common to modify the contents of a collection concurrently in multiple threads, so it is necessary to work with methods commonly used in concurrent programming, such as synchronous locks, special collections for concurrent modifications, and so on. Java has a small difference in how it solves this problem in single-threaded and multithreaded situations.
Collect objects and delete them in another loop
The direct solution is to put hats with ear flaps in a list and then delete it with another loop. However, this requires an additional collection to hold the hats that will be deleted.
new LinkedList<>();for (IHat hat : hats) { if (hat.hasEarFlaps()) { hatsToRemove.add(hat); }}for (IHat hat : hatsToRemove) { hats.remove(hat);}
How to use Iterator.remove
This method is simpler and does not require the creation of additional collections:
</>Iterator<IHat> hatIterator = hats.iterator();while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); }}
ListIterator
the method used
The list iterator is a very appropriate choice when the collection that needs to be modified implements the list interface. The iterator that implements the Listiterator interface not only supports the delete operation, but also supports add
and set
operation. The Listiterator interface implements the Iterator interface, so this example looks and Iterator
Remove
method is similar. The only difference is the type of hat iterator and the way we get iterator-using listiterator ()
method. The following fragment shows how to use listiterator.remove
and listiterator.add
method will have The ear flaps hat is replaced with sombreros .
</>IHat sombrero = new Sombrero();ListIterator<IHat> hatIterator = hats.listIterator();while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); hatIterator.add(sombrero); }}
With Listiterator, calls remove
and add
methods can be replaced with only one set
method called:
</>IHat sombrero = new Sombrero();ListIterator<IHat> hatIterator = hats.listIterator();while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.set(sombrero); // set instead of remove and add }}
Using the methods in Java 8 stream
In Java8, a developer can convert a collection to stream and filter the stream based on some criteria. This example describes how the stream API filters hats and avoids it ConcurrentModificationException
.
Hats = Hats.stream (). Filter ((Hat,!hat.hasearflaps ()))
</> .collect(Collectors.toCollection(ArrayList::new));
Collectors.toCollection
Method creates a new ArrayList that is responsible for storing the hats values that are filtered out. If the filter filters out a large number of entries, a large ArrayList will be generated here. Therefore, careful use is required.
Using the methods in Java 8 List.removeIf
You can use another, more concise, method in Java 8-- removeIf
methods:
</>hats.removeIf(IHat::hasEarFlaps);
At the bottom, it is used Iterator.remove
to complete this operation.
Use a special collection
If you decide to use it at the very beginning CopyOnWriteArrayList
instead ArrayList
of it, there is no problem. Because a CopyOnWriteArrayList
modified method (for example, Set,add,remove) is provided, it does not change the original collection array, but instead creates a new modified version. This allows changes to be made while traversing the original version collection, so that no exception is thrown ConcurrentModificationException
. The drawbacks of this collection are also obvious-a new set is generated for each modification.
There are other collections that apply to different scenarios, such as CopyOnWriteSet
and ConcurrentHashMap
.
About another error that may occur when the collection is concurrently modified is the creation of a stream from a collection that modifies the collection of the backend while traversing the stream. The general guideline for stream is to avoid modifying the backend's collection when querying the stream. The following example shows how to handle the stream correctly:
</>List<IHat> filteredHats = hats.stream().peek(hat -> { if (hat.hasEarFlaps()) { hats.remove(hat); }}).collect(Collectors.toCollection(ArrayList::new));
peek
Method collects all the elements and performs a given action on each element. Here, the action is an attempt to delete data from an underlying list, which is obviously wrong. To avoid this, you can try some of the methods explained above.
4. Breach of contract
Sometimes, for better collaboration, code provided by the standard library or by a third party must adhere to common dependency guidelines. For example, you must adhere to hashCode
and equals
common conventions that ensure that a series of collection classes and other classes of usage and methods in the Java collection framework hashCode
equals
work correctly. Non-compliance does not result in exception or corrupt code compilation, which is insidious because it can change application behavior at any time without warning.
Error codes can sneak into the production environment, causing a lot of bad effects. This includes poor UI experience, bad data reporting, poor application performance, data loss, or more. Fortunately, these catastrophic mistakes do not happen very often. The hashcode and equals conventions have been mentioned before, and it may appear that a collection relies on hashing or comparing objects, like HashMap and HashSet. In short, there are two criteria for this agreement:
- If two objects are equal, then hash code must be equal.
- If two objects have the same hash code, they may or may not be equal.
The first rule of breaking the Convention, when you attempt to retrieve data from a hashmap, will result in an error. The second criterion means that owning hash code
the same object is not necessarily equal.
Let's look at the consequences of breaking the first rule:
</>PublicStaticClassBoat {private String name; Boat (String name) {THIS.name = name; } @Override public boolean Equals (Object o) {if (this = = O) true; if (o = = null | | getclass ()! = O.getclass ()) return FALSE; Boat Boat = (Boat) o; return! (Name! = null?!name.equals (boat.name): Boat.name! = null);} Span class= "hljs-annotation" > @Override public int HashCode ( {return (int) (Math.random () * 5000); }}
As you can see, the Boat class overrides the equals
and hashCode
methods. However, it destroys the convention because Hashcode returns random values for the same object for each invocation. The following code will probably not find a boat named in HashSet Enterprise
, despite the fact that we have added this type of boat in advance:
</>public static void main(String[] args) { Set<Boat> boats = new HashSet<>(); boats.add(new Boat("Enterprise")); System.out.printf("We have a boat named ‘Enterprise‘ : %b\n", boats.contains(new Boat("Enterprise")));}
Another example of a contract is a finalize
method. Here is a reference to the official Java documentation about its functional description:
finalize
The general Convention is that when the JAVATM virtual machine determines that any thread can no longer access the specified object in any way, this method is called, and then the object can only be used as the result of a certain behavior at the end of some other (ready-to-Terminate) object or class. finalize
method has several features, including the ability to make this object available to other threads again, but finalize
the primary purpose is to perform a purge before the object is dropped irrevocably. For example, a method that represents an input/output connection object can finalize
perform an explicit I/O transaction so that the connection is interrupted before the object is permanently discarded.
You can decide to use methods in such a file processor finalize
to free up resources, but this usage is bad. Because it is called during garbage collection, the time of the GC is not deterministic, so finalize
the time to be called is not guaranteed.
5. Using the original type instead of the parameterized
According to the Java documentation description: The original type is either nonparametric or non-static member of Class R (and also non-inheriting r parent or parent interface). Before the Java generics were introduced, there was no alternative type for the original type. Java supports generic programming starting with version 1.5, which is undoubtedly an important feature enhancement. However, because of backwards compatibility, there is a trap that could break the entire type system. The following example:
new ArrayList();listOfNumbers.add(10);listOfNumbers.add("Twenty");listOfNumbers.forEach(n -> System.out.println((int) n * 2));
This is a list of numbers that is defined as the original ArrayList. Because it does not specify a type parameter, you can add any object to it. But the last line maps its contained elements to type int and multiplies it by 2, printing the doubled data to standard output.
This code compiles without errors, but when run, it throws a run-time error because it attempts to map the character type to shaping. Obviously, if the necessary information is hidden, the type system will not be able to help write the security code.
To solve this problem, you need to specify a specific type for the objects that are stored in the collection:
</>List<Integer> listOfNumbers = new ArrayList<>();listOfNumbers.add(10);listOfNumbers.add("Twenty");listOfNumbers.forEach(n -> System.out.println((int) n * 2));
The only difference from the previous code is the line that defines the collection:
</>List<Integer> listOfNumbers = new ArrayList<>();
The modified code compilation cannot be passed, as this attempts to add a string to the collection that only expects to store the shaping. The compiler displays an error message and points to the line that you are trying to add Twenty
characters to the list. Parameterized generic types is a good idea. In this way, the compiler is able to check all possible types, resulting in a significant decrease in the chance of a run-time exception due to inconsistent types.
5 most common errors in Java