JavaSE基础知识(十九)--Java接口之抽象类和抽象方法

Java SE 是什么,包括哪些内容(十九)?

本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
先回顾在前面的博文中,曾经提到过的一个父类"乐器(Instrument)",它的的代码如下:

// 乐器类Instrument 
   //枚举类Note代表乐符
   public enum Note{
      //代表枚举值(乐符),也就是枚举类Note仅有以下三个实例对象。
      MIDDLE_C,C_SHARP,B_FLAT;
      //前面有关枚举类的博文提到过,枚举类的值就是这个类的对象实例
   }
   //类Instrument 
   class Instrument{
      //方法play(Note n),带一个Note类型的形式参数
      public void play(Note n){
         //打印字符串"Instrument.play()"
         System.out.println("Instrument.play()");
      }
   }
   ---------------------------------------------------------------
   //重点在这里,你需要注意类Intrument是被如何使用的
   //创建一个类继承类Instrument
   //类Wind继承类Instrument
   public Wind extends Instrument{
      //方法play(Note n),带一个Note类型的形式参数
      public void play(Note n){
         //打印字符串"Wind.play()"
         System.out.println("Wind.play()");
      }
   }
   ----------------------------------------------------------------
   //测试类Music
   public class Music{
      //方法tune(Instrument i),带一个Instrument类型的形式参数i
      public static void tune(Instrument i){
         //调用方法play(Note n),传入了一个实际参数Note.MIDDLE_C
         i.play(Note.MIDDLE_C);
      }
      //程序执行入口main方法
      public static void main(String[] args){
         //创建子类Wind的对象,并赋值给引用w
         Wind w = new Wind();
         //调用方法tune(Instrument i),这个时候在这里将
         //Wind类型的对象实例向上转型成了Instrument类型。
         tune(w);
      }
   }
   //你需要特别注意的是,这个时候Instrument是当成一个类来使用的
   //它的关键字是class。

从实际编程的角度来说,父类Instrument存在的意义是为它的所有子类创建一个通用的接口(也就说是,Instrument代表了一种类型,只要是属于它这种类型的,接口都应该跟它一样或者在它的基础上扩展),它里面的方法的主要作用并不完全是为了实现功能,更多的是作为子类的标准而存在(也就是说,它的每一个子类都必须包含它所有的方法),所以一般不建议调用它的方法。
就好比设计模型/图纸和实物的关系,设计模型/图纸决定了你造出来的实物长什么样子,比如汽车的设计模型/图纸,肯定有车身,轮胎,方向盘,后备箱,车灯,前后挡风玻璃等等(这些对应了代码中的接口),但是你不会去开模型/图纸(就是调用模型/图纸的方法),你开的是在模型/图纸的通用"接口"下造出来的实物汽车(就是调用实物的方法),实物汽车的这些"接口"就有了具体的实现,比如轮胎用的是米其林的,发动机用的是涡轮增压的(自然吸气的也不错),玻璃用的是防弹的等等。再举一个例子,比如房子,实物的房子都是根据设计模型/图纸建造出来的,实际建造的房屋肯定会有电梯,但是你不可能乘坐模型/图纸上的电梯,你乘坐的肯定是建造出来的事物房子的电梯,父类就对应设计模型/图纸,而实际建造的房屋对应着子类…很多类似的例子,你还可以自己去想想。
那么,第一个问题来了,在代码为什么要这么做呢,为什么需要有一个专门的父类来规定子类通用的接口呢?实际上,这个问题很容易回答,就好比,为什么在建造汽车,房屋之前,你还需要设计,画在纸上呢?其实,它们是一个道理!
在有关Instrument以及它子类的那些代码示例中,建立这个通用接口(类Instrument)的唯一理由是,不同的子类可以用不同的方式表示此接口(不同的汽车品牌可以用不同的材料去实现设计图纸/模型,不同的建筑商可以用不同的方式去实现模型/图纸的设计),通用的接口建立起一种基本形式,以此表示所有子类的共同部分。
那么,第二个问题来了,如何将这个父类显著标识为特殊化呢?
前面已经说清楚了,这个父类仅仅是作为一个通用接口而存在,它的方法没有实际的使用意义(你也可以直接说,它的方法不能被调用),所以需要一种特殊的方法来标识它,因为如果你不用一种特殊的方法标识它,它还是可以用来创建对象,它的方法还是可以被调用的。
这里必须要做出提醒的是:你看到这里的时候千万不要和接口混淆了,现在说这些的前提都建立在类关键字还是"class"的基础上,还没有换成接口的"interface"。
Java的发明者用了一个关键字就解决了:“abstract”,还必须提醒的是,不但要给类加上abstract,那些不能调用的方法,也必须加上abstract
如果不使用特殊的标识,想阻止创建一个类的对象实例,就只能让它的所有方法都运行不了,但是这种办法不是很好,因为这会将错误信息延迟到运行期(因为是让方法运行不了),所以,最好是在编译器捕获这些问题,那么通过关键字abstract就能将这些信息在编译期被发现和识别。
代码示例:

