第七章-继承与多态
类的成员:指类的方法和实例变量。 子类继承父类的成员,子类可以使用覆盖代替父类的同名方法(覆盖只是针对方法而言的技术)。
继承的范例:
public class Doctor{
boolean worksAtHostipal;
void treatPatient(){
//执行检查
}
}
//家庭医生是医生的子类
public class FamilyDoctor extends Doctor{
boolean makeHouseCalls;
void giveAdvice(){
//提出诊断
}
}
覆盖的规则:
- 方法的参数和返回要和父类方法保持一致,即参数必须要一样,且返回类型必须要兼容。兼容指的是,不管父类声明的返回类型是什么,子类必须要声明返回一样的类型或该类型的子类(子类对象得保证可以执行父类的一切);
- 不能降低方法的存取权限,这指的是存取权限必须相同,或者更为开放。举个例子,你不能覆盖掉一个公有的方法并将其标记为私有()。
下面这个图说法没看懂????? 后来看懂了,
Animal myDog=new Dog();方法调用,编译时看的是引用类型(Animal)的方法,运行时看的是堆上对象(Dog)的方法。所以要遵守覆盖的规则。根据继承关系可以画出树形图。Java虚拟机会从继承关系的树形图的最下面开始搜索方法,这样不会忽略子类的覆盖。
子类可以继承父类的方法,但父类拿不到子类的方法。
如果子类中还打算引用父类的方法并再加上额外的行为(不打算完全覆盖掉原来的方法):
public void roam(){
//下面这条语句会先执行父类的该方法
super.roam();
//子类额外的行为
}
//例子
//抽象类中可以有非抽象的方法
abstract class Report {
void runReport() {
//设置报告
}
void printReport() {
//输出
}
}
class BuzzwordsReport extends Report {
void runReport() {
//super关键字调用父类方法
super.runReport();
buzzwordsCompliance();
printReport();
}
void buzzwordsCompliance() {...}
}
以下4种存取权限,左边最受限制,越到右边限制越小: private default protected public
public类型的成员会被继承,private类型的成员不会被继承。
继承的意义: 使用继承可以避免程序中出现重复的代码; 定义出共同的协议(父类是所有子类的共同协议);
多态(polymorphism)
多态的意义:当我定义一组类的父型时,我可以用子型的任何类来填补任何需要或期待父型的位置。 多态的用处:比如通过声明父型类型的对象引用来引用它的子型对象。 在多态下,引用和对象可以是不同的类型(引用类型可以是实际对象类型的父类)。
//Animal是Dog的父类
Animal myDog=new Dog();
Animal[] animals=new Animal[5];
animals[0]=new Dog();
animals[1]=new Cat();
animals[0].eat(); //调用Dog的eat()
animals[1].roam(); //调用Cat的roam()
此外,参数和返回类型也可以多态。 子类的对象可以传入声明为父类对象引用变量的参数位置。
通过多态,我可以写出引进新型子类时也不必修改的程序(我们将参数声明成父类类型,我就可以在运行时传入任何的子类对象)。
方法的重载(overload): 重载的意义是两个方法的名称相同,但参数不同,重载和继承或多态毫无关系,和覆盖方法也不一样。 重载可以有同一种方法的多个不同参数版本以方便调用。
- 重载方法的返回类型可以不同:只要使用不同的参数,重载方法就可以任意改变返回类型(参数相同的话,返回类型只能是父类版返回类型的子类)。
- 重载方法可以改变存取权限:可以任意改变。
//重载的范例
public class Overloads{
String uniqueID;
public int addNums(int a,int b){
return a+b;
}
public double addNums(double a,double b){
return a+b;
}
public void setUniqueID(String theID){
//许多验证码,然后:
uniqueID=theID;
}
public void setUniqueID(int ssNumber){
String numString=""+ssNumber;
setUniqueID(numString);
}
}
第八章-接口与抽象类(深入多态)
接口是一种纯抽象的类,是无法初始化的类。
抽象类的标记 abstract
有些类不该被初始化,比如:
//有意义的对象
Animal anim=new Wolf();
//这个Animal类的对象就没有意义
Animal anim=new Animal();
我们需要声明Animal这个类为引用来继承和产生多态,但又不需要这个类的对象。所以我们需要限制只有Animal的子类才能被初始化。通过标记类为抽象类的,编译器就知道不管在哪里。这个类就是不能创建任何类型的实例。
当我们设计继承结构时,我们必须要知道哪些类是抽象的,哪些类是具体的。
//声明抽象类Canine(是Animal的子类)
abstract class Canine extends Animal{
public void roam() {}
}
//声明抽象类的引用
Canine c;
//正确
c=new Dog();
//错误
c=new Canine();
抽象的方法: 除了类之外,也可以将方法标记为abstract的。
抽象的类代表此类必须要被extend过,抽象的方法代表此方法必须要被覆盖过。
方法没有通用的实现是可行的。抽象类中的某些方法在没有特定的运行时不会有任何意义。
抽象方法只能在抽象的类里面。 如果声明出一个抽象的方法,就必须将类也标记为抽象的。而抽象的类可以带有抽象和非抽象的方法。 例如抽象类Canine可以实现Animal的抽象方法,这样Dog就不必实现这个部分。但若Canine没有实现Animal中的抽象方法,那么Dog类就必须自己实现。
抽象的方法没有实体,因为没有意义(一定会被覆盖)。 他只是为了标记多态而存在,这表示在继承数结构下的第一个具体的类必须要实现所有的抽象方法。
//抽象方法实例,没有具体的实现,直接以分号结束
public abstract void eat();
抽象的类的意义:有时就是没办法给出对任何子类都有意义的共同程序代码,这种情况下,抽象类仍可以定义出一组子型共同的协议。
抽象的目的或者说好处是:就是实现多态。 使用父类作为方法的参数,返回的类型或数组的类型。通过这个机制,我就可以加入新的子型到程序中,而不必额外修改程序。
ArrayList类也是通过多态实现的,它通过对象这个类型来操纵所有类型的对象。 在Java中所有的类都是从Object这个类中继承出来的,Object是所有类的父类。 Object类包含一些通用方法。
//用于判断两个方法是否相等,相等的定义见附录B
equals(Object o)
//列出对象的哈希代码
hashCode()
//告知该对象是从哪里被初始化的
getClass()
//列出类的名称和一个我们不关心的数字
toString()
从定义上看,Object类并不是Java的抽象类,因为它没有必须被覆盖的方法。
Object类不是抽象类,创建一个Object类的对象看起来不是很合理。但是我们有时就是需要一个通用的对象,一个轻量化的对象,它(Object类的对象)最常见的用途是用在线程的同步化上面(见15章)。
Object类主要有两个目的:
- 作为多态让方法可以应付多种类型的机制;
- 提供Java在执行期对任何对象都有需要的方法的实现程序代码(让所有的类都能继承到),有一部分方法与线程有关。
既然Object类是所有对象的父类,那我们为什么不将所有的返回类型,参数类型都设为Object类的引用,这样泛用性不是很广么?
注意使用Object类型的多态引用会付出代价:
//Dog类引用
Arraylist<Dog> myDogArrayList=new ArrayList<Dog>();
Dog myDog=new Dog();
myDogArrayList.add(myDog);
Dog herDog=myDogArrayList.get(0);
//Object类引用使用多态
Arraylist<Object> myDogArrayList=new ArrayList<Object>();
Dog myDog=new Dog();
myDogArrayList.add(myDog);
//这句会报错,无法通过编译
//对Arraylist<Object>调用set()方法会返回Object类型,编译器无法确认它是Dog
Dog herDog=myDogArrayList.get(0);
任何从Arraylist<Object>中取出的东西都会被当做Object类型的引用而不管它原来是什么。
类似的,任何从Arraylist<Integer>中取出的东西都会被当做Integer类型的引用而不管它原来是什么,原来是int。
public void go(){
Dog myDog=new Dog();
//这句会报错,虽然这个方法会返回同一个Dog,但编译器以为这只能赋给Object类型的变量
Dog herDog=getObject(myDog);
}
public Object getObject(Object 0){
//返回同一个引用,但是类型已经变成Object了
return o;
}
public void go(){
Dog myDog=new Dog();
//这句正确
Object herDog=getObject(myDog);
}
上面说明了当一个对象被声明为Object类型的引用变量所引用时,它便无法再赋值给原来类型的引用变量。
当我把对象放进Arraylist<Object>时,不管他原来是什么,你只能把他当做是Object。从Arraylist<Object>中取出引用时(实际存的是对象的引用,而不是对象本身),引用的类型只会是Object。
Object o=new Dog();
//正确,Object本来就有hashCode()这个方法
int i=o.hashCode();
//错误,编译器不知道该对象实际是Dog,只知道对象引用是Object类型
o.bark();
//如果想要Dog特有的方法,就必须要将类型声明为Dog
//如果程序员能确定它就是个Dog对象,那么就可以从Object中拷贝出一个Dog引用,并且赋值给Dog引用变量
//如下面这条语句,类似强制转换,Dog(o)将类型转换为Dog
Dog hisdog=(Dog)o;
//这下可以使用Dog的方法
d.bark();
//如果不能确定它是Dog,可以使用instanceof这个运算符来检查
//如果类型转换错误,我会在执行期间遇到ClassCastException异常并且终止。
if(o instanceof Dog){
Dog d=(Dog) o;
}
编译器是根据引用类型来判断有哪些method可以调用,而不是根据对象实际的类型(编译器只管引用的类型,不管对象类型),运行时实际调用的是对象的方法。
通过上面的说明,可以体现Java非常注意引用变量的类型。 你只有在引用变量的类确实有该方法时才能调用它。
在编写类的时候,可以将一些方法标记为公有,这样这个方法就可以给其他程序使用。 一个常见的场景:以前为其他事情编写了一个类,现在新的事情可以用上这个类中的一些方法,则声明一个引用变量引用到以前的类的实例,这样通过该引用变量即可调用以前以前的类的方法。
现在我们有如下的继承树:
接口
现在有个问题:如何给狗和猫加入亲热和耍宝的方法。 要求:
- 方法只应用在宠物上;
- 确保所有宠物的类都拥有相同的方法定义的方法;
- 方法可以运用多态。
最好的解决办法就是使用接口(Java的interface关键词)实现多重继承(接口可以解决多重继承的致命方块问题)。Java不允许多重继承,只允许使用接口。
Java的接口类似于是个100%的纯抽象类(所有接口的方法都是抽象的,所以其任何子类都必须要实现这些方法)。
实现某接口的类必须实现接口的所有方法(这些方法都是public和abstract的)
接口的目的就是实现多态。 比如我以接口取代具体的子类或者抽象的父类作为参数或返回类型,则我就可以传入任何有实现该接口的东西。
//接口的定义,使用interface代替class
public interface pet {...}
//接口的实现,使用implements这个关键词,注意实现接口必须在继承的前提下
public class Dog extends Canine implements pet {...}
public interface pet {
//接口的方法一定是抽象的,所以方法没有内容,直接以分号结束
public abstract void beFriendly();
public abstract void play();
}
public class Dog extends Canine implements pet {
//实现接口的方法
public void beFriendly() {...}
public void play() {...}
//一般的覆盖方法
public void roam() {}
public void eat() {}
}
父类和接口的区别:extend只能有一个,implement可以有好多个(一个类只能有一个父类,但可以有多个接口)。
如何判断应该使用类,子类,抽象类,或者接口?
- 如果新的类无法对其他类通过IS-A测试,就设计不继承其他类的类。
- 只有在需要某类的特殊化版本时,以覆盖或增加新的方法来继承现有的类;
- 当我需要定义一群子类的模板,又不想让程序员初始化此模板时,设计出抽象的类给它们用;
- 如果想要定义出类可以扮演的角色,则使用接口。
一个遗留问题:为什么ArrayList<Dog>返回的引用无需转换(就是Dog类的引用),但是方法中使用的是Object而不是Dog。简单回答:编译器帮我做了类型转换,对编译器来说是个禁止将Dog类型以外的对象装进ArrayList<Dog>的标记,所以编译器认为将ArrayList中取出的对象引用转换为Dog类型是绝对安全的。(具体会在Collection章节中说明)