wedge
一、继承
/封装的概念比较简单,这里略过去了/
1)特点
- 【继承】子类拥有父类的 非private (public & protected)的属性和功能;
- 【扩展】子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
- 【重写】子类还可以以自己的方式实现父类的功能
2)构造方法
子类从它的父类中继承 非private 的方法、属性等,但对于构造方法,有一些特殊,它不能被继承,只能被调用。对于调用父类的成员,可以用super
关键字。
3)优点
继承的意义主要体现在【修改】上:
不用继承的话,如果要修改功能,就必须在所有重复的方法中修改,代码越多,出错的可能就越大;
而继承的优点是,继承使得所有 子类公共的部分都放在了父类,使得代码得到了共享,这就避免了重复,另外,继承可使得修改或扩展继承而来的实现都较为容易;
4)缺点
- 继承是有缺点的,那就是 【父类变,则子类不得不变】。比如让狗去继承于猫,显然不是什么好的设计;
- 另外,继承会【破坏包装】,父类的实现细节会被暴露给子类;
二、多态
1)概念
不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行
2)实现方式
子类可以选择使用override关键字,将父类实现替换为它自己的实现,这就是方法重写。
3)原理——静/动态类型
比如在
Animal ani = new Cat("cat")
中:编译时类型(静态类型)是 Animal,运行时类型(动态类型)是 Cat;编译时类型决定了你可以调用哪些方法而不引起编译错误,而运行时类型决定了实际执行的是哪个类的方法实现;
三、重构
1)引入
现在我们有一个Animal的父类,四个分别为猫狗牛羊的子类,比如牛类是这样实现的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Cattle extends Animal {
//默认构造函数
public Cattle() {
super();
}
//带参构造函数
public Cattle(String name) {
super(name);
}
//多态方法
public String shout() {
String result = "";
for (int i = 0; i < this.shoutNum; i++) {
result += "哞";
}
return "我的名字叫" + name + " " + result;
}
}
其中shout()
方法是多态的,四个子类分别有自己的叫声———喵汪咩哞。
但是如果我们要修改这个shout()
方法呢,比如我们想把”我的名字叫”改成“我叫”?
由于每个子类都各自重写了这个方法,所以我们只能修改每一个子类中的这个shout()
方法。
这无疑是低效的,解决方案是重构。
2)方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Animal {
...
public String shout() {
String result = "";
for(int i = 0; i < this.shoutNum; i++) {
result += getShoutSound() + ", ";
}
return "我的名字叫" + name + " " + result;
}
protected String getShoutSound() {
return "..."; //这个返回值由各子类自己来写
}
}
这样,shout()
方法就变成了一个非多态的方法,而由getShoutSound()
来实现多态,所以我们就可以直接在Animal类中改变shout()
方法,而四个子类的shout()
方法都会随之变动。
四、抽象类
1)引入
你会发现, Animal类其实根本就不可能实例化的,你想呀,说一只猫长什么样,可以想象,说new Animal();即实例化一个动物。一个动物长什么样?
所以我们完全可以考虑把实例化没有任何意义的父类,改成【抽象类】;
同样地,对于Animal类的getShoutSound方法,其实方法体没有任何意义,所以可以将修饰符改为abstract,使之成为【抽象方法】;
2)特点
- 抽象类不能实例化
- 抽象方法必须被子类重写
- 如果类中包含抽象方法,那么类就必须定义为抽象类,不论是否还包含其他一般方法
- 应该考虑让抽象类拥有尽可能多的共同代码,拥有尽可能少的”数据”
五、接口
1)引入
如果我们的动物运动会还要举办一场特殊的比赛:让拥有特异功能的动物们表演一个特技————从口袋里变出东西,我们定义为changeThing()
函数。
显然,不是所有动物都有这个特异功能,所以不能把这个函数写在抽象类Animal中。但是在具有特异功能的动物里一个个写也有点麻烦,那怎么办?
2)定义
- 接口是把隐式公共方法和属性组合起来,以封装特定功能的一个集合;
- 一旦类实现了接口,类就可以支持接口所指定的所有属性和成员;
- 声明接口在语法上与声明抽象类完全相同,但不允许提供接口中任何成员的执行方式;
- 所以接口不能实例化,不能有构造方法和字段;不能有修饰符,比如public、private等;不能声明为虚拟的或静态的等;
- 实现接口的类就必须要实现接口中的所有方法和属性;
- 定义接口时,在接口名前面加一个
I
是规范命名方式;
3)举例
声明一个IChange接口
1
2
3
public interface IChange{
public String changeThing(String thing);
}
然后我们来创建机器猫的类
1
2
3
4
5
6
7
8
//注意:子类可以同时extends一个父类并implements多个接口
public class MachineCat extends Cat implements IChange{
...
//实现接口的方法
public String changeThing(String thing){
return super.shout() + ", 我有万能的口袋,我可变出" + thing;
}
}
1
2
3
4
5
6
7
8
9
10
11
//创建两个类的实例
MachineCat mcat =new MachineCat("叮当");
StoneMonkey wukong =new StoneMonkey("孙悟空");
//声明了一个接口数组,将两个类的实例引用给接口数组
IChange[]array =new IChange[2];
array[0]=mcat;
array[1]=wukong;
//利用多态性,实现不同的changeThing
System.out.println(array[0].changeThing("各种各样的东西!"));
System.out.println(array[1].changeThing("各种各样的东西!"));
同样是飞,鸟用翅膀飞,飞机用引擎加机翼飞,而超人呢?举起两手,握紧拳头就能飞;
它们是完全不同的对象,但是,如果硬要把它们放在一起的话,用一个飞行行为的接口,比如命名为IFly的接口来处理就是非常好的办法;
4)接口vs.抽象类
- 抽象类可以给出一些成员的实现,接口却不包含成员的实现;
- 抽象类的抽象成员可被子类部分实现,接口的成员需要实现类完全实现;
- 一个类只能继承一个抽象类,但可实现多个接口等;
- 类是对对象的抽象,抽象类是对类的抽象,接口是对行为的抽象;
- 如果行为跨越不同类的对象,可使用接口;对于一些相似的类对象,用继承抽象类;
- 实现接口和继承抽象类并不冲突,我完全可以让超人extends人类,再implements飞行接口
- 从设计角度讲,抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类,而接口是根本不知子类的存在,方法如何实现还不确认,预先定义
六、集合
由于数组的局限性,Java提供了用于数据存储和检索的专用类,这些类统称集合。这些类提供对堆栈、队列、列表和哈希表的支持,大多数集合类实现相同的接口。