接口和继承

接口和继承

接口

在这里你可以学到更多关于接口的知识——有什么用,如何用。

继承

本章节描述如何在其他类派生出新类。即子类如何从父类中继承字段和方法。在这里你将会学到所有的类都派生子Object,已经如何子类如何覆盖从父类继承而来的方法。本章节也涉及到类似接口的抽象类。

接口

不同群组程序员同意某个条约(contract),即阐明他们的软件是如何交互的,在很多情况下都是非常重要的。每个群组可以在不清楚其他群组实现的情况下编写代码。通常,接口就是这样的条款

Java中的接口

在Java编程语言中,接口类似于类,它是引用类型,仅可以包含常量、方法签名、默认方法(method signatures)、静态方法和嵌套类型。仅仅默认方法和静态方法可以拥有方法体。接口不能实例化,它仅仅可以被实现或者继承其他接口。定义接口和定义类类似:

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
}

方法签名没有大括号({}),以分号结束。

为了使用接口,你编写一个实现接口的类。当实例化该类后,需要为声明在接口内的每一个方法提供方法体,如:

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
}

定义一个接口

接口由以下几部分构成:

  • 访问权限修饰符。public or no-modifield.
  • 关键字interface
  • 接口名称,如果有多个接口,用逗号分隔开。
  • 接口身体(body).
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);
}

public表示所有任意位置的类都可以使用该接口。如果略去,即默认访问级别,同包内可被其他类访问。

接口可以继承多个接口。用逗号分隔开。

接口体

接口体可以包含抽象方法、默认方法、静态方法。抽象方法以分号结束,没有方法体。默认方法用default关键字定义。静态方法用static关键字定义。所有的方法隐含着public,所以你可以省略public。

此外,接口可以包含常量定义,这些常量隐含着public,static,和final。因此你可以省略这些关键字。

实现接口

为了定义实现接口的类,你得用关键字interface关键字。你可以实现一个或者多个接口,用逗号分隔开。按照约定,先extends在implements。如:

public class  TestOne extends TestTwo implements TestThree{
}
一个简单的例子
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;

    // 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;               
    }
}
将接口作为一个类型

当你定义一个接口,意味着你正在定义一种新的数据类型。当你在任意地方使用接口名称作为类型名称,赋值给该类型的值应须是实现该接口的类实例,如:

//variableTemp必须是interfaceName的实现类(可实例化)
//或是interfaceName类型的
interfaceName variable = variableTemp;


//object1必须是Object的子类
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;
}
不断发展的接口

假设你编写了一个接口叫DoIt:

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

过了不久,你想增加一个方法,现在接口变成:

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

如果你做出这样的改变,那么所有的实现类将不可用,因为它们不在实现旧的接口。使用该接口的程序员将会抗议。

应该在一开始就预测接口所有的使用者,如果不得不增加一些额外的方法,你有很多种选择,比如你可以创建一个新的DoItPlus接口来继承DoIt

public interface DoItPlus extends DoIt {

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

}

现在你可以选择继续使用旧接口还是升级接口了。

或者说,你可以把新加的方法声明为默认方法,如下所示:

public interface DoIt {

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

}

你要为默认方法提供实现。你也可以定义位静态方法。

默认方法

如果经过一段时间又新增了一个方法,那么所有实现该接口的类都要重新编写它们的实现,如果不想的话,可以将新增的方法定义成静态方法,意味着程序员将新增的静态方法当成工具方法(可以不管),而不是接口核心的方法。

默认方法可使你在接口中添加新的方法,并兼容旧版本的接口。

扩展包含默认方法的接口

当你继承一个包含默认方法的接口时,你可以有如下三种选择:

  1. 不理会这个默认方法,即无作为。也就是让你的实现类自动继承这个默认方法。
  2. 重新定义这个默认方法,使它成为抽象方法(没有实现,没有方法体)。
  3. 重新定义默认方法,重新实现它。
//无作为
public interface AnotherTimeClient extends TimeClient { }

//使它变成抽象方法
public interface AbstractZoneTimeClient extends TimeClient {
    public ZonedDateTime getZonedDateTime(String zoneString);
}

//重新实现
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());
        }
    }
}
静态方法

除默认方法之外,你可以在接口内部定义静态方法(static method),静态方法有如下特点:

  1. 静态方法与类关联,而不是对象。它由包含该静态方法类的所有对象共享。
  2. 帮助你,让你在类库中更好的组织工具方法。让工具方法集中一个地方而不是分散开。
