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
It only contains definitions of terms used in books. You don't have to read it. (end)
For example, it looks like the following.
Consider the static factory method first, and if it's subtle, choose a constructor.
** ① You can give a descriptive name. ** **
The constructor has the following inconveniences.
The name of the constructor is limited to one (class name).
The only distinction between constructors is the difference in their arguments. As a result, it is difficult for users to understand the differences between constructors.
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.
is the
List` interface.Thanks to this, the Collections API is very simple. In particular···
Implementers do not need to explain to users how to use EmptyList (API).
Users do not need to be aware of the existence of EmptyList and do not need to understand how to use (API). You only need to know how to use the List
interface.
** ④ 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.
** ① 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の短縮版。 |
Overview of Collections Framework https://docs.oracle.com/javase/jp/9/docs/api/java/util/doc-files/coll-overview.html
[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
build ()
. This advantage is very important. This is because if the inconsistency is not detected here and an error occurs in a distant place due to the inconsistency, it will be difficult to identify the cause.build ()
, you can generate more and more unique objects.Disadvantages (trivial)
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 |
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.
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.
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?"
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.
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.
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.
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) |
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.
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.
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.
Reflectivity: x.equals (x) returns true.
Symmetry: x.equals (y) returns true only if y.equals (x) is true.
Transitive: If x.equals (y) and y.equals (z) are true, x.equals (z) returns true.
Consistency: If x.equals (y) is called multiple times, the result will be the same.
Non-null: x.equals (null) returns false.
It is assumed that x, y, and z are not null.
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.
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:
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:
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.
Object.clone () has a native modifier, so it is implemented in languages other than Java. In other words, there is content in the process.
Covariant return type https://www.slideshare.net/ryotamurohoshi/ss-38240061
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.
sgn (x.compareTo (y)) == -sgn (y.compareTo (x)). That is, when the order is reversed and the comparison is made, the sign of the result is also reversed.
If (x.compareTo (y)> 0 && y.compareTo (z)> 0), then x.compareTo (z)> 0. In other words, it becomes a transitive relationship.
If x.compareTo (y) == 0, then sgn (x.compareTo (z)) == sgn (y.compareTo (y)). That is, if the order of x and y is equal, the result will be equal when comparing other objects z with each of x and y.
If x.compareTo (y) == 0, then x.equals (y) is true, and vice versa ** is not required, but preferable **.
Sgn (expression) is a function that returns -1, 0, 1 when expression is negative, zero, and positive, respectively.
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();
}
}
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.
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).
Specifically, consider the following points.
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".
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
Each instance with a different value consumes system resources. As a result, it can consume a lot of memory and result in poor performance. For example, in each of multiple calculation steps, an ad hoc instance may be created and destroyed. The workaround is as follows.
Combine multiple processing steps into one public API.
Provides a public variable companion class, such as StringBuilder for String.
How to make an immutable object (requirements to be met)
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.
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.
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.
Creating a class that is supposed to be inherited is actually extremely difficult and difficult. Specifically, you need to consider the following:
It is necessary to show the user when the method that can be overridden in the subclass is called in the superclass. This information is exactly the "implementation details" of the superclass, so it breaks information hiding / encapsulation ...
You will need to provide the superclass with protected methods (possibly fields) to provide subclasses with hooks for the superclass's internal behavior. Too much will move you away from the benefits of information hiding / encapsulation. On the other hand, too few will make superclasses difficult for subclasses to use. It is necessary to design with this balance, which is extremely difficult.
Superclass constructors must not call overridable methods. This is because the superclass's constructor is called at the beginning of the subclass's constructor, so you're calling an overridable method when the subclass isn't ready.
There are some things to be aware of when superclasses implement Cloneable, Serializable. However, I think that such a situation is rare, so I will omit the explanation. See the book for details.
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.
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.
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.
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.
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.
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.
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.
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.
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.
If the "nested class" you are trying to create is all of the following, use ** local class **.
If the "nested class" you are trying to create is all of the following, use a ** non-static member class **.
If the "nested class" you are trying to create is all of the following, use a ** static member 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)
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
}
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.
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.
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 ...
Arrays with generics as elements, such as List <String> []
, will result in a compile error. If you allow it, you can get a ClassCastException. You might think it doesn't matter because you don't do that, but that's not the case. For example, a variadic method creates an array to hold variadic arguments. If you make the argument a generic type, you will get a warning that is difficult to understand. If you don't understand what's happening behind the scenes, you won't be able to solve it properly. new E []
is also useless.
As a result of implementing it ad hoc without understanding the meaning of compilation errors and warnings, the following NG classes are created.
public class Chooserr<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
//choices may be the type that holds T,
//Often toArray()Is T[]There should be no guarantee that it will return.
//You may get a ClassCastException at run time.
//I don't understand the meaning@It is NG to Suppress Warnings.
@SuppressWarning("unchecked")
choiceArray = (T[]) choices.toArray();
}
}
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を利用していない既存のコードと、利用する新しいコードが共存できるようにするための措置です。これが冒頭で触れた「歴史的経緯」です。 |
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.
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.
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
** 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
On the other hand, T in ʻUnaryOperator
If UnaryOperator
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
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.
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.
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.
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.
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.
Take the acronym and remember it as "PECS".
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.
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.
Personally, I think the content is quite advanced.
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.
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.
If you declare a constant with an int without using an enum ...
If you declare a constant with String ...
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.
The enum has the following features.
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.
public static E[] values();
public static E valueOf(String name);
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.
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.
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.
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)
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.
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)
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)
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:
@ Test
)How should these be used properly in this item? It is explained that.
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.
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.
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.
This
in Lambda represents an enclosing instance, and this
in an anonymous class represents an instance of an anonymous class.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:
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] |
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.
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.
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.
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:
The pipeline is lazily evaluated so it can handle an infinite sequence.
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.
The purpose of this item is "Let's use the collector API".
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.
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.
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.
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.
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:
@ throws
.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.
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.
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.
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.
Variadic methods are useful. However, please note the following points.
Some methods that return a sequence of data, in some cases return null, but this is NG.
The reason is as follows.
【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.
Prior to Java 8, the following steps were taken to write methods that did not return a value.
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.
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.
It is as follows.
{@code}
.@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.<> &
, use {@literal}
.{@literal}
as appropriate.{@index}
that hit the search.{@ InheritDoc}
is difficult to use and is said to have restrictions.What is written in this chapter is "natural". I will explain only the things that should be noted.
There is nothing special to mention.
It's fine, but let's just remember the following points.
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.
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.
There is nothing special to mention.
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.
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.
It is more flexible to refer to it in an interface or abstract class. This is because the implementation class can be replaced later.
The aspects in which reflection should be used are extremely limited. Don't use it blindly.
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.
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:
There is nothing special to mention. It's a matter of course.
As the title says. There is no need to explain the reason.
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. |
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.
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.
As the title says. There is no need to explain the reason.
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.
It overlaps considerably with what is explained in item 56. It will be okay if you understand it well.
Exception detail messages play a very important role in investigating when an exception occurs.
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.
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.
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.
When reading and writing the data of one object in multiple threads, it is necessary to pay attention to the following two points.
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).
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.
Excessive synchronization can cause bad things.
Excessive use of synchronization means:
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.
It is as follows.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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;
}
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;
}
}
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.
Consider the following points:
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.
If you do adopt a serialization mechanism, you should be aware of the costs involved. The price is as follows:
Note the following when implementing Serializable:
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.
If you have any mistakes, please let us know!
Recommended Posts