所看教程(视频):《浙江大学-翁恺-Java-面向对象程序设计》
作为我自己的复习笔记,也可以当做该视频的同步笔记

面向对象

什么是面向对象

面向对象是把一组数据结构处理他们的方法组成对象。
把具有相同行为的对象归纳成
通过封装隐藏类的内部细节
通过继承使类得到泛化
通过多态实现基于对象类型的动态分派

只能操作对象

一切事物都是调用加封装的结果
程序实现的一切功能都是调用封装的结果
程序调用一个个对象
封装的也是一个个对象

基于对象编程

同一个事物,构成它的对象能有多种划分方式
机器语言、汇编语言将电路上的开关,由1和0组成的指令作为对象

c语音将对象界定为一个个数据和一个个算法
java将数据和算法的结合作为一个对象

数据和算法分开的编程:面向过程编程
数据和算法结合的编程:面向对象编程

面向过程与面向对象

面向过程关注如何实现,关注如何做,将一个要实现的、复杂的功能,用一个或多个大函数去实现,再抽丝剥茧,用更多的函数去实现这些函数。
面向对象关注数据,方法就在这,处理什么数据(对象)

面向对象中也有面向过程的代码,只是重点不在如何做,而是对对象的抽象扩展
对于实现功能的核心算法,面向对象与面向过程并无区别,c语言也可通过结构体与函数指针实现面向对象

面向对象的封装继承多态,使得代码、功能的扩展、复用变得非常容易

这两种编程思想都是为了解决实际的问题

如何烧水

转自互联网
面向过程的烧水:
读取热水壶内水的水温,缓存
电热装置将发热量缓存
损耗算法读取发热量,并将将水的提升温度缓存
与水温相加
将这个数值重新赋给水温
直到温度达到沸点,完成烧水

面向对象的烧水:
定义热水壶类,继承自盛水容器,温度改变装置,温度计,水温控制接口
实例化一个热水壶类对象,命名为「我的热水壶」
为终止温度赋值:水.沸点
我的热水壶.温度处理(终止温度);

另:个人实现的烧水方法,仅图一乐:
为热水壶类实装烧水接口:实装水温监视事件
为热水壶类实装烧水接口:定义一个水温枚举器
为热水壶类实装烧水接口:实装温度处理方法
执行流程:
1.执行继承自盛水容器类的盛水方法
2.注册继承自温度改变装置类的温度监视事件
3.遍历水温枚举器:如果水温提升,则返回当前水温
4.如果返回值接近终止温度,跳出枚举过程,完成烧水。
5.否则,继续遍历枚举器。

你肯定会问这哪里优雅了,确实,看起来是复杂了不少,但再仔细想一想,这一整套流程只要稍加修改,稍加改变接口实现,你就可以直接用这个「热水壶类」实现一个热水器,甚至还可以是一个冰箱。
毕竟烧的又不一定是水,又不一定要烧水,又不一定要用壶烧水,又不一样要是个烧水壶


对象与类

对象是实体,需要被创建,可以为我们做事情
类是规范,根据类的定义来创建对象

一个类可以有多个对象

动物是一个类,每个对象,猫,狗,都是动物类的实体
我们用类制造出对象,再给对象所需要的数据,对象可以利用这些数据去做事情,我们大可无需知道对象是如何利用这些数据的,因为我们只要求,这个对象能实现一些功能

面向对象的思维

我们看到一个事物
它有什么东西?
能干什么?

第一个程序:自动售货机

售货机(VendingMachine)有什么?
商品的价格:price
显示的余额:balance
卖了多少钱:total

售货机能干什么?
输出一些提示:showPromot
取得一些钱:insertMomey
告诉用户余额:showBalance
给我们商品(食物):getFood
告诉商家总收入:showTotal

我们需要设计VendingMachine这个类,这个类有3个属性,有5个动作(方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.Scanner;
//创建一个类
public class VendingMachine {
int price = 80;//商品价格(假设就只有一个商品,且价格固定)
int balance = 0;//当前余额
int total = 0;//总收入
int amount = 0;//钱
Scanner s=new Scanner(System.in);

void showPromot() {
//输出提示
System.out.println("欢迎!");
}

void insertMomey(){
// 投入钱,更新余额
System.out.print("请充值余额:");
amount = s.nextInt();
balance = balance + amount;
}

void showBalance(){
//输出余额
System.out.println("现在余额: "+ balance);
}

void getFood(){
//给食物
if (balance >= price) {
System.out.println("给你。");
balance = balance - price;
total = total + price;
}
else{
System.out.println("没有足够的余额!");
}
}

void showTotal(){
System.out.println("目前总收入:"+total);
}
}

有了这个类,就可以通过类去制造一个对象,并让对象去实现一些功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
//制作一个对象
VendingMachine vm = new VendingMachine();
boolean t = true;
vm.showPromot();
vm.showBalance();
while (t) {
vm.insertMomey();
vm.getFood();
vm.showBalance();
vm.showTotal();
}
}
}

运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
欢迎!
现在余额: 0
请充值余额:100
给你。
现在余额: 20
目前总收入:80
请充值余额:20
没有足够的余额!
现在余额: 40
目前总收入:80
请充值余额:40
给你。
现在余额: 0
目前总收入:160
请充值余额:

创建对象

使用new运算符,来创建这个类的一个对象
然后将这个对象交给这个类型的一个变量
VendingMachine vm = new VendingMachine();

对象变量是对象的管理者

让对象做事情

使用 . 运算符
vm.insertMomey();
vm.getFood();
通过.运算符调用某个对象的方法

成员变量、成员方法

类定义了对象中所具有的变量,这些变量称作成员变量
每个对象有自己的变量,和同一个类的其他对象是分开的
在方法中可以直接写成员变量(方法)的名字来访问成员变量(方法)(省去了this关键字)

java会给成员变量默认0值

成员变量(方法)分为实例变量(方法)和类变量(方法)
加了static的就是类变量(方法)

类变量

声明类变量: static <类型> <变量名>

访问类变量:
通过对象访问:<对象名>.<类变量名>
通过类访问:<类名>.<类变量名>

类变量不属于任何一个对象,属于这个类,但任何一个对象都拥有这个变量

修改类变量的值,所有对象中的该变量的值都会改变

类变量的初始化只会进行一次(在类的装载时)

类方法

声明类方法: static <返回类型> <方法名>() { }

static方法只能调用static方法,只能访问static变量

类方法可以通过类的名字去访问,也可以通过对象去访问

本地(局部)变量

定义在方法内部的变量是本地变量
本地变量的生存期和作用域都是方法内部

本地变量没被赋值,会被禁止使用

成员变量的生存期是对象的生存期,作用域是类内部的成员方法

var局部变量

使用var时必须指出初始值(不可以是null)
var <变量名> = <值>;
编译器可以推断出该变量的类型,且之后该变量的类型都是确定的,不可以给该变量赋其它类型的值

对象初始化

可以在定义成员变量的地方直接赋值
int price = 80;

在创建一个对象的过程中,会首先去做各种初始化的动作

构造方法

与类同名的函数,没有返回值
在创建一个对象时会自动调用的方法
应该是public

1
2
3
4
5
6
7
VendingMachine(){
total = 10;
}

VendingMachine(int price ){
this.price = price;
}

方法重载

一个类里可以有多个不同参数的构造方法
创建对象的时候给出不同的参数值,就会自动调用不同的构造方法
通过this()还可以在构造方法中调用其他构造方法,写在第一行,且只能使用一次
一个类里的同名但参数表不同的方法构成了重载关系

对象的识别

通过巧妙的思想,识别不同出对象的特点,让类更通用
例如,要实现一个时钟
可以设计一个类,通过这个类可以制造出时、分、秒三个对象

对象的交互

时、分、秒三个对象可以共同组成一个时钟对象
控制时、分、秒之间的交互在时钟对象的方法中完成

Display.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Display {
private int value = 0;
private int limit = 0;

public Display(int limit){
this.limit = limit;
}

public void increase(){
value++;
if(value == limit){
value = 0;
}
}

public int getValue(){
return value;
}

}

Clock.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Clock {
Display hour = new Display(24);
Display minute = new Display(60);
Display second = new Display(60);

public void start() {
while (true) {
second.increase();
if (second.getValue() == 0) {
minute.increase();
if (minute.getValue() == 0) {
hour.increase();
}
}
System.out.printf("%02d:%02d:%02d\n", hour.getValue(), minute.getValue(), second.getValue());
}
}
}
Main.java
1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
Clock clock = new Clock();
clock.start();
}
}

访问属性

private:这个成员是私有的,只有在类的内部(成员方法和定义初始化 )才能访问
一般来说,成员变量都该是private
这个限制是对类的而不是对对象的:同一个类的不同对象可以互相访问对方的成员变量

public:任何人都可以访问
任何人指的是在任何类的方法或定义初始化中可以使用
使用指的是调用、访问或定义变量
很多的成员方法都是public
public的类,类名和文件名要一致,一个编译单元只能有一个public的类

protected:受保护的成员

friendly:默认属性,友好的成员

访问属性 本类 同包 子类 其它
private
friendly
protected
public

包package

包是java管理类的一个机制
源文件中同名类要在不同包内

声明该类的指定包名
package <包名>;

包名中的.代表文件夹的层次

没有package语句的源程序都将视为在同一个无名包内

import

使用import语句引入包中的类和接口

import test.Hallo
test包中的Hallo类

只要用到的类和本类不在同一个包内,就要import它

如果不使用import,当要用到类时要给出全名:<包名>.<类名>

引入一个包内的所有东西:import <包名>.*;(注意同名类的冲突)


NoteBook例子

记事本可以做什么?
1、能存储记录
2、不限制能存储的记录的数量
3、能知道已经存储的记录的数量
4、能查看存进去的每一条记录
5、能删除一条记录
6、能列出所有的记录

确定需求后,进行接口设计

接口设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class NoteBook {

public void add(String s) {
//添加内容
}

public int getSize() {
//放了多少个
return 0;
}

public String getNote(int index) {
//得到指定位置的内容
return " ";
}

public boolean removeNote(int index) {
//删除
return true;
}

public String[] list() {
//返回全部内容
}

}


接口设计完,考虑实际功能的实现,首先是数据的存放

顺序容器

1
private ArrayList<String> notes = new ArrayList<String>();  

用来存放String的一个ArrayList
ArrayList内的东西是有顺序的,是加入数据的顺序,形成对应下标的索引(从0开始)

这种类型叫做范型类:泛型类封装不特定于特定数据类型的操作
这种范型类是一种容器

容器类有两个类型:容器的类型、元素的类型

利用容器类的方法可以实现需要的功能
notes.add(s); //向容器添加数据
notes.size(); //容器存了多少个东西
notes.get(1); //得到1位置处的数据

完成全部功能接口

NoteBook.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.ArrayList;

public class NoteBook {
private ArrayList<String> notes = new ArrayList<String>();//容器类

public void add(String s) {
//添加内容
notes.add(s);
}

public void add(String s, int location) {
//加到指定位置前,后面的内容下标后推
notes.add(location, s);
}

public int getSize() {
//放了多少个
return notes.size();
}

public String getNote(int index) {
//得到指定位置的内容
return notes.get(index);
}

public void removeNote(int index) {
//删除,后面下标前移,因为remove方法自会抛异常,所以无需返回boolean
notes.remove(index);
}

public String[] list() {
//返回全部内容
String[] a = new String[notes.size()];

//for (int i=0; i< notes.size(); i++){
// a[i] = notes.get(i);
//}
notes.toArray(a);//会自己把数组按顺序填好
//要熟悉系统类库里有的方法,无需重复造轮子
return a;
}

}

写出上层程序
Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) {
NoteBook nb = new NoteBook();

nb.add("first");
nb.add("second");
System.out.println(nb.getSize());
System.out.println(nb.getNote(1));

nb.add("third", 1);
System.out.println(nb.getNote(1));
System.out.println(nb.getNote(2));
System.out.println(nb.getSize());

nb.removeNote(1);
String[] b = nb.list();
for (String s : b) {
System.out.println(s);
}
}

}


输出:
1
2
3
4
5
6
7
2
second
third
second
3
first
second

对象数组

1
String[] a = new String[notes.size()];  

对象数组中的每个元素都是对象的管理者而非对象本身
当创建了一个对象数组,只是管理者们被创建了,但对象还没有,得想办法把每个对象创建出来

for-each循环

对于普通数组:

1
2
3
4
5
6
7
8
int[] a = new int[10];
for (int i = 0; i < a.length; i++) {
a[i] = i;//赋值
}
for ( int k : a ) {
System.out.println(k);
k++;//每个k都是a中元素的复制品,不会起作用
}

对于对象数组:
1
2
3
4
5
6
7
8
9
Value[] a = new Value[10]; 
for (int i=0; i< 10; i++){
a[i] = new Value[];
a[i].set(i);
}
for ( Value v : a ){
System.out.println(v.get());
v.set(0);//起作用,因为对象数组存的是对象管理者,v=a[i],v也会成为对象管理者
}

集合容器

集合容器内所有元素都不相同
而且里面的元素不排序

1
2
3
4
5
HashSet<String> s = new HashSet<String>();
s.add("first");
s.add("second");
s.add("first");
System.out.println(s);//容器都可以这样输出

输出:
1
[second, first]

public String toString

在java中只要类中实现了这样一个方法
就可以直接用对象名输出这个对象
容器当中都有这样一个方法

1
2
3
public String toString(){  
return "";
}

Hash表

例子:数字与美元硬币名字对应,查找硬币名称
1=penny
5=nickel
10=dime
25=quarter
50=half-dollar

定义接口:

1
2
3
4
5
public class Coin {
public String getName(int amount){
return "";
}
}

为什么不用switch-case?
体现在代码中的硬编码越少越好

使用Hash表(一种数据结构)
在这个表中,所有东西是以一对值放入的,一个叫做key(键),一个叫做值
一个key对应一个值,可以用key取值
Hash表中的元素没有顺序

Coin.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.HashMap;

public class Coin {
//不能使用int,容器当中所有的类型都得是对象,而不能是基本类型
//Integer是int的包裹类型
private HashMap<Integer, String> coinnames = new HashMap<Integer, String>();

public Coin(){
coinnames.put(1, "penny");//1对应penny
coinnames.put(10, "dime");
coinnames.put(25, "quarter");
coinnames.put(50, "half-dolar");

System.out.println(coinnames.keySet().size());//keySet(),把所有key做为一个HashSet的集合给你,在这个集合可以得到size
System.out.println(coinnames);//也可以直接输出
coinnames.put(50, "五十");//会替换掉前面的
System.out.println(coinnames);
for (Integer k : coinnames.keySet()){//遍历Hash表
String s = coinnames.get(k);
System.out.println(s);
}
}

public String getName(int amount){
if (coinnames.containsKey(amount))
return coinnames.get(amount);
else
return "NOT FOUND";//不判断的话,不存在会返回null
}


}

Main.java
1
2
3
4
5
6
7
8
9
10
11
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int amount = in.nextInt();
Coin coin = new Coin();
String name = coin.getName(amount);
System.out.println(name);
}
}

输出:
1
2
3
4
5
6
7
8
9
10
4
{1=penny, 50=half-dolar, 25=quarter, 10=dime}
{1=penny, 50=五十, 25=quarter, 10=dime}
penny
五十
quarter
dime
dime


继承与子类

媒体资料库设计

和NoteBook一样,需要设计一个类,用类去表达一种媒体(CD,DVD)
然后用一个媒体类的容器去装媒体对象,一个资料库就完成了

CD有什么?
名称:title
艺术家:artist
多少首歌:numofTracks
持续时间:playingTime
是否被借出:gotIt
描述:comment

能做什么?
输出一些信息:print

CD.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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) {
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playingTime = playingTime;
this.comment = comment;
}

public void print() {
System.out.print("CD:");
System.out.print(title+":");
System.out.println(artist);
}
}
Database.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;

public class Database {
private ArrayList<CD> listCD = new ArrayList<CD>();

public void add(CD cd){
listCD.add(cd);
}

public void list(){
for (CD cd : listCD){
cd.print();
}
}

public static void main(String[] args) {
Database db = new Database();
db.add(new CD("abc","aaa",4,60,"bb"));
db.add(new CD("adc","dgh",5,40,"ak"));
db.list();
}
}

现在资料库中已经可以存各种CD媒体了
但我们还想在资料库中存DVD媒体或者其它媒体类型

当然,我们可以再创建一个类表示DVD

DVD.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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) {
this.director = director;
this.title = title;
this.playingTime = playingTime;
this.comment = comment;
}

public void print() {
System.out.print("DVD:");
System.out.print(title+":");
System.out.println(director);
}
}

设计好类后在Database.java里创建一个放DVD的容器,以及配套的方法
Database.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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);
}
//+
public void add(DVD dvd){
listCD.add(dvd);
}

public void list(){
for (CD cd : listCD){
cd.print();
}
//+
for(DVD dvd : listDVD){
dvd.print();
}
}

public static void main(String[] args) {
Database db = new Database();
db.add(new CD("abc","aaa",4,60,"bb"));
db.add(new CD("adc","dgh",5,40,"ak"));
//+
db.add(new DVD("add","eee",45,"qqq"));
db.list();
}
}

现在资料库中能存放两种媒体
运行一下:
输出
1
2
3
CD:abc:aaa
CD:adc:dgh
DVD:add:eee

上面发生了什么?
我们创建了一个资料库类
资料库类里有两个容器,用来存放两种不同类型的媒体的对象管理者

这样的结构虽然能实现我们需要的功能,但DVD和CD类几乎一模一样
出现了很多代码复制,这是代码质量不良的表现
当我们需要修改print,add等方法,就得逐个去改
当我们需要新增一种媒体,就得为它做很多的工作

继承

CD和DVD类很相似,我们可以从中提取一些它们共有的东西封装成一个类Item
Item可以表达CD或者DVD
而Database只需管Item

Item.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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) {
this.title = title;
this.playingTime = playingTime;
this.gotIt = gotIt;
this.comment = comment;
}

public Item(){

}

public void print() {
System.out.print(title+":");
}

}
CD.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
}
DVD.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
// this.title = title;
// this.playingTime = playingTime;
// this.comment = comment;
}

public void print() {
System.out.print("DVD:");
super.print();
System.out.println(director);
}
}
Database.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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);
// }
//
// public void add(DVD dvd){
// listCD.add(dvd);
// }
public 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();
}
}

public static void main(String[] args) {
Database db = new Database();
db.add(new CD("abc","aaa",4,60,"bb"));
db.add(new CD("adc","dgh",5,40,"ak"));
db.add(new DVD("add","eee",45,"qqq"));
db.list();
}
}

运行一下:

输出
1
2
3
CD:abc:aaa
CD:adc:dgh
DVD:add:eee

上面发生了什么?
CD extends Item:CD扩展了Item
即CD变成了Item的子类
这就是继承
CD得到了Item里所有的东西

子类与父类

当父类里的东西是private时
private String title;
子类得到了这个东西,但不能用(可以通过父类的方法去用)
解决办法:将private改成protect
但这样不好,有很多时候父类和子类不在同一个包内

title本来就是父类的东西
可以让title在父类中初始化完,再让子类得到title

1
2
3
public Item(String title) {
this.title = title;
}

在子类构造器中使用super()来得到父类的title
1
2
3
 public CD(String title) {
super(title);
}

super()

当程序初始化对象时,会先运行super()
然后去运行父类的构造器,再回来继续运行自己的构造器

super():去父类调用一个没有参数的构造器
super(<参数>):去父类调用一个有对应参数的构造器

当子类没有super(),会默认去调用父类没有参数的构造器

通过super关键字来实现对父类成员的访问,用来引用当前对象的父类
super.<父类成员>

通过this来区分子类父类中的同名成员
this.aaa(); // this 调用自己的方法
super.aaa(); // super 调用父类方法

子类和子类型

类定义了类型
子类定义了子类型

子类的对象可以被当作父类的对象来使用
-赋值给父类的变量(父类的对象管理者可以管理子类的对象)

-传递给需要父类对象的方法

-放进存放父类对象的容器里


多态

多态变量

所有的对象变量都是多态的(它们能保存不止一种类型的对象,不同时刻可以放不同类型的对象(例如父类的对象变量放子类的对象))
它们可以保存的是声明类型的对象,或声明类型的子类的对象

当把子类的对象赋给父类的变量的时候,就发生了向上造型

每一个java的对象变量,都具有两个类型
一个是声明类型
一个是动态类型
有时候两者是一致的,有时候又不一样

这就是变量的多态(在运行过程中,它所管理的对象类型是会变化的)

造型

造型:把一个类型的对象,赋给另一个类型的变量

对象变量的赋值并不是把一个对象赋给另一个对象(注在c++中可以做两个对象之间的赋值)
而是让这两个对象的管理者去管理同一个对象

1
2
3
4
5
String s = "hello";
//原本这个String类型的对象变量s管理着一个对象
//这个对象里面有个"hello"
s = "bye";
//后来s去管理另一个对象,里面有"bye"

并不是将bye替换掉hello,java不能做这种事

java中”=“的赋值运算,实际上是在改变指向

1
2
3
4
String s = "hello";
String t = "bye";
s = t;
//原本s和t各管理一个对象,现在s和t管理同一个对象,里面有”hello“

当给一个对象变量管理着与它声明(静态)类型不符的对象时,就发生了造型
CD是Item的子类
1
2
3
CD cd = new CD("abc","aaa",4,60,"bb");
Item item = cd;
//把子类的对象赋给父类的变量,让父类的对象变量去管理子类的对象

父类对象是不能直接赋给子类对象变量的
但可以强制把父类对象当成子类的对象,然后去造型
CD是Item的子类
1
2
3
4
5
CD cd = new CD("abc","aaa",4,60,"bb");
Item item = cd;
CD cc = item;//不行。父类对象不能直接交给子类对象变量去管理
CD cc = (CD)item;//行,因为item已经管理着一个CD的对象了
//强制把item的类型当做CD

如果没有Item item = cd;
CD是Item的子类
1
2
3
CD cd = new CD("abc","aaa",4,60,"bb");
//Item item = cd;
CD cc = (CD)item;//编译可以通过,但运行会出错

将一个变量强制造型成另一个类型,然后赋给另一个变量
CD cc = (CD)item;
只有当item这个变量实际管理着CD类型的对象才不会出错

在C语言中,有类似写法,但是是类型转换(对于基本类型int、double,java也能强制类型转换)
int i = (int)10.2;//强制类型转换
这与造型是不同的
类型转换是将10.2变成了10
但造型只是把item当做CD类型来看待
item本身还是Item类型

(类型名)对象名:将一个对象当做这个类型来看待

向上造型

向上造型是特殊的造型,无需写(父类类型)
拿一个子类的对象,当作父类的对象来用
向上造型总是安全的

方法调用的绑定

1
2
3
4
5
public void list(){
for (Item item : listItem){
item.print();
}
}

item每次循环管理的对象不一样,甚至管理的对象的类型也不一样,可以是CD或是DVD
当item管理CD(DVD)类型的对象时,去调用print方法,调用的是CD(DVD)类型里的print