小结
  • 接口声明可以包括方法签名、默认方法、静态方法和常量定义。就默认方法和静态方法可以有方法体(即可以实现)。
  • 如果一个类实现了一个接口,那么该类必须实现该接口中所有的方法。
  • 一种类型所能存在的地方,接口名称都可以将它取代。

继承

在之前的章节中,多次提到了继承。在Java语言中,类可以从其它类中派生出来,因此继承了其他类的字段(fields)和方法(methods)。


定义:从其它类中派生而来的类被称为子类(派生类、扩展类)。被派生的类称之为父类(基类、超类)。

除了Object没有超类外,每一个类都有且仅有一个直接的父类(单继承)。如果米诶有显示地声明一个类的父类,那么该类的父类隐式地继承自Object类。

A1继承自A2,A2继承自A3,A3继承自A4,…,An继承自Am,Am继承自Object,循着继承链,可以把所有的类的祖先类找到,即Object


使用继承的想法很简单,但却强有力:当你新创建类中所包含的字段和方法在已有类中存在,你可以从这些已有类中派生而来,这样你就可以重用以后类的方法和字段,从而不需要亲自去编写和调试了。

Java平台类继承层次结构

这里写图片描述

从上图可以发现,所有的类都是Object的子类(后裔)。越往上,类越普遍(抽象);越往下,类越具体。

Object类位于java.lang包,定义了所有类共有的行为。在java平台中,很多类派生自Object类,其他类又派生自这些类,以此类推,它们构成了类的层次结构。

继承的一个小例子
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;
    }

}

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;
    }   
}
子类能做什么

无论子类位于哪个包,子类从父类那里继承所有的publicprotected成员。假如子类和父类处于同一个包,那么子类也会把父类的包私有成员给继承过来。你可以使用继承把父类的成员给继承过来,也可以取代父类成员、追加新成员等:

  • 继承的字段可以直接使用,和使用本类一样。
  • 声明和父类一样的字段,效果是隐藏父类的字段(不鼓励)。
  • 你可以声明不存在与父类的字段
  • 继承的方法可以直接使用。
  • 重写方法,即声明的方法签名和父类的一样。
  • 隐藏,即编写的静态方法签名和父类的一样。
  • 新增方法,即声明的方法不存在与父类。
  • 声明一个构造函数,用super关键字显式调用父类构造函数,或者隐式调用。
父类中的私有成员

虽说子类只能继承父类的共有和保护成员,但是,假如父类拥有可以访问私有字段的共有或保护成员方法的话,子类也是可以访问到父类的私有字段的。(曲线救国,间接访问)

嵌套类可以访问外围类的所有成员(包括私有),因此,如果public,protected嵌套类如果被子类所继承,那么该子类可以间接访问父类的私有成员。

对象造型(Casting Object)
//我们说myBike是MountainBike类型的
public MountainBike myBike = new MountainBike();

/*
  MountainBike继承自或实现Bicycle或Object,也就是说以下表达成立:
  1. MountainBike是Object。
  2. Bicycle是Object
  但是以下表达式不成立:
  1. Object是Bicycle。只是可能是,Object可以是Person或其他的。
  2. Object是MountainBike。
*/
Object obj = new MountainBike();//成立,隐式造型

//不成立,编译器不知道obj实际上是MountainBike
MountainBike myBike = obj;

/* 
   成立,显示造型,告诉(许诺给)编译器,obj就是MountainBike
   假如obj实际不是MountainBike,则会抛出异常。
   可以用 obj instanceof MountainBike来测试,如果是,则造型。
   如以下可以防止造型异常:
   if (obj instanceof MountainBike) {
    MountainBike myBike = (MountainBike)obj;
   }
 */
MountainBike myBike = (MountainBike)obj;

多重继承的状态、实现、和类型(Multiple Inheritance of State, Implementation, and Type)

类和接口之间一个显著的差异是类有字段而接口没有。此外你可以对类进行实例化,但是接口不能。

对象将它的状态(state)保存在字段(fields)内。不能进行多继承的原因之一是因为多继承状态( multiple inheritance of state),即从多个类中继承字段的能力。例如假设你能定义一个继承自多个类的新类。当你实例化该类时,该对象将会继承所有父类的多有字段。假如不同父类的方法或者构造函数实例化同一个字段,那哪个方法先执行?因为接口不包含字段,所以你可以不用考虑多重继承状态所带来的问题。

