Java接口和继承

接口 Interfaces

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.

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.

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:

1
2
3
4
5
6
7
8
9
10
11
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.

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.

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. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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);
}

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

// ...

// 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;
}
}

A class that implements an interface must implement all the methods declared in the interface.

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:

1
2
3
4
5
6
7
8
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.

Default and Static 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.

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.

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.

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.) 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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));
}
}

继承 Inheritance

In the Java language, classes can be derived from other classes, thereby inheriting fields and methods from those classes.

A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).

ExceptingObject, which has no superclass, every class has one and only one direct superclass (single inheritance). In the absence of any other explicit superclass, every class is implicitly a subclass of Object.Classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, Object. Such a class is said to be descended from all the classes in the inheritance chain stretching back to Object.

A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.

The Object class, defined in the java.lang package, defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object, other classes derive from some of those classes, and so on, forming a hierarchy of classes.

All Classes in the Java Platform are Descendants of Object

The Object class is the top of the class hierarchy. All classes are descendants from this class and inherit methods from it. Useful methods inherited from Object include toString(), equals(), clone(), and getClass().

Classes near the bottom of the hierarchy provide more specialized behavior.

Here is the sample code for a Bicycle class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Bicycle {

// the Bicycle class has three fields
public int cadence;
public int gear;
public int speed;

// the Bicycle class has one constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}

// the Bicycle class has four methods
public void setCadence(int newValue) {
cadence = newValue;
}

public void setGear(int newValue) {
gear = newValue;
}

public void applyBrake(int decrement) {
speed -= decrement;
}

public void speedUp(int increment) {
speed += increment;
}

}

A class declaration for a MountainBike class that is a subclass of Bicycle might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MountainBike extends Bicycle {

// the MountainBike subclass adds one field
public int seatHeight;

// the MountainBike subclass has one constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}

// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}

What You Can Do in a Subclass

A subclass inherits all of the public and protected members of its parent, no matter what package the subclass is in. If the subclass is in the same package as its parent, it also inherits the package-private members of the parent. You can use the inherited members as is, replace them, hide them, or supplement them with new members:

  • The inherited fields can be used directly, just like any other fields.
  • You can declare a field in the subclass with the same name (even if their types are different) as the one in the superclass, thus hiding it (not recommended).
  • You can declare new fields in the subclass that are not in the superclass.
  • The inherited methods can be used directly as they are.
  • You can write a new instance method in the subclass that has the same signature as the one in the superclass, thus overriding it.
  • You can write a new static method in the subclass that has the same signature as the one in the superclass, thus hiding it.
  • You can declare new methods in the subclass that are not in the superclass.
  • You can write a subclass constructor that invokes the constructor of the superclass, either implicitly or by using the keyword super.

A subclass does not inherit the private members of its parent class. However, if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass. A nested class has access to all the private members of its enclosing class—both fields and methods. Therefore, a public or protected nested class inherited by a subclass has indirect access to all of the private members of the superclass.

Casting Objects

We have seen that an object is of the data type of the class from which it was instantiated. For example, if we write

1
public MountainBike myBike = new MountainBike();

then myBike is of type MountainBike.

MountainBike is descended from Bicycle and Object. Therefore, a MountainBike is a Bicycle and is also an Object, and it can be used wherever Bicycle or Object objects are called for.

The reverse is not necessarily true: a Bicycle may be a MountainBike, but it isn’t necessarily. Similarly, an Object may be a Bicycle or a MountainBike, but it isn’t necessarily.

Casting shows the use of an object of one type in place of another type, among the objects permitted by inheritance and implementations. For example, if we write

1
Object obj = new MountainBike();

then obj is both an Object and a MountainBike (until such time as obj is assigned another object that is not a MountainBike). This is called implicit casting.

If, on the other hand, we write

1
MountainBike myBike = obj;

we would get a compile-time error because obj is not known to the compiler to be a MountainBike. However, we can tell the compiler that we promise to assign a MountainBike to obj by explicit casting:

1
MountainBike myBike = (MountainBike)obj;

This cast inserts a runtime check that obj is assigned a MountainBike so that the compiler can safely assume that obj is a MountainBike. If obj is not a MountainBike at runtime, an exception will be thrown.

You can make a logical test as to the type of a particular object using the instanceof operator. This can save you from a runtime error owing to an improper cast. For example:

1
2
3
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}

Here the instanceof operator verifies that obj refers to a MountainBike so that we can make the cast with knowledge that there will be no runtime exception thrown.

Multiple Inheritance

One significant difference between classes and interfaces is that classes can have fields whereas interfaces cannot. In addition, you can instantiate a class to create an object, which you cannot do with interfaces. An object stores its state in fields, which are defined in classes. One reason why the Java programming language does not permit you to extend more than one class is to avoid the issues of multiple inheritance of state, which is the ability to inherit fields from multiple classes. For example, suppose that you are able to define a new class that extends multiple classes. When you create an object by instantiating that class, that object will inherit fields from all of the class’s superclasses. What if methods or constructors from different superclasses instantiate the same field? Which method or constructor will take precedence? Because interfaces do not contain fields, you do not have to worry about problems that result from multiple inheritance of state.

Multiple inheritance of implementation is the ability to inherit method definitions from multiple classes. Problems arise with this type of multiple inheritance, such as name conflicts and ambiguity. When compilers of programming languages that support this type of multiple inheritance encounter superclasses that contain methods with the same name, they sometimes cannot determine which member or method to access or invoke. In addition, a programmer can unwittingly introduce a name conflict by adding a new method to a superclass. Default methods introduce one form of multiple inheritance of implementation. A class can implement more than one interface, which can contain default methods that have the same name. The Java compiler provides some rules to determine which default method a particular class uses.

The Java programming language supports multiple inheritance of type, which is the ability of a class to implement more than one interface. An object can have multiple types: the type of its own class and the types of all the interfaces that the class implements. This means that if a variable is declared to be the type of an interface, then its value can reference any object that is instantiated from any class that implements the interface.

As with multiple inheritance of implementation, a class can inherit different implementations of a method defined (as default or static) in the interfaces that it extends. In this case, the compiler or the user must decide which one to use.

Overriding and Hiding Methods

An instance method in a subclass with the same signature (name, plus the number and the type of its parameters) and return type as an instance method in the superclass overrides the superclass’s method.

The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is “close enough” and then to modify behavior as needed. The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called a covariant return type.

When overriding a method, you might want to use the @Override annotation that instructs the compiler that you intend to override a method in the superclass. If, for some reason, the compiler detects that the method does not exist in one of the superclasses, then it will generate an error.

If a subclass defines a static method with the same signature as a static method in the superclass, then the method in the subclass hides the one in the superclass.

The distinction between hiding a static method and overriding an instance method has important implications:

  • The version of the overridden instance method that gets invoked is the one in the subclass.
  • The version of the hidden static method that gets invoked depends on whether it is invoked from the superclass or the subclass.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
public void testInstanceMethod() {
System.out.println("The instance method in Animal");
}
}

public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
public void testInstanceMethod() {
System.out.println("The instance method in Cat");
}

public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}

The Cat class overrides the instance method in Animal and hides the static method in Animal. The main method in this class creates an instance of Cat and invokes testClassMethod() on the class and testInstanceMethod() on the instance.

The output from this program is as follows:

1
2
The static method in Animal
The instance method in Cat

Default methods and abstract methods in interfaces are inherited like instance methods. However, when the supertypes of a class or interface provide multiple default methods with the same signature, the Java compiler follows inheritance rules to resolve the name conflict. These rules are driven by the following two principles:

  • Instance methods are preferred over interface default methods.
  • Methods that are already overridden by other candidates are ignored. This circumstance can arise when supertypes share a common ancestor.

If two or more independently defined default methods conflict, or a default method conflicts with an abstract method, then the Java compiler produces a compiler error. You must explicitly override the supertype methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface OperateCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
public interface FlyCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}

public class FlyingCar implements OperateCar, FlyCar {
// ...
public int startEngine(EncryptedKey key) {
FlyCar.super.startEngine(key);
OperateCar.super.startEngine(key);
}
}

Inherited instance methods from classes can override abstract interface methods.

Static methods in interfaces are never inherited.

The access specifier for an overriding method can allow more, but not less, access than the overridden method. For example, a protected instance method in the superclass can be made public, but not private, in the subclass.

You will get a compile-time error if you attempt to change an instance method in the superclass to a static method in the subclass, and vice versa.

The Java virtual machine (JVM) calls the appropriate method for the object that is referred to in each variable. It does not call the method that is defined by the variable’s type. This behavior is referred to as virtual method invocation and demonstrates an aspect of the important polymorphism features in the Java language.

Using the Keyword super

If your method overrides one of its superclass’s methods, you can invoke the overridden method through the use of the keyword super. You can also use super to refer to a hidden field (although hiding fields is discouraged).

With super(), the superclass no-argument constructor is called. With super(parameter list), the superclass constructor with a matching parameter list is called.

If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the super class does not have a no-argument constructor, you will get a compile-time error.

If a subclass constructor invokes a constructor of its superclass, either explicitly or implicitly, you might think that there will be a whole chain of constructors called, all the way back to the constructor of Object. In fact, this is the case. It is called constructor chaining, and you need to be aware of it when there is a long line of class descent.

Writing Final Classes and Methods

You can declare some or all of a class’s methods final. You use the final keyword in a method declaration to indicate that the method cannot be overridden by subclasses. The Object class does this—a number of its methods are final.

Methods called from constructors should generally be declared final. If a constructor calls a non-final method, a subclass may redefine that method with surprising or undesirable results.

Note that you can also declare an entire class final. A class that is declared final cannot be subclassed.

Abstract Methods and Classes

An abstract class is a class that is declared abstract—it may or may not include abstract methods. Abstract classes cannot be instantiated, but they can be subclassed.

An abstract method is a method that is declared without an implementation (without braces, and followed by a semicolon).

If a class includes abstract methods, then the class itself must be declared abstract.

When an abstract class is subclassed, the subclass usually provides implementations for all of the abstract methods in its parent class. However, if it does not, then the subclass must also be declared abstract.

Abstract Classes Compared to Interfaces

Abstract classes are similar to interfaces. You cannot instantiate them, and they may contain a mix of methods declared with or without an implementation. However, with abstract classes, you can declare fields that are not static and final, and define public, protected, and private concrete methods. With interfaces, all fields are automatically public, static, and final, and all methods that you declare or define (as default methods) are public. In addition, you can extend only one class, whether or not it is abstract, whereas you can implement any number of interfaces.

Which should you use, abstract classes or interfaces?

  • Consider using abstract classes if any of these statements apply to your situation:
    • You want to share code among several closely related classes.
    • You expect that classes that extend your abstract class have many common methods or fields, or require access modifiers other than public (such as protected and private).
    • You want to declare non-static or non-final fields. This enables you to define methods that can access and modify the state of the object to which they belong.
  • Consider using interfaces if any of these statements apply to your situation:
    • You expect that unrelated classes would implement your interface. For example, the interfaces Comparable and Cloneable are implemented by many unrelated classes.
    • You want to specify the behavior of a particular data type, but not concerned about who implements its behavior.
    • You want to take advantage of multiple inheritance of type.

抽象类和接口

抽象类和接口的不同包括:

  • 抽象层次不同
    • 抽象类是对类抽象,而接口是对行为的抽象
    • 抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部行为进行抽象
  • 跨域不同
    • 抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类
    • 抽象类所体现的是一种继承关系,考虑的是子类与父类本质“是不是”同一类的关系
    • 而接口并不要求实现的类与接口是同一本质,它们之间只存在“有没有这个能力”的关系
  • 设计层次不同
    • 抽象类是自下而上的设计,在子类中重复出现的工作,抽象到抽象类中
    • 接口是自上而下,定义行为和规范

在进行选择时,可以参考以下几点:

  • 若使用接口,我们可以同时获得抽象类以及接口的好处
  • 所以假如想创建的基类没有任何方法定义或者成员变量,那么无论如何都愿意使用接口,而不要选择抽象类
  • 如果事先知道某种东西会成为基础类,那么第一个选择就是把它变成一个接口
  • 只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类

此外使用接口最重要的一个原因:实现接口可以使一个类向上转型至多个基础类。