I tried to explain Effective Java 3rd edition "almost all chapters" in "easy-to-read Japanese".

I think Effective Java is an inevitable book to become a "full-fledged" Java engineer. Especially engineers who are in a position to make public APIs can not make decent ones unless they understand what is written in this book.

It goes without saying that it is a wonderful book, but on the other hand, I also feel that it is difficult to understand that it is such a thing.

The cause is probably as follows.

The essence of the content itself is not very difficult, but I think it's hard to say that the threshold of this book is raised for this reason.

Therefore, in this article, I would like to explain (almost) all items in "easy-to-read Japanese" as much as possible.

However, I have omitted explanations for items that are too obvious or easy to read. Also, my personal opinion is mixed. I hope you can see it on that premise.

Effective Java 3rd Edition is written targeting Java 9. Therefore, I will post the official Java 9 documentation just in case (because it is unexpectedly difficult to reach).

[Java 9] Official Document Top https://docs.oracle.com/javase/jp/9/

[Java 9] JDK Javadoc (followed from the top screen) https://docs.oracle.com/javase/jp/9/docs/toc.htm

[Java 9] Java language specifications (can be traced from the top screen) https://docs.oracle.com/javase/specs/jls/se9/html/index.html

Chapter 1 Introduction

It only contains definitions of terms used in books. You don't have to read it. (end)

Chapter 2 Object Creation and Disappearance

Item 1 Consider a static factory method instead of a constructor.

For example, it looks like the following.

Consider the static factory method first, and if it's subtle, choose a constructor.

◆ Advantages

** ① You can give a descriptive name. ** **

The constructor has the following inconveniences.

Static factory methods do not have this inconvenience.

** ② There is no need to create a new object. Reuse the same object. ** **

** ③ You can return an object of that subtype instead of the return type itself. ** **

For example, java.util.Collections has a static factory method called ʻemptyList ()`. It has the following features.

Thanks to this, the Collections API is very simple. In particular···

** ④ You can switch the subtype to be returned according to the situation. ** **

In ③, the explanation is based on the assumption that the same subtype is always returned. What I want to say in ④ is that you can select and return the one that suits your situation from multiple subtypes.

** ⑤ The subtype to be returned can be decided at runtime. (It is OK even if it is not decided at the time of implementation of the static factory method.) **

For example, JDBC DriverManager.getConnection () corresponds to this.

As a result, it becomes more flexible as an API.

◆ Disadvantages

** ① The user cannot create a subclass of the return type. ** **

For example, ʻemptyList ()` in java.util.Collections returns EmptyList, but since EmptyList is a private class, users cannot create subclasses of EmptyList.

Not limited to this example, I couldn't think of a case that would make me want to create a subclass. In practice, I don't think there are any weaknesses or restrictions.

** ② It is difficult for users to find static factory methods. ** **

This is certainly the case. In Javadoc, the constructor is a separate section, which makes it stand out, but static factory methods are buried in the list of methods.

Try to make the API easy for users to understand by following the general naming pattern below.

Naming pattern Example Meaning
from Date d = Date.from(instant); Type conversion.
of Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING); Summarize.
valueOf BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE); It is used interchangeably with from and of.
instance / getInstance StackWalker luke = StackWalker.getInstance(options); Returns the instance according to the parameter.
create / newInstance Object newArray = Array.newInstance(classObject, arrayLen); Returns a new instance for each call.
get type name FileStore fs = Files.getFileStore(path); Return a class that is different from your own class.
new type name BufferedReader br = Files.newBufferedReader(path); Return a class that is different from your own class. Returns a new instance for each call.
Model name List<Complaint> litany = Collections.list(legacyLitany); getModel name、newModel nameの短縮版。

reference

Overview of Collections Framework https://docs.oracle.com/javase/jp/9/docs/api/java/util/doc-files/coll-overview.html

Item 2 Consider a builder when faced with many constructor parameters

[NG] Telescoping pattern

public class NutritionFacts {
    //Field definition omitted

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }

    //The above flow continues endlessly ...
}

NG point

[NG] JavaBeans pattern

//It's just plain JavaBeans.
public class NutritionFacts {
    //Field definition omitted

    public NutritionFats() {}
    
    public void setServingSize(int val) { //Abbreviation}
    public void setServings(int val) { //Abbreviation}
    public void setCalories(int val) { //Abbreviation}
    //The setter continues ...
}

NG point

[Builder pattern]

//User code
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
    calories(100).sodium(35).carbohydrate(27).build();

Advantages

Disadvantages (trivial)

Item 3 Enforce singleton characteristics with private constructor or enum type

There are three ways to achieve a singleton. Choose the best one for your situation.

If you implement a singleton with an enum type, it will look like this.

enum type singleton


public enum Elvis {
    INSTANCE;
    public void someMethod();
}

The best way is to use an enum, as other methods run the risk of not being a singleton and you will have to work around it. Specifically, it is as follows.

I think that these risks can be ignored in practice, but you should think about them properly.

I haven't seen the method of using enum type in practice, but it is rational in the above points, so it should be used positively.

The two methods of using the private constructor have the following advantages. However, I feel that there are only a limited number of cases where you want to obtain these advantages, so in the end, enums are the best choice in many cases.

Method Advantages
Set to public field and publish ・ It can be clearly understood from the API that it is a singleton.
・ Simple
Return with static factory method ・ You can change later whether it should be a singleton
・ Can be a generic singleton factory
・ You can use method references

Item 4 Force impossibility of instantiation with private constructor

A class consisting of only static methods and static fields is commonly referred to as a utility class.

Such classes shouldn't be instantiated and used, but if you can use the constructor, you risk accidentally instantiating them. I don't think there is much actual harm, but it makes me very disappointed when it is used in that way.

So let's implement a private constructor like this:

public class UtilityClass {
    //Suppress the default constructor so that it cannot be instantiated.
    //By leaving a comment like this, let's convey the implementation intention of this constructor to posterity.
    private UtilityClass() {
        throw new AssertionError();
    }
}

This will prevent you from being accidentally instantiated or accidentally inherited to create a subclass.

Item 5: Select dependency injection rather than directly linking resources

Let's take a spell checker as an example.

[NG] Implemented as a utility class

public class SpellChecker{
    //Dictionary used for Speccheck
    private static final Lexicon dictionary = ...;

    //Suppress instantiation according to the method of item 4
    private SpellChecker() {} 

    public static boolean isValid(String word) { ... }
}

[NG] Implemented to be a singleton

// SpellChecker.INSTANCE.isValid("some-word");Use like.
public class SpellChecker{
    //Dictionary used for Speccheck
    private final Lexicon dictionary = ...;

    //Suppress instantiation according to the method of item 4
    private SpellChecker() {} 
    public static SpellChecker INSTANCE = new SpellChecker(...); 

    public static boolean isValid(String word) { ... }
}

Since only one dictionary can be used in these NG examples, it is not possible to switch dictionaries depending on the situation. This difficulty applies not only to production code, but also to testing.

There is a way to provide a method like setDictionary (lexicon) so that you can change it later, but it's confusing to the user. It's also not thread safe.

In the first place, the fact that a dictionary changes depending on the situation means that the dictionary is a "state". Therefore, you should implement the spell checker as a class that can be instantiated and used.

Specifically, it is as follows.

[OK] Implemented in the style of dependency injection

public class SpellChecker{
    //Dictionary used for Speccheck
    private final Lexicon dictionary;

    //Since it has a "state" called a dictionary, it is instantiated and used.
    //At this time, a dependency called a dictionary is injected.
    public SpellChecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    } 

    public static boolean isValid(String word) { ... }
}

This way, you can switch dictionaries depending on the situation and it will be thread safe.

Item 6 Avoid creating unnecessary objects

Reusing objects can minimize the cost of creating objects and increase their speed.

On the other hand, if you create a lot of unnecessary objects, it will be extremely slow.

The immutable nature is very important because immutable objects (immutable objects) can always be safely reused.

[NG Part 1]

// new String()So, we are creating an unnecessary object.
// "bikini"So, we are creating an unnecessary object.
String s = new String("bikini");

【OK】

//The generated object is"bikini"String instance only.
//String literals within the same JVM"bikini"Instances of are always reused.
String s = "bikini";

[NG Part 2]

//Since it is a constructor, a new object is always created.
new Boolean(String);

【OK】

//The static factory method does not need to create a new object.
//A true or false Boolean object is reused.
Boolean.valueOf(String);

[NG Part 3]

//A Pattern object is created inside matches.
// isRomanNumeral()Every time is called, a Pattern object is created.
static boolean isRomanNumeral(String s){
    return s.matches("Regular expressions. The content is omitted.");
}

【OK】

public class RomanNumerals {
    //You are reusing a Pattern object.
    private static final Pattern ROMAN = Pattern.compile("Regular expressions. The content is omitted.");

    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}

[NG Part 4]

//NG example in auto boxing
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        //Since Long is immutable, a new instance will be created each time it is added.
        sum += i;

    return sum;
}

【OK】

private static long sum() {
    //Change to primitive type (Long)-> long)
    long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;

    return sum;
}

By the way, for example, Map.keySet () returns a Set view (adapter), but no matter how many times you call keySet (), the same Set instance will be returned. It's easy to think that a new instance is created each time you call it, but in fact, the instance is reused in these places as well, and efficiency is improved. Even when we implement the API, we should implement it optimally from the perspective of "Is it necessary to create a new instance?"

Item 7 Remove object references that are no longer used

If your class manages its own memory that is beyond the control of the garbage collector, you need to clear references to objects that you no longer need (set the variable to null).

Unmanaged means that the garbage collector is unaware of virtually unused objects.

Variables that are out of scope are usually subject to garbage collection. On the other hand, if you manage objects in a class, they will not be out of scope and will not be subject to garbage collection. From the garbage collector's point of view, the object is considered to be in use.

The book explains the above using a simple stack implementation as an example. Please refer to the book for details.

Case of implementing your own cache

Even if you implement your own cache, it corresponds to the above-mentioned "case of managing your own memory in the class".

For example, when caching image data with HashMap, HashMap should be managed as a field of some object A, so the references are connected as follows.

Source-> Object A-> HashMap-> key and value

Even if the data (key and value) managed by HashMap is actually no longer needed, that data will not be garbage collected as long as object A and HashMap continue to be referenced. As a result, the memory grows.

The remedy in this case is as follows. (I don't think there are many opportunities to implement the cache yourself ...)

** Method ① Implement the cache with WeakHashMap. ** **

When a key is no longer referenced by anything other than its WeakHashMap object, the WeakHashMap will be subject to the next GC for that entry (key-value pair). We use a so-called weak reference mechanism.

If you want to delete the key from the cache with the state that "the key is not referenced by other than that WeakHashMap object", consider adopting it.

As explained in this book, if it is deleted from the cache so easily, it can no longer be called a cache. For details, see [See below. ](# What is a reference weak reference)

** Method (2) Use ScheduledThreadPoolExecutor to periodically delete old data in the cache. ** **

This article is recommended for a quick understanding of how to use ScheduledThreadPoolExecutor. https://codechacha.com/ja/java-scheduled-thread-pool-executor/

Consider using it if you want to delete something that has been registered in the cache for some time.

** Method ③ When adding a new entry to the cache, delete the old one. ** **

It's simple. As with (2), if you want to delete something that has been registered in the cache for some time, consider using it.

Case of registering listeners and callbacks in memory

When creating an API that allows listeners and callbacks to be registered from the client, the registered listeners and callbacks will not be subject to GC unless they are created with proper consideration.

It's a good idea to use a weak reference mechanism, such as saving it as a WeakHashMap key. If it is no longer used by anyone other than WeakHashMap, it will be subject to GC.

Reference: What is a weak reference?

Objects that normally cannot reach the referrer (objects that are not used by anyone) are subject to GC and are deleted from memory.

However, it can be a problem if it is deleted immediately. There is a mechanism to prevent objects that cannot be reached from the reference source from being immediately subject to GC. That is the java.lang.ref package.

In the world of this package, ordinary references are called "strong references", and their own "references" are defined as follows.

java.lang.Types of references in ref Difficulty in becoming a GC target (relative value) Description Use
Weak reference
Erased easily
If only you (WeakReference object) are referencing an object A, you will be subject to the next GC. It is used when you want to delete object A from memory immediately after the strong reference to object A disappears. WeakHashMap uses WeakReference for this very purpose. In this book, it is written that it can be used as a cache, but if the strong reference disappears, it will disappear, I think that it is no longer a cache, so I think that there is almost no opportunity to use it.
Software reference ★★
Pretty stubborn
If only you (SoftReference object) are referencing an object A, the referenced object has recently been created./If referenced, it will not be subject to the next GC. If not, it will be subject to the next GC. I think this is for caching purposes, but Java SE doesn't have a Map to support it. There seems to be almost no chance to actually use it.
Phantom reference ★★★
Basically it doesn't disappear
Even if only you (PhantomReference object) is referencing a certain object A, it will not be the target of GC. (unknown)

Item 8 Avoid finalizers and cleaners

The finalizer here is java.lang.Object # finalize () or a method that overrides it in a subclass.

A cleaner is a java.lang.ref.Cleaner.

There are various reasons why this is not good, but there is almost no need to understand the contents.

Never use it because it is dangerous anyway. That's fine.

Item 9 Select try-with-resources rather than try-finally

Try-finally has the following problems:

Try-with-resources solves these problems.

If an exception occurs during try and then an exception occurs even with close, the former will be given priority and thrown. You can catch this in the catfh clause.

To access the latter, use the getSuppressed method in the former exception object. However, in many cases you will want to know the former, so it seems that you will not have many opportunities to use it.

Chapter 3 Methods common to all objects

Item 10 When overriding equals, follow the general contract

I think the opportunity to override the equals method is limited in the first place. If you are overriding, there are requirements to meet.

If you need to override the equals method, stick to that requirement. Specifically, the requirements are as follows.

The book says there are things to keep in mind about each requirement, but there aren't many opportunities to override equals in the first place, so it's probably less costly to learn more when you don't need it.

For this reason, I'll just refer to the book when I need it, and I won't go into detail in this article either.

Item 11 When overriding equals, always override hashCode

As you can see in item 10, there aren't many chances to override equals, so this item doesn't seem to be very important either. I'll just give you an overview.

The requirements for overriding the hashCode method are as follows:

Item 12 Always override toString

The advantage of overriding the toString method is that it makes it easier for users of that class to debug.

However, the system created in practice is not a work of art, and human and temporal resources are limited. Therefore, I think that you should decide whether or not to override if necessary.

If you override the toString method, be aware of the following:

Item 13 Carefully override clone

Overriding Object.clone () is basically NG. The reason is as follows.

Since it is NG, you should not override Object.clone () unless you already have a class that overrides Object.clone () and you have to fix it for maintenance.

Instead, use the following methods. These do not have the above drawbacks.

//Copy constructor
public Yum(Yum yum) { ... }

//Copy factory
public static Yum newInstance(Yum yum) { ... }

Regardless of whether you use a copy constructor or copy factory method or Object.clone (), be aware of the following points in common.

In other words, when copying a field, it is not enough to set the copy source object reference to the copy destination field. This is because the same object reference is shared between the copy source and the copy destination. This is the so-called shallow copy. It's a natural idea, but it's quite cumbersome to implement. There are exceptions to this rule, and if it's an immutable object, you can copy the reference.

To understand this item, it is recommended that you know the following in advance.

Item 14 Consider implementing Comparable

If you implement Compareable.compareTo () in the class you develop, you can store the objects of that class in the collection and use the convenient API of the collection. For example, you will be able to sort nicely.

If you want to get this benefit, implement a Comparable interface.

In that case, requirements similar to overriding the equals method must be met.

There are notes on the first three requirements. Given an existing class that implements Comparable, it is virtually impossible to meet these requirements if you extend it and add new fields. If you force it, it's no longer object-oriented code. In such a case, let's realize it with composition instead of extends (item 18).

What if I violate the fourth requirement? An example of a violation is Big Decimal. new BigDecimal ("1.0") and new BigDecimal ("1.00") are not equal in the equals method and equal in the compareTo method.

If you put them in a new HashSet, they will be compared by the equiqls method, so the number of elements will be 2. On the other hand, if you put it in a new TreeSet, the number of elements will be 1 because it will be compared by the compareTo method. If you don't keep this behavior in mind, you'll have no idea what the cause is in the unlikely event of a problem.

In addition to the four requirements, note the following:

private static final Comparator<PhoneNumber> COMPARATOR = 
            comparingInt((PhoneNumber pn) -> pn.areaCode)
                .thenComparingInt(pn -> pn.prefix)
                .thenComparingInt(pn -> pn.lineNum);

public int compareTo(PhoneNumber pn) {
    return COMPARATOR.compare(this, pn);
}
//NG example
static Comparator<Object> hashCodeOrder = new Comparator<>() {
    public int compare(Object o1, Object o2) {
        return o1.hashCode() - o2.hashCode();
    }
}

Chapter 4 Classes and Interfaces

Item 15 Minimize accessibility to classes and members

What is information hiding and encapsulation?

Minimize the part of the component that can be accessed from outside (public API). Make other parts (internal data, implementation details) inaccessible from the outside.

To put it plainly, minimize what you declare in public or protected. Carefully design your class to do so.

Reasons for information hiding and encapsulation (why)

Information hiding and encapsulation allows components to be developed individually and optimized individually. This greatly reduces the worry of harming other components.

The purpose of information hiding and encapsulation is to aim for the following.

If you cannot achieve these goals, there is no point in aiming for information hiding or encapsulation. Practical programs are not works of art, but a means to a successful business. So you shouldn't be satisfied that you've made something beautiful (I'm very careful because it's easy to be satisfied that way).

How to do it (how)

Specifically, consider the following points.

Item 16 In the public class, use the accessor method instead of the public field.

It's a matter of course to add setter / getter.

In practice, I think it's common sense to do this, and I think it's easy to add setters / getters without thinking about anything. However, if you do not understand the reason, it will not be a good class design. Let's understand the reason why we should add setter / getter again.

The reason you should add setter / getter is to get the benefits of information hiding and encapsulation. Specifically, it is as follows.

Since the essential point is to reduce the public API as much as possible, there is little need to prepare setters / getters in the fields for package private classes and private inner classes. If you try to provide setters / getters unnecessarily, it will take more time to implement and the code will be difficult to read. Don't feel like "just add a setter / getter without thinking".

Item 17 Minimize variability

Immutable objects (immutable objects) have several advantages, including the fact that they can be used thread-safely. Typical examples in the JDK are boxed basic data classes such as String and Integer, BigDecimal and BigInteger.

If you create your own class that has values, make it immutable unless you have a good reason to make it variable. Even if it's not practical to make it perfectly immutable, it's a good idea to make it as immutable as possible, such as making the field as final as possible. This is because if the number of possible states is reduced, there are merits such as less trouble.

Benefits of immutable objects

Disadvantages of immutable objects

How to make an immutable object (requirements to be met)

Item 18 Choose composition over inheritance

Inheritance breaks encapsulation. In other words, subclasses depend on the implementation details of the superclass. As a result, changes in the implementation details of superclasses can lead to subclasses not working as intended or security holes.

You might think it's okay if the subclass doesn't override the superclass's methods, but that's not the case. The method signatures that the superclass added later may conflict with the methods implemented in the subclass.

Because of these inconveniences, don't jump into inheritance suddenly. Compositions do not have the same inconvenience.

However, inheritance is 100% bad, and composition is not 100% positive. Let's be able to choose between composition and inheritance depending on the situation.

What is composition

Instead of inheriting the existing class, keep the existing class in the private field, as shown below. Extend an existing class by calling the methods of that existing class.


//The transfer class. Composition has been applied in this class.
//A class is provided separately from the InstrumentedSet so that it can be reused.
public class ForwardingSet<E> implements Set<E> {
    //Hold the object of the existing class you want to extend in the field.
    private final Set<E> s;

    public ForwardingSet(Set<E> s) {
        this.s = s;
    }

    //Throw the process to the object of the existing class you want to extend.
    public void clear() {
        this.s.clear();
    }

    //Omitted thereafter.
}

/*
Create your own class by inheriting the transfer class.
You might think, "What? Inheritance is not good, right?", But inheritance here is
It's a reasonable decision to make the ForwardingSet reusable for other purposes.
*/
public class InstrumentedSet<E> extends FowardingSet<E> {
    //This class is responsible for managing how many times a set has been added in total.
    private int addCount = 0;

    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    //Omitted thereafter.
}

By the way, the technique in the above code example is not exactly "delegation". Please be careful.

When it is okay to inherit

You may inherit in the following cases.

As a major premise when adopting inheritance, it is necessary that an "is-a" relationship is established between the classes. That is, if class B inherits from class A, the answer "Are all Bs A?" Must be yes. Inheritance is a technology for realizing such relationships in code.

Item 19 Design and document for inheritance, otherwise prohibit inheritance

Creating a class that is supposed to be inherited is actually extremely difficult and difficult. Specifically, you need to consider the following:

As you can see, it's extremely difficult. If you make a class that is supposed to be inherited by all means, be prepared to take on the above points. That is a professional.

I think there are more cases where this is not the case than when creating a class that is supposed to be inherited. In that case, either make the class final or make the constructor private and prepare a static factory method so that the created class is not inherited by mistake.

Item 20 Select an interface over an abstract class

This item is extremely difficult to read. I will explain with priority on comprehensibility.

There are multiple implementations of a "type". For example, there are multiple implementations of a "type" called Comparable. Java provides the following two mechanisms to realize such a "allow multiple implementations" type.

If you want to create a new type that "allows multiple implementations", basically do it with an interface. Interfaces are superior to abstract classes in the following ways: It is easy for users to use.

Techniques for creating complicated interfaces "Implementation assistance" "Skeletal implementation"

If you want to create a simple interface yourself, this is a technique you don't have to worry about. Please see only those who need it.

When you create your own interface, suppose you define multiple methods for that interface. For example, suppose you define method A and method B. Usually, if you know that method A calls method B, it's easier for you to implement the typical method A logic in your interface. For users, it's easier if there are few parts that they implement.

There are the following two patterns to achieve this.

Item 21 Design the interface for the future

Once the interface is published, it's the last. It's not so easy to change. Let's verify it thoroughly before publishing.

If you add a method to an interface later, the class that implements that interface will get a compile error. You can use default as a "Tips" to avoid the problem, but this is NG in the following points. It is important to design firmly without relying on default.

Item 22 Use the interface only to define the type

There is a method of defining constants in an interface and implementing the interface so that the class can use the constants. This is NG. The JDK actually has such an interface, but don't copy it.

This is because the class makes use of the constants of other components, which is an implementation detail. Implementing an interface means making that part a public API. Implementation details should not be public APIs. In the first place, exposing constants is out of the question because it is far from the essence of the interface.

If you want to provide the constant to the outside, do one of the following.

//Non-instantiable utility class
public class PhysicalConstatns(){
    private PhysicalConstants() {} //Prevent instantiation

    public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
}

By statically importing the constant class, the user does not have to write the class name every time the constant is used. It is explained in the book, but when you look back at it later, you will be asked "Where is this constant defined?", Which can be difficult to read. Consider the balance when deciding whether a static import is appropriate.

Item 23 Select a class hierarchy rather than a tagged class

Suppose you have a class in one class that expresses whether it is a circle or a rectangle. Some of them have constructors, fields, and methods that are used for circles, and some are used for rectangles as well.

Classes that try to express multiple concepts in one class in this way are called "tagged classes" in books.

If you want to express multiple concepts, divide the classes obediently. Specifically, let's use inheritance / subtyping to organize them into a hierarchical structure. It's natural.

Item 24 Select a static member class over a non-static member class

Sometimes you want to declare another class inside a class. In this context, the former class is called the "enclosing class" and the latter class is called the "nested class".

There are several ways to implement a nested class. Specifically, there are the following four.

Let's use these properly. Considering the following flow, there should be no mistake.

Examination step 0

Is it possible that the "nested class" I'm trying to create will be used by another class, regardless of the enclosing class?

If YES, it shouldn't be created as a nested class in the first place. It should be created as just an ordinary class, independent of the enclosing class.

Examination step 1

If the "nested class" you are trying to create is all of the following, use ** anonymous class **.

If an anonymous class is declared in a non-static method of the enclosing class, the instance of the anonymous class will automatically refer to the instance of the enclosing class. In some cases this causes a memory leak. Be aware of the danger before using it.

This does not happen if it is declared inside a non-static method.

Examination step 2

If the "nested class" you are trying to create is all of the following, use ** local class **.

Examination step 3

If the "nested class" you are trying to create is all of the following, use a ** non-static member class **.

Examination step 4

If the "nested class" you are trying to create is all of the following, use a ** static member class **.

Item 25 Limit source files to a single top-level class

Normally, you wouldn't implement multiple top-level classes in a single source file. This item explains why it's NG, but you don't need to know the reason because you don't do that in practice in the first place. (end)

Chapter 5 Generics

Item 26: Do not use the prototype

A prototype is an expression that does not involve a type parameter, such as List instead ofList <String>.

It's no longer common sense that you shouldn't use the prototype. The reason is that you may get a ClassCastException at run time. If you implement it with type parameters, you can notice such risks in the form of compile errors and compile-time warnings. Let's not use the prototype.

However, there is a situation where you may inadvertently use the prototype. Specifically, it is as follows.

【NG】

//Don't do this just because you don't know the type parameters of Set.
static int numElementsInCommon(Set s1, Set s2) {
    int result = 0;
    for (Object o1 : s1)
        if(s2.contains(o1))
            result++;
    return result;
}

Let's implement it as follows.

【OK】

//If you do this, you will get a compile error when you try to add an element to s1 or s2.
//It's definitely better than noticing at runtime. (However, null can be added)
static int numElementsInCommon(Set<?> s1, Set<?> s2) {
    //Abbreviation
}

Item 27 Remove uninspected warning

When implemented using generics, the risks leading to a ClassCastException at runtime can be noticed by compile errors and compile-time warnings.

You can compile the warnings if you leave them alone, but as a general rule, respond to all the warnings and fix them so that they do not appear. Add @SuppressWarning ("unchecked ") to suppress the warning only if there is really no problem. By the way, "unchecked" is a warning that means "I haven't checked the type, but is it okay?"

If you don't suppress the warning when it's really okay, you won't notice the really problematic warning. Let's not cut corners there either.

Item 28 Select a list rather than an array

Due to historical background, sequences and generics have different properties. Because of that, using them in combination causes problems. Unless you have a noticeable problem with code simplicity or performance, implement it uniformly in Generics.

What kind of problems will occur when used in combination?

If you implement both in combination, you will not understand the meaning of the errors and warnings issued by the compiler, and you will be unnecessarily confused. In some cases, the warning is suppressed without careful consideration, resulting in a ClassCastException or asking maintenance members "Why are you doing @SuppressWarning here ...? " Will embrace and confuse you.

For example ...

Why does this happen?

This is because there are the following differences between the two. (Although the book explains, it doesn't explain why these differences lead to the above problems ... I'll add more commentary later if I have time.)

Difference ① Difference ②
Array For exampleObject[] objectArray = new Long[1];ThenLong[]IsObject[]As a subtype ofWill be treated.. So these assignments are allowed. These properties are called covariants. It is good to remember with nuances such as "flexibly change according to the other party". ArrayIs、At run time自身がどんな型を格納できるのか、ということを知っています。なので、不適切な型のオブジェクトを代入しようとすると、At run timeI get an exception. These properties are called "concrete".
Generics For exampleList<Object> objectList = new ArrayList<Long>();ThenArrayList<Long>IsList<Object>As a subtype ofNot treated.. Therefore, a compile error will occur. These properties are called invariants. It's not as flexible as covariant. GenericsIsAt compile timeのみ型制約を強制し、実行時にIs要素の型情報を廃棄(erase)します。こういったことを「イレイジャで実装されている」と表現します。これIs、Genericsが導入された時に、Genericsを利用していない既存のコードと、利用する新しいコードが共存できるようにするための措置です。これが冒頭で触れた「歴史的経緯」です。

Item 29 Use generic type

When you create your own class, you should make it as generic as possible. By doing so, users will have the following benefits:

In some cases, it may be better to use arrays inside your own class. For example, if you want to create a basic generic type like ArrayList, or if you have performance reasons.

In these cases, you'll have to do @SuppressWanings ("unchecked ") inside the class to suppress compile-time warnings. Of course, it goes without saying that we should carefully consider whether it is really appropriate to suppress it.

Item 30 Use generic method

When you write your own method, you should make it as generic as possible. By doing so, the user will get the same benefits as item 29.

First, I will explain the case of defining a normal generic method.

[NG] Non-generic method

//If the type of the object to be held is different between s1 and s2, ClassCastException will be thrown at runtime.
public static Set union(Set s1, Set s2) {
    Set reslut = new HashSet(s1);
    result.addAll(s2);
    return result;
}

[OK] Generic method

//The type parameters used in the method (list of) must be declared between the qualifier and the return type.
//In this example, it's right after static<E>That is.
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> reslut = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

Next is the case of implementing an advanced generic method. I will introduce the following two techniques.

Technique # 1: Generic Singleton Factory

Let's say you need an API that has the role of returning an object that works with the type specified by the user. If the object to be created does not have a state, it is useless to create the object every time because the type specified by the user is different.

The generic singleton factory is a technique that can make an object a singleton (that is, reduce the cost of creating an instance and the amount of memory used) while operating with the type specified by the user.

The book gives an identity function as an example of this. By the way, the identity function is a function that returns the parameters as they are. What is it useful for? You may think that it appears as one of the activation functions in the area of machine learning. I have to specify something in the API as an activation function, but I don't want to do anything, so there is a use such as specifying a function that does virtually nothing.

//Point of this technique ①
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

//Point of this technique ②
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
    return (UnaryOperator<T>) IDENTITY_FN;
}

** Points of this technique ① **

Because the generics are implemented by eraser, it is OK to just return one instance called IDENTITY_FN no matter what type is specified by the user.

On the other hand, if the generics are embodied, that is, if IDENTITY_FN remembers the type parameter specified by the user even at runtime, one instance called IDENTITY_FN cannot handle it.

For example, if a user calls identityFunction () with a String as the type parameter, IDENTITY_FN must be ʻUnaryOperator ` (a world where generics are embodied). In this case, in order to correspond to the person who wants to specify Long as the type parameter, the Long version of IDENTITY_FN must be prepared. You'll also need a Long version of the identityFunction () method that returns that object.

** Point of this technique ② **

When I try to implement a generic singleton factory, I cast it without inspection, is that okay? Will be warned. But in many cases it's okay. Recognize the reason and suppress the warning.

First, what does the uninspected cast warning mean in this example?

ʻUnaryOperator ` is supposed to work with a "specific type" called Object. Object is the most general type, but since it is positioned as one type included in T that represents all types, it is expressed as "specific type".

On the other hand, T in ʻUnaryOperator ` means all types, but it is fixed to one type when the user uses it. It is determined by the type specified by the user.

If UnaryOperator that is supposed to operate with a specific type is treated as UnaryOperator of the type specified by the user, "type specified by the user" is changed to "" You may not be able to cast to a specific type and you may get a ClassCastException. From a compiler that doesn't know the circumstances, the possibility cannot be denied.

These messages are included in the warning.

However, in this case, the argument passed by the user is simply returned without any change. To be precise, it is cast to Object from the "user-specified type" internally at runtime, but since it is cast to the one that stands at the top of the class hierarchy called Object, there is no room for ClassCastException to occur. For this reason, it is okay to suppress the warning with the feeling that "Compiler, this time it's okay".

** Reference: Why doesn't this cast cause a compile error? ** **

The return (UnaryOperator <T>) IDENTITY_FN; part gives an uninspected cast warning. Why can ʻUnaryOperator be cast to ʻUnaryOperator <T> in the first place? So why don't you get a compile error?

The generics are invariant so that List <Object> objectList = new ArrayList <Long> (); will result in a compilation error. So, at first glance, it seems that such a cast is not possible.

However, if one uses a generic type like T as a type parameter, the compiler will determine that they are not completely different types, so casting is allowed. By the way, bidirectional casting is allowed.

This is called narrowing reference conversion and is defined by the Java language convention. For more information, here is very helpful.

Technique # 2: Recursive boundary

This is a technique to set some restrictions on the type parameters that can be specified by the user. It's easier to see why we call it "recursive" by looking at an example.

public static <E extends Comparable<E>> E max(Collection<E> c);

What does <E extends Comparable <E >> mean?

The type specified by the user in the type parameter must be comparable to other objects of the same type.

about it.

To put it more simply, the collection specified by the user as an argument must be able to compare the elements with each other.

With this kind of feeling, you can set restrictions on the type parameters that can be specified by the user.

Item 31 Use boundary wildcards to improve API flexibility

This item is also quite difficult to read. I will explain by chewing.

Let's say your API takes a parameterized type as an argument. For example, List <E>, Set <E>, ʻIterable , Collection `.

In such cases, there are some things that need to be devised to make the API easy for users to use.

What is "ease of use" for users?

First, let's take the user's point of view. Suppose someone exposes a class called Stack as an API and you are using it.

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;

//Since Integer is a subtype of Number, do you feel that you can intuitively use it like this?
numberStack.pushAll(integers);

As a user, you implicitly think this way. "If we provide an object to the API, passing an object of a more specific type will surely work."

On the contrary, what about the following cases?

Stack<Number> numberStack = new Stack<>();
Collection<Object> objectsHolder = ... ;

//Since Object is a super type of Number, do you feel that it can be used intuitively like this?
numberStack.popAll(objectsHolder);

As a user, you implicitly think this way. "If this is the recipient of the object from the API, it will be perceived as a more abstract type of object."

For users, it would be helpful if the API had this kind of flexibility.

What should I do to do that?

Let's return to the position of creating the API.

First of all, the first case.

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;

//Since Integer is a subtype of Number, do you feel that you can intuitively use it like this?
numberStack.pushAll(integers);

To give the API this flexibility, implement the API as follows:

//The point is<? extends E>It is the part of. The logic part is omitted.
public <E> void pushAll(Iterable<? extends E> src) {
    ...
}

The user implicitly thinks like this. "If we provide an object to the API, passing an object of a more specific type will surely work."

If the user's feelings are realized by API, it should be "E itself or E's subtype Iterable" instead of "E's Iterable".

When a parameter provides an object to the API in this way, it is said to be a producer. "In the case of producer, extends".

The type that is parameterized like <? Extends E> is called "** boundary wildcard type **".

Next is the second case.

Stack<Number> numberStack = new Stack<>();
Collection<Object> objectsHolder = ... ;

//Since Object is a super type of Number, do you feel that it can be used intuitively like this?
numberStack.popAll(objectsHolder);

To give the API this flexibility, implement the API as follows:

//The point is<? super E>It is the part of. The logic part is omitted.
public <E> void popAll(Collection<? super E> dst) {
    ...
}

The user implicitly thinks like this. "If this is the recipient of the object from the API, it will be perceived as a more abstract type of object."

If the user's feelings are realized by API, it should be "E itself or E's super type Collection" instead of "E's Collection".

When a parameter receives an object from the API in this way, it is said to be a consumer. "In the case of consumer, it's super."

In summary, it is as follows.

  • If the parameter is ** p ** roducer, <? ** e ** xtends E>
  • If the parameter is ** c ** onsumer, <? ** s ** upper E>

Take the acronym and remember it as "PECS".

Other advice

Here are some relatively diligent advice.

It is NG to apply the boundary wildcard type to the ** return value ** type of the * API. Instead of giving the user flexibility, it enforces constraints.

  • If you make the user aware of the wildcard type, it means that the API is difficult for the user to use. Let's review the API design.

  • Always use T extends Comparable <? Super T> as the API argument, not T extends Comparable <T>. Since <? Super T> is the comparison destination and the consumer who receives T, add super. It means "T itself or T that can be compared with the super type of T". By doing this, T itself does not implement Comparable, but if T's supertype implements Comparable, it can be passed as an argument to the API. An example is shown below.

    //API example (It's very complicated ... It's a price to make it flexible.)
    public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        ...
    }
    
    //API usage example
    List<ScheduledFuture<?>> scheduledFutures = ...;
    ScheduledFuture<?> max = max(scheduledFutures);
    

ScheduledFuture itself does not implement Comparable, but the supertype Delayed does. It means that you can get the help of the super type Delayed. max is a flexible API.

Item 32 Carefully combine generics and variadic arguments

Attempting to generate an "array with generics as an element" such as List <String> [] will result in a compile error. This is because if you allow the existence of such an array, a ClassCastException may occur at runtime (item 28).

But there are exceptions to this. Variadic arguments are realized by arrays, but by specifying generics for variable-length arguments, an array with generics as an element is created.

static void dangerous(List<String>... stringLists) {
    //The identity of stringLists is List<String>It is an "array" that has.
    //This may cause a ClassCastException somewhere.
    List<String>[] array = stringLists;
    ...
}

Because of these circumstances, consider the following points when creating an API.

  • If there is no problem in terms of performance and code redundancy, let's accept variable length arguments in List instead of specifying generics in variable length arguments. You don't have to aggressively combine variadic and generics, which are incompatible. You may be wondering, "Is it troublesome for the user to generate a List of arguments?", But it is enough to have the user use List.of ().

  • If you really want to specify generics for variadic arguments, support all of the following.

  • Eliminate the risk of a ClassCastException at runtime. for that purpose···

  • Do not save (do not overwrite) elements in the array of generics.

  • Do not expose (see) an array of generics to untrusted code.

  • Let's indicate with @SafeVarargs that there is no danger of ClassCastException. Annotate the method with this annotation. This way, you won't get unnecessary compiler warnings when you call your API.

Item 33: Consider a safe heterogeneous container

Personally, I think the content is quite advanced.

What is a "heterogeneous container" in the first place? When are you happy?

Let's take a concrete example.

If you are in a position to create an application FW (framework) and have other members use it, you may use annotations to control individual applications. I make annotations, and members annotate their own classes. I use that annotation as a guide to control the apps (classes) created by the members.

In FW, annotations are obtained from the class created by the member to determine how to control the class of the member.

Information about what annotations are attached to the class created by the member is stored as a "heterogeneous container" in the Class object of that class.

The FW wants to know what value is set for @ MyAnnotaion1 that the member has attached to the class. So call ClassCreatedByMember.class.getAnnotation (MyAnnotation1.class) to get the @ MyAnnotation1 (the object that represents) that the member has attached to that class.

Similarly, if you want to know the information for @ MyAnnotation2, the FW callsClassCreatedByMember.class.getAnnotation (MyAnnotation2.class)s.

In this way, you may want to use a specific Class object (MyAnnotation1.class or MyAnnotation2.class in this case) as a key to store the corresponding object. That is the "heterogeneous container" introduced in this item.

One day, you may have the opportunity to create such a "heterogeneous container" yourself. Remember the content of this item as a technique.

How to make a good heterogeneous container

This section describes how to make a good heterogeneous container. Specifically, it explains how to make it type-safe (to prevent ClassCastException from occurring).

The points are as follows.

public class Favorites {
    //・ Wildcard "?Uses to give you the flexibility to use different types as keys.
    //-Map value is Object type. Is this type safe? You might think, but we ensure type safety elsewhere.
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        //-As a countermeasure when the prototype is specified by mistake, type.cast()Check the type with.
        favorites.put(Objects.requireNonNull(type), type.cast(instance));
    }

    public <T> T getFavorite(Class<T> type) {
        //-The type specified by the argument type is always returned.
        //For example, String.Integer is returned even though class is specified,
        //There is no such thing as a ClassCastException.
        //・ Favorites.get()The result of is Object type, but I am casting it because it is a problem as it is.
        return type.cast(favorites.get(type));
    }
}

In the Favorites example, the user can store basically any type of object. However, in some cases you may want to put some restrictions on the types that can be stored.

For example, if you want to set a restriction that it must be a subtype of Annotation

public <T extends Annotation> T getAnnotation(Class<T> annotationType);

You can impose restrictions on users with <T extends Annotation>, as in.

Chapter 6 enums and annotations

Item 34 Use enum instead of int constant

"Difficulty" when declaring a constant without using an enum

If you declare a constant with an int without using an enum ...

  • As long as the mold matches, it can be used for purposes that are far from the original purpose. I can't notice such a situation with a compile error.
  • It does not have its own namespace, so it must have a name that does not overlap with other unrelated constants.
  • The name of the constant does not appear in the log or debugger. Displaying only the value will not help your investigation.
  • You cannot iterate constants or count the number of constants.

If you declare a constant with String ...

  • You cannot force the use of that constant field. Even if it is hard-coded and mistyped, it will not cause a compile error, so you will not notice such a situation.

Enum type mechanism and features

It can be said that the essence of the function called enum type is to give "unique type" to "enumerated things" such as constants. Since it is a unique type, there is no "difficulty when not using enum" mentioned above.

The enum type is, after all, a class. It's a special form of class.

You can (implicitly) have your own instance in your public static final field and define your own fields and methods. These properties are the same as in a normal class, but they differ from a normal class in that various work is done behind the scenes.

For example, it looks like the following.

  • Types declared with enum will automatically inherit java.lang.Enum. In addition, java.lang.Enum is an abstract class, and the functions that enumerations should have in common are firmly implemented as shown below.
  • Appropriately overrides equals () and hashCode () in the Object class.
  • Implements Comparable.
  • Implements Serializable. No matter how the enum type is implemented, it can be supported.
  • Public static final field is provided, enum type is instantiated and set in that field, etc. are automatically done behind the scenes.

The enum has the following features.

  • One instance of public static final field is assigned to one enumeration constant defined in enum type. Each enumeration constant is a singleton.
  • Since the enum type does not have a constructor that can be accessed from the outside, it has the following "happy restrictions".
  • The enum type itself cannot be instantiated externally.
  • The enum type cannot be inherited from the outside.
  • enum types have their own namespace. You don't have to worry about constant name collisions.
  • toString () allows you to display information in the log or debugger that will help you investigate.
  • Methods and fields can be defined for enum types.
  • The enum type can implement any interface.

Personally, I think it's harder to understand the mechanism of enum types than people think in the world. Unless you have a solid understanding of the Java language specification, it may be a little difficult to use enums effectively.

Java language specifications 8.9. Enum Types https://docs.oracle.com/javase/specs/jls/se9/html/jls-8.html#jls-8.9

I think the following points should be known because it is written in the Java language specification.

  • The following results are guaranteed to be a singleton.
  • Calling the enum clone method does not duplicate the instance.
  • You cannot instantiate an enum type even with reflection.
  • Deserialization of enum type instances does not create duplicate instances.
  • You can define your own constructor for enum types, but you cannot add public or protected. It is defined without an access modifier, but in that case it is automatically treated as private.
  • If you do not define your own constructor for the enum type, a private default constructor is automatically provided.
  • The static field of the enum is not yet initialized when the constructor is executed. So you can't access static fields from the constructor.
  • When you define an enum type, the following methods are implicitly declared.
    • public static E[] values();
    • public static E valueOf(String name);

Points when making an enum type

When creating your own enum type, consider the following points.

  • Minimize visibility of enum types, just like regular classes.

  • You may want to have the same method name but different behavior for each constant. It's polymorphism. You can inherit yourself inside an enum type. Specifically, you can define an anonymous class when declaring an enum constant in an enum type. You can inherit the enum type itself from that anonymous class, override the absolute methods defined in the enum type itself, or implement the methods of the interface that the enum type implements.

If you want to share the code between constants, you can override the abstract method for each constant and make the common part a private method. As you can see in the book, I think you can adopt the strategic enum pattern. In any case, what is required is "whether to notice a compilation error when improper implementation", and the criterion for choosing which one is "how to balance simplicity and flexibility". That is.

  • You can extend the behavior by adding a switch statement to the existing enum type. This is useful in the following cases.

  • Let's say you have an existing enum type that behaves differently for each constant. You can't modify that enum type. But we have to extend that existing enum-type behavior.

  • It's not enough to add it as a method to the existing enum type, but I need some extended behavior for myself.

  • If you override toString () to return a unique name, valueOf (String) will not support that unique name. It is better to have a method like fromString (String) that can handle your own name.

Item 35 Use instance fields instead of ordinal numbers

java.lang.Enum has a method called ordinal (). Calling ordinal () on an instance of an enum constant returns an int indicating the number of the enum constant declared in the enum type.

If you try to do anything with this method, it often breaks. Logic that depends on what number is declared seems to be vulnerable to change.

So don't use ordinal () unless it's a very good case.

If you understand this item at such a level, there is no problem in practice.

Item 36 Use EnumSet instead of bitfield

Sometimes you want to work with a set of constants.

For example, suppose a format enum has elements such as "bold", "italic", and "underline". In this case, you need to be able to represent a combination of elements, such as "bold" and "italic".

Before the advent of enum types, this was represented by bits.

//It is NG in modern times.

//Constant declaration
private static final int STYLE_BOLD      = 1 << 0; // 0001
private static final int STYLE_ITALIC    = 1 << 1; // 0010
private static final int STYLE_UNDERLINE = 1 << 2; // 0100

//Bold and italic
int styles = STYLE_BOLD | STYLE_ITALIC // 0011

It's like that.

While this method has the advantages of being concise and performing well, it has the disadvantage of having to determine the number of bits at the beginning, in addition to the drawbacks of the int constant described in item 34.

In modern times, there is a good way to express this "combination of constant elements". That is EnumSet.

//This is recommended in modern times.

//Constant declaration
enum Style {BOLD, ITALIC, UNDERLINE}

//Bold and italic
Set<Style> styles = EnumSet.of(Style.BOLD, Style.ITALIC);

This is clearly concise. Since bit operations are performed inside EnumSet, performance is also good. Of course, there are no drawbacks to int constants.

Item 37 Use EnumMap instead of ordinal index

You may want to create a Map by using an enumeration constant (instance of enum type) as a key and some other data as a value.

In that case, use EnumMap.

As with item 35, don't use java.lang.Enum.ordinary () if you make a mistake.

(end)

Item 38 Imitate an extensible enum with an interface

The enum type enumeration constants you expose may not be enough for you.

For example, if you publish an enum type that represents four arithmetic operations, the user may want to have an enumeration constant that represents a power operation.

In order to give the API that kind of flexibility, let's implement the interface in the exposed enum type. Ask the user to create their own enum type and implement the interface. And in your API, write the logic for the interface, not for the implementation class enum that represents the four arithmetic operations. By doing this, the user can operate the extended enum type.

Item 39 Select annotations over naming patterns

It's a paged item, but it's just a long code example, and there's not much to learn. Specifically, it is as follows.

In the old days, it was common practice to set some rules for the names of program elements such as methods, and tools and frameworks control the program using the "markers" given according to the rules as clues. .. For example, JUnit has a rule that test method names start with test. These techniques are called naming patterns.

Such techniques are clearly vulnerable.

If you want to control the program from a tool or framework, annotate it with "clues". It is free from the vulnerabilities of naming patterns.

The JDK already has a lot of useful annotations. Let's make good use of them.

(end)

Item 40 Always use Override annotation

Be sure to add @Override when overriding supertype methods. The compiler will tell you the mistake that you intended to override it but didn't.

(end)

Item 41 Use the marker interface to define the type

This item is pretty hard to read ... I will chew and explain.

For example, suppose you are developing a FW or tool and want to control the individual programs that use them. In that case, it is necessary to put some kind of "marker" (marker) on the individual program so that the FW and the tool can judge what part of the individual program is controlled and how.

There are two ways to achieve these markers:

  • Marker interface (Serializable, etc.)
  • Marker annotations (such as JUnit @ Test)

How should these be used properly in this item? It is explained that.

Advantages of the marker interface

  • The marker interface can define types. This allows you to notice mistakes at compile time. Marker annotations, on the other hand, don't do that.

  • The marker interface can have conditions to apply.

For example, suppose you have an interface A and you want the marker interface to be applied only to the classes that implement that interface A. In that case, let the marker interface extend interface A. Then, the class that implements the marker interface will automatically implement interface A as well.

You can add the condition that "to attach this marker interface, you need to implement interface A". (I can't think of a concrete example of this situation ...)

Marker annotations, on the other hand, don't do that.

Advantages of marker annotation

  • Applicable to other than classes and interfaces. Marker interfaces, on the other hand, can only be applied to classes and interfaces.

Concept of proper use

The message for this item is something like "Use the marker interface as much as possible because you will notice the error at compile time." With that in mind, take a look below.

  • If you need to apply it to something other than a class or interface, you have no choice but to use marker annotations.

  • If you think you need a "method that takes a marked object as an argument", use the marker interface because you can check the type at compile time. If not, you can use marker annotations.

  • If you have a framework that makes heavy use of annotations, you may want to use marker annotations for consistency. You should judge by looking at the balance.

Chapter 7 Lambda and Stream

Item 42: Choose Lambda over Anonymous Class

In the olden days, anonymous classes were used to represent function objects.

Starting with Java 8, a functional interface was introduced to make it easier to represent function objects. At the same time, a lambda expression (or simply "lambda") was introduced as a mechanism to concisely represent an instance of a functional interface.

Before using lambda, understand the following.

  • The good thing about lambda is its simplicity, so it's best to write code with as few types as possible.
  • You can omit the type because Lambda does the type inference. Since the type inference is performed using generics as a clue, making the best use of generics is an important point to bring out the goodness of lambda.
  • Lambda has no name and documentation, so if it's not trivial logic or logic that exceeds a few lines, it shouldn't be implemented in lambda.
  • Anonymous classes may be more appropriate than lambdas. Let's choose according to the situation.
  • Lambda can only implement functional interfaces. Anonymous classes can implement abstract classes.
  • Anonymous classes cannot implement interfaces with multiple abstract methods.
  • This in Lambda represents an enclosing instance, and this in an anonymous class represents an instance of an anonymous class.

Item 43 Select method reference over lambda

In some cases, method references are more concise to implement than lambdas. Make method references one of your options.

However, the following points should be considered:

  • For lambda, write the parameter name. If that parameter name is needed for readability, you should opt for lambda.
  • For lambda, write the logic. Even if it's a fixed logic, if the logic is written and it makes it very easy to read, you should choose Lambda.
  • You also have the option of extracting the lambda processing into a method and using that method reference. In that case, you can write a document in the extracted method.
  • If the class name is very long, the method reference will be verbose.

There are five types of method references. The table of the book is very easy to understand, so I will quote it as it is. You may not get used to it at first, but I think it's worth the effort to learn.

Method reference type Example Equivalent lambda
static Integer::parseInt str -> Integer.parseInt(str)
bound Instant.now()::isAfter Instant then = Instant.now();
t -> then.isAfter(t)
Unbound String::toLowerCase str -> str.toLowerCase()
Class constructor TreeMap<K,V>::new () -> new TreeMap<K,V>()
Array constructor int[]::new len -> new int[len]

Item 44: Use a standard functional interface

With Java's functional interfaces and lambdas, best practices for creating APIs have changed considerably. Specifically, it has become commonplace to create constructors and methods that take function objects as arguments.

For example, it looks like this.

/**
*This is an example of API that uses function objects.
* @param funcSpecifiedByUser A function that takes a subject as the first argument and an object as the second argument and returns some text. This result is displayed on standard output.
*/
public static void apiUsingFuncObj(BinaryOperator<String> funcSpecifiedByUser) {
    System.out.println(funcSpecifiedByUser.apply("I", "you"));
}

//This is an example of using the API. For clarity+Characters are concatenated with.
public static void main(String[] args) {
    apiUsingFuncObj((subjectWord, objectWord) -> subjectWord + " love " + objectWord + ".");

    // I love you.It will be displayed.
}

In this way, you can adopt the function interface as an argument of your own API. The user can use lambda to create a function object that implements the function interface and pass it to the API.

At this time, some function interface is specified for the argument type of the API that you create, but in many cases ** the function interface provided as standard in Java is sufficient **. As an API provider, you don't have to define an extra function interface.

From the user's point of view, it is easier for the API to adopt the standard function interface. If your own function interface was defined, you would have to understand its specifications. With a standard function interface, it's easy because you can use your existing knowledge as it is, just like "Oh, that's it."

So, when adopting a function interface as an API parameter, first consider using the Java standard function interface.

What are the standard Java function interfaces?

There are many articles in the world that introduce the Java standard function interface, so I will leave the details to that. Here, please get an overview by introducing the six basic function interfaces.

Basic function interface

Function interface Signature Description Example method reference
UnaryOperator<T> T apply(T t) Returns the same type as the argument type. String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) Returns the same type as the argument type. It takes two arguments. BigInteger::add
Predicate<T> boolean test(T t) Takes an argument and returns a boolean. Collection::isEmpty
Function<T,R> R apply(T t) Returns a type different from the argument. Arrays::asList
Supplier<T> T get() Returns a value with no arguments. Instant::now
Consumer<T> void accept(T t) Takes arguments but returns nothing. System.out::println

The functions are not orthogonal. You shouldn't worry too much about that area.

Points to consider

  • It is NG to specify a boxed base data class for the type parameter of the base function interface, such as ʻUnaryOperator . This is because boxing and unboxing are costly. Instead, use a function interface that supports basic data types, such as ʻIntUnaryOperator.

  • In some cases it is better to create your own function interface. If any of the following apply, you may want to make your own.

  • Widely used and can benefit from descriptive names.

  • Have a strong contract associated with the interface.

  • Benefit from a special default method.

  • Add @FunctionalInterface to your own function interface.

  • A function interface that can tell the reader that it can be used for lambdas.

  • If you mistakenly define multiple abstract methods, you will be notified by a compile error. It's great for you who create your own function interface and for the other members who take care of it.

  • If you create your own API, do not have a method with the same name that receives different functional interfaces at the same argument position. The user is in trouble. For example, the submit method of ExecutorService applies to this.

Item 45 Use the stream carefully

What is a stream? What is Stream API?

A stream is a finite or infinite sequence of data elements. In Java 8, a stream API has been added to make this stream easier to work with.

In the stream API, you can operate the stream by using the "stream pipeline".

The stream pipeline consists of:

  • Source stream
  • Intermediate operation
  • Termination operation

The pipeline is lazily evaluated so it can handle an infinite sequence.

Points to note

The stream API is "fashionable", but abuse can reduce readability. The purpose of the stream API is to "simplify the code", so it is NG to use it in such a way that the purpose is not achieved.

Specifically, consider the following points.

  • At the extreme, you can't tell whether you should implement it using the stream API or loop until you write it. It depends on how familiar your team members are with the Stream API. Determine which one is easier to read, depending on the situation.

  • Stream API is likely to be suitable in the following cases.

  • Convert the sequence of elements uniformly

  • Filter the sequence of elements

  • Use a single operation (for example, add, combine, calculate minimum) to bring together the elements in the sequence

  • Accumulate the elements in the sequence in the collection, such as by grouping by common attributes

  • Search for elements that match a specific upper limit from the elements of the sequence

  • Although not limited to the stream API, lambda parameter names have a significant impact on readability. Think carefully about the parameter names.

  • Helper methods can play an important role in the Stream API. If you cut out the process to be executed in the stream pipeline into a helper method, you can name the helper method. When you call a helper method from the stream pipeline, you can see what you're doing by looking at the name of the helper method, which makes it more readable.

  • There are times when you want to access data that was valid in the scope of the previous intermediate operation in a later intermediate operation. In that case, don't implement it around between intermediate operations so that you can remember that data from the previous intermediate operation. It's just hard to read. Instead, back-calculate the data you are looking for based on the data that you can access in the scope of later intermediate operations.

Item 46 Select a function that has no side effects in the stream

The purpose of this item is "Let's use the collector API".

Accessing "outside" from the stream pipeline is NG

What should I get by using the Stream API? What we aim for with the stream API is not "somehow cool".

The most important thing is "conciseness". In addition, "efficiency" (reducing CPU and memory load) is also important. In some cases, you should also aim for "parallelism" (improving processing performance by processing with multiple threads). Even if you use the stream API, it doesn't make sense if you don't get these things.

In order to use the Stream API properly, conversions at individual stages should only have access to the conversion results from the previous stage.

On the contrary, you should not access variables etc. outside the stream pipeline. If you do this, at least you lose "conciseness". Code readers can't read what's going on unless they care about things outside the stream pipeline. It will also be easy for defects to be mixed in.

For example, the following code is NG.

Map<String, Long> freq = new HashMap<>();
try(Stream<String> words = new Scanner(file).tokens()) {
    words.forEach(word -> {
        freq.merge(word.toLowerCase(), 1L, Long::sum);
    });
}

/*
[What's wrong? ]

The forEach termination operation cannot return the final conversion result outside the stream pipeline.

Nevertheless, I'm trying to use a forEach termination operation to bring the final conversion result out of the stream pipeline.

As a result, we are accessing the variable freq outside the stream pipeline, losing "conciseness".
To put it plainly, it's hard to read.
*/

Since the stream pipeline constitutes one conversion process as a whole, it can be said that the final conversion result is returned to the outside of the stream pipeline. In this sense, forEach termination operations have limited opportunities to be useful. I think it's for debugging purposes and log output.

What should I do?

So how do you make each conversion stage independent and return the final conversion result outside the stream pipeline? To that end, a role called "collector" is provided.

Specifically, it calls Stream.collect (collector) as termination. Pass the collector's object as an argument to the collect method. This collector collects the elements of the stream. Often, you collect elements in some collection. The collect method returns the results collected by the collector outside the stream pipeline.

Various collectors are available as standard. To get these collector objects, call the factory method in java.util.stream.Collectors.

Below, we will introduce some of the typical collectors that are available as standard.

Use Use How to get a collector object Remarks
Collect stream elements in List - Collectors.toList()
Collect stream elements in Set - Collectors.toSet()
Collect stream elements in any collection - Collectors.toCollection(collectionFactory)
Collect stream elements in Map Simply collect Collectors.toMap(keyMapper, valueMapper)
If the key is duplicated, collect it while merging it properly Collectors.toMap(keyMapper, valueMapper, mergeFunction)
In addition to ↑, specify to use a specific Map implementation Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
Divide the stream elements into groups and store the element list for each group as Map values. Collectors.groupingBy(classifier)
Almost the same as ↑, but listed as a Map valueOther thanSpecify a collection of Collectors.groupingBy(classifier, downstream) A downstream (downstream collector) is a collector (function object) that takes a substream (a set of elements that belong to a group) as input and creates a collection. For example, Collectors.counting()You can use the downstream collector obtained in to count the number of cases for each group.
In addition to ↑, specify to use a specific Map implementation Collectors.groupingBy(classifier, mapFactory, downstream) The order of downstream is different from ↑. Let's watch out.
Get the maximum value element in a stream element - Collectors.maxBy(comparator) Takes a comparator that indicates the comparison rule as an argument
Get the minimum value element in a stream element - Collectors.minBy(comparator)
Simply concatenate the strings of stream elements - Collectors.joining()
Concatenate stream element strings with delimiters - Collectors.joining(delimiter)

Of course, there are other than the above.

In the table, I wrote Collectors.toList () etc. for explanation, but when actually using it, let's statically import all the members defined in Collectors so that Collectors. can be omitted. .. It will be much easier to read.

Item 47 Select Collection over Stream as the return type

I think it's common for your own API to return a sequence of elements. In that case, depending on the user, you may want to treat the returned value as Stream, or you may want to treat it as Iterable.

Therefore, the return type that can handle both is the best. Specifically, Collection or its subtypes are good. This is because the Collection interface is a subtype of Iterable and has a stream method.

  • If the return type can ** be a Collection or its subtypes **, consider the following:

  • If the number of elements is small enough to be stored in memory, a standard implementation of a collection such as ArrayList is fine.

  • Otherwise, you need to implement a special collection that requires a small memory area.

  • If the return type can be a Collection or its subtypes ** not **, consider the following:

  • It is preferable to choose either Iterable or Stream, whichever is more natural.

  • Sometimes, the ease of implementation determines which one to use.

  • Either way, you'll need an adapter to convert from one to the other. Using an adapter clutters the implementation and is slow.

Item 48 Pay attention when parallelizing streams

Calling Stream.parallel () in a stream pipeline causes the pipeline to be multithreaded, which often leads to terrible results. In other words, what doesn't get faster is catastrophically slower than running it in a single thread. It's pretty hard to understand why, and it's also very hard to implement to be fast.

So don't parallelize streams unless you have a good reason or confirmation.

Chapter 8 Method

Item 49 Check the validity of parameters

Let's check the validity of the parameters accepted by the method and constructor at the beginning. Failure to do so can result in incomprehensible exceptions due to incorrect parameters, or unexpected anomalies outside of your code.

Consider the following points:

  • If there are any restrictions on the parameters, write them in the Javadoc @ throws.
  • The Objects.requireNonNull method added in Java 7 is useful for null checking. Let's use it.
  • Starting with Java 9, checkFromIndexSize, checkFromToIndex, and checkIndex methods have been added to Objects for checking list and array indexes. It is convenient if it suits your purpose.
  • If the processing cost of the validity check is high and ** and ** the validity check is implicitly performed in the middle of the process, the validity check should not be explicitly provided.

Item 50 Defensive copy if necessary

Classes exposed as APIs should be considered to be treated badly by users, even if they are not malicious. Specifically, think of it as being used in such a way that the invariants of that class are broken.

Therefore, no matter how improper the user uses it, the invariants of the class should not be broken. That is a defensive copy. Specifically, take the following actions.

  • If you receive a variable object from a user and want to save it as a state, make a copy of that object and save the reference. In that case, it is dangerous to use the clone method of the received object. You can't trust that subclass.
  • If you want to return a variable object or array that the class has as a state to the user, make a copy of that object and return it.
  • In the first place, try to use immutable classes as much as possible. You don't have to worry about the above. In particular, starting with Java 8, use immutable classes such as java.time.Instant (another class in the same package) instead of java.util.Date.

However, there are times when you decide not to make a defensive copy. That is the case in the following cases. In such a case, it is necessary to take measures such as stating that in Javadoc.

  • If the processing cost of the defensive copy is unacceptable.
  • If you can trust the user for some reason.
  • Even if the invariant is broken, only the user is in trouble.

Item 51: Carefully design the signature of the method

This item is a collection of tips. If you follow these rules, your API will be easier to learn and use. And it also makes it harder to lead to errors.

  • Choose your method name carefully.
  • Be understandable.
  • Must be consistent with other names in the same package.
  • Consistent with the widespread consensus that exists.
  • Be short.
  • Don't provide too many useful methods.
  • If there are too many methods, it will be difficult for both the maintainer and the user.
  • Provide a process that can be performed by combining multiple methods as one more convenient method only when it is clear that it will be used frequently.
  • Keep the number of parameters to 4 or less.
  • Users cannot remember many parameters.
  • It is NG to have multiple parameters of the same type. The compiler will not tell you if you inadvertently make a mistake in the order.
  • Here's how to reduce the parameters.
  • Divide into multiple methods. List's subList, indexOf, and lastIndexOf are examples.
  • Let's create a helper class that holds a collection of parameters. It will be a static member class.
  • Apply the Builder pattern to method calls. Execute at the end, but it is a good idea to check the validity of the parameters at that time.
  • Use interfaces as much as possible for parameter types.
  • Gives you the flexibility to switch to a different implementation.
  • Use an enum type that has two elements rather than a boolean parameter.
  • Easy to read and extensible.

Item 52 Use with caution overload

When creating your own API, it is NG to provide multiple overloaded methods and constructors with the same number of parameters. It may not work as intended by the user and may confuse the user.

Therefore, let's deal with it as follows.

  • If it is a method, change the method name.
  • If it is a constructor, you cannot change the name, so let's implement it with a static factory method.
  • If you have to overload and want them to behave the same, transfer from the limited to the general to ensure that they both have the same behavior.

Item 53: Use variable length arguments with caution

Variadic methods are useful. However, please note the following points.

  • If the variadic argument contains required parameters, a compile error will not occur if the user does not specify the variadic argument by mistake. Do not include the required parameters in the variadic arguments and define them separately from the variadic arguments.
  • Variadic arguments are achieved by generating an array each time the method is called. Recognize the cost of creating such an array and define your API. If that cost is unacceptable, find out how many commonly used arguments you have and define the methods for those arguments individually.

Item 54 Returns an empty collection or an empty array instead of null

Some methods that return a sequence of data, in some cases return null, but this is NG.

The reason is as follows.

  • Users will have to write code that handles nulls. In the first place, the correspondence to null may be leaked.
  • For the API implementer, the process of returning null unnecessarily clutters the code.

【NG】

public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? null
        : new ArrayList<>(cheesesInStock);
}

【OK】

public List<Cheese> getCheeses() {
    //There is no need to bother with conditional branching.
    //This will return an empty list.
    return new ArrayList<>(cheesesInStock);
}

The cost of generating an empty list each time is often negligible. If that matters, try returning an immutable empty collection, such as with Collections.emptyList (). However, this is rarely the case, so don't do it blindly.

Item 55: Carefully return the option

Prior to Java 8, the following steps were taken to write methods that did not return a value.

  • Returns null.
  • Throw an exception.

Obviously there is a problem with these methods, and Java 8 has added good methods. That is Optional.

By returning Optional from the API, you can make the user aware of the possibility that the return value is empty and force the user to handle it if it is empty.

The method to create an Optional object is as follows.

How to generate Optional Description
Optional.empty() Returns an empty option.
Optional.of(value) Returns an optional that contains a non-null value. If null is passed, a NullPointerException will be thrown.
Optional.ofNullable(value) Accepts a potentially null value and returns an empty option if null is passed.

API users handle Optional as follows.

//If it is empty, the default value specified by orElse is used.
String lastWordInLexicon max(words).orElse("No words...");

//If it is empty, the exception will be thrown by the exception factory specified by orElseThrow.
//The exception factory is now specified so that the cost of raising an exception only occurs when the exception is actually thrown.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

//If you know that the option is not empty, you can get the value all at once.
//In the unlikely event that the option is empty, a NoSuchElementException will be thrown.
Element lastNobleGas = max(Elements.NOBLE_GASES).get();

Various useful methods are defined in Optional. For example, the orElseGet method takes a Supplier <T> and can call it when Optional is empty. This house isPresent method exists because I couldn't do what I wanted to do with other methods, and I shouldn't use it aggressively.

When dealing with Streams that have Optional as an element, you often want to filter only those that are not empty. In that case, it is better to implement as follows.

//Java 8 and earlier
// Optional.isPresent()Is one of the few situations where is active.
streamOfOptional
    .filter(Optional::isPresent)
    .map(Optional::get)

//Java 9 or later
//Stream added to Optional()In the method
//Returns a Stream that contains the element if Optional has a value, and nothing if it has no value.
//You can take advantage of this property and implement it as follows:
streamOfOptional
    .flatMap(Optional::stream)

Regarding Optional, please note the following.

  • Of course, you should not return the collection etc. wrapped in Optional. If it's a collection, just return an empty collection.
  • Do not return Optional for boxed basic data types. This is because there is wasted processing cost, which is often not negligible.
  • Optional should not be used for anything other than returning the return value of the method. For example, holding Optional in an instance field is often inappropriate. There must be other means.

Item 56 Write document comments for all public API elements

Why do you need document comments (Javadoc)? [Why]

You should write a Javadoc for public APIs to prevent users from misusing them.

In addition, it is not exposed so that maintenance members can maintain the API so that it does not modify it in the wrong direction and that maintenance members can understand the intent and purpose of the original implementation. You should also write a Javadoc for.

In practice, you should write the minimum content that can serve these purposes. Code is not a work of art, but a means of making a profit. It's also important that the cost of Javadoc is worth the money.

What should i do? [How]

It is as follows.

  • Specify the API and the "contract" that the user should keep.
  • Precondition: It is a matter that must be established in order for the user to call the API.
  • Post-condition: A matter that must be met after the call completes successfully.
  • Side effects: Observable changes in system status. Start a thread in the background, etc.
  • Parameters: Basically write in noun phrases.
  • Return value: Basically a noun phrase. It can be omitted if it is the same as the outline explanation of API.
  • Exceptions thrown: Write whether checked or unchecked. Write the exception class name and the conditions to be thrown.
  • Express the code with {@code}.
  • For example, "this" in "this list" represents the object for which the method was called.
  • For classes that are inherited and used, write in @implSpec that you are calling your other methods. Otherwise, the user will use it incorrectly. Note that this tag is not enabled by default, at least in Java 9.
  • If you want to represent characters such as <> &, use {@literal}.
  • Write a brief description at the beginning of the document comment.
  • Different APIs must not have the same overview.
  • Please note that the outline explanation ends with a period (.). You should use something like {@literal} as appropriate.
  • In the overview description, the subject is often omitted for brevity. For English you should use the third person present form.
  • Noun phrases should be used for classes, interfaces and fields.
  • From Java 9, you can search by keyword in the search box at the top right of the Javadoc screen. At this time, it is not only the class name and method name but also the keywords explicitly set by {@index} that hit the search.
  • Document all type parameters for generic types and methods.
  • For enum types, add a document comment for each of all the constants.
  • Document all members for annotations.
  • Package-level documentation comments should be in package-info.java.
  • Let's describe whether it is okay to operate with multithreading.
  • Describe if it can be serialized and in what format it will be serialized.
  • {@ InheritDoc} is difficult to use and is said to have restrictions.
  • You may need documentation that describes their relationships, not just individual classes.
  • The above rules should be inspected automatically. In Java 8 and above, it is checked by default. Checkstyle etc. will be inspected more firmly.

Chapter 9 General Programming

What is written in this chapter is "natural". I will explain only the things that should be noted.

Item 57 Minimize the scope of local variables

There is nothing special to mention.

Item 58 Select a for-each loop over the conventional for loop

It's fine, but let's just remember the following points.

  • Collection.removeIf (filter) allows you to remove specific elements while scanning without resorting to traditional for loops.

Item 59 Know the library and use the library

Know what features the major subpackages have, including java.lang, java.util, java.io, java.util.concurrent. If you don't know, you can't use it. Be sure to check out the new features that will be added in the release.

For example, as of Java 7, you should use ThreadLocalRandom rather than Random. This is faster.

Item 60 Avoid floats and doubles if you need an accurate answer

Floats and doubles exist for quick calculations that ** approximate ** to accurate results. It does not exist to obtain accurate calculation results. So if you want accurate calculation results, you shouldn't use them.

Use Big Decimal instead.

However, Big Decimal has the drawback of being "slow". If BigDecimal's slowness is unacceptable, use integers such as int and long (for example, convert dollars to cents). You can use int for up to 9 digits and long for up to 18 digits. If you go beyond that, you have no choice but to use Big Decimal.

Item 61 Select a basic data type rather than boxed basic data

There is nothing special to mention.

Item 62 Avoid strings where other types are appropriate

For example, if it is a character string that essentially represents a numerical value, treat it with an int, etc.

It's fine, but let's just remember the following points.

  • String is immutable, so the same instance will be used throughout the JVM if it represents the same string. Therefore, it cannot be used as a key when threads share the same namespace.

Item 63 Beware of string concatenation performance

Since String is immutable, a new instance will be created each time it is concatenated with +.

Use StringBuilder to apply + many times to combine strings (note that it's not thread-safe).

That doesn't mean that all + connections are bad. With one or two +s, it should be concise and have no performance issues.

Item 64 Refer to the object on the interface

It is more flexible to refer to it in an interface or abstract class. This is because the implementation class can be replaced later.

Item 65 Select an interface over reflection

The aspects in which reflection should be used are extremely limited. Don't use it blindly.

Item 66: Use native methods with caution

JNI is a feature that allows you to call methods implemented in C or C ++, but you probably don't have to implement it yourself. The content is that you should be careful if you do it by any chance. I think you should read this item after you really face such a situation.

In rare cases, you will need to use some product with PJ, and JNI is the only way to use it. Since the process called by JNI runs outside the control of the JVM, there is a risk of memory corruption. Be aware of these dangers and test them thoroughly.

Item 67 Carefully optimize

Optimizing is, in the extreme, writing code that focuses on the lower layers to be "faster". Optimization is only done when it is needed, not from the beginning. That's because optimization can clutter your code and can be ineffective in the first place.

Here's what to do:

  • At the architecture level, there should be no performance issues. Design your architecture carefully. Particular attention should be paid to the public API, external communication section, and data persistence section. These have a significant impact on performance and are nearly impossible to replace later.
  • Carefully design your public API.
  • If you make the exposed type variable, you will need a defensive copy.
  • Improperly adopting inheritance can hamper subclasses and prevent them from being tuned.
  • If you do not use it for the interface, you will not be able to replace it with an efficient implementation later.
  • If you twist your API design for better performance, you will have more trouble continuing to support that API.
  • If you think optimization is needed, use profiling tools to identify bottlenecks. For example, jmh is a highly visible microbenchmark framework.
  • If you really want to optimize, measure to see if it really improves. How the program is actually processed depends on the hardware on which the bytecode runs. With so much hardware today, you can't tell if an optimization worked without measuring it.

Item 68: Observe the generally accepted naming convention

There is nothing special to mention. It's a matter of course.

Chapter 10 Exceptions

Item 69 Use exceptions only for exceptional conditions

As the title says. There is no need to explain the reason.

Item 70 Use checked exceptions for recoverable states and run-time exceptions for programming errors

The class that represents an abnormal situation has the following structure (inheritance relationship). I have also described the proper use and precautions for each.

Description
Throwable
Error and its subtypes It is used by the JVM. Don't make your own.
Exception
RuntimeException and its subtypes This is the so-called "unchecked exception". This should be thrown if the user is making some mistake. This is because even if it is handled by the user, it will only be harmful.
Those that are not subtypes of RuntimeException This is the so-called "checked exception". This should be thrown if the caller can recover properly. This is because you can force the user to perform recovery processing. Let's prepare a method for information acquisition in the exception class so that the user can handle it properly. However, as shown in item 71, first consider returning Optional.

Item 71 Avoid unnecessarily using checked exceptions

If you throw an exception that is checked by the API, you should examine whether it is really necessary. Even if you receive the exception, don't throw it if you can't do anything practically.

APIs that throw checked exceptions can also be annoying to users when it is necessary and worthwhile to force the user to do some recovery. This is because it has the following drawbacks.

  • You have to write a try-catch. It's a hassle and the code is cluttered.
  • The API cannot be used in the stream. This is because streams cannot handle checked exceptions.

There is a way to return Optional as a means to solve or alleviate these difficulties. Instead of throwing an exception, it returns an empty Optional. However, Optional cannot have additional information like exception classes. Let's judge by looking at the balance.

Item 72 Use standard exceptions

As the title says. There is no need to explain the reason.

Item 73 Throw an exception suitable for the abstract concept.

Exceptions can be propagated in Java. If the exception thrown in the lower layer propagates to the upper layer via multiple layers, the code in the lower layer and the code in the upper layer will be combined. In other words, it becomes difficult to modify both independently.

For this reason, keep the following in mind.

  • Let's not raise an exception in the first place. In other words, when calling the lower layer, let's check that the preconditions for calling the lower layer are satisfied. Try to avoid propagation as much as possible.
  • If you really need to propagate it, throw a new exception at the appropriate abstract level at the appropriate abstract level layer to separate the upper and lower layers. At that time, the original exception should be set as cause. This is because it makes it easier to investigate when an exception occurs.

Item 74 Document all exceptions thrown by each method

It overlaps considerably with what is explained in item 56. It will be okay if you understand it well.

Item 75 Include error recording information in the detailed message

Exception detail messages play a very important role in investigating when an exception occurs.

  • Include all parameter and field values that caused the exception.
  • Logs are also viewed by general programmers, so of course do not include secure information.
  • It is useless to include in the detail message that it is easily readable from the Javadoc and source code.
  • If you make your own exception, you should be able to receive such information in the constructor argument of the exception class so that the user always sets the information necessary for investigation.

Item 76: Work on error atomicity

Suppose a method of an object with a state is called and something goes wrong inside the method. After that, it is desirable that the object returns to or remains in the state it was in before the method call. This property is called "error atomicity".

This property is important when throwing checked exceptions. A checked exception is thrown because the user can do some recovery. Unless it returns to its original state, it will be impossible for the user to perform the recovery process.

To achieve error atomicity, take the following methods.

  • Let's make the class immutable in the first place. If it doesn't change, you don't have to think about anything.
  • Make sure that the following processing is executed before "Process to change the state of the object".
  • Parameter validation
  • Processing that may fail
  • Perform an operation on the temporary copy of the object and replace the contents of the object with the contents of the temporary copy when the operation is complete.

Error atomicity is desirable, but not always achievable, and in some cases it can be too costly to achieve. If error atomicity cannot be achieved, specify in the Javadoc what state the object will be in if the method fails.

Item 77 Do not ignore exceptions

As the title says. There is no need to explain the reason.

In rare cases, it may be appropriate to ignore the exception. In that case, be sure to leave the reason in the comments.

Chapter 11

Item 78 Synchronize access to shared variable data

"Mutual exclusion" and "inter-thread communication" are realized in synchronization

When reading and writing the data of one object in multiple threads, it is necessary to pay attention to the following two points.

  • Mutual exclusion: You want to prevent other threads from seeing an object in an inconsistent state while one thread modifies the object.
  • ** Inter-thread communication: Changes made by one thread must be reliably visible to other threads. ** **

The latter is often forgotten, so be careful. If not implemented properly, the compiler may arbitrarily optimize changes made by one thread so that they are forever invisible to others. The result is annoying glitches that may or may not occur depending on the timing.

Focusing on the latter is the volatile modifier. Putting this in a field ensures that the last written value is visible to the reading thread, as the per-thread cache is no longer used. However, volatile does not do mutual exclusion. In the situation where objects are shared by multiple threads, it is almost always necessary to satisfy both of the above two points, so there are few situations where volatile is sufficient.

In order to satisfy both of the above two points, it is necessary to perform synchronization. Specifically, let's add the synchronized modifier to the method (the syncronized block alone does not guarantee the visibility of the value in other threads). In some cases the java.util.concurrent.atomic package may be appropriate (such as AtomicLong).

Bonus: What is "atomic"?

The term "atomic" is often used in the context of multithreading, so it's a good idea to understand it. Atomicity is the property that multiple operations on data appear to other threads as a single operation.

Specifically, I think you should understand the following points.

  • Usually long and double variables are not guaranteed to be atomic. As a result of two writes of 32 bits each being performed separately, a halfway state can be seen from another thread. Even if it is long and double, by adding volatile to the variable, it will be visible to other threads as one 64-bit write.
  • The increment operator ʻi ++` is not atomic. There are two operations, reading and writing variables, but to other threads, those operations appear to be separate operations. As a result, reads and writes from other threads can get in between reads and writes in one thread. The library that addresses this issue is the java.util.concurrent.atomic package.

Item 79 Avoid excessive synchronization

Excessive synchronization can cause bad things.

What does it mean to overuse synchronization?

Excessive use of synchronization means:

  • Use synchronization where it shouldn't be.
  • Should be synchronized, but the scope of synchronization is too large.
  • The scope is simply too large.
  • Give control to the user in the scope of synchronization (eg, call a method overridden by the user or a function object passed by the user within the scope of synchronization).

What happens if you use excessive synchronization?

Excessive synchronization can cause the following problems:

  • Accuracy issues (the code examples in the book are very helpful)

  • One thread will take duplicate locks, and that thread itself will break the class invariants. Java locks are reentrant. In other words, if you acquire a lock and then try to acquire the same lock again, you will not get an error.

  • Deadlock will occur. In other words, multiple threads wait for each other to release their locks.

  • Performance issues

  • Threads other than the thread that acquired the lock will be kept waiting longer than necessary.

  • There is a delay to give all CPU cores a consistent view of memory. In the multi-core era, this cost is very high.

  • The JVM will not be able to fully optimize code execution.

What should I do?

It is as follows.

  • Try not to sync as much as possible.
  • When creating a variable class, you have the following options. Let's adopt the former as much as possible.
  • Make users sync. Let's adopt this as much as possible.
  • Synchronize inside the class. If this is adopted, it will not be possible to provide the user with the option of synchronizing / not synchronizing. Consider getting your users to sync first, and only if that's a problem, use internal syncing within the class.
  • Minimize scope if you want to sync. In particular, do not call processes that leave control to the user within the scope of synchronization.

Item 80 Select an executor, task, or stream rather than a thread

Instead of using your own Thread class, it's in the java.util.concurrent package Use the executor framework. That is the message of this item.

The content written in this item is halfway. You should read "Java Concurrency in Practice (Java Concurrency Programming-Exploring its" foundation "and" latest API "-)" introduced in this section.

Item 81 Select a concurrency utility over wait and notify

What you used to do with wait and notify can now be easily achieved with the high-level concurrency utility in the java.util.concurrent package.

High-level concurrency utility

The high-level concurrency utilities in the java.util.concurrent package are classified into the following three categories.

  • Executor framework

  • See item 81.

  • Concurrent collection

  • Implements standard interfaces such as List, Queue, and Map, and performs appropriate synchronization processing internally. It achieves high performance while synchronizing.

  • Since it is not possible to intervene in the synchronization process from the outside, it is not possible to combine multiple basic operations on the concurrent collection and make them atomic. To achieve this, APIs are provided for atomic operations by combining multiple basic operations. (PutIfAbsent in Map, etc.).

  • Processing that combines multiple basic operations is built into interfaces such as Map as its default implementation, but only the implementation of concurrent collections becomes atomic. Interfaces such as Map have a built-in default implementation because it doesn't have to be atomic.

  • Synchronized collections (such as Collections.sysnchronizedMap) are a product of the past and are slow. Unless you have a specific reason, use concurrent collections.

  • Implementations such as Queue have been extended to allow "blocking operations" to wait until the operation is complete. For example, the take method of BlockingQueue waits if the queue is empty, and processes it when it is registered in the queue. The executor framework makes use of this mechanism.

  • Synchronizer

  • Acts like a bulletin board between threads, allowing you to keep pace between threads.

  • A popular one is CountDownLatch. For example, the parent thread creates an object for new CountDownLatch (3), launches child threads A, B, and C, calls the CountDownLatch object ʻawait (), and waits. When threads A, B, and C call countDown ()` on this CountDownLatch object, the parent thread is unwaited and the parent thread's subsequent processing is executed. Wait and notify are executed behind the series of processing, but CountDownLatch is in charge of all the complicated parts.

  • Although it is not limited to the context of concurrency, use System.nanoTime () instead of System.currentTimeMillis () to measure the time interval. The latter is more accurate and accurate and is unaffected by adjusting the system's real-time clock.

wait and notify

You may also take care of the code using wait and notify for maintenance etc. In that case, you should know the standard of wait and notify.

This part is almost the same as the following, so detailed explanation is omitted.

Item 82 Document thread safety

Everything that is written is important. It's easy to understand the contents of this item, so you don't need to explain it in particular.

Item 83: Use delay initialization with caution

In rare cases, it may delay the initialization of a field until the value of the field is needed. This is called delayed initialization.

The purpose of delayed initialization is to:

  • For optimization (to "fasten")
  • There is some kind of circulation processing in the initialization, and to break the circulation

If you're trying to optimize, think twice about "does it really make sense?" The effect can be very small or counterproductive. It's out of the question to clutter the code for that.

If there is no problem with normal initialization, add final as shown below to initialize.

private final FieldType field = computeFieldValue();

The same is true for static fields.

Delayed initialization method

Let's take the case where the field type is an object reference as an example.

The basic data is almost the same. In the case of this data, the only difference is that it is not null and is checked against the default value of 0.

① When breaking the initialization cycle

Use a "synchronized accessor" as shown below. It's a simple and straightforward method.

private FieldType field;

//By the synchronized method
//Both "mutual exclusion" and "inter-thread communication" are realized.
private synchronized FieldType getField() {
    if (field == null) 
        field = computeFieldValue();
    return field;
}

The same is true for static fields.

(2) When delay initializing a static field for optimization

If you do not want to delay initialization, do as follows ...

private static final FieldType field = computeFieleValue();

If you want to lazy initialize a static field for optimization, use the "delayed initialization holder class idiom" as follows.

private static class FieldHolder {
    //Give the static member class the fields you want to lazy initialize.
    static final FieldType field = computeFieleValue();
}

private static FieldType getField() {
    //· Load the static member class only when the field value is needed.
    //As a result of the class being loaded, the static field initialization process is performed.
    //-In a typical JVM, access to fields is synchronized when the class is initialized, so
    //There is no need to write an explicit synchronization process.
    return FieldHolder.field;
}

(3) When deferring initialization of instance fields for optimization

Use the "double check idiom" as shown below.

//The initialized field cannot be seen by other threads only by the syncronized block in the getField method.
//By adding volatile, the initialized field can be seen immediately by other threads.
private volatile FieldType field;

private FieldType getField() {
    //To improve performance by loading the field only once
    //The value of field is assigned to the local variable result.
    FieldType result = field;

    //Once initialized, no lock is needed,
    //It will not lock in the first inspection.
    if (result != null)
        return result;

    //It locks for the first time during the second inspection.
    synchronized(this) {
        if (field == null)

            //If not locked, at this timing (between the if judgment and the call to the computeFieldValue method)
            //There is a risk that another thread will initialize the field.

            field = computeFieldValue();
        return field;
    }
}

Item 84: Does not depend on the thread scheduler

Why is it NG to rely on the thread scheduler?

The thread scheduler is one of the components of the JVM, and as the name implies, it schedules threads. One of the components of the JVM is that it uses the functions of the OS after all, and its behavior is strongly dependent on the OS.

As a result, we do not know exactly how the thread scheduler behaves, nor do we have control over it. If the correctness and performance of a program depends on the thread scheduler, it will behave erratically, sometimes it works but sometimes it doesn't, sometimes it's fast but sometimes it's slow. Let's do it. Also, if you port to a JVM with a different OS, it may not work as it did before the port.

For this reason, the correctness and performance of your program should not depend on the thread scheduler.

What should I do?

Consider the following points:

  • Keep the average number of RUNNABLE threads low. Specifically, don't make it much larger than the number of processors. A large number of RUNNABLE threads gives the scheduler choices and, as a result, depends on the scheduler. Please see Official document here for the possible states of threads. .. To keep the number of RUNNABLE threads low, consider the following:
  • When the processing of the thread is finished, let's put the thread in the waiting state (WAITING).
  • In the executor framework
  • Make the thread pool the right size.
  • Make the size of the task moderate. If the task granularity is too small, the cost of dispatching threads to the task will be high and slow.
  • In while (true), repeating the condition judgment and realizing "waiting" is called "busy waiting". Doing this leads to the following problems:
  • Makes the program vulnerable to unpredictable processing by the thread scheduler.
  • Increases the load on the processor and makes it harder for other threads to process.
  • Don't use Thread.yield. This method indicates to the scheduler that the CPU usage allocated to the own thread may be transferred to another thread. Do not use Thread.yield as it is unpredictable and unstable how the scheduler will behave in response to this.
  • Don't adjust thread priorities. You can adjust the thread priority with Thread.setPriority (int newPriority), but like Thread.yield, I'm not sure how the scheduler will behave in response to this.

Chapter 12 Serialization

Item 85: Choose an alternative over Java serialization

If you are deserializing even one place in your system, you risk deserializing a malicious object created by an attacker. In the process of deserialization, malicious code is executed, which leads to fatal problems such as system hang.

Due to these security issues, you should not use serialization / deserialization at all. Instead, use technologies like JSON and protobuf. By adopting these technologies, you can avoid many of the security issues mentioned above and benefit from cross-platform support, high performance, an ecosystem of tools, and community support.

If you have no choice but to use the serialization mechanism for maintenance of the existing system, take the following measures.

  • Don't deserialize untrusted data.
  • Use java.io.ObjectInputFilter added in Java 9 to deserialize only whitelisted classes. However, this does not cover attack code that consists only of common classes.

Item 86: Implement Serializable with the latest attention

If you do adopt a serialization mechanism, you should be aware of the costs involved. The price is as follows:

  • Once released, you almost lose the flexibility to change the implementation of a class that implements Serializable.
  • Increases the likelihood of bugs and security holes.
  • Increasing the test load associated with the release of a new version of the class.

Note the following when implementing Serializable:

  • Explicitly set the serial version UID for the class. If you do not set it explicitly, the ID will be automatically assigned and it will be judged as incompatible even though it is a compatible change.
  • Classes designed for inheritance should not implement Serializable in principle. Interfaces should not extend Serializable in principle. Those who use those classes and interfaces must implement Serializable.
  • When implementing a class that is serializable, extensible, and has instance fields, consider the following:
  • Subclasses should not be able to override the finalize method. Prevent finalizer attacks.
  • If you're having trouble initializing your instance fields to their default values, add a readObjectNoData method.
  • Except for static member classes, inner classes should not implement Serializable. This is because there are elements such as references to enclosing instances whose serialization format is uncertain.

Items 87-90

I'm sorry, I've run out of time to write an article, so I'd like to write it when I have time. However, I would like to mention that it is not too late to start learning after you really need to implement Seirializable.

in conclusion

If you have any mistakes, please let us know!

Recommended Posts