黑马程序员--Java基础--重温Java的继承和重写

——Java培训、Android培训、iOS培训、.Net培训 期待与您共同交流!——

继承和重写(extends&override)

1. 继承

1.1. 泛化的过程

前面的案例中定义了T类和J类, 通过分析可以发现, 在这两个类中存在着大量的重复代码,像cells属性、print方法、drop方法、moveLeft方法、moveRight方法,在这两个类中都存在,并且实现上基本也是相同的,本着代码重用的原则,可以使用继承的方式来实现。

首先,构建T类和J类的父类Tetromino类,将公共的(T类和J类公有的)信息存放在父类中, T类和J类继承Tetromino父类。此时,子类即可以共享父类的数据。这个过程就是泛化的过程。

1.2. extends关键字

使用继承可以实现代码的重用,在java语言中,需要通过extends关键字实现类的继承。继承完成后,子类(Sub class)可以继承父类(Super class)的成员变量及成员方法,同时子类也可以定义自己的成员变量和成员方法。届时,子类将具有父类的成员及本类的成员。

需要注意的是,Java语言不支持多重继承,即:一个类只能继承一个父类,但一个父类可以有多个子类。看下面的代码:

publicclass Tetromino {
    Cell[] cells;
publicTetromino(){
    cells =new Cell[4];
}
public void drop(){//同写过的T类 }
public void moveLeft(){//同写过的T类}
public void moveRight(){//同写过的T类}
public void print(){//同写过的T类}
}
publicclass TetrominoT extends Tetromino {
publicTetrominoT(int row, int col){
        cells[0]=newCell(row, col);
        cells[1]=newCell(row, col +1);
        cells[2]=newCell(row, col +2);
        cells[3]=newCell(row +1, col +1);
}
}

如上代码说明:声明父类Tetromino,将公共信息放在其中,包括Cell[]声明、drop()方法、moveLeft()方法、moveRight()方法,print()方法。声明无参构造函数,对成员变量Cell数组进行实例化。声明子类TetrominoT继承Tetromino,并声明有参构造函数,传递行row,列col参数,进行T型数组元素的初始化。

下面在main方法中,声明一个T型对象,即可以实现T型对象的构建:

TetrominoT t =newTetrominoT(1,1);

上面的代码,在创建子类对象时,调用了子类的有参构造函数进行数据的初始化,试想下,父类Tetromino的无参构造函数执行了吗?通过分析可以肯定的是,父类的无参构造函数被执行了。在程序中并没有声明父类的构造函数,那它是如何执行的呢?

1.3. 继承中构造方法

父类的无参构造方法之所以被执行,是因为java规定,子类在构造之前必须先构造父类。

事实上,子类的构造方法中是必须要通过super关键字来调用父类的构造方法的,这样才可以保证妥善的初始化继承自父类的成员变量。

但是看上一个案例中的代码并没有super调用父类构造方法,那是因为,如果子类的构造方法中没有调用父类的构造方法,则java编译器会自动的加入对父类无参构造方法的调用。请看如下代码,演示了super关键字的用法:

publicTetrominoT(int row, int col){
super();
    cells[0]=newCell(row, col);
    cells[1]=newCell(row, col +1);
    ……
}

上面的代码中,super();为编译器自动加入的,并且super关键字必须位于子类构造方法的第一行,否则会有编译错误。

另外一点需要注意的是,若父类没有提供无参的构造方法,则会出现编译错误。请看如下的示例:

class Foo {//父类
    int value;
Foo(int value){
this.value = value;
}
}
class Goo extends Foo {//子类
    int num;
Goo(int num){
this.num = num;
}
}

分析上面的代码,在子类构造方法中没有写super调用父类构造方法,这时编译器会默认添加super()来调用父类的无参构造方法,但是父类中又没有定义无参的构造方法,因此会发生编译错误。

针对上面的问题,可以有两种解决方案,方案一为在父类中添加无参的构造方法,方案二为在子类构造方法中显示调用父类的有参构造方法(常常使用),这样可以保证父类的成员变量均被初始化,参见下面的代码:

class Goo extends Foo {
    int num;
Goo(int value, int num){
super(value);
this.num = num
}
}

如上的代码,在子类中调用了父类的构造方法,初始化了继承自父类的value成员变量,编译正确。

1.4. 父类的引用指向子类的对象

一个子类的对象可以向上造型为父类的类型。即,定义父类型的引用可以指向子类的对象。看如下代码所示:

class Foo {
    int value;
public void f(){…}
Foo(int value){
this.value = value;
}
}
class Goo extends Foo {
    int num;
public void g(){…}
Goo(int value, int num){
super(value);
this.num = num
}
}
class Test{publicstatic void main(String[] args){
        Foo obj =newGoo(100,3);
}
}

上面的代码,在main方法中,声明了父类型的引用来指向子类型的对象。但是通过父类的引用只能访问父类所定义的成员,而不能访问子类所扩展的部分。看下面的代码:

class Foo {
    int value;
public void f(){…}
    … … …
}
class Goo extends Foo {
    int num;
public void g(){…}
    … … …
}
class Test{
publicstatic void main(String[] args){
        Foo obj =newGoo(100,3);
obj.value=200;
obj.f();
obj.num =5;
obj.g();
}
}

分析上面的代码,在main方法中,声明父类型的引用指向了子类的对象,而后,访问父类的成员变量value及调用父类的方法f,均可以正常编译。但是,当通过obj引用访问num变量及g的方法时,会出现编译错误。那是因为,当用父类型引用指向了子类对象后,java编译器会根据引用的类型(Foo),而不是对象的类型(Goo)来检查调用的方法是否匹配。

2. 重写

2.1. 方法的重写

下面增加需求,在输出图形之前先打印格子坐标,即调用print()方法。想实现这个需求做法很简单,只需要父类型引用直接调用print()方法即可,因为print()方法是在父类中定义的,所以可以直接调用此方法。

现在需求继续增加,要求,不同的图形类型在打印输出之前先输入相应的语句,例如: TetrominoT对象调用print()方法后,增加输出“I am a T”,TetrominoJ对象调用print()方法后,增加输出“I am a J”。因为现在print()方法是在父类中定义的,只有一个版本,无论是T类对象还是J类对象调用,都将输出相同的数据,所以现在无法针对对象的不同而输出不同的结果。若想实现此需求,需要介绍一个概念,叫做方法的重写。

在java语言中,子类可以重写(覆盖)继承自父类的方法,即方法名和参数列表与父类的方法相同,但是方法的实现不同。

当子类重写了父类的方法后,该重写方法被调用时(无论是通过子类的引用调用还是通过父类的引用调用),运行的都是子类重写后的版本。看如下的示例:

class Foo {
public void f(){
        System.out.println("Foo.f()");
}
}
class Goo extends Foo {
public void f(){
        System.out.println("Goo.f()");
}
}
class Test{
publicstatic void main(String[] args){
        Goo obj1 =newGoo();
obj1.f();
Foo obj2 =newGoo();
obj2.f();
}
}

分析代码得出结论:输出结果均为“Goo.f()”,因为都是Goo的对象,所以无论是子类的引用还是父类的引用,最终运行的都是子类重写后的版本。

2.2. 重写中使用super关键字

在子类重写的方法中,可以通过super关键字调用父类的版本,参见如下的代码:

class Foo {
public void f(){
        System.out.println("Foo.f()");
}
}
class Goo extends Foo {
public void f(){
super.f();
        System.out.println("Goo.f()");
}
}
class Test{
publicstatic void main(String[] args){
        Foo obj2 =newGoo();
obj2.f();
}
}

上面的代码中,super.f()即可以调用父类Foo的f()方法,此程序输出结果为:Foo.f() Goo.f()。这样的语法通常用于子类的重写方法在父类方法的基础之上进行的功能扩展。

2.3. 重写和重载的区别

重载与重写是完全不同的语法现象,区别如下所示:

重载: 是指在一个类中定义多个方法名相同但参数列表不同的方法,在编译时,根据参数的个数和类型来决定绑定哪个方法。

重写: 是指在子类中定义和父类完全相同的方法,在程序运行时,根据对象的类型(而不是引用类型)而调用不同的方法。

分析如下代码的输出结果:

class Super {
public void f(){
System.out.println("super.f()");
}
}
class Sub extends Super {
public void f(){
System.out.println("sub.f()");
}
}
class Goo {
public void g(Super obj){
System.out.println("g(Super)");
obj.f();
}
public void g(Sub obj){
System.out.println("g(Sub) ");
obj.f();
}
}
class Test{
publicstatic void main(String[] args){
        Super obj =newSub();
Goo goo =newGoo();
goo.g(obj);
}
}

分析如上代码,输出结果为:g(Super) sub.f()

首先,重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法, 因为变量obj为Super类型引用, 所以,Goo的g(Super)被调用,先输出g(Super)。

重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法,因为obj实际指向的是子类Sub的对象,因此,子类重写后的f方法被调用,即sub.f()。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-28 12:03:12

黑马程序员--Java基础--重温Java的继承和重写的相关文章

黑马程序员____第一阶段Java开发前奏(1)

  黑马程序员____第一阶段Java开发前奏(1) ⒈软件开发:        软件是由一系列按照特定顺序组织的计算机数据和指令的集合,软件开发就是制作软件.软件的出现实现了人与计算机之间的交互.    ⒉人机交互方式:        图形化界面和命令行方式,图形化界面简单直观,使用者易于接受,容易上手操作.命令行方式需要有一个控制台,输入特定的指          令,让计算机完成一些操作,较为麻烦,需要记住一些命令.    3.常用的DOS命令:     dir:列出当前目录下的文件以及文

黑马程序员_JAVA 基础加强学习笔记

一.面向对象 (一)继承  1.继承的好处: (1) 提高了代码的复用性. (2) 让类与类之间产生了关系,提供了另一个特征多态的前提. 注意: 子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();  如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数. 如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数. 2.final特点

黑马程序员_Java基础加强(下)

8.注解类 注解相当于一种标记,加了注解就等于打上了某种标记,没加就等于没打特殊标记,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记就去干什么事,标记可以加在包.类.字段.方法,方法的参数以及局部变量上. 注解的应用结构图: 为注解增加属性 定义基本类型的属性和应用属性 在注解类中增加String color(); @MyAnnotation(color = "red") 用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法 MyAnno

黑马程序员_Java基础加强(上)

1.静态导入 静态导入是jdk1.5版本以后出现的新特性,一般是指导入静态方法,如:import static java.lang.System.out 是指导入系统输出的静态方法. 例: import static java.lang.System.out //导入java.lang包下的System类的静态方法out public class StaticImport { public static void main(String[] args) { int x=1; x++; out.p

黑马程序员-正则表达式基础

正则表达式是一种描述字符串集的方法,它是以字符串集中各字符串的共有特征为依据的.正则表达式可以用于搜索.编辑或者是操作文本和数据.它超出了 Java 程序设计语言的标准语法,因此有必要去学习特定的语法来构建正则表达式.正则表达式的变化是复杂的,一旦你理解了它们是如何被构造的话,你就能解析或者构建任意的正则表达式了..正则表达式由一些普通字符和一些元字符组成. 普通字符包括大小写的字母和数字,而元字符则具有特殊的含义.在最简单的情况下,一个正则表达式看上去就是一个普通的查找串.例如,正则表达式"a

黑马程序员_Java基础_接口

------- android培训.java培训.期待与您交流! ---------- 0.接口知识体系 Java接口的知识体系如下图所示,掌握下图中的所有知识就可精通接口. 1.接口概论 1)接口概念 接口是从多个相似类中抽象出来的规范,接口中不包含普通方法,所有方法都是抽象方法,接口不提供实现.接口体现的是规范和实现分离的哲学.规范和实现分离正是接口的好处,让软件系统的各个组件之间面向接口耦合,是一种松耦合的设计.接口定义的是多个类共同的公共行为规范,定义的是一组公用方法. 2)接口与抽象类

黑马程序员——集合基础知识(Map)

Map概念 要同时存储两个元素,他们之间有映射关系,每个键不能重复,每个键只能映射到一个值. 存储键值对,并且键是唯一的. 1.添加. put()如果添加的键原来有值,后添加的值会覆盖前面的值,并返回之前的值. 2.删除 remove()按键删除. 3.判断 4.获取 get(object key) size() value()拿值value返回的是值的集合... HashTable 底层是哈西数据结构,不可以存入null键null值,线程同步. HashMap 底层是哈西表数据结构,允许使用n

黑马程序员_Java基础String类

- - - - - android培训.java培训.期待与您交流! - - - - - - - String是一个对象 String不属于8种基本数据类型(byte, char, short, int, float, long, double, boolean),String是对象,所以其默认值是null. String是一种特殊的对象,有其它对象没有的一些特性,通过JDK发现: public final class String implements java.io.Serializable

黑马程序员——集合基础知识(泛型)

集合:泛型基础知识 泛型.(泛型就是类型参数化,默认的时object,虽然不用强制类型转换,这个就要你自己去写特性方法,比如compareto是string的特有方法吧,你可以写但是父类肯定调用不了) itnex t对象都是obj要使用对象的特性功能必须强.编译的时候没问题,因为都不知道你会传什么对象,你橙子也可以当作apple来传,设计的时候并不知道! 泛型作用.1.用于解决安全问题.运行时期出现的问题classcastexception转移到编译时期.2.迭代器里面的itnext()不用强转

黑马程序员——集合基础知识(Collection)

集合基础知识 数组:长度固定,可存基本数据和对象. 集合:只能放对象,不固定.容器也有共性,不断抽取成一个体系,集合框架.参阅顶层创建底层.顶层是collection.collection里有两个常见的接口,List和Set.常见集合有Arraylist,linkedlist,vector,hashSet TreeSet.为什么会出现这么多的容器呢,因为每一个容器对数据的存储方式都有不一样,.这个存储方式称之为数据结构!!因为他们的特点不一样 list因为有脚标存储和删除的效率很低,Set的效率