There are a number of situations in software engineering when it is important for disparate groups of PRogrammers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts.
在軟件工程的很多情形中,不想干的小組或程序員達成一致的合約(合約用于闡述軟件如何交互)是非常重要的。在合約的幫助下,每個小組寫代碼時不必了解其它小組的代碼時如何編寫的。一般來說,這樣的合約由接口來充當。
For example, imagine a futuristic society where computer-controlled robotic cars transport passengers through city streets without a human Operator. Automobile manufacturers write software (java, of course) that operates the automobile—stop, start, accelerate, turn left, and so forth. Another industrial group, electronic guidance instrument manufacturers, make computer systems that receive GPS (Global Positioning System) position data and wireless transmission of traffic conditions and use that information to drive the car.
譬如說,想象一下在不久的將來,由電腦控制的機器人汽車載著乘客穿過城市街道,而車是無人駕駛的。而汽車制造商用JAVA編寫的軟件操作汽車——停止、啟動、加速、向左轉(zhuǎn)等等。而另一個產(chǎn)業(yè)群電子制導儀器制造商使用電腦系統(tǒng)接收GPS數(shù)據(jù)和交通狀況的無線傳輸數(shù)據(jù),并用接收到的數(shù)據(jù)駕車汽車。
The auto manufacturers must publish an industry-standard interface that spells out in detail what methods can be invoked to make the car move (any car, from any manufacturer). The guidance manufacturers can then write software that invokes the methods described in the interface to command the car. Neither industrial group needs to know how the other group's software is implemented. In fact, each group considers its software highly proprietary and reserves the right to modify it at any time, as long as it continues to adhere to the published interface.
該汽車的制造商必須發(fā)布行業(yè)標準接口,闡述調(diào)用什么方法可以使車移動,然后指導制造商可以調(diào)用接口描述中的方法控制汽車,而工業(yè)集團不必知道其他集團的軟件是如何實現(xiàn)的。事實上,每個集團都認為自己對軟件高度專有,可以隨時修改軟件,前提是遵循發(fā)布的接口規(guī)范。
In the Java programming language, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Method bodies exist only for default methods and static methods. Interfaces cannot be instantiated—they can only be implemented by classes or extended by other interfaces. Extension is discussed later in this lesson.
在JAVA中,接口和類相似,都是引用類型。接口只能包含方法、默認方法、靜態(tài)方法和嵌套方法。方法主題只能以默認方法和靜態(tài)方法的形式存在。接口不能被實例化,只能被類實現(xiàn)或被其它的接口繼承。詳細會在稍后討論。
Defining an interface is similar to creating a new class:
定義一個結(jié)合和創(chuàng)建一個類非常相似:
1 public interface OperateCar { 2 3 // constant declarations, if any 4 5 // method signatures 6 7 // An enum with values RIGHT, LEFT 8 int turn(Direction direction, double radius, double startSpeed, double endSpeed); 9 10 int changeLanes(Direction direction, double startSpeed, double endSpeed);11 12 int signalTurn(Direction direction, boolean signalOn);13 14 int getRadarFront(double distanceToCar, double speedOfCar);15 16 int getRadarRear(double distanceToCar,17 double speedOfCar);......18 // more method signatures19 }
Note that the method signatures have no braces and are terminated with a semicolon.
To use an interface, you write a class that implements the interface. When an instantiable class implements an interface, it provides a method body for each of the methods declared in the interface. For example,
注意:方法沒有(),以分號結(jié)束。
要使用接口,只需寫一個類實現(xiàn)接口即可。當一個類實現(xiàn)一個接口時,它必須實現(xiàn)接口描述中的每個方法。例如:
1 public class OperateBMW760i implements OperateCar { 2 3 // the OperateCar method signatures, with implementation -- 4 // for example: 5 int signalTurn(Direction direction, boolean signalOn) { 6 // code to turn BMW's LEFT turn indicator lights on 7 // code to turn BMW's LEFT turn indicator lights off 8 // code to turn BMW's RIGHT turn indicator lights on 9 // code to turn BMW's RIGHT turn indicator lights off10 }11 12 // other members, as needed -- for example, helper classes not13 // visible to clients of the interface14 }
In the robotic car example above, it is the automobile manufacturers who will implement the interface. Chevrolet's implementation will be substantially different from that of Toyota, of course, but both manufacturers will adhere to the same interface. The guidance manufacturers, who are the clients of the interface, will build systems that use GPS data on a car's location, digital street maps, and traffic data to drive the car. In so doing, the guidance systems will invoke the interface methods: turn, change lanes, brake, accelerate, and so forth.
以上述無人駕駛汽車為例,汽車制造商將實現(xiàn)接口。雪福來和豐田的實現(xiàn)可能有所不同,但是他們講遵循相同的接口。作為接口客戶端的指導制造商將構(gòu)建一個系統(tǒng),該系統(tǒng)使用汽車位置的GPS數(shù)據(jù)、數(shù)字街道地圖、交通數(shù)據(jù)來駕車汽車。這樣做,指導系統(tǒng)將調(diào)用接口的方法:轉(zhuǎn)彎、變道、剎車、加速等等。
The robotic car example shows an interface being used as an industry standard application Programming Interface (API). APIs are also common in commercial software products. Typically, a company sells a software package that contains complex methods that another company wants to use in its own software product. An example would be a package of digital image processing methods that are sold to companies making end-user graphics programs. The image processing company writes its classes to implement an interface, which it makes public to its customers. The graphics company then invokes the image processing methods using the signatures and return types defined in the interface. While the image processing company's API is made public (to its customers), its implementation of the API is kept as a closely guarded secret—in fact, it may revise the implementation at a later date as long as it continues to implement the original interface that its customers have relied on.
無人駕駛汽車的例子展示了接口被用作行業(yè)標準API。APIs在商業(yè)軟件產(chǎn)品中也很常見。通常情況下,有的公司出售包含復雜方法的軟件包,讓其他公司在他們的軟件產(chǎn)品中使用。可能有這樣的例子:一個提供圖像處理方法的包賣給了做終端用戶圖形程序的公司。圖形處理公司編寫實現(xiàn)公共接口的類,圖形公司調(diào)用接口中定義的方法,并返回接口中定義的類型。對客戶來說,圖像處理公司的API是公開的,API的實現(xiàn)是私密的——事實上,圖像處理公司今后可以修改API的實現(xiàn),只要它繼續(xù)實現(xiàn)客戶依賴的接口。
An interface declaration consists of modifiers, the keyWord interface
, the interface name, a comma-separated list of parent interfaces (if any), and the interface body. For example:
接口聲明包括:修飾符、接口關(guān)鍵字、接口名稱、用逗號分隔的父接口列表(如果有父接口的話)和接口主體。例如:
1 public interface GroupedInterface extends Interface1, Interface2, Interface3 { 2 3 // constant declarations 4 5 // base of natural logarithms 6 double E = 2.718282; 7 8 // method signatures 9 void doSomething(int i, double x);10 11 int doSomethingElse(String s);12 }
The public
access specifier indicates that the interface can be used by any class in any package. If you do not specify that the interface is public, then your interface is accessible only to classes defined in the same package as the interface.
An interface can extend other interfaces, just as a class subclass or extend another class. However, whereas a class can extend only one other class, an interface can extend any number of interfaces. The interface declaration includes a comma-separated list of all the interfaces that it extends.
其中public修飾符說明此接口可以被任何package中的class使用。如果不指定public修飾符,那么接口只能被和此接口在同一個package中的類使用。
接口可以繼承其它接口,就像子類或繼承其它類一樣。然而,類只能繼承一個類,而接口可以繼承多個接口。接口聲明包含一個用逗號分隔的要繼承的接口列表。
The interface body can contain abstract methods, default methods, and static methods. An abstract method within an interface is followed by a semicolon, but no braces (an abstract method does not contain an implementation). Default methods are defined with the default
modifier, and static methods with the static
keyword. All abstract, default, and static methods in an interface are implicitly public
, so you can omit thepublic
modifier.
In addition, an interface can contain constant declarations. All constant values defined in an interface are implicitly public
, static
, and final
. Once again, you can omit these modifiers.
接口主題可以包含抽象方法、默認方法和靜態(tài)方法。接口內(nèi)的抽象方法后面緊跟著一個分號,但是沒有括號(抽象方法不包含方法實現(xiàn));默認方法定義時使用default修飾符;靜態(tài)方法使用static關(guān)鍵字。接口中的所有抽象方法、默認方法和靜態(tài)方法默認都是public,所以你可以省略public修飾符。
另外,接口可以包含常量聲明。在接口中定義的所有常量默認都是public、static、finale。你可以省略這些修飾符。
To declare a class that implements an interface, you include an implements
clause in the class declaration. Your class can implement more than one interface, so the implements
keyword is followed by a comma-separated list of the interfaces implemented by the class. By convention, the implements
clause follows the extends
clause, if there is one.
要聲明一個實現(xiàn)接口的類,只需在類的聲明中包含一個implements字句。類可以實現(xiàn)多個接口,一個類實現(xiàn)多個接口時,只需在 implements
關(guān)鍵字后跟用逗號分隔的多個接口名即可。按照慣例,implements的條款遵循繼承的條款。
Consider an interface that defines how to compare the size of objects.
想象有這樣一個接口:如何比較對象的大小。
1 public interface Relatable {2 3 // this (object calling isLargerThan)4 // and other must be instances of 5 // the same class returns 1, 0, -1 6 // if this is greater than, 7 // equal to, or less than other8 public int isLargerThan(Relatable other);9 }
If you want to be able to compare the size of similar objects, no matter what they are, the class that instantiates them should implement Relatable
.
如果你想比較兩個相似對象的大小,不管這個兩個對象是什么,用于實例化的類需要實現(xiàn)Relatable接口。
Any class can implement Relatable
if there is some way to compare the relative "size" of objects instantiated from the class. For strings, it could be number of characters; for books, it could be number of pages; for students, it could be weight; and so forth. For planar geometric objects, area would be a good choice (see the RectanglePlus
class that follows), while volume would work for three-dimensional geometric objects. All such classes can implement the isLargerThan()
method.
不管是什么類,只要它需要比較兩個實例化的對象的大小,它都可以實現(xiàn)Relatable接口。例如,對于字符串,它可能需要比較一些字符的大小;對于書,它可能是需要比較書的頁面的大小;對于學生,他可能是學生的重量;對于平面幾何對象,可能是面積。所有這些類都可以實現(xiàn)Relatable接口的isLargerThan()方法。
If you know that a class implements Relatable
, then you know that you can compare the size of the objects instantiated from that class.
如果一個類實現(xiàn)了Relatable接口,那么你可以比較這個類的實例化的對象的大小。( 通過isLargerThan()
)
Here is the Rectangle
class that was presented in the Creating Objects section, rewritten to implement Relatable
.
下面是在創(chuàng)建對象一節(jié)講到的Rectangle類,現(xiàn)在重寫為實現(xiàn)Relatable接口:
1 public class RectanglePlus 2 implements Relatable { 3 public int width = 0; 4 public int height = 0; 5 public Point origin; 6 7 // four constructors 8 public RectanglePlus() { 9 origin = new Point(0, 0);10 }11 public RectanglePlus(Point p) {12 origin = p;13 }14 public RectanglePlus(int w, int h) {15 origin = new Point(0, 0);16 width = w;17 height = h;18 }19 public RectanglePlus(Point p, int w, int h) {20 origin = p;21 width = w;22 height = h;23 }24 25 // a method for moving the rectangle26 public void move(int x, int y) {27 origin.x = x;28 origin.y = y;29 }30 31 // a method for computing32 // the area of the rectangle33 public int getArea() {34 return width * height;35 }36 37 // a method required to implement38 // the Relatable interface39 public int isLargerThan(Relatable other) {40 RectanglePlus otherRect 41 = (RectanglePlus)other;42 if (this.getArea() < otherRect.getArea())43 return -1;44 else if (this.getArea() > otherRect.getArea())45 return 1;46 else47 return 0; 48 }49 }
Because RectanglePlus
implements Relatable
, the size of any two RectanglePlus
objects can be compared.
因為RectanglePlus實現(xiàn)了Relatable接口,所以任意兩個RectanglePlus對象都能比較。
Note: The isLargerThan
method, as defined in the Relatable
interface, takes an object of type Relatable
. The line of code, shown in bold in the previous example, casts other
to a RectanglePlus
instance. Type casting tells the compiler what the object really is. Invoking getArea
directly on the other
instance (other.getArea()
) would fail to compile because the compiler does not understand that other
is actually an instance of RectanglePlus
.
When you define a new interface, you are defining a new reference data type. You can use interface names anywhere you can use any other data type name. If you define a reference variable whose type is an interface, any object you assign to it must be an instance of a class that implements the interface.
As an example, here is a method for finding the largest object in a pair of objects, for any objects that are instantiated from a class that implements Relatable
:
1 public Object findLargest(Object object1, Object object2) {2 Relatable obj1 = (Relatable)object1;3 Relatable obj2 = (Relatable)object2;4 if ((obj1).isLargerThan(obj2) > 0)5 return object1;6 else 7 return object2;8 }
By casting object1
to a Relatable
type, it can invoke the isLargerThan
method.
If you make a point of implementing Relatable
in a wide variety of classes, the objects instantiated from any of those classes can be compared with the findLargest()
method—provided that both objects are of the same class. Similarly, they can all be compared with the following methods:
1 public Object findSmallest(Object object1, Object object2) {2 Relatable obj1 = (Relatable)object1;3 Relatable obj2 = (Relatable)object2;4 if ((obj1).isLargerThan(obj2) < 0)5 return object1;6 else 7 return object2;8 }
1 public boolean isEqual(Object object1, Object object2) {2 Relatable obj1 = (Relatable)object1;3 Relatable obj2 = (Relatable)object2;4 if ( (obj1).isLargerThan(obj2) == 0)5 return true;6 else 7 return false;8 }
These methods work for any "relatable" objects, no matter what their class inheritance is. When they implement Relatable
, they can be of both their own class (or superclass) type and a Relatable
type. This gives them some of the advantages of multiple inheritance, where they can have behavior from both a superclass and an interface.
Consider an interface that you have developed called DoIt
:
1 public interface DoIt {2 void doSomething(int i, double x);3 int doSomethingElse(String s);4 }
Suppose that, at a later time, you want to add a third method to DoIt
, so that the interface now becomes:
1 public interface DoIt {2 3 void doSomething(int i, double x);4 int doSomethingElse(String s);5 boolean didItWork(int i, double x, String s);6 7 }
If you make this change, then all classes that implement the old DoIt
interface will break because they no longer implement the old interface. Programmers relying on this interface will protest loudly.
Try to anticipate all uses for your interface and specify it completely from the beginning. If you want to add additional methods to an interface, you have several options. You could create a DoItPlus
interface that extends DoIt
:
public interface DoItPlus extends DoIt { boolean didItWork(int i, double x, String s); }
Now users of your code can choose to continue to use the old interface or to upgrade to the new interface.
Alternatively, you can define your new methods as default methods. The following example defines a default method named didItWork
:
1 public interface DoIt {2 3 void doSomething(int i, double x);4 int doSomethingElse(String s);5 default boolean didItWork(int i, double x, String s) {6 // Method body 7 }8 9 }
Note that you must provide an implementation for default methods. You could also define new static methods to existing interfaces. Users who have classes that implement interfaces enhanced with new default or static methods do not have to modify or recompile them to accommodate the additional methods.
The section Interfaces describes an example that involves manufacturers of computer-controlled cars who publish industry-standard interfaces that describe which methods can be invoked to operate their cars. What if those computer-controlled car manufacturers add new functionality, such as flight, to their cars? These manufacturers would need to specify new methods to enable other companies (such as electronic guidance instrument manufacturers) to adapt their software to flying cars. Where would these car manufacturers declare these new flight-related methods? If they add them to their original interfaces, then programmers who have implemented those interfaces would have to rewrite their implementations. If they add them as static methods, then programmers would regard them as utility methods, not as essential, core methods.
Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.
Consider the following interface, TimeClient
, as described in Answers to Questions and Exercises: Interfaces:
import java.time.*; public interface TimeClient { void setTime(int hour, int minute, int second); void setDate(int day, int month, int year); void setDateAndTime(int day, int month, int year, int hour, int minute, int second); LocalDateTime getLocalDateTime();}
The following class, SimpleTimeClient
, implements TimeClient
:
package defaultmethods;import java.time.*;import java.lang.*;import java.util.*;public class SimpleTimeClient implements TimeClient { private LocalDateTime dateAndTime; public SimpleTimeClient() { dateAndTime = LocalDateTime.now(); } public void setTime(int hour, int minute, int second) { LocalDate currentDate = LocalDate.from(dateAndTime); LocalTime timeToSet = LocalTime.of(hour, minute, second); dateAndTime = LocalDateTime.of(currentDate, timeToSet); } public void setDate(int day, int month, int year) { LocalDate dateToSet = LocalDate.of(day, month, year); LocalTime currentTime = LocalTime.from(dateAndTime); dateAndTime = LocalDateTime.of(dateToSet, currentTime); } public void setDateAndTime(int day, int month, int year, int hour, int minute, int second) { LocalDate dateToSet = LocalDate.of(day, month, year); LocalTime timeToSet = LocalTime.of(hour, minute, second); dateAndTime = LocalDateTime.of(dateToSet, timeToSet); } public LocalDateTime getLocalDateTime() { return dateAndTime; } public String toString() { return dateAndTime.toString(); } public static void main(String... args) { TimeClient myTimeClient = new SimpleTimeClient(); System.out.println(myTimeClient.toString()); }}Suppose that you want to add new functionality to the TimeClient interface, such as the ability to specify a time zone through a ZonedDateTime object (which is like a LocalDateTime object except that it stores time zone information):public interface TimeClient { void setTime(int hour, int minute, int second); void setDate(int day, int month, int year); void setDateAndTime(int day, int month, int year, int hour, int minute, int second); LocalDateTime getLocalDateTime(); ZonedDateTime getZonedDateTime(String zoneString);}
Following this modification to the TimeClient
interface, you would also have to modify the class SimpleTimeClient
and implement the method getZonedDateTime
. However, rather than leaving getZonedDateTime
as abstract
(as in the previous example), you can instead define a default implementation. (Remember that an abstract method is a method declared without an implementation.)
package defaultmethods; import java.time.*;public interface TimeClient { void setTime(int hour, int minute, int second); void setDate(int day, int month, int year); void setDateAndTime(int day, int month, int year, int hour, int minute, int second); LocalDateTime getLocalDateTime(); static ZoneId getZoneId (String zoneString) { try { return ZoneId.of(zoneString); } catch (DateTimeException e) { System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead."); return ZoneId.systemDefault(); } } default ZonedDateTime getZonedDateTime(String zoneString) { return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString)); }}
You specify that a method definition in an interface is a default method with the default
keyword at the beginning of the method signature. All method declarations in an interface, including default methods, are implicitly public
, so you can omit the public
modifier.
With this interface, you do not have to modify the class SimpleTimeClient
, and this class (and any class that implements the interface TimeClient
), will have the method getZonedDateTime
already defined. The following example, TestSimpleTimeClient
, invokes the methodgetZonedDateTime
from an instance of SimpleTimeClient
:
package defaultmethods; import java.time.*;import java.lang.*;import java.util.*;public class TestSimpleTimeClient { public static void main(String... args) { TimeClient myTimeClient = new SimpleTimeClient(); System.out.println("Current time: " + myTimeClient.toString()); System.out.println("Time in California: " + myTimeClient.getZonedDateTime("Blah blah").toString()); }}
When you extend an interface that contains a default method, you can do the following:
abstract
.Suppose that you extend the interface TimeClient
as follows:
public interface AnotherTimeClient extends TimeClient { }
Any class that implements the interface AnotherTimeClient
will have the implementation specified by the default method TimeClient.getZonedDateTime
.
Suppose that you extend the interface TimeClient
as follows:
public interface AbstractZoneTimeClient extends TimeClient { public ZonedDateTime getZonedDateTime(String zoneString);}
Any class that implements the interface AbstractZoneTimeClient
will have to implement the method getZonedDateTime
; this method is an abstract
method like all other nondefault (and nonstatic) methods in an interface.
Suppose that you extend the interface TimeClient
as follows:
public interface HandleInvalidTimeZoneClient extends TimeClient { default public ZonedDateTime getZonedDateTime(String zoneString) { try { return ZonedDateTime.of(getLocalDateTime(),ZoneId.of(zoneString)); } catch (DateTimeException e) { System.err.println("Invalid zone ID: " + zoneString + "; using the default time zone instead."); return ZonedDateTime.of(getLocalDateTime(),ZoneId.systemDefault()); } }}
Any class that implements the interface HandleInvalidTimeZoneClient
will use the implementation of getZonedDateTime
specified by this interface instead of the one specified by the interface TimeClient
.
In addition to default methods, you can define static methods in interfaces. (A static method is a method that is associated with the class in which it is defined rather than with any object. Every instance of the class shares its static methods.) This makes it easier for you to organize helper methods in your libraries; you can keep static methods specific to an interface in the same interface rather than in a separate class. The following example defines a static method that retrieves a ZoneId
object corresponding to a time zone identifier; it uses the system default time zone if there is no ZoneId
object corresponding to the given identifier. (As a result, you can simplify the method getZonedDateTime
):
public interface TimeClient { // ... static public ZoneId getZoneId (String zoneString) { try { return ZoneId.of(zoneString); } catch (DateTimeException e) { System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead."); return ZoneId.systemDefault(); } } default public ZonedDateTime getZonedDateTime(String zoneString) { return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString)); } }
Like static methods in classes, you specify that a method definition in an interface is a static method with the static
keyword at the beginning of the method signature. All method declarations in an interface, including static methods, are implicitly public
, so you can omit the public
modifier.
Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces. This section demonstrates how the Comparator
interface has been enhanced with default and static methods.
Consider the Card
and Deck
classes as described in Questions and Exercises: Classes. This example rewrites the Card
and Deck
classes as interfaces. The Card
interface contains two enum
types (Suit
and Rank
) and two abstract methods (getSuit
and getRank
):
package defaultmethods;public interface Card extends Comparable<Card> { public enum Suit { DIAMONDS (1, "Diamonds"), CLUBS (2, "Clubs" ), HEARTS (3, "Hearts" ), SPADES (4, "Spades" ); private final int value; private final String text; Suit(int value, String text) { this.value = value; this.text = text; } public int value() {return value;} public String text() {return text;} } public enum Rank { DEUCE (2 , "Two" ), THREE (3 , "Three"), FOUR (4 , "Four" ), FIVE (5 , "Five" ), SIX (6 , "Six" ), SEVEN (7 , "Seven"), EIGHT (8 , "Eight"), NINE (9 , "Nine" ), TEN (10, "Ten" ), JACK (11, "Jack" ), QUEEN (12, "Queen"), KING (13, "King" ), ACE (14, "Ace" ); private final int value; private final String text; Rank(int value, String text) { this.value = value; this.text = text; } public int value() {return value;} public String text() {return text;} } public Card.Suit getSuit(); public Card.Rank getRank();}
The Deck
interface contains various methods that manipulate cards in a deck:
package defaultmethods; import java.util.*;import java.util.stream.*;import java.lang.*; public interface Deck { List<Card> getCards(); Deck deckFactory(); int size(); void addCard(Card card); void addCards(List<Card> cards); void addDeck(Deck deck); void shuffle(); void sort(); void sort(Comparator<Card> c); String deckToString(); Map<Integer, Deck> deal(int players, int numberOfCards) throws IllegalArgumentException;}
The class PlayingCard
implements the interface Card
, and the class StandardDeck
implements the interface Deck
.
The class StandardDeck
implements the abstract method Deck.sort
as follows:
public class StandardDeck implements Deck { private List<Card> entireDeck; // ... public void sort() { Collections.sort(entireDeck); } // ...}
The method Collections.sort
sorts an instance of List
whose element type implements the interface Comparable
. The member entireDeck
is an instance of List
whose elements are of the type Card
, which extends Comparable
. The class PlayingCard
implements theComparable.compareTo
method as follows:
public int hashCode() { return ((suit.value()-1)*13)+rank.value();}public int compareTo(Card o) { return this.hashCode() - o.hashCode();}
The method compareTo
causes the method StandardDeck.sort()
to sort the deck of cards first by suit, and then by rank.
What if you want to sort the deck first by rank, then by suit? You would need to implement the Comparator
interface to specify new sorting criteria, and use the method sort(List<T> list, Comparator<? super T> c)
(the version of the sort
method that includes aComparator
parameter). You can define the following method in the class StandardDeck
:
public void sort(Comparator<Card> c) { Collections.sort(entireDeck, c);}
With this method, you can specify how the method Collections.sort
sorts instances of the Card
class. One way to do this is to implement the Comparator
interface to specify how you want the cards sorted. The example SortByRankThenSuit
does this:
package defaultmethods;import java.util.*;import java.util.stream.*;import java.lang.*;public class SortByRankThenSuit implements Comparator<Card> { public int compare(Card firstCard, Card secondCard) { int compVal = firstCard.getRank().value() - secondCard.getRank().value(); if (compVal != 0) return compVal; else return firstCard.getSuit().value() - secondCard.getSuit().value(); }}
The following invocation sorts the deck of playing cards first by rank, then by suit:
StandardDeck myDeck = new StandardDeck();myDeck.shuffle();myDeck.sort(new SortByRankThenSuit());
However, this approach is too verbose; it would be better if you could specify what you want to sort, not how you want to sort. Suppose that you are the developer who wrote the Comparator
interface. What default or static methods could you add to the Comparator
interface to enable other developers to more easily specify sort criteria?
To start, suppose that you want to sort the deck of playing cards by rank, regardless of suit. You can invoke the StandardDeck.sort
method as follows:
StandardDeck myDeck = new StandardDeck();myDeck.shuffle();myDeck.sort( (firstCard, secondCard) -> firstCard.getRank().value() - secondCard.getRank().value());
Because the interface Comparator
is a functional interface, you can use a lambda expression as an argument for the sort
method. In this example, the lambda expression compares two integer values.
It would be simpler for your developers if they could create a Comparator
instance by invoking the method Card.getRank
only. In particular, it would be helpful if your developers could create a Comparator
instance that compares any object that can return a numerical value from a method such as getValue
or hashCode
. The Comparator
interface has been enhanced with this ability with the static method comparing
:
myDeck.sort(Comparator.comparing((card) -> card.getRank()));
In this example, you can use a method reference instead:
myDeck.sort(Comparator.comparing(Card::getRank));
This invocation better demonstrates what to sort rather than how to do it.
The Comparator
interface has been enhanced with other versions of the static method comparing
such as comparingDouble
and comparingLong
that enable you to create Comparator
instances that compare other data types.
Suppose that your developers would like to create a Comparator
instance that could compare objects with more than one criteria. For example, how would you sort the deck of playing cards first by rank, and then by suit? As before, you could use a lambda expression to specify these sort criteria:
StandardDeck myDeck = new StandardDeck();myDeck.shuffle();myDeck.sort( (firstCard, secondCard) -> { int compare = firstCard.getRank().value() - secondCard.getRank().value(); if (compare != 0) return compare; else return firstCard.getSuit().value() - secondCard.getSuit().value(); } );
It would be simpler for your developers if they could build a Comparator
instance from a series of Comparator
instances. The Comparator
interface has been enhanced with this ability with the default method thenComparing
:
myDeck.sort( Comparator .comparing(Card::getRank) .thenComparing(Comparator.comparing(Card::getSuit)));
The Comparator
interface has been enhanced with other versions of the default method thenComparing
(such as thenComparingDouble
and thenComparingLong
) that enable you to build Comparator
instances that compare other data types.
Suppose that your developers would like to create a Comparator
instance that enables them to sort a collection of objects in reverse order. For example, how would you sort the deck of playing cards first by descending order of rank, from Ace to Two (instead of from Two to Ace)? As before, you could specify another lambda expression. However, it would be simpler for your developers if they could reverse an existing Comparator
by invoking a method. The Comparator
interface has been enhanced with this ability with the default method reversed
:
myDeck.sort( Comparator.comparing(Card::getRank) .reversed() .thenComparing(Comparator.comparing(Card::getSuit)));
This example demonstrates how the Comparator
interface has been enhanced with default methods, static methods, lambda expressions, and method references to create more expressive library methods whose functionality programmers can quickly deduce by looking at how they are invoked. Use these constructs to enhance the interfaces in your libraries.
An interface declaration can contain method signatures, default methods, static methods and constant definitions. The only methods that have implementations are default and static methods.
A class that implements an interface must implement all the methods declared in the interface.
An interface name can be used anywhere a type can be used.
java.lang.CharSequence
interface have to implement?public interface SomethingIsWrong { void aMethod(int aValue){ System.out.println("Hi Mom"); }}
public interface Marker {}
CharSequence
interface found in the java.lang
package. Your implementation should return the string backwards. Select one of the sentences from this book to use as the data. Write a small main
method to test your class; make sure to call all four methods.Check your answers.
新聞熱點
疑難解答