封装,继承,多态,抽象,接口 (2).doc_第1页
封装,继承,多态,抽象,接口 (2).doc_第2页
封装,继承,多态,抽象,接口 (2).doc_第3页
封装,继承,多态,抽象,接口 (2).doc_第4页
封装,继承,多态,抽象,接口 (2).doc_第5页
已阅读5页,还剩26页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1.1 封装封装是面向对象编程的三大特征之一。封装就是将通过抽象得到的属性和方法相结合,形成一个有机的整体“类”。封装的目的是增强数据安全性和简化编程,使用者不必了解具体的实现细节,所有对数据的访问和操作都必须通过特定的方法,否则便无法使用,从而达到数据隐藏的目的。封装是面向对象编程语言对客观世界的模拟,客观世界的属性都是被隐藏在对象内部的,外界不能直接进行操作或者修改。譬如:常见的空调电视机等对象,这些对象都是封装好的,普通人只可以通过对小小的按钮操作来控制这些家电;不可以随意打开设备进行修改对象内容的配置。但是专业人员可以修改这些家电,而我们就是要做这些“专家”;如下图所示。操作按钮操作按钮图1.1.1 封装对象1.1.1 为什么需要封装通过第一阶段的学习,我们知道类由属性和方法组成,在类的外部通过本类的实例化对象可以自由访问和设置类中的属性信息,这样不利于属性信息的安全,示例1.1就是如此。示例1.1public class Person public String name; public int age; public void sayHello() System.out.print(你好!); public class Test public static void main(String args) Person p=new Person();=皇帝;p.age=1000;/属性信息可以直接设置p.sayHello();上述代码在第一阶段Java的课程中经常见到,大致一看没什么问题,但是仔细分析过之后会发现:把年龄设置成1000合理吗?由于Person类的属性都是公有的(public),那也就意味着在Person类的外部,通过Person类的实例化对象可以对这些公有属性任意修改,这就使得我们无法对类的属性进行有效的保护和控制。这属于设计上的缺陷,那能不能避免这种情况呢?这就需要用到下面的封装了。1.1.2 现实生活中的封装现实生活中封装的例子随处可见,例如药店里出售的胶囊类药品,我们只需要知道这个胶囊有什么疗效,怎么服用就行了,根本不用关心也不可能去操作胶囊的药物成分和生产工艺。再例如家家户户都用的电视机,我们只需要知道电视机能收看电视节目,知道怎么使用就行了,不用关心也不可能去搞清楚电视机内部都有哪些硬件以及是如何组装的。这些都是现实生活中封装的例子。在刚才的两个例子中,我们可以认为药物成分是胶囊的属性,但是用户不需要也不可能去操作它。我们也可以认为内部硬件是电视机的属性,但是用户也不需要去操作它。这就是现实生活中封装的特征,程序中的封装与此类似。1.1.3 程序中的封装封装就是:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部的信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。简而言之,封装就是将类的属性私有化,并提供公有方法访问私有属性的机制,我们看示例1.2。示例1.2public class Person /将属性使用private修饰,从而隐藏起来private String name;private int age; public void sayHello() System.out.print(你好!); public class Test public static void main(String args) Person p=new Person();=杰克; /编译报错p.age=1000; /编译报错p.sayHello();大致一看这段代码跟前面的代码没什么两样,其实只是做了一点改动,Person类中属性的访问修饰符由public改成了private,即属性私有化了。这样一改就造成了main方法中的两行代码出现了编译错误,因为私有属性只能在其所在类的内部使用,在类的外部就无法访问了。1.1.4 如何实现封装(1) 使用private修饰符将类的属性私有化,如示例1.3所示。使用private修饰的属性只能在类的内部调用,访问权限最小。示例1.3public class Personprivate String name;private int age;(2) 属性被私有化之后在类的外部就不能访问了,我们需要给每个属性提供共有的Getter/Setter方法,如示例1.4所示。示例1.4public class Personprivate String name;private int age;public String getName() /获得name属性的getter方法return name;public void setName(String name) /设置name属性的setter方法 = name;public int getAge() /获得age属性的getter方法return age;public void setAge(int age) /设置age属性的setter方法this.age = age;(3) 在Getter/Setter方法中加入存取控制语句,如示例1.5所示。示例1.5public void setAge(int age) if(age0 & age100)/存取限制this.age = age;elseSystem.out.println(年龄不规范!);上述代码中多次出现了this关键字,this表示当前类对象本身。this.属性表示调用当前对象的属性,this.方法表示调用当前对象的方法。在上面的Setter方法中,因为方法的参数和类的属性同名,所以需要使用this进行区别。从以上示例可以看出,封装实现以下目的:(1) 隐藏类的实现细节。(2) 用户只能通过事先提供的共有的方法来访问属性,从而在该方法中加入控制逻辑,以限制对属性不合理的访问。(3) 可以进行对数据检查,有利于对象信息的完整性。(4) 便于后期的代码修改,有利于提高代码的安全性和可维护性。1.1.5 使用Eclipse生成Getter/Setter方法Java类里的属性的Getter/Setter方法有非常重要的意义,例如某个类里包含了一个名为name的属性,则其对应的Getter/Setter方法名应为setName和getName(即将原属性名的首字母大写,并在前面分别加上get和set动词,这就变成Getter/Setter方法名)。如果一个Java类的每个属性都被使用private修饰,并为每个属性提供了public修饰Getter/Setter方法,这个类就是一个符合Sun公司制定的JavaBean规范类。因此,JavaBean总是一个封装良好的类。编写类是程序人员日常工作中经常要做的事情,但是如果每一次都要手工编写Getter/Setter方法无疑会影响开发效率。Eclipse充分考虑到了这一点并提供了快速生成Getter/Setter方法的功能。在类中编写好私有属性后,在空白地方单击右键会弹出如图1.1.2所示的菜单。图1.1.2 生成Getter/Setter方法依次单击【Source】|【Generate Getters and Setters】菜单就弹出了如图1.1.3所示的对话框。图1.1.3 生成Getter/Setter方法单击【Select All】按钮将所有的属性选中,然后单击【OK】按钮就可以看到Getter/Setter方法已经自动生成了。1.2 构造方法1.2.1 为什么需要构造方法为什么需要构造方法,先看示例1.6public class Test public static void main(String args) Person p=new Person();System.out.print(姓名是:+p.getName()+t年龄是:+p.getAge();上述代码是对经过封装的Person类进行测试,代码非常简单。下面我们看一下这段代码的运行结果,如图1.1.4所示。图1.1.4 运行结果上述代码的运行结果充分说明:当我们使用new关键字创建对象时,属性都有默认值,例如String类型属性的默认值是null,int类型属性的默认值是0。常见数据类型的默认值如下表所示。表1-1-1 常见数据类型的默认值类型缺省值类型缺省值byte(byte)0charu0000 short(short)0float0.0Fint0double0.0Dlong0L对象引用nullbooleanfalse但是,这些默认值毫无意义。那有解决的办法吗?能不能在创建对象时给属性初始化一些有意义的值呢?我们看下面的代码。示例1.7public class Personprivate String name; private int age;public Person() =杰克;this.age=28;/省略Getter/Setter方法我们在示例1.7的Person类中加入了一个特殊的方法Person(),然后再运行上面那个测试类,运行结果就变成了如图1.1.5所示的情况。图1.1.5 运行效果上述代码的运行效果说明,那个特殊的Person()方法实现了对属性的初始化操作,而且这个方法无需我们显式调用。Person()就是构造方法。1.2.2 什么是构造方法构造方法所起的主要作用就是在使用new关键字创建对象时完成对属性的初始化操作。构造方法有三个特征: 没有返回类型。 方法名和类名相同。 无需显式调用。另外需要注意的是:如果一个类没有显式声明构造方法,那么这个类会有一个默认的构造方法,这个构造方法没有参数,方法体也为空。但是如果显式地声明了构造方法,那么这个默认的无参构造方法就不存在了。1.2.3 有参的构造方法通过无参构造方法可以很方便的实现对属性的初始化操作,但也存在一定的弊端:就是通过无参构造方法所创建的对象的属性值都是一样的。那能不能在创建对象时可以灵活得给属性赋不同的值呢?这就需要有参的构造方法,我们看一下示例1.8。示例1.8public class Personprivate String name; private int age;public Person(String name,int age) /有参的构造方法= name;this.age= age;public class Test public static void main(String args) Person p1=new Person(杰克,28); /给参数赋值Person p2=new Person(玛丽,24); /给参数赋值构造方法和普通方法一样是可以带有参数的,通过带参的构造方法就可以灵活的创建对象,每个对象的属性值都不一样。因为构造方法主要用于被其他方法来调用创建对象,并返回该类的实例,所以通常把构造方法设置成public访问权限,从而允许系统中任何位置的类都可以创建该类的对象。除非在一些极端的情况下,我们需要限制创建该类的对象,可以把构造方法设置成其他访问权限:例如设置为protected,主要用于被其子类调用(下一小节讲解),把其设置为private阻止其他类创建该类的实例。1.3 方法重载在第一阶段的学习中我们已经在使用方法重载了,如示例1.9所示。示例1.9public class Test public static void main(String args) System.out.println(100); /int类型System.out.println(true); /boolean类型System.out.println(我爱Java); /String类型同样一个println方法却可以接收不同类型的参数,这是因为在一个类中存在多个println方法,但是这些println方法的参数是不同的,这其实就是方法重载。当我们写的方法满足以下条件时就构成了方法重载: 在同一个类中。 方法名必须相同。 方法参数不同,包括参数个数不同或参数类型不同或参数顺序不同。 与方法的返回类型和访问修饰符没有任何关系。有了方法重载,程序开发人员就不用显式的根据参数去判断该调用哪个方法了,而是由Java系统在运行时自动进行判断和调用。下面以主人训练宠物的案例来说明方法重载的好处;请看示例1.10。示例1.10public class Dog /Dog类private String color;/描述狗的颜色private String name; /描述狗的昵称private float price; /描述狗的价格public Dog(String color, String name) this.color = color; = name;.省略Getter/Setter方法public class Monkey /Monkey类private String color;/描述猴子的颜色private String name; /描述猴子的昵称private float price; /描述猴子的价格public Monkey(String color, String name) this.color = color; = name;.省略Getter/Setter方法public class Master /主人类private String name;public void train(Dog dog)/训练DogSystem.out.println(dog.getColor() + 的 + dog.getName() + 向主人摇尾巴。);System.out.println(dog.getColor() + 的 + dog.getName() + 接飞盘。);public void train(Monkey monkey)/训练MonkeySystem.out.println(monkey.getColor() + 的 + monkey.getName() + 向游人伸手要食物。);System.out.println(monkey.getColor() + 的 + monkey.getName() + 在爬小树。);.省略Getter/Setter方法public class Test /测试类public static void main(String args) /创建Dog对象Dog dog = new Dog(黑色,小黑);/创建Monkey对象Monkey monkey = new Monkey(黄色,阿黄);/创建主人对象Master master = new Master();/调用训练Dog的方法master.train(dog);/调用训练Monkey的方法master.train(monkey);执行效果如图1.1.6所示。图1.1.6 运行效果从上例执行的效果可以看出,在调用重载的方法时;只需把参数dog或者monkey传给train方法,Java系统在运行时会根据方法的类型执行对应的方法。构造方法和普通方法一样也可以构成方法重载,示例1.11就是如此。示例1.11public class Personprivate String name; private int age;public Person() /无参构造方法public Person(String name,int age) /有参构造方法= name;this.age= age;上述代码中有一个无参数的构造方法和有参的构造方法,完全符合方法重载的特征,所以这两个构造方法形成了方法重载。构造方法重载的好处是完成多种初始化行为,因为有些时候在创建对象的时候,如果仅仅知道姓名属性的值可以调用Person(String name)构造方法;如果两个属性的值都知道则可以调用Person(String name,int age)构造方法。构造方法重载提供了创建对象的灵活性;另外要注意的是通常建议保留类的无参数构造方法。1.4 继承1.4.1 为什么需要继承我们现在封装了两个类:Student和Teacher,示例1.12给出了这两个类的参考代码。示例1.12public class Teacher /教员类 private String name; private int age;public Teacher() =张老师;this.age=28;/此处省略getter/setter方法public void sayHello() System.out.print(你好!);public class Student /学生类 private String name; private int age;public Student() =张无忌;this.age=24;/此处省略getter/setter方法public void sayHello() System.out.print(你好!);public void study() System.out.print(学习!);上述代码存在的主要问题是:代码重复,还有上面的Dog类和Monkey类。不管是教员类还是学生类,或者是工人类都需要姓名、年龄等属性,这就会出现很多重复代码。如何有效的解决这个问题呢?我们可以把Teacher类和Student类中相同的属性和方法提取出来放到另外一个单独的Person类中,然后让Teacher类和Student类继承Person类,同时保留自己独有的属性和方法,这就是Java中的继承,如示例1.13所示。示例1.13public class Person /人类private String name;private int age;/此处省略getter/setter方法public void sayHello() System.out.print(你好!);publicclass Teacher extends Person/教员类publicclass Student extends Person /学生类publicvoid study() /学生类独有的方法System.out.print(学习!);在上述代码中,Teacher类没有定义任何属性和方法,Student类只定义了一个study方法,这样一来重复的代码的确没有了,但是会不会影响使用呢?我们看示例1.14的测试代码。示例1.14public class Test public static void main(String args) Teacher t=new Teacher(); /创建一个教员t.setName(张三丰);t.setAge(28);t.sayHello();Student s=new Student(); /创建一个学生t.setName(张无忌);t.setAge(24);t.sayHello();上述代码经过测试是可以正常运行的,这充分说明继承不但解决了代码重复的问题,也保证了我们自定义的类是可以正常使用的。1.4.2 什么是继承图1.1.7现实中的继承图1.1.7给出了一个现实生活中继承的例子,通过这个例子我们可以总结出继承具有如下特征。 为什么说兔子和棉羊是食草动物,而不把兔子和狮子放在一起?因为兔子和棉羊具有相同的特征和行为。这些相同的特征和行为可以抽象出一个父类食草动物。 继承具有树形结构,越顶层的越模糊、越通用,越底层的越具体、越个性。 子类只能直接继承一个父类,一个父类可以有多个子类。就像一个父亲可以有多个儿子,但是一个儿子只能有一个父亲一样。Java只支持单重继承。 树形结构的顺序:兔子是食草动物是正确的,但食草动物是兔子是不正确的。这就是is-a关系。这就是继承的特征,Java中的继承与之相同。那么Java中的子类究竟可以从父类中继承哪些“财产”呢? 子类可以继承父类中使用public和protected修饰的属性和方法,不论子类和父类是否在同一包中。 子类可以继承父类中使用默认修饰符修饰的属性和方法,但子类和父类必须在同一个包中。 子类无法继承父类中使用private修饰的属性和方法。 子类无法继承父类的构造方法。1.4.3 如何实现继承在Java中实现继承很简单,使用extends关键字即可,语法如下所示。publicclassSubClassextends SuperClass /继承SubClass称为子类,SuperClass称为父类、基类或者超类。1.4.4 类的祖先Object在Java中,Object类是所有类的祖先,它处于继承关系的最顶层。在定义一个类时,即使没有使用关键字extends继承Object类,系统也会默认去继承Object类。其实我们在以前使用类时没有注意这一点,如图1.1.8所示。图1.1.8Object类在Teacher类中我们只定义了getter/setter方法和一个sayHello方法,但是在使用时却发现多出来很多其他的方法,例如:toString()、wait()等,这些方法都是从Object类中自动继承下来的。1.5 继承中的构造方法在上一章我们学习了一个特殊的方法构造方法,它的主要作用是在创建对象时完成初始化操作。构造方法在继承关系中的调用比较特殊,我们看一下示例1.15。示例1.15public class Person private String name;private int age;public Person() /构造方法System.out.println(创建一个Person对象); /此处省略getter和setter方法public class Student extends Person /学生类继承Person类public void study() /学生类独有的方法System.out.print(学习!);public class Test public static void main(String args) Student s=new Student(); /创建一个学生s.setName(张无忌);s.setAge(28);s.study();在上述代码中,Student类继承了Person类。在main方法我们中对Student类进行了测试,运行结果如图1.1.9所示。图1.1.9 运行结果这样的运行结果是不是有点出乎意料呢?下面给出继承关系中构造方法的调用规则。 如果子类没有显式调用父类的有参构造方法,则在创建子类对象时会默认先调用父类无参的构造方法。 如果子类显式调用了父类的有参构造方法,则在创建子类对象时会先调用父类相应的有参构造方法,而不会调用无参的构造方法。在继承中我们可以使用super调用父类的构造方法,super代表对父类对象的默认引用,并且super语句必须出现在子类构造方法中的第一行,我们看一下示例1.16。示例1.16public class Person private String name;private int age;public Person()System.out.println(通过无参构造方法创建一个Person对象);public Person(String name,int age)=name;this.age=age;System.out.println(通过有参构造方法创建一个Person对象);public class Student extends Person /Student类继承Person类public Student(String name,int age)super(name,age); /使用super显式调用父类构造方法System.out.println(创建一个Studnet对象);public class Test public static void main(String args) Student s=new Student(张无忌,24); /创建一个学生在Student类的构造方法中,我们使用super语句显式的调用了父类的构造方法,运行结果如图1.1.10所示。要注意的是在子类的构造方法中调用父类的构造方法super语句要写在第一行,因为要先构造一个父类然后才能构造子类对象;就像先有父亲在有儿子一样。在普通方法里没有此限制,super语句可以出现任何位置。图1.1.10 运行结果1.6 方法重写通过前面的学习我们知道子类可以继承父类中的公有方法,但是如果这个方法不能满足子类的需求该怎么办呢?这时我们可以通过在子类中重写父类的方法来解决这个问题。我们看下面示例1.17的代码。示例1.17public class Person private String name;private int age; /此处省略getter/setter方法public void sayHello() System.out.print(你好!);public class Teacher extends Person /教员类继承Person类public void sayHello() System.out.print(教员说:你好!);public class Student extends Person /学生类继承Person类public void sayHello() System.out.print(学生说:你好!);父类Person中定义了一个sayHello()方法,它的两个子类并没有简单的继承这个方法,而是在自身又定义了一个sayHello()方法。那这样会不会编译错误呢?我们看下面示例1.18的测试代码。示例1.18public class Test public static void main(String args) Teacher t=new Teacher(); /创建一个教员t.sayHello();Student s=new Student(); /创建一个学生s.sayHello();上述代码的运行结果如图1.1.11所示。图1.1.11 运行结果图1.1.11的运行结果充分说明了上面的代码能够正常运行。在子类中根据需要对从父类中继承下来的方法进行重写编写,称之为方法重写或方法覆盖(overriding)。方法重写必须满足以下条件: 子类的方法必须和父类中被重写的方法的名称相同。 子类的方法必须和父类中被重写的方法的参数相同,包括参数的个数、数据类型以及顺序。 子类方法的返回类型必须和父类中被重写的方法的返回类型相同或是其子类。 子类方法的访问修饰符权限不能小于父类中被重写的方法的访问修饰符权限。第2章 多态和接口学习内容 多态 接口 抽象类和抽象方法能力目标 理解并会使用多态 掌握抽象类和抽象方法的使用 理解并会使用面向接口编程本章简介上一章我们学习了面向对象三大特征中的封装和继承,本章将继续学习面向对象另外两个重点内容:多态和接口。多态是面向对象编程的核心特征。在实际应用中,通过多态可以提高程序的可扩展性和可维护性。Java不支持多重继承,即一个类同时继承多个类。为了解决这个问题,Java引入了接口。接口表示一种约定或标准。使用接口可以把“做什么”和“怎么做”分离开,使程序具备较好的可扩展性和可维护性。本章将系统的讲解多态和接口的实现方式以及抽象类、抽象方法的基本应用。核心技能部分2.1 多态2.1.1 为什么需要多态多态是OOP中最核心的一个特征。下面我们先通过一个现实生活中的例子来认识一下多态,这个例子模拟的是主人喂养宠物。代码如示例2.1所示。示例2.1public class Pet / 宠物类private String name = 无名氏; / 宠物昵称private int health = 100; / 宠物健康值public void eat()public class Dog extends Pet /狗类继承宠物类public void eat() /重写eat方法System.out.println(狗狗在吃饭.);public class Cat extends Pet /小猫类继承宠物类public void eat() /重写eat方法System.out.println(小猫在吃饭.);public class Master /主人类private String name = ; / 主人名字public void feed(Dog dog) /主人给狗喂食dog.eat();public void feed(Cat pgn) /主人给猫喂食pgn.eat();在上述代码中,Pet类是父类,Dog类和Cat类是子类,并且重写了父类的eat方法。Master类分别为Dog和Cat定义了喂食的方法,这两个方法构成了方法重载。这样一来就实现了主人喂养宠物的功能。但是,假如主人以后会喂养更多的不同种类的宠物时该怎么办呢?比如说现在主人要喂养鹦鹉,我们除了需要先定义一个鹦鹉类之外,还必须在Master类中重载一个给鹦鹉喂食的方法。宠物越多,重载的方法(feed)就越多。那有没有更好的解决办法呢?这就需要使用下面的多态。2.1.2 什么是多态现实生活中存在很多多态的例子。例如:H2O在100摄氏度的高温下是气体,在常温下是液体,在0摄氏度以下是固体。这里的多态是指一个对象具有多种形态,OOP中的多态与之类似。换句话说多态就是:同一个引用类型,使用不同的实例可以执行不同的操作,即父类引用子类对象。我们看一下示例2.2。示例2.2Pet p1=new Dog();p1.eat();Pet p2=new Cat();p2.eat();在上述代码中,我们将子类对象赋值给了一个父类变量,这就是所谓的父类引用子类对象,或者说一个父类的引用指向了一个子类对象。代码在执行时调用的都是子类中重写过的eat方法,而不是父类中的eat方法,这就是多态。2.1.3 如何实现多态我们明白了什么是多态之后,下面就通过多态来优化前面主人喂养宠物的代码,并介绍实现多态的步骤。(1) 实现继承,继承通常是多态存在的前提,如示例2.3所示。示例2.3public class Pet / 宠物类private String name = 无名氏; / 宠物昵称private int health = 100; / 宠物健康值public void eat()public class Dog extends Pet /狗类继承宠物类public class Cat extends Pet /小猫类继承宠物类(2) 子类重写父类的方法,如示例2.4所示。示例2.4public class Dog extends Pet public void eat() /重写eat方法System.out.println(狗狗在吃饭.);public class Cat extends Pet public void eat() /重写eat方法System.out.println(小猫在吃饭.);(3) 父类引用子类对象,如示例2.5所示。示例2.5public class Master /主人类private String name = ; / 主人名字public void feed(Pet p) /通过传参实现父类引用子类对象p.eat();(4) 示例2.6是测试代码。示例2.6public static void main(String a)Dog d = new Dog();Cat c = new Cat();Master m = new Master();m.feed(d);m.feed(c);通过以上几步就实现了使用多态来解决主人喂养宠物的问题。这时不管主人将来喂养多少种宠物,我们只需要定义相应的类继承Pet并重写eat方法就行了,而Master类中始终只需要一个feed方法。使用多态可以提高代码的可扩展性和可维护性,使用父类作为方法形参是实现多态的常用方式。这里要注意的是在进行方法调用时必须调用子类重写过的父类的方法,子类中独有的方法是不能通过父类引用调用到的。2.1.4 向上转型和向下转型示例2.7Pet p1=new Dog();p1.eat();Pet p2=new Cat();p2.eat();上述代码是多态的一个例子,我们称之为父类引用子类对象。实际上父类和子类之间存在一种类型转换问题,称之为“向上转型”。“向上转型”也就是把子类转换成父类,这符合继承中的is-a关系(狗是宠物),所以这时进行了自动类型转换。在使用“向上转型”时我们无法调用子类独有的方法。例如:Dog类有一个独有的方法catchFly(接飞盘),如果像示例2.8这样子写就错了。示例2.8Pet p1=new Dog(); /向上转型p1.eat(); /调用子类中的eat方法p1.catchFly(); /不能调用子类独有的方法,编译错误与“向上转型”相对的就是“向下转型”,即将一个指向子类对象的父类引用赋值给一个子类引用,称之为“向下转型”,也就是把父类转换成子类,此时必须进行强制类型转换。我们看示例2.9的代码。示例2.9Pet p1=new Dog(); /向上转型p1.eat(); /调用子类中的eat方法Dog d=(Dog)p1; /向下转型d.catchFly(); /可以调用子类独有的方法p1经过向下转型被强制转换成了Dog类,这时就可以访问子类独有的方法了。但是在进行向下转型时需要注意一点:p1只能被强转成Dog类,因为p1指向的是Dog对象。例如示例2.10的代码就不正确。示例2.10Pet p1=new Dog(); /向上转型,p1指向一个Dog对象Cat d=(Cat)p1; /编译错误2.2 抽象类和抽象方法2.2.1 抽象类示例2.11Pet p=new Pet();p.eat();示例2.11的代码有问题吗?从语法上来说没有任何问题,也可以正确运行。但是我们仔细想一下,现实生活中有小狗、小猫等具体存在的对象,但是有一种叫宠物的具体对象吗?宠物是我们抽象出来的一个概念,因此上述代码虽然没有问题,但是不符合逻辑。这时我们可以使用abstract关键字来修饰Pet类,即把Pet类改成抽象类,因为在Java中抽象类不能被实例化。示例2.12public abstractclass Pet / 宠物类private String name = 无名氏; / 宠物昵称private int health = 100; / 宠物健康值public void eat()在示例2.12中,我们只需要在class前面加上abstract关键字就把Pet类改成了抽象类,这时如果运行下面示例2.13的代码就会出错。示例2.13public class Test public static void main(String a)Pet p=new Pet(); /抽象类不能被实例化p.eat();2.2.2 抽象方法在继承关系中,子类可以直接继承父类中的公有方法,也可以重写父类中的公有方法。如果在某种情况下必须强制子类重写父类的方法该怎么办呢?这时可以通过Java中的抽象方法来解决,即用abstract关键字来修饰父类方法。我们看下面示例2.14的代码。示例2.14public abstract class Pet / 宠物类private String name = 无名氏; private int health = 100; public abstract void eat(); /抽象方法在返回类型前加上abstract关键字就把eat()方法改成了抽象方法,这时在子类中就必须重写eat()方法,否则报错。一定要注意的是:抽象方法只有方法声明,没有方法体。下面深入说明抽象类为什么不能被实例化,这可以从语义和语法两个方面来解释。(1) 在语义上,抽象类表示从一些具体类中抽象出来的类型。从具体类到抽象类,这是一种更高层次的抽象。例如苹果类、香蕉类和桔子类是具体类,而水果类则是抽象类,在自然界并不存在水果类本身的实例,而只存在它的具体子类的实例。Fruit fruit = new Apple();/创建一个苹果对象,把它看做水果对象在继承关系树上,总可以把子类的对象看做父类的对象,例如苹果对象是水果对象,香蕉对象也是水果对象。当父类是具体类时,父类的对象包括父类本身的对象及所有具体子类的对象;当父类是抽象类时,父类的对象包括所有具体子类的对象。因此,所谓的抽象类不能被实例化,是指不能创建抽象类本身的实例。尽管如此,可以创建一个苹果对象,并把它看做水果对象。(2) 从语法上,抽象类中可以包含抽象方法。例如Pet类的eat()方法,假如允许创建抽象类本身的实例:Pet pet = new Pet(); /假定编译器未报错

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论