您的位置:首页 > 编程语言 > Java开发

009 The Interfaces In JAVA(官网文档翻译)

2015-10-30 10:13 597 查看

Interfaces

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编写的软件操作汽车——停止、启动、加速、向左转等等。而另一个产业群电子制导仪器制造商使用电脑系统接收GPS数据和交通状况的无线传输数据,并用接收到的数据驾车汽车。

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.

该汽车的制造商必须发布行业标准接口,阐述调用什么方法可以使车移动,然后指导制造商可以调用接口描述中的方法控制汽车,而工业集团不必知道其他集团的软件是如何实现的。事实上,每个集团都认为自己对软件高度专有,可以随时修改软件,前提是遵循发布的接口规范。

Interfaces in Java

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中,接口和类相似,都是引用类型。接口只能包含方法、默认方法、静态方法和嵌套方法。方法主题只能以默认方法和静态方法的形式存在。接口不能被实例化,只能被类实现或被其它的接口继承。详细会在稍后讨论。

Defining an interface is similar to creating a new class:

定义一个结合和创建一个类非常相似:

public interface OperateCar {

// constant declarations, if any

// method signatures

// An enum with values RIGHT, LEFT
int turn(Direction direction, double radius, double startSpeed, double endSpeed);

int changeLanes(Direction direction, double startSpeed, double endSpeed);

int signalTurn(Direction direction, boolean signalOn);

int getRadarFront(double distanceToCar, double speedOfCar);

int getRadarRear(double distanceToCar,
double speedOfCar);......
// more method signatures
}


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,

注意:方法没有(),以分号结束。

要使用接口,只需写一个类实现接口即可。当一个类实现一个接口时,它必须实现接口描述中的每个方法。例如:

public class OperateBMW760i implements OperateCar {

// the OperateCar method signatures, with implementation --
// for example:
int signalTurn(Direction direction, boolean signalOn) {
// code to turn BMW's LEFT turn indicator lights on
// code to turn BMW's LEFT turn indicator lights off
// code to turn BMW's RIGHT turn indicator lights on
// code to turn BMW's RIGHT turn indicator lights off
}

// other members, as needed -- for example, helper classes not
// visible to clients of the interface
}


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.

以上述无人驾驶汽车为例,汽车制造商将实现接口。雪福来和丰田的实现可能有所不同,但是他们讲遵循相同的接口。作为接口客户端的指导制造商将构建一个系统,该系统使用汽车位置的GPS数据、数字街道地图、交通数据来驾车汽车。这样做,指导系统将调用接口的方法:转弯、变道、刹车、加速等等。

Interfaces as APIs

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.

无人驾驶汽车的例子展示了接口被用作行业标准API。APIs在商业软件产品中也很常见。通常情况下,有的公司出售包含复杂方法的软件包,让其他公司在他们的软件产品中使用。可能有这样的例子:一个提供图像处理方法的包卖给了做终端用户图形程序的公司。图形处理公司编写实现公共接口的类,图形公司调用接口中定义的方法,并返回接口中定义的类型。对客户来说,图像处理公司的API是公开的,API的实现是私密的——事实上,图像处理公司今后可以修改API的实现,只要它继续实现客户依赖的接口。

---------------------------------------------------------------------------------------

Defining an Interface

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:

接口声明包括:修饰符、接口关键字、接口名称、用逗号分隔的父接口列表(如果有父接口的话)和接口主体。例如:

public interface GroupedInterface extends Interface1, Interface2, Interface3 {

// constant declarations

// base of natural logarithms
double E = 2.718282;

// method signatures
void doSomething(int i, double x);

int doSomethingElse(String s);
}


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

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 the
public
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.

接口主题可以包含抽象方法、默认方法和静态方法。接口内的抽象方法后面紧跟着一个分号,但是没有括号(抽象方法不包含方法实现);默认方法定义时使用default修饰符;静态方法使用static关键字。接口中的所有抽象方法、默认方法和静态方法默认都是public,所以你可以省略public修饰符。

另外,接口可以包含常量声明。在接口中定义的所有常量默认都是public、static、finale。你可以省略这些修饰符。

Implementing an Interface

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.