多种继承的实现是从多个类中继承方法的能力。当名称冲突或者歧义时多重继承会产生问题。当编程语言的编译器允许多重继承,遇到父类拥有同名方法时,它们有时候不能决定是哪个成员被访问并调用。此外,程序员可能不知不觉中就在父类引入了名称冲突的方法。默认方法引入了多重继承实现的一种形式。一个类可以继承多个接口,这些接口可能拥有同名的默认方法。编译器提供一些规则来判定到底是哪个接口的默认方法被调用。

Java编程语言支持多重继承类型,即实现一个或多个接口的能力。对象拥有多种类型:类本身的类型,所实现接口的类型。意味着假如一个变量被声明为接口类型,那么该变量可以引用任意实例化的对象。

public class A implements B,C,D{
}

B a = new A();//成立
B b = new A();//成立

至于多继承的实现,即类可以继承多个包含默认方法、静态方法的接口。在这种情况下,编译器或者用户必须指定到底调用的是哪一个方法。

//A 会产生编译错误
public class A implements B, C {
    public static void main(String[] args) {
        new A().methodName();
    }
}


interface B {
    default void methodName() {
        System.out.println("别装逼");
    }
}

interface C {
    default void methodName() {
        System.out.println("别装逼");
    }
}

上述例子会报错误,信息为A inherits unrelated defaults for methodName() from type B and C

重写、隐藏方法(Overriding and Hiding Methods)

实例方法(Instance Methods)

子类拥有父类一样的实例方法(方法签名一样,返回值一样),则称该方法重写(Override)了父类的方法。

重写方法解决了当子类和父类行为相似(方法实现近似相同,但却有差异)时,可以在必要的时候修正子类的行为。重写方法与被重写方法拥有相同的方法名称、参数的个数、参数的类型以及返回类型。此外重写方法返回类型可以是被重写方法的子类型,该子类型被称为协变返回类型(covariant return type)。

public class Main extends Person {
    @Override
    public Main getALock() { //Main是Object的子类型,所以不会报错
        return new Main();
    }
}
class Person {
    public Object getALock(){
        return new Object();
    }
}

当重写一个方法时,在方法的上面加上注解@Override,告诉编译器你要重写父类的某个方法,编译器会侦测父类中有无你要重写的方法,假如没有,则报错。

静态方法(Static Methods)

假如子类定义了和父类具有方法签名一样的静态类,则子类的该静态方法隐藏了父类中的该静态方法。

隐藏静态方法和重写实例方法的隐喻:

  • 对于重写,调用那个实例方法,取决于调用方实际是哪个类的实例。
  • 对于隐藏静态方法,哪个静态方法被调用取决于调用方是子类还是父类。

例子如下:

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 output from this program is as follows:

The static method in Animal //调用方是Animal
The instance method in Cat //实际是Cat实例
接口方法(Interfaces Methods)

继承自包含默认方法和抽象方法的接口和继承包含实例方法是一样的。当父类(普通类、接口)拥有签名一样的默认方法时,Java编译器遵循了继承的规则,以解决命名冲突,规则如下:

  1. 实例方法优先于默认方法。
public class Horse {
    public String identifyMyself() {
        return "I am a horse.";
    }
}
public interface Flyer {
    default public String identifyMyself() {
        return "I am able to fly.";
    }
}
public interface Mythical {
    default public String identifyMyself() {
        return "I am a mythical creature.";
    }
}
//如果去掉extends Horse,则会编译错误
public class Pegasus extends Horse implements Flyer, Mythical {
    public static void main(String... args) {
        Pegasus myApp = new Pegasus();
        System.out.println(myApp.identifyMyself());
    }
}

output
I am a horse
  1. 被重写的方法将被忽略。当超类型共享同一个祖先时会发生。
public interface Animal {
    default public String identifyMyself() {
        return "I am an animal.";
    }
}
public interface EggLayer extends Animal {//重写了Animal的默认方法
    default public String identifyMyself() {
        return "I am able to lay eggs.";
    }
}
public interface FireBreather extends Animal { }
public class Dragon implements EggLayer, FireBreather {
    public static void main (String... args) {
        Dragon myApp = new Dragon();
        System.out.println(myApp.identifyMyself());
    }
}
output
 I am able to lay eggs

假如两个或者多个独立的类、接口定义的默认方法冲突,或者默认方法与抽象方法冲突(同一类、接口内),则会产生编译错误。此时,你必须明确地重写超类型的方法。

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);//注意super关键字
        OperateCar.super.startEngine(key);//注意super关键字
    }
}

super关键字可以将命名冲突的默认方法给区分开,否则将产生编译错误。

public interface Mammal {
    String identifyMyself();
}
public class Horse {
    public String identifyMyself() {
        return "I am a horse.";
    }
}
public class Mustang extends Horse implements Mammal {
    public static void main(String... args) {
        Mustang myApp = new Mustang();
        System.out.println(myApp.identifyMyself());
    }
}

output
I am a horse

从类中继承而来的实例方法可以重写接口内的抽象类,如上代码所示。

接口内的静态类不会被继承。

修饰词(Modifiers)

子类重写的方法的访问权限修饰词必须和父类被重写的一样,或者更宽松。如假如父类被重写方法是protected,则子类只能为protected或者public,不能为private.

静态方法不能被重写成实例方法,反之亦然。

总结

下面的表格总结了当你在子类中定义一个与父类拥有一样的方法签名时会发生什么。

Superclass Instance MethodSuperclass Static Method
Subclass Instance MethodOverrides
Subclass Static MethodGenerates a compile-time error

你可以重载(overload)父类的方法,它们拥有别与父类的参数列表,所以是新的方法。

多态(Polymorphism)

字典定义中,多态是指生物学原理上有机体或者物种拥有不同的形态或阶段。该原理也可以被面向对象编程语言所应用,如Java语言。子类可以定义它们自己的特殊行为,并共享父类的一些行为,状态。

例如Bicycle有以下行为:

public void printDescription(){
    System.out.println("\nBike is " + "in gear " + this.gear
        + " with a cadence of " + this.cadence +
        " and travelling at a speed of " + this.speed + ". ");
}

创建两个子类:

public class MountainBike extends Bicycle {
    private String suspension;

    public MountainBike(
               int startCadence,
               int startSpeed,
               int startGear,
               String suspensionType){
        super(startCadence,
              startSpeed,
              startGear);
        this.setSuspension(suspensionType);
    }

    public String getSuspension(){
      return this.suspension;
    }

    public void setSuspension(String suspensionType) {
        this.suspension = suspensionType;
    }

    public void printDescription() {
        super.printDescription();
        System.out.println("The " + "MountainBike has a" +
            getSuspension() + " suspension.");
    }
} 

public class RoadBike extends Bicycle{
    // In millimeters (mm)
    private int tireWidth;

    public RoadBike(int startCadence,
                    int startSpeed,
                    int startGear,
                    int newTireWidth){
        super(startCadence,
              startSpeed,
              startGear);
        this.setTireWidth(newTireWidth);
    }

    public int getTireWidth(){
      return this.tireWidth;
    }

    public void setTireWidth(int newTireWidth){
        this.tireWidth = newTireWidth;
    }

    public void printDescription(){
        super.printDescription();
        System.out.println("The RoadBike" + " has " + getTireWidth() +
            " MM tires.");
    }
}

//测试类
public class TestBikes {
  public static void main(String[] args){
    Bicycle bike01, bike02, bike03;

    bike01 = new Bicycle(20, 10, 1);
    bike02 = new MountainBike(20, 10, 5, "Dual");
    bike03 = new RoadBike(40, 20, 8, 23);

    bike01.printDescription();
    bike02.printDescription();
    bike03.printDescription();
  }
}
output
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10. 

Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10. 
The MountainBike has a Dual suspension.

Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20. 
The RoadBike has 23 MM tires.

运行时,JVM会为每个变量(bike01,bike02,bike03)调用对象适当的方法。调用哪个方法并不取决于变量的类型(例子中都是Bicycle)。这种行为被称为虚方法调用( virtual method invocation ),该例子演示了Java语言中一个重要的特性——多态。

隐藏字段(hiding Fields)

当子类拥有和父类一样的字段名称时,子类的字段会隐藏父类的字段(即使类型不同)。在子类内,不能用简单名称访问父类的字段,字段的访问必须加上super关键字。通常情况下,不鼓励隐藏父类的字段,因为这使代码难以阅读。

使用关键字super

访问父类成员(Accessing Superclass Members)

你可以使用关键字super来访问父类的成员,如:

public class Superclass {

    public void printMethod() {
        System.out.println("Printed in Superclass.");
    }
}
public class Subclass extends Superclass {

    // overrides printMethod in Superclass
    public void printMethod() {
        super.printMethod();//注意super
        System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.printMethod();    
    }
}

output
Printed in Superclass.
Printed in Subclass
子类构造器
public MountainBike(int startHeight, 
                    int startCadence,
                    int startSpeed,
                    int startGear) {
    super(startCadence, startSpeed, startGear);
    seatHeight = startHeight;
}   

调用父类的构造器形式为super()或者super(参数列表)。前者是调用父类无参构造函数,后者是有参构造函数。

必须先调用父类的构造函数。

假如一个构造函数不明确地调用父类的构造函数,则java编译器会自动调用父类的无参构造函数,如果父类没有无参构造函数,则会产生编译错误。

无论明确或者不明确地调用父类构造函数,构造器链( constructor chaining)会一直执行,直到Object的构造函数被调用位置。

作为超类的Object

java.lang包中的Object类,位于类层次结构的最顶层。所有的类或直接或间接的是Object类的后裔。每一个你使用的或编写的类都从Object那里继承了实例方法。你可以不使用这些方法,但是使用了,你也许会重写这些方法:

  • protected Object clone() throws CloneNotSupportedException。创建并返回该对象的一个拷贝。
  • public boolean equals(Object obj)。表明其它对象与该对象是否相同(equals)
  • protected void finalize() throws Throwable。当对象没有其他变量引用了,垃圾收集器会调用它。
  • public final Class getClass()。返回对象运行时的类。
  • public int hashCode()。返回对象的hash code值。
  • public String toString()。返回对象的String表现。

下面的方法多用于不同线程之间的同步活动中:

  • public final void notify()
  • public final void notifyAll()
  • public final void wait()
  • public final void wait(long timeout)
  • public final void wait(long timeout, int nanos)

编写final类或者方法

方法声明为final,说明该方法不能被重写。使用场景,当一个构造函数内调用一个方法,那么该方法通常被声明为final,如果是非final,则子类可能会重写它,导致不良后果。

类被声明为final,则该类不能有子类。

抽象方法和抽象类

用关键字abstract声明的类称为抽象类——它可以不包含抽象方法。抽象类不能被实例化,但是可以被继承

没有实现(没有花括号)的方法声明称为抽象类,如:

public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
   abstract void draw();
}

假如一个抽象类被一个类所继承,那么该类必须提供抽象类内所有抽象方法的实现,除非子类也是抽象。

如果接口内的方法既不是默认方法,又不是静态方法,那么该方法是抽象方法。你可以用abstract修饰它,但是没必要(冗余).

抽象类与即接口的对比

抽象类类似于接口。共同点列举如下;

  1. 都不能实例化。
  2. 包含的方法都可以为抽象的或有实现的方法(具体方法)。

不同点:

  1. 抽象类内,你可以定义非静态非final的字段。
  2. 抽象类内,你可以定义public,protected,private具体的方法。
  3. 对抽象类而言,你只能继承一个抽象类。
  4. 接口内,所有的字段自动成为public,static,final的
  5. 接口内,定义的方法自动为public。
  6. 对接口而言,你可以继承多个接口。

使用抽象类的场景:

  • 你想在相关的类之间共享代码。(继承,可以共享,重用)
  • 你期望抽象类的子类拥有共同的方法或字段,或者需要public意外的访问权限修饰符。
  • 你期望声明非静态,非final的字段。这样就可以访问这些字段,并修改它们。

使用接口的场景:

  • 你期望不相关的类实现你的接口。如接口Comparable或者Cloneable.
  • 你想规范特殊数据类型的行为,但不关心具体的实现。
  • 你想充分利用多继承的特性。

例如AbstractMap拥有get, put, isEmpty, containsKey, and containsValue方法,被它的子类HashMap, TreeMapConcurrentHashMap所共享。

HashMap继承了多个接口,如Serializable, Cloneable, and Map<K, V>.

抽象类举例

这里写图片描述

如一个画图应用程序,可以画矩形,直线,贝塞尔曲线,原型。这些对象享有共同的状态是位置、线条颜色等。

abstract class GraphicObject {
    int x, y;
    ...
    void moveTo(int newX, int newY) {
        ...
    }
    abstract void draw();
    abstract void resize();
}

class Circle extends GraphicObject {
    void draw() {
        ...
    }
    void resize() {
        ...
    }
}
class Rectangle extends GraphicObject {
    void draw() {
        ...
    }
    void resize() {
        ...
    }
}
类成员

抽象类也可以声明静态方法或者静态字段。访问他们可以用类引用,如 AbstractClass.staticMethod()

继承的总结
  1. 除了Object对象外,所有类都有一个直接的父类。无论直接或者间接,类从父类内继承字段和方法。子类可以重写父类方法,或者隐藏父类字段。
  2. Object类是类层次结构中的顶端,所有的类都继承它,它是所有类的根祖先。它拥有toString()、equals()、clone()、和getClass()等方法。
  3. 你可以用final来声明类或者方法来防止被继承或重写。
  4. 抽象类可以被继承,但不能实例化。抽象类可以包含抽象方法。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值