Java 面向对象程序设计 继承与多态
继承是面向对象程序设计方法中实现软件重用的一种重要手段,通过继承可以有效的组织程序结构,明确类之间的关系,并充分利用已有的类来创建新类,从而完成复杂的设计与开发。多态则可以同一多个相关类的对外接口,并在运行时根据不同的情况执行不同的操作,提高类的抽象度和灵活性。
1.继承的介绍
在面向对象技术中,继承是一个最为显著的特性,继承表示的时存在面向对象程序中的两个类之间的关系。当一个类充当另一个类的子类,这个类就自动拥有另一个类的所有非私有属性(域和方法)时,那么我们就称这两个类之间具有继承关系。
在类的定义过程中,继承是一种由已有的类创建新类的机制。继承而得到的类为子类,被继承的类为父类,父类包括所有直接或间接被继承的类。
一般面向对象程序设计语言中的继承分为:单继承和多继承,其中单继承采用树状结构,设计实现容易,而多继承采用的是网状结构、设计、实现复杂。其中Java提供的是单继承机制。这使得Java类是具有严格的层次结构的。
除Object类之外,每个类都有唯一的父类。Object类定义和实现了Java系统所需要的众多类的共同行为,它是所有类的父类,也即这个树状结构中的根类,所有的类都是由这个类继承、扩充而来的,这个Object类定义在java.lang包中。
2.继承的实现
2.1 子类的定义
定义一个子类,即在定义一个类的时候加上extends关键字,并在之后带上其父类名,其一般格式为:
[类的修饰符] class <子类名> extends <父类名>{ <域定义>; <方法定义>; }
新定义的子类可以从父类那里继承所有非private的域和方法作为自己的属性。
2.2 域的继承与隐藏
2.2.1 域的继承
子类可以继承父类的所有非私有域。
package study; class bird { final int age = 10; private double weight = 10; public boolean fly(){ System.out.println("bird can fly"); return true; }; } class eagle extends bird{ public void features(){ System.out.println("Eagle is fierce"); } } public class fly{ public static void main(String[] args) { eagle a = new eagle(); System.out.println(a.age);//10 System.out.println(a.fly());//bird can fly System.out.println(a.weight); //报错,'weight' has private access in 'study.bird',子类无法访问父类的私有域。 } }
2.2.2 域的隐藏
子类重新定义一个与从父类继承来的域变量完全相同的变量,称为域的隐藏。即子类中定义了与父类同名的域变量,就是子类变量对同名父类变量的隐藏。这里所谓隐藏是指子类拥有了两个相同名字的变量,一个来自继承父类,另一个由自己定义。在这种情况下,当子类执行继承的父类方法时,处理的是父类的变量,而当子类执行它自己定义的方法时,所操作的就是它自定义的变量,而把来自继承父类的变量“隐藏”起来了。
class A { static int data_a = 3; } class B extends A { static int data_a = 5; } class C extends B { int data_a = 10; void print_out() { System.out.println("" + this.data_a); //当子类执行它自己定义的方法时,所操作的就是它自定义的变量,把父类的变量隐藏起来了。 System.out.println("" + A.data_a); //当子类执行继承的父类方法时,处理的是父类的变量 System.out.println("" + B.data_a); } } class demo { public static void main(String args[]) { C c = new C(); c.print_out(); } }
2.2.3 方法的继承与覆盖
方法的继承:父类的非私有方法可以被子类所继承。
方法的覆盖:指子类重定义从父类继承来的一个同名方法,此时父类和子类中都存在一个同名方法,父类这个方法在子类中不复存在。这是子类通过重新定义与父类同名的方法,实现自身的行为。
class a { public double area(double x, double y) { return x * y; } } class b extends a { public double area(double x, double y) { return (x + y) * 2; } } public class Override { public static void main(String[] args) { b b1 = new b(); System.out.println(b1.area(2, 3)); //输出10.0 //如果注释掉b类的area方法,那么输出的是6.0 } }
子类在重新定义父类已有的方法是,应保持与父类完全相同的方法头部声明,即完全相同的方法名、返回类型和参数列表。
方法的覆盖和域的隐藏的不同之处:域的隐藏只是子类隐藏父类的域使之不可见,父类的同名域在子类的对象中仍然占有自己的独立内存空间。而子类方法对父类方法的覆盖则是清除父类方法占用的内存空间,从而使父类方法在子类对象中不复存在。
3.多态性
3.1多态性的概念
多态性是指同名的不同方法在程序中共存。即为同一个方法名定义几个版本的实现,运行时根据不同情况执行不同的版本。调用者只需使用同一个方法名,系统会根据不同情况,调用相应的不同方法,从而实现不同的功能。
多态性又被称为“一个名字,多个方法”。
3.2多态性的实现有两种方式:
3.2.1覆盖实现多态性:
通过子类对继承父类方法的重定义来实现。使用时注意:在子类重定义父类方法时,要求与父类中方法的原型(参数个数、类型、顺序)完全相同。
在覆盖实现多态性的方式中,子类重定义父类方法,此时方法的名字、参数个数、类型、顺序完全相同。由于这些方法是存在不同的类层次结构中,在调用方法时只需要指明是调用哪个类(或对象)的方法,就很容易把它们区分开来,其调用形式为:
对象名.方法名 或 类名.方法名
3.2.2重载实现多态性:
在一个类中的定义多个同名方法的不同实现。定义方法时方法名相同,但方法的参数不同(参数的个数、类型、顺序不同)。这些方法同名的原因是具有类似的功能且目的相同,但在实现该功能的具体方式和细节方面有所不同,因此需要定义多种不同的方法体。
由于重载发生在同一个类中,不能再用类名或对象名来区分不同的方法了,所以在重载中采用的区分方法是使用不同的形式参数表,包括形式参数的个数不同、类型不同或顺序的不同。
public class Aa { public void Aa(){ System.out.println("无参数方法"); }; public void Aa(int a){ System.out.println("输入一个参数"+a); }; public void Aa(int a,int b){ System.out.println("输入两个参数"+a+b); } public static void main(String[] args) { Aa a1 = new Aa(); a1.Aa(); //无参数方法 a1.Aa(1); //输入一个参数1 a1.Aa(1,5); //输入两个参数15 } }
4.构造方法的继承与重载
4.1 构造方法的重载
构造方法的重载是指同一个类中定义不同参数的多个构造方法,以完成不同情况下对象的初始化。
一个类的若干个构造方法之间可以相互调用。当类中一个构造方法需要调用另一个构造方法时,可以使用关键字this,并且这个调用语句应该是该构造方法的第一个可执行语句。
public class Ba { void Ba(){ System.out.println("无参数构造方法"); } void Ba(int a){ System.out.println("一个参数构造方法 "+ a ); } void Ba(int a,int b){ this.Ba(); this.Ba(100); System.out.println("两个参数的构造方法 "+a+b); } public static void main(String[] args) { Ba ba = new Ba(); ba.Ba(1,5); } }
//输出 //无参数构造方法 //一个参数构造方法 100 //两个参数的构造方法 15
4.2 构造方法的继承
子类可以继承父类的构造方法,继承的方式遵循以下原则:
- 子类无条件地继承父类的无参数的构造方法。
- 如果子类没有定义构造方法,则它将继承父类的无参数构造方法作为自己的构造方法;如果子类定义了构造方法,则在创建新对象时,将先执行来自继承父类的无参数构造方法,然后再执行自己的构造方法。
- 对于父类的带参数构造方法,子类可以通过在自己的构造方法中使用super关键字来调用它,但这个调用语句必须是子类构造方法的第一个可执行语句。
下面对这三个原则进行分别介绍:
- 前面说到,子类无条件继承父类的无参数构造方法。如果子类有无参数构造函数,那么父类一定要有无参数构造函数。若子类不适用无参数构造函数,那么父类可以没有无参数构造函数。
- 在创建新对象时,子类先执行来自继承父类的无参数构造方法,然后再执行自己的构造方法。系统不会在调用有参构造方法的时候自动调用无参构造方法,需要自己手动调用实现。
class grandfather{ public grandfather(){ System.out.println(" String a"); } } class father extends grandfather{ public father(){ System.out.println(" String b"); } } class person extends father{ public person(){ System.out.println(" String c"); } } public class ConstructorTest{ public static void main(String args[]){ person zhangsan = new person(); } } //输出 // String a // String b // String c
- super是表示父类对象的关键字,super表示当前对象的直接父类对象的一个引用,通过super可使用父类对象的方法或域。
class FatherClass { public FatherClass() { System.out.println("父类 无参 构造函数"); } public FatherClass(int i) { //this();调用自身的无参构造函数 System.out.println("父类 一个参数构造函数 " + i); } public FatherClass(int i, String j) { //this(i);调用自身一个参数的构造函数 System.out.println("父类 两个参数构造函数 " + i + " " + j); } } class SonClass extends FatherClass { public SonClass() { System.out.println("子类 无参 构造函数"); } public SonClass(int a) { //super(33, "Hello"); 调用父类的两个参数构造函数 System.out.println("子类一个参数构造函数 " + a); } public void fun(int a) {//子类中定义一个实例函数 System.out.println("子类一个参数构造函数 " + a); } } public class ConstructorExtend {//测试子类继承父类的构造函数 public static void main(String args[]) { SonClass son1 = new SonClass(12); FatherClass father = new FatherClass(12, "hello"); } } //输出 //父类 无参 构造函数 //子类一个参数构造函数 12 //父类 两个参数构造函数 12 hello