// 给普通类加上关键字abstract
   //类加上关键字abstract,就是抽象类了
   //抽象类Instrument
   abstract class Instrument{
      //方法加上关键字abstract,就是抽象方法了
      //抽象方法play(Note n),带一个Note类型的形式参数
      public abstract void play(Note n);
   }

abstract修饰的方法不能有方法体:
JavaSE基础知识(十九)--Java接口之抽象类和抽象方法_第1张图片
abstract修饰的类不能创建对象,abstract修饰的方法不能被调用(方法体都没有,所以调用不了):
JavaSE基础知识(十九)--Java接口之抽象类和抽象方法_第2张图片
在这里,可以引出两个新的概念:
abstract修饰的类叫做抽象类,abstract修饰的方法叫做抽象方法
如果我们只有一个像Instrument这样的抽象类,那么创建该类的对象几乎没有任何意义(创建对象就是为了给对象发送消息,也就是调用它的方法,但是抽象类中的抽象方法不能调用)。我们创建抽象类是希望通过这个通用接口操纵一系列类。因此,从某种程度上来说,Instrument只是表示了一个接口(对应设计的模型/图纸),没有具体的内容。
为了显著标识方法不可调用,Java提供了一种叫抽象方法的机制,这种方法是不完整的,仅有声明而没有方法体。
包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的(否则编译器就会报错)—这句话我在前面的博文中也纠正过,应该是先有抽象类才能在类中去定义抽象方法,但是由于这句话出自Java编程思想一书,暂且这么记忆也没什么问题。
抽象类实际上是不完整的,当我们试图产生该类的对象时,编译器会怎么处理?由于为抽象类创建对象是不安全的,所以编译器会提示报错。这样编译器就确保了抽象类的纯粹性,从而不必担心误用它。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为父类中的所有抽象方法提供方法体实现。如果不这样做(实际上,你可以只给部分方法提供方法体实现),那么这个子类也是抽象类,且编译器将会强制我们继续使用abstract关键字来限定这个类。
某种情况下,我们也可能会创建一个没有任何抽象方法的类。如果有一个类,让它包含任何的abstract方法都没有意义,但是我们也想要阻止产生这个类的对象实例,那么你可以将这个类声明为抽象类。
下面的示例将之前博文中的类Instrument转化成了抽象类(既然使某个类成为抽象类并不需要所有的方法都是抽象的,所以仅需将某些方法声明为抽象的即可):
如图:
JavaSE基础知识(十九)--Java接口之抽象类和抽象方法_第3张图片
根据上面的继承层次结构图来实现代码。
代码示例:

// 将类Instrument转化成抽象类
   //抽象类Instrument
   abstract class Instrument{
      //抽象类的类变量与普通类完全一样,没有任何区别
      //可以根据实际需要设定。
      private int i;
      //抽象方法play(Note n),带一个Note类型的形式参数,没有方法体
      //待继承的子类去实现
      public abstract void play(Note n);
      //方法what(),有具体的实现
      public String what(){
         //返回字符串"Instrument"
         return "Instrument";
      }
      //抽象方法adjust(),没有方法体
      //待继承的子类去实现
      public abstract void adjust();
   }
   //类Wind继承抽象类Instrument
   class Wind extends Instrument{
      //实现了抽象方法play(Note n)
      //继承抽象类的子类必须实现抽象类的所有抽象方法
      public abstract void play(Note n){
         //打印字符串"Wind.play()" + n
         System.out.println("Wind.play()" + n);
      }
      //方法what(),重写父类中的实现
      public String what(){
         //返回字符串"Wind"
         return "Wind";
      }
      //实现了抽象方法adjust()
      //继承抽象类的子类必须实现抽象类的所有抽象方法
      public abstract void adjust(){
         //没有任何实现,是一个空方法
         //没有实际的使用意义
      }
   }
   //类Percussion继承抽象类Instrument
   class Percussion extends Instrument{
      //实现了抽象方法play(Note n)
      //继承抽象类的子类必须实现抽象类的所有抽象方法
      public abstract void play(Note n){
         //打印字符串"Percussion.play()" + n
         System.out.println("Percussion.play()" + n);
      }
      //方法what(),重写父类中的实现
      public String what(){
         //返回字符串"Percussion"
         return "Percussion";
      }
      //实现了抽象方法adjust()
      //继承抽象类的子类必须实现抽象类的所有抽象方法
      public abstract void adjust(){
         //没有任何实现,是一个空方法
         //没有实际的使用意义
      }
   }
   //类Stringed继承抽象类Instrument
   class Stringed extends Instrument{
      //实现了抽象方法play(Note n)
      //继承抽象类的子类必须实现抽象类的所有抽象方法
      public abstract void play(Note n){
         //打印字符串"Stringed.play()" + n
         System.out.println("Stringed.play()" + n);
      }
      //方法what(),重写父类中的实现
      public String what(){
         //返回字符串"Stringed"
         return "Stringed";
      }
      //实现了抽象方法adjust()
      //继承抽象类的子类必须实现抽象类的所有抽象方法
      public abstract void adjust(){
         //没有任何实现,是一个空方法
         //没有实际的使用意义
      }
   }
   class Woodwind extends Wind{
      //重新实现了抽象类Instrument的子类Wind的方法
      //play(Note n)
      public abstract void play(Note n){
         //打印字符串"Woodwind.play()" + n
         System.out.println("Woodwind.play()" + n);
      }
      //方法what(),重写父类Wind中的实现
      public String what(){
         //返回字符串"Woodwind"
         return "Woodwind";
      }
   }
   //类Brass继承了抽象类Instrument的子类Wind
   class Brass extends Wind{
      //重新实现了抽象类Instrument的子类Wind的方法
      //play(Note n)
      public abstract void play(Note n){
         //打印字符串"Brass.play()" + n
         System.out.println("Brass.play()" + n);
      }
      //方法adjust(),重写父类Wind中的实现
      public void adjust(){
         //打印字符串"Brass.adjust()"
         System.out.println("Brass.adjust()");
      }
   }
   //类Woodwind继承了抽象类Instrument的子类Wind
   class Woodwind extends Wind{
      //重新实现了抽象类Instrument的子类Wind的方法
      //play(Note n)
      public abstract void play(Note n){
         //打印字符串"Woodwind.play()" + n
         System.out.println("Woodwind.play()" + n);
      }
      //方法adjust(),重写父类Wind中的实现
      public void adjust(){
         //打印字符串"Woodwind.adjust()"
         System.out.println("Woodwind.adjust()");
      }
   }
   //测试类Music4
   public class Music4{
      //方法tune(Instrument i),带一个Instrument类型的形式参数
      //这个方法忽略了传入的实际参数的具体类型,只要是抽象Instrument
      //的子类都可以,所以如果需要增加新的类型,只要继承了抽象Instrument
      //就能起作用
      //方法play(Note n)默认开启了后期绑定(多态)
      //会表现出传入的实际参数类型对象的对应行为。
      static void tune(Instrument i){
         //调用方法play(Note n)
         i.play(Note.MIDDLE_C);
      }
      //方法tuneAll(Instrument[] e),带一个Instrument数组类型的形式参数e
      static void tuneAll(Instrument[] e){
         //循环遍历数组中的每一个对象
         for(Instrument i : e){
            //将每个对象作为实际参数传入方法tune(Instrument i)
            //中,会表现出不同的,与实际对象类型对应的行为。
            tune(i);
         }
      }
      //程序执行入口main方法
      public static void main(String[] args){
         //创建Instrument数组类型的对象,引用为orchestra
         Instrument[] orchestra = {
            //自动向上转型为Instrument类型
            new Wind(),
            //自动向上转型为Instrument类型
            new Percussion(),
            //自动向上转型为Instrument类型
            new Stringed(),
            //自动向上转型为Instrument类型
            new Brass(),
            //自动向上转型为Instrument类型
            new Woodwind()
         };
         //由于Java中方法默认为动态绑定,将会根据实际的对象类型
         //表现出具体的行为(多态)。
         tuneAll(orchestra);
      }
   }

我们从代码示例中可以看出,除了父类Instrument,实际上没有多大的改变。
在实际的编程过程中,创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样来使用它们。
抽象类还是很重要的重构工具(在实际工作中经常有需要将老代码进行重构的需求)。因为它使得我们可以很容易地将公共方法沿着继承层次结构向上移动(实际上就是向上转型而产生的多态,比如上面代码示例中,各种子类放入了类Insreument类型的数组中就自动发生了向上转型,而公共方法就是play())。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正。

你可能感兴趣的:(Java,SE)