要声明一个实现接口的类,只需在类的声明中包含一个implements字句。类可以实现多个接口,一个类实现多个接口时,只需在
implements
关键字后跟用逗号分隔的多个接口名即可。按照惯例,implements的条款遵循继承的条款。

A Sample Interface, Relatable

Consider an interface that defines how to compare the size of objects.

想象有这样一个接口:如何比较对象的大小。

public interface Relatable {

// this (object calling isLargerThan)
// and other must be instances of
// the same class returns 1, 0, -1
// if this is greater than,
// equal to, or less than other
public int isLargerThan(Relatable other);
}


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
.

如果你想比较两个相似对象的大小,不管这个两个对象是什么,用于实例化的类需要实现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.

不管是什么类,只要它需要比较两个实例化的对象的大小,它都可以实现Relatable接口。例如,对于字符串,它可能需要比较一些字符的大小;对于书,它可能是需要比较书的页面的大小;对于学生,他可能是学生的重量;对于平面几何对象,可能是面积。所有这些类都可以实现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.

如果一个类实现了Relatable接口,那么你可以比较这个类的实例化的对象的大小。( 通过
isLargerThan()


Implementing the Relatable Interface

Here is the
Rectangle
class that was presented in the Creating Objects section, rewritten to implement
Relatable
.

下面是在创建对象一节讲到的Rectangle类,现在重写为实现Relatable接口:

public class RectanglePlus
implements Relatable {
public int width = 0;
public int height = 0;
public Point origin;

// four constructors
public RectanglePlus() {
origin = new Point(0, 0);
}
public RectanglePlus(Point p) {
origin = p;
}
public RectanglePlus(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public RectanglePlus(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}

// a method for moving the rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}

// a method for computing
// the area of the rectangle
public int getArea() {
return width * height;
}

// a method required to implement
// the Relatable interface
public int isLargerThan(Relatable other) {
RectanglePlus otherRect
= (RectanglePlus)other;
if (this.getArea() < otherRect.getArea())
return -1;
else if (this.getArea() > otherRect.getArea())
return 1;
else
return 0;
}
}


Because
RectanglePlus
implements
Relatable
, the size of any two
RectanglePlus
objects can be compared.

因为RectanglePlus实现了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
.

---------------------------------------------------------------------------------------

Using an Interface as a Type

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
:

public Object findLargest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) > 0)
return object1;
else
return object2;
}


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:

public Object findSmallest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) < 0)
return object1;
else
return object2;
}




public boolean isEqual(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ( (obj1).isLargerThan(obj2) == 0)
return true;
else
return false;
}


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.

-------------------------------------------------------

Evolving Interfaces

Consider an interface that you have developed called
DoIt
:

public interface DoIt {
void doSomething(int i, double x);
int doSomethingElse(String s);
}


Suppose that, at a later time, you want to add a third method to
DoIt
, so that the interface now becomes:

public interface DoIt {

void doSomething(int i, double x);
int doSomethingElse(String s);
boolean didItWork(int i, double x, String s);

}


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
:

public interface DoIt {

void doSomething(int i, double x);
int doSomethingElse(String s);
default boolean didItWork(int i, double x, String s) {
// Method body
}

}


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.

Default 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 method
getZonedDateTime
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());
}
}


Extending Interfaces That Contain Default Methods

When you extend an interface that contains a default method, you can do the following:

Not mention the default method at all, which lets your extended interface inherit the default method.

Redeclare the default method, which makes it
abstract
.

Redefine the default method, which overrides it.

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
.

Static Methods

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.

Integrating Default Methods into Existing Libraries

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 the
Comparable.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 a
Comparator
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.

Summary of Interfaces

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.

Questions and Exercises: Interfaces

Questions

What methods would a class that implements the
java.lang.CharSequence
interface have to implement?

What is wrong with the following interface?

public interface SomethingIsWrong {
void aMethod(int aValue){
System.out.println("Hi Mom");
}
}


Fix the interface in question 2.

Is the following interface valid?

public interface Marker {
}


Exercises

Write a class that implements the
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.

Suppose you have written a time server that periodically notifies its clients of the current date and time. Write an interface the server could use to enforce a particular protocol on its clients.

Check your answers.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: