4.1 继承
面向对象程序设计语言有三大特性:封装、继承和多态性。继承是面向对象语言的重要特征之一,没有继承的语言只能被称作“使用对象的语言”。
类是规则,用来制造对象的规则。我们不断地定义类,用定义的类制造一些对象。类定义了对象的属性和行为,就像图纸决定了房子要盖成什么样子。
基于已有的设计创造新的设计,就是面向对象程序设计中的继承。通过继承,新的类自动获得了基础类中所有的成员,包括成员变量和方法,包括各种访问属性的成员,无论是public还是private。当然,在这之后,程序员还可以加入自己的新的成员,包括变量和方法。继承是支持代码重用的重要手段之一。
重点在于弄清楚子类和父类的关系,理解子类从父类继承得到了什么。
4.1.1 资料库设计
Database.java:
package demo;
import java.util.ArrayList;
public class Database {
private ArrayList<CD> listCD=new ArrayList<CD>();
private ArrayList<DVD> listDVD=new ArrayList<DVD>();
public void add(CD cd){
listCD.add(cd);
}
//函数的重载
private void add(DVD dvd) {
listDVD.add(dvd);
}
public void list() {
for(CD cd:listCD)
{
cd.print();
}
for(DVD dvd:listDVD)
{
dvd.print();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Database db=new Database();
db.add(new CD("梦的河流","伍佰",12,2001,"..."));
db.add(new CD("Time of The Gypsies","Emir Kusturica",14,2007, "..."));
db.add(new DVD("La strada","Federico Fellini",108, "..."));
db.list();
}
}
CD.java:
package demo;
public class CD {
private String title;
private String artist;
private int numofTracks;
private int playingTime;
private boolean gotIt=false;
private String comment;
//自动生成的构造函数
public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
// super(); 现在不需要这个super
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playingTime = playingTime;
this.comment = comment;
}
public void print() {
// TODO Auto-generated method stub
System.out.println("CD"+":"+title+":"+artist);
}
}
用这些字段帮我们做一个构造器
选择需要构造的变量然后generate就好
DVD.java:
package demo;
public class DVD {
private String title;
private String director;
private int playingTime;
private boolean gotIt=false;
private String comment;
public DVD(String title, String director, int playingTime, String comment) {
super();
this.title = title;
this.director = director;
this.playingTime = playingTime;
this.comment = comment;
}
public void print() {
// TODO Auto-generated method stub
System.out.println("DVD"+":"+title+":"+director);
}
}
4.1.2 继承
在我们的代码里面,CD和DVD有很多重复的代码,我们可以想到做一个公共的东西,让database管这个公共的东西。
新建一个Item类,在CD.java里改成public class CD extends Item{
在DVD.java里改成public class DVD extends Item{
(翻译:CD扩展了Item,这样比CD继承自Item好理解一点耶)
Database.java:
package demo;
import java.util.ArrayList;
public class Database {
// private ArrayList<CD> listCD=new ArrayList<CD>();
// private ArrayList<DVD> listDVD=new ArrayList<DVD>();
private ArrayList<Item> listItem=new ArrayList<Item>();
// public void add(CD cd){
// listCD.add(cd);
// }
// private void add(DVD dvd) {
// listDVD.add(dvd);
// }
private void add(Item item) {
listItem.add(item);
}
public void list() {
// for(CD cd:listCD)
// {
// cd.print();
// }
// for(DVD dvd:listDVD)
// {
// dvd.print();
// }
for(Item item:listItem) {
item.print();//然后自动用eclipse的提示create print()
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Database db=new Database();
db.add(new CD("梦的河流","伍佰",12,2001,"..."));
db.add(new CD("Time of The Gypsies","Emir Kusturica",14,2007, "..."));
db.add(new DVD("La strada","Federico Fellini",108, "..."));
db.list();
}
}
Item.java:
package demo;
public class Item {
public void print() {
// TODO Auto-generated method stub
}
}
CD是Item的子类,我们new出来的CD可以当做Item add到db里。listItem既可以接收CD,又可以接收DVD,都被认为是Item了。
CD extends了Item,所有Item里面的东西CD都得到了。加入CD没有自己的print(),那么db.list();
时就会调用Item的print()。
我们把用来做基础派生其它类的那个类叫做父类、超类或者基类,而派生出来的新类叫做子类。Java用关键字extend表示这种继承/派生关系:
class ThisClass extends SuperClass {
//…
}
继承表达了一种is-a关系,就是说,子类的对象可以被看作是父类的对象。比如鸡是从鸟派生出来的,因此任何一只都可以被称作是一只鸟。但是反过来不行,有些鸟是鸡,但并不是所有的鸟都是鸡。如果你设计的继承关系,导致当你试图把一个子类的对象看作是父类的对象时显然很不合逻辑,比如你让鸡类从水果类得到继承,然后你试图说:这只本鸡是一种水果,所以这本鸡煲就像水果色拉。这显然不合逻辑,如果出现这样的问题,那就说明你的类的关系的设计是不正确的。Java的继承只允许单继承,即一个类只能有一个父类。
4.2 子类父类关系
super()
创建了Item父类之后,解决了Database中重复的代码。
でも、CD类和DVD类里面还有一些重复的成员变量,我们可以把这些相同的东西移出去,放到Item类里。
在Item中加上private String title;
后,CD子类没有办法继承到private的title,也没有办法在CD里构造。如果CD想要访问title,可以在Item中将title的属性改成protected。
父类的private的成员在子类里仍然是存在的,只是子类中不能直接访问。
public的成员直接成为子类的public的成员,protected的成员也直接成为子类的protected的成员。
Java的protected的意思是包内和子类可访问,所以它比缺省的访问属性要宽一些。
些移到Item里的成员变量应当在Item里构造,子类CD和DVD的构造函数中加上super();
传递变量的值,这样也可以获得title的值。
package demo;
public class CD extends Item{
// private String title;
private String artist;
private int numofTracks;
private int playingTime;
private boolean gotIt=false;
private String comment;
//自动生成的构造函数
public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
super(title);
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playingTime = playingTime;
this.comment = comment;
}
public void print() {
// TODO Auto-generated method stub
System.out.println("CD"+":"+title+":"+artist);
}
}
package demo;
public class Item {
private String title;
public Item(String title) {
super();
this.title = title;
}
public void print() {
// TODO Auto-generated method stub
}
}
初始化的顺序:先父类后子类
目前是将CD的titile删去,CD类会继承父类Item的title,初始化顺序是这样的:
设断点可以看到,new一个CD类的时候,先去父类把super()传过来的变量初始化,然后才来做自己的定义初始化,然后在进构造函数做其他的初始化。
这时,DVD报叉了,因为DVD构造器里的super();
没有填任何变量,那么在DVD构造的时候会调用Item中没有参数的那个构造器,而现在的Item中只有一个有String title参数的构造器。我们试着建一个空参数的构造器:
package demo;
public class Item {
private String title;
//建一个空参数的构造器
public Item() {
super();
}
public Item(String title) {
super();
this.title = title;
}
public void print() {
// TODO Auto-generated method stub
}
}
这时,DVD好了,设断点观察new一个DVD类的过程,在没有super()或super()中没有变量的情况下,还是先去父类的没有参数的构造器,然后才来做自己的定义初始化,然后在进构造函数做其他的初始化。
初始化包括:定义初始化和构造器
重名的成员
如果我们试图重新定义一个在父类中已经存在的成员变量,那么我们是在定义一个与父类的成员变量完全无关的变量,在子类中我们可以访问这个定义在子类中的变量,在父类的方法中访问父类的那个。
尽管它们同名但是互不影响。
super.
这时CD和DVD的print出错了,因为System.out.println("CD"+":"+title+":"+artist);
需要打印title,但title是private的。
实在没办法可以把 title设为protected,但尽量不要这样做。
可以在Item的print()打印出title,然后在CD和DVD中调用Item的print()。
Item:
package demo;
public class Item {
private String title;
private int playingTime;
private boolean gotIt=false;
private String comment;
public Item(String title, int playingTime, boolean gotIt, String comment) {
super();
this.title = title;
this.playingTime = playingTime;
this.gotIt = gotIt;
this.comment = comment;
}
public void print() {
System.out.print(title);
}
}
DVD:
package demo;
public class DVD extends Item{
// private String title;/
private String director;
// private int playingTime;
// private boolean gotIt=false;
// private String comment;
public DVD(String title, String director, int playingTime, String comment) {
super(title,playingTime,false,comment);
this.director = director;
}
public void print() {
System.out.print("DVD"+":");
super.print();
System.out.println(":"+director);
}
}
CD:
package demo;
public class CD extends Item{
// private String title;
private String artist;
private int numofTracks;
// private int playingTime;
// private boolean gotIt=false;
// private String comment;
//自动生成的构造函数
public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
super(title,playingTime,false,comment);
// this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
// this.playingTime = playingTime;
// this.comment = comment;
}
public void print() {
System.out.print("CD"+":");
super.print();
System.out.println(":"+artist);
}
}
4.3 多态变量和向上造型
子类和子类型
类定义了类型
子类定义了子类型
子类的对象可以被当作父类的对象来使用:
- 赋值给父类的变量
- 传递给需要父类对象的函数
- 放进存放父类对象的容器中
- 子类型与赋值
子类的对象可以复制给父类变量
Vehicle v1=new Vehicle();
Vehicle v2=new Car();
Vehicle v3=new Bicycle();
- 子类与参数传递
子类的对象可以赋值给需要父类对象的函数 - 子类型和容器
子类的对象可以放在存放父类对象的容器中
多态变量
Java的对象变量是多态的,它们能保存不止一种类型的对象
他们可以保存的是声明类型的对象,或声明类型子类的对象
当把子类的对象赋给父类的变量的时候,就发生了向上造型
造型 casting
- 子类的对象可以赋值给父类的变量
注意!Java中不存在对象对对象的赋值!! - 父类的对象不能赋值给子类的变量
Vehicle v;
Car c=new Car;
v-c;//可以
c=v;//编译错误!
- 可以用造型 c=(Car)v;
(只有当v这个变量实际管理的是Car才行)
Item item =new Item("a",0,true,"…");
CD cd=new CD("a","a",0,0,"…");
item=cd;
CD cc=(CD)item;
造型就是用括号围起类型放在值的前面。
- 造型中对象本身并没有发生任何变化(区别类型转化)
所以不是类型转化(int(10.2)) - 运行时有机制来检查这样的转化是否合理
ClassCastException
向上造型
拿一个子类的对象当作父类的对象来用
向上造型是默认的,不需要运算符
向上造型总是安全的
4.4 多态
在database的例子里,为什么调用Item的print()时,会自动识别到item变量所管理的对象(CD、DVD)的print()去执行呢?
这就是多态。
函数调用的绑定
当通过对象变量调用函数的时候,调用哪个函数这件事情叫做绑定
- 静态绑定:根据变量的声明类型来决定(这种绑定在编译的时候就确定了)
- 动态绑定:根据变量的动态类型来决定(Java缺省使用动态绑定)
在成员函数中调用其他成员函数也是通过this这个对象变量来调用的,这也属于动态绑定
覆盖override
子类和父类中存在名称和参数表完全相同的函数,这一对函数构成覆盖关系
通过父类的变量调用存在覆盖关系的函数时,会根据实际,调用变量当时所管理的对象所属的类的函数
4.5 类型系统
Object类–Java里所有类都是继承自Object的(单根结构)
Object类的函数
-
toString();//用一个字符串表达那个对象
默认的toString()返回的是这个类的名字和地址,我们也可以自己写toString(),让它返回我们想要的:
给类generate一个toString
选择需要返回的字段,就会生成:
@Override
public String toString() {
return "Game [numOfPlayers=" + numOfPlayers + ", toString()=" + super.toString() + "]";
}
-
equals();//比较内容是否相同
==判断的是两个变量是否管理的同一个对象。
直接用equals()是比较不了的,object并不了解我们写出来的类里有什么,无法判断,所以我们要自己写类的equals()。
override/implement methods
override/implement methods里会列出父类有哪些方法,可以选择自己想改写哪个方法。选择equals()之后就自动生成了equals()的代码。
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return super.equals(obj);
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Game ga=(Game)obj;
return numOfPlayers.equals(ga.numOfPlayers);
}
这时就阔以比较了。
public static void main(String[] args) {
// TODO Auto-generated method stub
Game ga=new Game("aa",3,"bb","cc");
Game ga1=new Game("aa",3,"bb","cc");
System.out.println(ga.equals(ga1));
}
OUTPUT:
true
其中,override的函数的形式应该与父函数的形式相同,且不能改变public/private这样的属性。不然程序就不会override重写的函数。
可扩展性:给Item新增一个Game
package demo;
public class Game extends Item {
private int numOfPlayers;
public Game(String title, int playingTime, String comment,int numOfPlayers) {
super(title, playingTime, false, comment);
this.numOfPlayers=numOfPlayers;
// TODO Auto-generated constructor stub
}
@Override
public void print() {
// TODO Auto-generated method stub
System.out.print("Game:");
super.print();
System.out.println(":"+numOfPlayers);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
网友评论