当通过对象变量调用方法的时候,调用哪个方法这件事情叫做绑定
-静态绑定:根据变量的声明类型来决定
-动态绑定:根据变量的动态类型来决定
在成员函数中调用其他成员函数也是通过this这个对象变量来调用的

java默认所有的绑定都是动态绑定

覆盖

子类和父类中存在名称和参数表完全相同的函数,这一对函数构成覆盖关系
通过父类的变量调用存在覆盖关系的函数时,调用变量当时所管理的对象所属的类的函数
这是一种动态绑定

多态总结

多态性是对象多种表现形式的体现

通过一个变量去调用一个函数,我们不去判断变量运行中实际类型是什么,我们只想它能print
多态是同一个行为具有多个不同表现形式或形态的能力
item是CD类型时它这样print,是DVD类型时那样print,但都是print行为


类型系统

Object类

java中所有类都是Object类型的子类
这是一种单根结构

发生继承时,父类所有public的东西子类都会得到
所以java中所有的类,都从Object类中得到了两个函数
-toString()
-equals()

toString()

toString()会返回一个字符串,用来表达对象

当一个类中没有toString()方法时,会调用继承自Object类的toString()

1
2
3
4
5
6
CD cd = new CD("abc","aaa",4,60,"bb");
System.out.println(cd.toString());
System.out.println(cd);//和上面的效果一个月,编译器会知道这个地方需要调用toString()

String s = "aa"+cd;//编译器知道这个地方需要调用toString()
System.out.println(s);

输出
1
2
3
4
CD@3d075dc0
//类型名+一个类似地址、编号的东西
CD@3d075dc0
aaCD@3d075dc0

显然,默认的表达这个对象的toString(),是返回一个类型名+一个类似地址、编号的东西

我们可以在类中自定义一个toString()
即设计一个表达对象的toString()

1
2
3
4
5
6
7
@Override
public String toString() {
return "CD{" +
"artist='" + artist + '\'' +
", numofTracks=" + numofTracks +
'}';
}

输出
1
CD{artist='aaa', numofTracks=4}

equals()

==无法比较两个对象的内容是否相同,只能比较这两个对象变量是否管理着同一个对象
我们需要使用equals()去比较内容

当类中没有equals(),会调用继承自Object类的equals()

1
2
3
CD cd1 = new CD("abc","aaa",4,60,"bb");
CD cd2 = new CD("abc","aaa",4,60,"bb");
System.out.println(cd1.equals(cd2));

输出
1
false

Object这个公共父类的equals()无法知道它的子类长什么样子,所以也无法比较这两个对象内容是否相等
Object的equals()实际上也是在比较两个对象变量是否管理着同一个对象

我们需要使用自定义的equals()去比较内容

1
2
3
4
5
@Override
public boolean equals(Object o) {
CD cc = (CD) o;//将Object o看作是CD类型的
return numofTracks == cc.numofTracks && artist.equals(cc.artist);
}

输出
1
true

@Override

作用:告诉编译器,这个函数覆盖了父类的同属性、同名、同参方法
也可能会在代码界面报错,如果这个函数没有和父类的同名方法有相同属性、参数

不带@Override,如果自定义的equals()和父类的同属性、同名、同参
那么也会覆盖掉父类的,@Override只是起帮助检查作用


可扩展性

现在要往Database这个资料库里增加新的媒体类型,是一件非常容易的事情

VideoGame.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class VideoGame extends Item {
private int numberofPlayers;

public VideoGame(String title, int playingTime, boolean gotIt, String comment, int numberofPlayers) {
super(title, playingTime, gotIt, comment);
this.numberofPlayers = numberofPlayers;
}

public void print() {
System.out.print("VideoGame:");
super.print();
System.out.println(numberofPlayers);
}
}

只需要增加一个子类,然后构造一下,覆盖下方法,父类完全不需要去动
这种特性叫可扩展性:代码无需修改即可扩展去适应新的数据、新的内容

如果需要修改去适应新的数据、新的内容,则叫可维护性


下接JAVA/面向对象学习笔记(2)