Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.
20. Interface is better than abstract class
Java has two mechanisms for defining types that allow multiple implementations: interfaces and abstract classes. Because the default method of the interface is introduced in Java 8 [JLS 9.4.3], both of these mechanisms allow implementations to be provided for some instance methods. A major difference is that to implement a type defined by an abstract class, a class must be a subclass of an abstract class. Because Java only allows single inheritance, this restriction on abstract classes strictly restricts their use as type definitions. Any class that defines all the required methods and obeys the general conventions can implement an interface, regardless of the position of the class in the class hierarchy.
existing classes can be easily improved to implement a new interface . You simply add the desired method (if it does not already exist) and add a clause to the class declaration implements
. For example, when, Comparable
Iterable
and when interfaces are Autocloseable
added to the Java platform, many existing classes need to be implemented to improve them. In general, existing classes cannot be improved to inherit a new abstract class. If you want to have two classes inherit the same abstract class, you have to put it in the top position in the type hierarchy, which is the ancestor of the two classes. Unfortunately, this can cause significant collateral damage to the type hierarchy, forcing all descendants of the new abstract class to subclass it, regardless of whether the descendant classes are appropriate.
an interface is an ideal choice for defining mixed types (mixin) . In general, Mixin is a class that, in addition to its "primary type", can declare that it provides some optional behavior. For example, Comparable
it is a type interface that allows a class to declare its instances to be ordered relative to other objects that can be compared to each other. Such an interface is called a type because it allows optional functionality to be "mixed" to the primary functionality of the type. Abstract classes cannot be used to define mixed classes, because they cannot be loaded into existing classes: A class cannot have more than one parent class, and there is no reasonable place in the class hierarchy to insert a type.
interfaces allow for the construction of non-hierarchical types of frames . The type hierarchy is good for organizing something, but other things do not fall neatly into the strict hierarchy. For example, suppose we have an interface representing a singer and another interface that represents a composer:
public interface Singer {????AudioClip sing(Song s);}public interface Songwriter {????Song compose(int chartPosition);}
In real life, some singers are also composers. Because we use interfaces rather than abstract classes to define these types, it is entirely permissible for a single class to implement two interfaces for singers and composers. In fact, we can define a third interface that inherits singers and composers, and adds a new method that is appropriate for this combination:
public interface SingerSongwriter extends Singer, Songwriter {????AudioClip strum();????void actSensitive();}
You don't always need this flexibility, but when you do that, the interface is a lifesaver. Another approach is to include a bloated class hierarchy of a single class for each supported attribute combination. If there are n properties in the type system, you may need to support a possible combination of 2n. This is called the combinatorial explosion (combinatorial explosion). Bloated class hierarchies can result in bloated classes with many methods that differ only on parameter types, because there is no type in the class hierarchy to capture the common behavior.
The interface ensures a secure, powerful enhancement of functionality through the wrapper class pattern (entry). If you use abstract classes to define types, then let the programmer want to add functionality that can only be inherited. The generated analogy wrapper class is weaker and more fragile.
When other interface methods have obvious interface method implementations, consider providing the programmer with the default form of method implementation help. For an example of this technique, see the method on page 104th removeIf
. If you provide default methods, be sure to use the @implSpec
Javadoc tag (entry 19) to describe their documentation as inheritance.
Using the default method to provide implementation help is somewhat limited. Although many interfaces specify the Object
behavior of the class Chinese law (such as equals
and hashCode
), they are not allowed to provide default methods for them. Additionally, the interface does not allow the inclusion of instance properties or non-public static members (except private static methods). Finally, the default method cannot be added to an uncontrolled interface.
However, you can work with interfaces by providing an abstract skeleton implementation class (Abstract?skeletal implementation Class) that combines the benefits of interfaces and abstract classes. The interface defines the type and may provide some default methods, while the skeleton implementation class implements the remaining non-primitive interface methods at the top level of the original interface method. Inheriting skeleton implementations requires most of the work to implement an interface. This is the template method design pattern [Gamma95].
By convention, skeleton implementation classes are called Abstractinterface, where interface are the names of the interfaces they implement. For example, the collection framework (collections framework) provides a framework implementation to accommodate each major collection interface: AbstractCollection
, AbstractSet
, AbstractList
and AbstractMap
. It can be said that they are called, SkeletalCollection
SkeletalSet
SkeletalList
and SkeletalMap
are justified, but now the abstract conventions have been established. If designed properly, skeleton implementations (whether individual abstract classes or only the default methods on the interface) can make it easy for programmers to provide their own interface implementations. For example, here is a static factory method, with the AbstractList
top level containing a complete, fully functional list implementation:
//concrete implementation built atop skeletal implementationstatic list<integer> intarrayaslist (int[ ] a) {???? Objects.requirenonnull (a);???? The diamond operator is only legal here in Java 9 and later???? If you ' re using a earlier release, specify <Integer>???? return new abstractlist<> () {[email protected] public Integer get (int i) {???????????? return a[i];?? Autoboxing ([Item 6] (https://www.safaribooksonline.com/library/view/effective-java-third/9780134686097/ CH2.XHTML#LEV6))????????} [email protected] public Integer set (int i, integer val) {???????????? int oldval = A[i];???????????? A[i] = val;???? Auto-unboxing???????????? return oldval;?? Autoboxing????????} [email protected] public int size () {???????????? return a.length;????????}????};}
This example is a powerful demonstration of a skeleton implementation when you consider all the things a list implements for you. By the way, this example is an adapter (Adapter) [Gamma95], which allows an int array to be treated as a Integer
list of instances. The performance is not very good because of the conversion between the int value and the integer instance (boxing and unpacking). Notice that the implementation takes the form of an anonymous class (entry 24).
The advantage of skeleton implementation classes is that they provide help for all implementations of an abstract class without imposing strict constraints on the abstract class as a type definition. Inheriting this class is an obvious choice for most implementations of interfaces with skeleton implementation classes, but it is not required. If a class cannot inherit the implementation of a skeleton, the class can implement the interface directly. The class still benefits from any default method of the interface itself. In addition, the skeleton implementation class can still assist in the implementation of the interface. A class that implements an interface can forward the invocation of an interface method to the containing instance of the private inner class that inherits the skeleton implementation. This technique, called simulation of multiple inheritance, is closely related to the wrapper class pattern discussed in article 18. It provides many of the benefits of multiple inheritance while avoiding defects.
Writing a skeleton implementation is a relatively simple process, albeit somewhat tedious. First, we study the interfaces and determine which methods are basic, and other methods can be implemented according to them. These basic methods are the abstract methods in your skeleton implementation class. Next, provide the default method in the interface for all methods that can be implemented directly on top of the basic method, and recall that you might not Object
provide a default method for methods such as classes equals
and the hashCode
like. If the basic method and the default method cover the interface, then it is done, and the skeleton implementation class is not required. Otherwise, write a class that declares the implementation interface and implements all the remaining interface methods. For this task to work, this class may contain any non-public properties and methods.
As a simple example, consider the Map.entry interface. The obvious basic method is
GetKey , GetValue
and (optional) SetValue
. The interface specifies the behavior of equals
and hashcode
, and there is an obvious implementation of toString
in the basic aspect. Because the object class method is not allowed to provide a default implementation, all implementations are placed in the skeleton implementation class:
//Skeletal Implementation Classpublic abstract class abstractmapentry<k,v>???????? Implements map.entry<k,v> {???? Entries in a modifiable map must override this method[email protected] public V setValue (V value) {???????? throw new Unsupportedoperationexception ();????}???? Implements the contract of map.entry.equals[email protected] public boolean equals (Object o) {???????? if (o = = this)???????????? return true;???????? if (! ( o instanceof map.entry))???????????? return false;???????? map.entry<?,? > E = (map.entry) o;???????? Return Objects.equals (E.getkey (),?? GetKey ())???????????? && objects.equals (E.getvalue (), GetValue ());????}???? Implements the contract of map.entry.hashcode[email protected] public int hashcode () {???????? Return Objects.hashcode (GetKey ())???????????? ^ Objects.hashcode (GetValue ());????} [email protected] public String toString () {???????? return GetKey () + "=" + GetValue ();????}}
Note that this skeleton implementation cannot be implemented in the Map.Entry
interface or as a sub-interface, because the default method does not allow overriding such equals
methods as, and the like hashCode
toString
Object
.
Since skeleton implementation classes are designed for inheritance, you should follow all the design and documentation instructions in entry 19. For brevity, the document comment is omitted from the previous example, but a good document is absolutely necessary in the skeleton implementation , regardless of whether it contains an interface or a separate default method for the abstract class.
Slightly different from skeleton implementations is the simple implementation, as an AbstractMap.SimpleEntry
example. A simple implementation is like a skeleton implementation, which implements an interface and is designed for inheritance, but it differs in that it is not abstract: it is the simplest work implementation. You can use it as you like, or you can subclass it according to the situation.
In summary, an interface is typically the best way to define types that allow multiple implementations. If you export an important interface, you should strongly consider providing a skeleton implementation class. Where possible, the skeleton implementation should be provided through the default method on the interface, so that all the implementations of the interface can use it. That is, restrictions on interfaces often require the skeleton implementation class to take the form of an abstract class.
Effective Java Third edition--20. Interface is better than abstract class