软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> 编程语言 -> Java编程手册—OOP中的继承和多态 -> 正文阅读
编程语言 最新文章
Java面试题(1)
ReactiveX序列——RxSwift
C++STL之ACM相关知识大全
c++中vector向量几种情况的总结(向量指针,
SSH框架整合demo
JAX
UVA
curl备忘(1)
C#机房重构——万事开头难(二)
OJ刷题

[编程语言]Java编程手册—OOP中的继承和多态

  2016-04-03 20:45:00

1. 组合
如果我们想要重用一个已知的类,有两种方法可以使用:组合和继承。组合就是我们定义一个新类,这个类包含了我们希望重用的那个类,继承就是使用我们希望重用的那个类来派生一个新类。
下面举个使用组合来重用一个类的例子,假设我们有一个已经存在的类叫做Point,定义如下类图所示

Point.java


// The Point class definition
public class Point {
   // Private member variables
   private int x, y;   // (x, y) co-ordinates
 
   // Constructors
   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
   public Point() {    // default (no-arg) constructor
      x = 0;
      y = 0;
   }
 
   // Public getter and setter for private variables
   public int getX() { 
      return x; 
   }
   public void setX(int x) { 
      this.x = x; 
   }
   public int getY() { 
      return y; 
   }
   public void setY(int y) { 
      this.y = y; 
   }
 
   // toString() to describe itself
   public String toString() { 
      return "(" + x + "," + y + ")"; 
   }
}

假设我们需要一个叫做Line的新类,这样我们就可以使用组合来重用Point类,可以说一条线是由两个点组成或者一条线有两个点,组合是一种"has-a"的关系。


Line.java


// The Line class definition
public class Line {
   // Private member variables
   Point begin, end;   // Declare begin and end as instances of Point
 
   // Constructors
   public Line(int x1, int y1, int x2, int y2) {
      begin = new Point(x1, y1);  // Construct Point instances
      end   = new Point(x2, y2);
   }
   public Line(Point begin, Point end) {
      this.begin = begin;  // Caller constructed Point instances
      this.end   = end;
   }
 
   // Public getter and setter for private variables
   public Point getBegin() {
      return begin;
   }
   public Point getEnd() {
      return end;
   }
   public void setBegin(Point begin) {
      this.begin = begin;
   }
   public void setEnd(Point end) {
      this.end = end;
   }
 
   public int getBeginX() {
      return begin.getX();
   }
   public void setBeginX(int x) {
      begin.setX(x);
   }
   public int getBeginY() {
      return begin.getY();
   }
   public void setBeginY(int y) {
      begin.setY(y);
   }
   public void setBeginXY(int x, int y) {
      begin.setX(x);
      begin.setY(y);
   }
   public int getEndX() {
      return end.getX();
   }
   public void setEndX(int x) {
      end.setX(x);
   }
   public int getEndY() {
      return end.getY();
   }
   public void setEndY(int y) {
      end.setY(y);
   }
   public void setEndXY(int x, int y) {
      end.setX(x);
      end.setY(y);
   }
 
   public String toString() {
      return "Line from " + begin + " to " + end;
   }
 
   public double getLength() {
      int xDiff = begin.getX() - end.getX();
      int yDiff = begin.getY() - end.getY();
      return Math.sqrt(xDiff*xDiff + yDiff*yDiff);
   }
}

TestLine.java
// A test driver program for the Line class
public class TestLine {
   public static void main(String[] args) {
      Line l1 = new Line(0, 3, 4, 0);
      System.out.println(l1);   // toString()
      System.out.println(l1.getLength());
      l1.setBeginXY(1, 2);
      l1.setEndXY(3, 4);
      System.out.println(l1);
 
      Point p1 = new Point(3, 0);
      Point p2 = new Point(0, 4);
      Line l2 = new Line(p1, p2);
      System.out.println(l2);
      System.out.println(l2.getLength());
      l2.setBegin(new Point(5, 6));
      l2.setEnd(new Point(7, 8));
      System.out.println(l2);
   }
}

2. 继承
在面向对象编程里面,为了避免重复并且减少冗余,我们经常使用一种层级关系来组织类。低层级的的类继承高层级的类的所有属性和方法,在低层级的类称为子类(或者派生类,孩子,扩展类),高层级的类称为超类(或者基类,父类),一般是提取所有公共的变量和方法到超类中,剩下特殊的变量和方法放在子类中,这样冗余就可以很大限度的减少并且避免公共的变量和方法在子类中重复定义。



子类会继承超类中所有的变量和方法,包括它的直接父类和所有祖先。需要注意的是,子类并不是父类的子集,相反,子类是父类的扩展集,因为子类继承父类所有的变量和方法并且它提供了额外的变量和方法来扩展父类。

在Java中,我们使用关键字"extends"去定义子类。

class Goalkeeper extends SoccerPlayer {......}
class MyApplet extends java.applet.Applet {.....}
class Cylinder extends Circle {......}


2.1 继承的例子
在这个例子中,我们从父类Circle派生一个子类叫做Cylinder,可以看到,我们重用了Circle类,重用性是OOP中一个非常重要的属性,Cylinder继承了Circle类的所有属性(radius 和 color)和方法(getRadius(), getArea()以及其他),另外,它自己定义了一个属性height,两个公有方法getHeight() 和 getVolume()以及它自己的构造器。



Circle.java


// Define the Circle class
public class Circle {    // Save as "Circle.java"
   // Private variables
   private double radius;
   private String color;
   
   // Constructors (overloaded)
   public Circle() {                   // 1st Constructor
      radius = 1.0;
      color = "red";
   }
   public Circle(double r) {           // 2nd Constructor
      radius = r;
      color = "red";
   }
   public Circle(double r, String c) { // 3rd Constructor
      radius = r;
      color = c;
   }
   
   // Public methods
   public double getRadius() {
      return radius;
   }
   public String getColor() {
      return color;
   }
   public double getArea() {
      return radius*radius*Math.PI;
   }
}
Cylinder.java

// Define Cylinder class, which is a subclass of Circle
public class Cylinder extends Circle {
   private double height;   // Private member variable
   
   public Cylinder() {      // constructor 1
      super();              // invoke superclass' constructor Circle()
      height = 1.0;
   }
   public Cylinder(double radius, double height) {  // Constructor 2
      super(radius);        // invoke superclass' constructor Circle(radius)
      this.height = height;
   }
   
   public double getHeight() {
      return height;
   }
   public void setHeight(double height) {
      this.height = height;
   }
   public double getVolume() {
      return getArea()*height;   // Use Circle's getArea()
   }
}
TestCylinder.java
// A test driver program for Cylinder class
public class TestCylinder {
   public static void main(String[] args) {
      Cylinder cy1 = new Cylinder();         // Use constructor 1
      System.out.println("Radius is " + cy1.getRadius()
         + " Height is " + cy1.getHeight()
         + " Color is " + cy1.getColor()
         + " Base area is " + cy1.getArea()
         + " Volume is " + cy1.getVolume());
   
      Cylinder cy2 = new Cylinder(5.0, 2.0); // Use constructor 2
      System.out.println("Radius is " + cy2.getRadius()
         + " Height is " + cy2.getHeight()
         + " Color is " + cy2.getColor()
         + " Base area is " + cy2.getArea()
         + " Volume is " + cy2.getVolume());
   }
}
输出结果如下:

Radius is 1.0 Height is 1.0 Color is red Base area is 3.141592653589793 Volume is 3.141592653589793
Radius is 5.0 Height is 2.0 Color is red Base area is 78.53981633974483 Volume is 157.07963267948966

2.2 方法重写与变量隐藏

子类继承了父类的所有成员变量和方法,这样在子类中就可以使用继承下来的成员变量和方法,当然也可以通过重写继承的方法来提供自己的实现版本,如果希望隐藏继承下来的变量,可以在子类中定义一个与父类相同名称的变量。
例如,Cylinder类继承的方法getArea()计算的是
圆柱的底面积,如果我们希望使用这个方法计算圆柱的表面积,这样我们就可以在子类Cylinder中重写这个方法。


public class Cylinder extends Circle {
   ......
   // override the getArea() method inherited from superclass Circle
   @Override
   public double getArea() {
      return 2*Math.PI*getRadius()*height + 2*super.getArea();
   }
   // need to change the getVolume() as well
   public double getVolume() {
      return super.getArea()*height;   // use superclass' getArea()
   }
   // override the inherited toString()
   @Override
   public String toString() {
      return "Cylinder: radius = " + getRadius() + " height = " + height;   
   }
}
这样,如果getArea()被Circle对象调用,它计算的就是圆的面积,如果getArea()被对象调用,它计算的就是重写实现的圆柱的表面积。需要注意的是,Circle里面的方法getRadius()必须是公有的,这样子类Cylinder才能使用到这个方法,如果这个方法是私有的,那么这个类是不允许其他类使用的,包括它的子类。

如果你在Cylinder中重写了getArea(),这样getVolume() (=getArea()*height)就会有问题,因为我们这里调用的getArea()就是我们重写的这个方法,显然跟我们想要不同,因为我们希望getArea()得到的是底面积,所以这里需要使用super.getArea()去显式的调用父类的getArea()方法。需要注意的是,super.getArea()是来自子类的定义,它并没有重新创建一个实例,当我们重新这个方法之后,这个方法就会被隐藏,父类调用的就是重写的方法,如果我们显式使用c1.super.getArea(),他就会打破隐藏信息和闭包原则。
2.3 @Override注解(JDK 1.5)
@Override注解是在JDK 1.5引入的,它是为了告诉编译器检查是否在父类中存在这个需要被重写的方法,它主要是为了防止我们在重写的时候出现拼写错误,例如,假如你想在子类重新方法toString(),如果你没有使用@Override并且把方法名拼写为TOString(),它就会被当做子类中的一个新方法对待,如果使用了 @Override,这种错误就会避免。
@Override是可选的,但是建议重写的时候加上。另外,需要注意的是,注解并不是一个编程结构,它对程序的输出没有任何影响,他只是被编译器使用,编译完成之后就会被丢弃,并不会用在运行时。
2.4 关键字"super"
关键字super允许在子类定义中访问父类的方法和变量,例如super() 和 super(argumentList)可以访问父类的构造函数,如果子类重写了父类的某个方法,例如上面的getArea(),那么如果希望访问父类的getArea()方法,可以使用super.getArea()来在子类的定义中访问父类的这个方法。同样的,如果子类中定义了跟父类同样的变量,那么在子类定义中,父类的这个变量会被隐藏,可以使用super.variableName来访问这个隐藏的父类变量。

2.5 关于构造器的其他说明
前面说过,子类继承了父类所有的变量和方法,但是子类是无法继承父类的构造器方法,在Java中,每个类都定义了它自己的构造器。
在构造器的方法体中,我们可以使用super(args)来调用它直接父类的构造器,如果super(args)在子类中被使用,它必须是子类构造器中的第一条语句,如果在子类构造器中没有使用super(args),编译器会自动插入一条super()语句去调用直接父类的无参构造器,这也说明了一个事实,父亲必须在孩子之前出生,所以需要在构造子类之前构造父类。
2.6 默认无参构成器

如果一个类中没有定义构造器,java编译器会自动的创建一个无参的构造器,这个构造器里面只有super()调用。
例如:


// If no constructor is defined in a class, compiler inserts this no-arg constructor 
public ClassName () {  
   super();   // call the superclass' no-arg constructor
}

需要注意的是:
  • 如果你定义了一个或者多个有参构造器,那么默认无参构造器是不会自动生成的,也就是如果你定义了其他有参构造器,你就需要显式的定义一个无参的构造器,因为只有在没有定义任何构造器的时候,编译器才会自动创建一个无参构造器。
  • 如果直接父类定义了一些其他有参构造器,但是它没有定义无参构造器,那么如果你在子类中调用super(),编译器就会报错。

2.7 单继承
Java不支持多继承,多继承就是允许一个子类有多个直接父类,它有一个严重的缺陷就是可能多个父类有相同的方法,这样子类对于父类方法的实现就会出现歧义。在Java中,每个子类有且只有一个直接父类,另外,一个父类可能存在多个子类。
2.8 公共根类——java.lang.Object
在Java中,所有的类都是派生自一个公共根类java.lang.Object。Object类定义和实现了其他所有运行在JRE上的类都需要的基本的操作。



3、更多继承的例子
3.1 Point3D



父类 Point.java就是前面定义的

子类 Point3D.java

// Define Point3D, subclass of Point
public class Point3D extends Point {
   // Private member variable
   private int z;
 
   // Constructors
   public Point3D() {  // default no-arg constructor
      super();      // Call superclass' no-arg constructor Point()
      z = 0;
   }
   public Point3D(int x, int y, int z) {
      super(x, y);  // Call superclass' Point(x, y)
      this.z = z;
   }
 
   // Public getter/setter for private variable
   public int getZ() {
      return z;
   }
   public void setZ(int z) {
      this.z = z;
   }
 
   // toString() to describe itself
   @Override
   public String toString() {
      return "(" + super.getX() + "," + super.getY() + "," + z + ")";
   }
}
TestPoint3D.java
// A test driver program for Point3D class
public class TestPoint3D {
   public static void main(String[] args) {
      Point3D p1 = new Point3D(1, 2, 3);
      System.out.println(p1);
      System.out.println(p1.getX());  // Inherited from superclass
      System.out.println(p1.getY());  // Inherited from superclass
      System.out.println(p1.getZ());  // this class
      p1.setX(4);  // Inherited from superclass
      p1.setY(5);  // Inherited from superclass
      p1.setZ(6);  // this class
      System.out.println(p1);
   }
}
3.2 Person和它的子类

父类Person.java

// Define superclass Person
public class Person {
   // Instance variables
   private String name;
   private String address;
   
   // Constructor
   public Person(String name, String address) {
      this.name = name;
      this.address = address;
   }
   
   // Getters
   public String getName() {
      return name;
   }
   public String getAddress() {
      return address;
   }
   
   public String toString() {
      return name + "(" + address + ")";
   }
}

// Define Student class, subclass of Person
public class Student extends Person {
   // Instance variables
   private int numCourses;   // number of courses taken so far, max 30 
   private String[] courses; // course codes
   private int[] grades;     // grade for the corresponding course codes
   private static final int MAX_COURSES = 30; // maximum number of courses
   
   // Constructor
   public Student(String name, String address) {
      super(name, address);
      numCourses = 0;
      courses = new String[MAX_COURSES];
      grades = new int[MAX_COURSES];
   }
   
   @Override
   public String toString() {
      return "Student: " + super.toString();
   }
   
   // Add a course and its grade - No validation in this method 
   public void addCourseGrade(String course, int grade) {
      courses[numCourses] = course;
      grades[numCourses] = grade;
      ++numCourses;
   }
   
   // Print all courses taken and their grade
   public void printGrades() {
      System.out.print(this);
      for (int i = 0; i < numCourses; ++i) {
         System.out.print(" " + courses[i] + ":" + grades[i]);
      }
      System.out.println();
   }
   
   // Compute the average grade
   public double getAverageGrade() {
      int sum = 0;
      for (int i = 0; i < numCourses; i++ ) {
         sum += grades[i];
      }
      return (double)sum/numCourses;
   }
}

// Define class Teacher, subclass of Person
public class Teacher extends Person {
   // Instance variables
   private int numCourses;   // number of courses taught currently
   private String[] courses; // course codes
   private static final int MAX_COURSES = 10; // maximum courses
   
   // Constructor
   public Teacher(String name, String address) {
      super(name, address);
      numCourses = 0;
      courses = new String[MAX_COURSES];
   }
   
   @Override
   public String toString() {
      return "Teacher: " + super.toString();
   }
   
   // Return false if duplicate course to be added
   public boolean addCourse(String course) {
      // Check if the course already in the course list
      for (int i = 0; i < numCourses; i++) {
         if (courses[i].equals(course)) return false;
      }
      courses[numCourses] = course;
      numCourses++;
      return true;
   }
   
   // Return false if the course does not in the course list
   public boolean removeCourse(String course) {
      // Look for the course index
      int courseIndex = numCourses;
      for (int i = 0; i < numCourses; i++) {
         if (courses[i].equals(course)) {
            courseIndex = i;
            break;
         }
      }
      if (courseIndex == numCourses) { // cannot find the course to be removed
         return false;   
      } else {  // remove the course and re-arrange for courses array
         for (int i = courseIndex; i < numCourses-1; i++) {
            courses[i] = courses[i+1];
         }
         numCourses--;
         return true;
      }
   }
}

// A test driver program for Person and its subclasses
public class Test {
   public static void main(String[] args) {
      // Test Student class
      Student s1 = new Student("Tan Ah Teck", "1 Happy Ave");
      s1.addCourseGrade("IM101", 97);
      s1.addCourseGrade("IM102", 68);
      s1.printGrades();
      System.out.println("Average is " + s1.getAverageGrade());
      
      // Test Teacher class
      Teacher t1 = new Teacher("Paul Tan", "8 sunset way");
      System.out.println(t1);
      String[] courses = {"IM101", "IM102", "IM101"};
      for (String course: courses) {
         if (t1.addCourse(course)) {
            System.out.println(course + " added.");
         } else {
            System.out.println(course + " cannot be added.");
         }
      }
      for (String course: courses) {
         if (t1.removeCourse(course)) {
            System.out.println(course + " removed.");
         } else {
            System.out.println(course + " cannot be removed.");
         }
      }
   }
}

输出结果:
Student: Tan Ah Teck(1 Happy Ave) IM101:97 IM102:68
Average is 82.5
Teacher: Paul Tan(8 sunset way)
IM101 added.
IM102 added.
IM101 cannot be added.
IM101 removed.
IM102 removed.
IM101 cannot be removed.

4. 组合与继承
4.1 一条线是由两个点组成与一条线是将一个点扩展为另一个点
回想重用一个存在类的两种方式:组合与继承。我们前面看到可以使用Point类的组合来实现一个Line类,也就是一条线由两个点组成。
一条线也可以通过继承来实现,一条线是将一个点扩展为另一个点。



超类Point.java就是前面定义的那个类

子类LineSub.java

// Define class LineSub, subclass of Point
public class LineSub extends Point {  // Inherited the begin point
   // Private member variables
   Point end;   // Declare end as instance of Point
 
   // Constructors
   public LineSub(int x1, int y1, int x2, int y2) {
      super(x1, y1);
      end   = new Point(x2, y2);   // Construct Point instances
   }
   public LineSub(Point begin, Point end) {
      super(begin.getX(), begin.getY());
      this.end = end;
   }
 
   // Public getter and setter for private variables
   public Point getBegin() {
      return this;   // upcast to Point (polymorphism)
   }
   public Point getEnd() {
      return end;
   }
   public void setBegin(Point begin) {
      super.setX(begin.getX());
      super.setY(begin.getY());
   }
   public void setEnd(Point end) {
      this.end = end;
   }
 
   public int getBeginX() {
      return super.getX();  // inherited, super is optional
   }
   public void setBeginX(int x) {
      super.setX(x);        // inherited, super is optional
   }
   public int getBeginY() {
      return super.getY();
   }
   public void setBeginY(int y) {
      super.setY(y);
   }
   public void setBeginXY(int x, int y) {
      super.setX(x);
      super.setY(y);
   }
   public int getEndX() {
      return end.getX();
   }
   public void setEndX(int x) {
      end.setX(x);
   }
   public int getEndY() {
      return end.getY();
   }
   public void setEndY(int y) {
      end.setY(y);
   }
   public void setEndXY(int x, int y) {
      end.setX(x);
      end.setY(y);
   }
 
   public String toString() {
      return "Line from " + super.toString() + " to " + end;
   }
 
   public double getLength() {
      int xDiff = super.getX() - end.getX();
      int yDiff = super.getY() - end.getY();
      return Math.sqrt(xDiff*xDiff + yDiff*yDiff);
   }
}

TestLineSub.java
public class TestLineSub {
   public static void main(String[] args) {
      LineSub l1 = new LineSub(0, 3, 4, 0);
      System.out.println(l1);   // toString()
      System.out.println(l1.getLength());
      l1.setBeginXY(1, 2);
      l1.setEndXY(3, 4);
      System.out.println(l1);
 
      Point p1 = new Point(3, 0);
      Point p2 = new Point(0, 4);
      LineSub l2 = new LineSub(p1, p2);
      System.out.println(l2);
      System.out.println(l2.getLength());
      l2.setBegin(new Point(5, 6));
      l2.setEnd(new Point(7, 8));
      System.out.println(l2);
   }
}

5. 多态
多态polymorphism的意思就是多种形式,它来自于希腊语poly(意思为多种)和morphos(意思为形式)。例如,在化学中,碳呈现的就是多态,因为它可以以多种形式出现:石墨和钻石,每种形式都有它自己的属性。
5.1 可替换性
一个子类可以处理它父类中的所有的属性和操作,因为子类继承了父类所有的属性和操作,这也就意味着子类对象可以做它的父类对象可以做的任何事情。因此,如果我们希望使用父类对象的属性和方法的时候,我们可以使用使用父类引用来替换子类引用,这就是替换性。
前面我们举个Circle和Cylinder的例子,Cylinder是Circle的子类,其实我们也可以说Cylinder是一个Circle。子类和父类呈现一种"is-a"的关系。

通过使用上面所说的替换性,我们可以创建一个Cylinder,但是为他分别Circle引用,如下:
// Substitute a subclass instance to its superclass reference
Circle c1 = new Cylinder(5.0);
这样你可以通过引用c1调用所有定义在Circle中方法,例如:c1.getRadius()和c1.getColor(),但是你不能通过引用c1调用定义在Cylinder中的方法,例如:c1.getHeight() 和 c1.getVolume(),原因也很明显,c1引用是Circle类型的,它根本不知道Cylinder类中的方法,它只知道自己类中的方法。
c1是一个Circle类型的引用,但是它引用的是一个Cylinder类型的对象,但是引用保持着它内部的一种性。例如子类Cylinder重写了getArea() 和 toString()方法,通过c1.getArea() 或者 c1.toString(),调用的是子类Cylinder中定义的重写版本,而不是Circle中定义的方法,因为c1本质上引用的是Cylinder对象,通过父类调用只是保持了接口的一致性,本质调用的是子类的重写方法。
总结:
子类实例可以分别父类引用
一旦使用父类引用,我们只能调用父类中声明的方法,不能调用子类中声明的方法。
但是,如果子类重写了父类方法,通过父类调用的是子类重写的版本,而不是父类中的那个方法。


5.2 向上转换与向下转换

向上转换一个子类实例为一个父类引用
将一个子类实例替换为一个父类引用叫做向上转换,向上转换总是安全的,因为子类继承了父类的变量和方法,它可以做父类所做的所有事情,编译器可以检查向上转换的有效性,如果不匹配会提示错误。

Circle c1 = new Cylinder();  // Compiler checks to ensure that R-value is a subclass of L-value.
Circle c2 = new String();    // Compilation error: incompatible types

向下转换一个替换引用到它原始的类
也可以将一个替换引用转为为它原始的引用,这个叫做向下转换。
Circle c1 = new Cylinder(5.0);        // upcast is safe
Cylinder aCylinder = (Cylinder) c1;   // downcast needs the casting operator
向下转换需要进行显式的类型转换,并且它不是类型安全的,如果一个替换引用不能向下转换为对应的子类,它就会抛出一个运行时异常ClassCastException。
对于显式的类型转换,编译器是不能检测到它的错误,只能在运行时检测。
Circle c1 = new Circle(5);
Point p1 = new Point();
 
c1 = p1;          // compilation error: incompatible types (Point is not a subclass of Circle)
c1 = (Circle)p1;  // runtime error: java.lang.ClassCastException: Point cannot be casted to Circle

5.3 "instanceof"操作符
Java提供了一个类型检查的二进制操作,叫做instanceof,如果一个对象实例是某个类型,就会返回true。

anObject instanceof aClass
Circle c1 = new Circle();
System.out.println(c1 instanceof Circle);  // true
 
if (c1 instanceof Cirlce) { ...... }
需要注意的是子类实例也属于它父类的实例。
Circle c1 = new Circle(5);
Cylinder cy1 = new Cylinder(5, 2);
System.out.println(c1 instanceof Circle);    // true
System.out.println(c1 instanceof Cylinder);  // false
System.out.println(cy1 instanceof Cylinder); // true
System.out.println(cy1 instanceof Circle);   // true
 
Circle c2 = new Cylinder(5, 2);
System.out.println(c2 instanceof Circle);    // true
System.out.println(c2 instanceof Cylinder);  // true
5.4 多态的总结
  • 一个类的引用既可以引用该类的实例,也可以引用它的子类的实例。
  • 子类实例如果被父类引用所引用,只能调用父类中声明的方法,不能调用子类中声明的方法。
  • 但是,如果子类重写了父类方法,通过父类调用的是子类重写的版本,而不是父类中的那个方法。

5.5 多态的例子

多态在OOP中是非常有用的,它可以区分接口和实现,使得我们可以面向接口编程。

下面来举个例子,假如我们的程序中使用了很多的形状,例如三角形,矩形等等。我们可以定义一个超类叫做Shape,它定义了所有形状的公共接口和操作,例如,如果我们希望所有形状都具有getArea()方法,它返回的是形状的面积,我们就可以在Shape中定义该方法。

Shape.java

// Define superclass Shape
public class Shape {
   // Private member variable
   private String color;
   
   // Constructor
   public Shape (String color) {
      this.color = color;
   }
   
   @Override
   public String toString() {
      return "Shape of color=\"" + color + "\"";
   }
   
   // All shapes must has a method called getArea()
   public double getArea() {
      System.err.println("Shape unknown! Cannot compute area!");
      return 0;   // Need a return to compile the program
   }
}
注意上面对应Shape类中的getArea()存在一个问题,因为在Shape中是不能计算面积的,因为它的形状没有确定所以它里面只能打印一条错误信息,这个问题在后面我们可以使用抽象类来实现,对于不能实现的方法我们可以声明成一个抽象方法,这个后面会说到。
Rectangle.java
// Define Rectangle, subclass of Shape
public class Rectangle extends Shape {
   // Private member variables
   private int length;
   private int width;
   
   // Constructor
   public Rectangle(String color, int length, int width) {
      super(color);
      this.length = length;
      this.width = width;
   }
   
   @Override
   public String toString() {
      return "Rectangle of length=" + length + " and width=" + width + ", subclass of " + super.toString();
   }
   
   @Override
   public double getArea() {
      return length*width;
   }
}
Triangle.java
// Define Triangle, subclass of Shape
public class Triangle extends Shape {
   // Private member variables
   private int base;
   private int height;
   
   // Constructor
   public Triangle(String color, int base, int height) {
      super(color);
      this.base = base;
      this.height = height;
   }
   
   @Override
   public String toString() {
      return "Triangle of base=" + base + " and height=" + height + ", subclass of " + super.toString();
   }
   
   @Override
   public double getArea() {
      return 0.5*base*height;
   }
}
TestShape.java
// A test driver program for Shape and its subclasses
public class TestShape {
   public static void main(String[] args) {
      Shape s1 = new Rectangle("red", 4, 5);
      System.out.println(s1);
      System.out.println("Area is " + s1.getArea());
      
      Shape s2 = new Triangle("blue", 4, 5);
      System.out.println(s2);
      System.out.println("Area is " + s2.getArea());
   }
}
上面代码的完美之处就是所有的引用都是来自超类,我们可以实例化不同的子类使用父类进行引用,相当于父类通过了一个统一的调用接口。
前面也提到,上面的Shape存在一个很明显的缺陷,就是Shape可以被实例化并且调用它的getArea()方法,这个是不合逻辑的。
public class TestShape {
   public static void main(String[] args) {
      // Constructing a Shape instance poses problem!
      Shape s3 = new Shape("green");
      System.out.println(s3);
      System.out.println("Area is " + s3.getArea());
   }
}
Shape类的作用只是为所有子类提供一个公共的接口,我们是不希望它被实例化的,解决这个问题就是使用抽象类。
6. 抽象类与接口
6.1 抽象方法
抽象方法是只有声明没有实现的方法,可以使用abstract去声明一个抽象方法。
例如:在Shape类中声明了三个抽象方法methods getArea(), draw()。
abstract public class Shape {
   ......
   public abstract double getArea();
   public abstract void draw();
}
对于这几个方法是不可能在这里面实现的,因为真实的形状不知道,只有在子类中确定了形状之后,我们就可以实现这些方法了,另外重新方法不可能被调用,因为它没有实现。


6.2 抽象类
包含了一个或者多个抽象方法的类叫做抽象类,抽象类必须使用限定符abstract来进行声明,下面我们来写一个Shape的抽象类,它包含了一个getArea()的抽象方法。

Shape.java

abstract public class Shape {
   // Private member variable
   private String color;
   
   // Constructor
   public Shape (String color) {
      this.color = color;
   }
   
   @Override
   public String toString() {
      return "Shape of color=\"" + color + "\"";
   }
   
   // All Shape subclasses must implement a method called getArea()
   abstract public double getArea();
}
因为抽象类是不完整的类,因为它的抽象方法的实现是缺失的,因此抽象类不能被实例化,换句话说就是不能创建一个抽象类的实例。
为了使用抽象类,我们必须从抽象类中派生一个子类,在派生的子类中,必须重写抽象方法,提供所有抽象方法的实现。因为派生的子类是完整的类,所以抽象类的派生类可以被实例化,当然,如果子类没有提供父类的抽象方法的实现,那么子类也必须声明为抽象类。
可以看到,使用抽象类很方面的解决了我们前面提到的问题,也就我们可以创建一个子类对象,例如Triangle 和 Rectangle,并且将它们向上转换为Shape类型,但是不能创建一个Shape实例。
public class TestShape {
   public static void main(String[] args) {
      Shape s1 = new Rectangle("red", 4, 5);
      System.out.println(s1);
      System.out.println("Area is " + s1.getArea());
      
      Shape s2 = new Triangle("blue", 4, 5);
      System.out.println(s2);
      System.out.println("Area is " + s2.getArea());
      
      // Cannot create instance of an abstract class
      Shape s3 = new Shape("green");   // Compilation Error!!
   }
}
总的来说,抽象类提供了一个未来实现的模板,抽象类的目的就是为所有子类提供一个公共的接口,然后结合前面所说的多态,将子类实例向上转换为Shape,这样就可以面向接口编程,将接口和实现分离可以更好了进行软件的设计和程序的扩展。


需要注意的是:
  • 抽象方法不能声明为final,因为final方法是不可重新的,这个跟抽象方法的目的恰好相违背。
  • 抽象方法不能声明为private,因为抽象方法是对子类不可见的,因此不能被重写。

6.3 接口
Java接口是一个完全抽象的超类,它里面声明的全部都是抽象方法,都必须被子类实现,接口只包含公共的抽象方法和一些静态常量,必须使用关键字"interface"去定义接口,在接口中,它里面声明的方法可以省略关键字public 和 abstract。
接口命名规范:包含一个或者多个形容词,并且使用驼峰法命名,例如:Serializable, Extenalizable, Movable, Clonable, Runnable等等。


例子:Movable接口与它的实现

Interface Moveable.java
public interface Movable {
   // abstract methods to be implemented by the subclasses
   public void moveUp();
   public void moveDown();
   public void moveLeft();
   public void moveRight();
}

跟抽象类一样,接口也不能被实例化,因为它是不完整的类。为了使用接口,必须派生一个提供了所有方法实现的子类,因为子类是完整的,所以可以被实例化。


MovablePoint.java
对应接口的实现,使用的是关键字"implements"而不是 "extends",也就是说实现接口使用关键字implements,扩展抽象类或者普通类使用的是关键字extends。需要注意的是,子类实现接口需要重写接口中所有的抽象方法,否则编译器将会报错。

public class MovablePoint implements Movable {
   // Private member variables
   private int x, y;   // (x, y) coordinates of the point
      
   // Constructor
   public MovablePoint(int x, int y) {
      this.x = x;
      this.y = y;
   }
 
   @Override  
   public String toString() {
      return "Point at (" + x + "," + y + ")";
   }

   // Implement abstract methods defined in the interface Movable
   @Override
   public void moveUp() {
      y--;
   }
   
   @Override
   public void moveDown() {
      y++;
   }
   
   @Override
   public void moveLeft() {
      x--;
   }
   
   @Override
   public void moveRight() {
      x++;
   }
}
TestMovable.java
使用的时候同样可以使用多态
public class TestMovable {
   public static void main(String[] args) {
      Movable m1 = new MovablePoint(5, 5);  // upcast
      System.out.println(m1);
      m1.moveDown();
      System.out.println(m1);
      m1.moveRight();
      System.out.println(m1);
   }
}

6.4 实现多个接口
前面说过,Java只支持单继承,也就是子类只能派生自一个父类,它不支持多继承注意是为了避免继承了冲突的方法或者属性。假如我们有两个类A和B,这两个类都定义了方法test(),如果我们有一个类C同时继承了A和B,那么如果我们在子类中调用super.test()就会出现冲突,因为无法判定是执行A中的test()方法还是执行B中的test()方法。
但是,在子类中,可以实现多个接口,原因是因为接口里面没有方法的实现,这样就会避免上面的问题产生。所以在Java中不支持多继承但是支持多接口。

public class Circle extends Shape implements Movable, Displayable {  // One superclass but implement multiple interfaces
   .......
}

6.5 接口语法
接口的语法定义如下:

[public|protected|package] interface interfaceName
[extends superInterfaceName] {
   // constants
   static final ...;

   // abstract methods' signature
   ...
}
  • 接口中的所有方法默认都是public和abstract的,因此不能使用其他的限定符private, protected 和 default, 或者限定符static, final。
  • 接口中的所有属性字段都是public, static 和 final的。
  • 一个接口可以继承一个父接口。
下图为普通父类,抽象类,接口的继承关系图。



6.6 为什么使用接口

一个接口定义了类的一个行为协议,当一个类实现一个确定的接口,它必须实现这个接口的所有方法,接口中定义的就是类的通用行为集合,类实现接口相当于这个类认可这些行为方法,并且提供自己的行为实现,也就是我们可以通过接口定义一套统一的行为,这些行为是其他类都可能需要实现使用的。另外,Java中不支持多继承,但是如果希望实现类似多继承,我们就可以使用多接口


原文链接:Java Programming Tutorial OOP - Inheritance & Polymorphism

上一篇文章      下一篇文章      查看所有文章
2016-04-03 20:43:25  
360图书馆 论文大全 母婴/育儿 软件开发资料 网页快照 文字转语音 购物精选 软件 美食菜谱 新闻中心 电影下载 小游戏 Chinese Culture
生肖星座解梦 人民的名义 人民的名义在线看 三沣玩客 拍拍 视频 开发 Android开发 站长 古典小说 网文精选 搜图网 天下美图
中国文化英文 多播视频 装修知识库
2017-4-29 9:35:26
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --