第十天 面向对象-继承&抽象类【悟空教程】 第10天 面向对象 继承是面向对象的核心特性,是面向对象的学习重点。同时继承是代码复用的重要方式,可以表示类与类之间的关系,是所有面向对象语言不可缺少的组成部分。 当一个类的属性与行为均与现有类相似,属于现有类的一种时,这一个类可以定义为现有类的子类。 或者换成相反的角度来看,如果多个类具有相同的属性和行为,我们可以抽取出共性的内容定义父类,这时再创建相似的类时只要继承父类即可。 子类拥有父类的所有属性与方法,无需重新定义。并且可以直接使用非私有的父类成员。 从类与类之间的设计关系来看,子类必须属于父类的一种时,才会继承。 我们在完成一个庞大项目体系的时候,都是将共性的内容抽取出,后续构建过程是从各种父类“向外”扩散的。 下例展示了一个继承关系 图1-1 动物继承关系图 动物类可以有姓名、年龄的成员变量,可以有吃饭、睡觉的方法。 所有猫科与犬科均有动物的成员变量与成员方法,且猫科与犬科均属于动物,所以猫科与全科均可以继承动物类。 猫科可以在动物的基础上再添加抓老鼠的方法 犬科可以在动物的基础上再添加看门的方法 犬科与猫科仍可以继续出现子类,如波斯猫、巴厘猫、沙皮狗、斑点狗等,而其子类仍可以再出现该品种的特性。 class 子类 extends 父类{ //父类的非私有方法与属性均继承过来 } 如: 父类的定义: class Person{ private String name; public void eat(){ System.out.println(“吃饭”); } get/set方法 } 子类继承父类的定义: class Chinese extends Person{} 继承关系的产生通常是为了定义出功能更为具体、更为强大的子类。所以,定义子类后,一般创建子类对象使用。子类可以直接使用父类非私有的成员变量与成员方法 (注:如果成员变量没有使用private修饰,则子类也可直接访问。) 子类的使用: class Test{ public static void main(String[] args) { Chinese c = new Chinese(); c.setName(“张大力”); String name = c.getName(); System.out.println(name);//打印结果为张大力 c.eat(); //打印结果吃饭 } Java只支持单继承,不支持多继承。即只能有一个父类。 父类可以继续有父类。 所有类均有父类,只有Object类没有父类。 在所有使用父类类型的地方均可以传入其子类对象。 /* * Animal的类 * 属性 * name * age * 行为 * 吃 * 睡 */ public abstract class Animal { //成员变量 private String name; private int age; // //吃 // public abstract void eat(); //睡 public void sleep(){ System.out.println("睡"); } //-----------get/set------------------- public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } /* *定义一个猫类 * 属性 * name * age * kind * 行为 * 吃 * 睡 * 抓老鼠 */ public class Cat extends Animal{ private String kind; // @Override // public void eat(){ // System.out.println("猫吃鱼"); // } //猫特有的功能 抓老鼠 public void catchMouse(){ System.out.println("抓耗子"); } public String getKind() { return kind; } public void setKind(String kind) { this.kind = kind; } } /* *定义一个狗类 * 属性 * name * age * kind * 行为 * 吃 * 睡 * 看门 */ public class Dog extends Animal{ private String kind; // @Override // public void eat(){ // System.out.println("狗吃肉"); // } //狗特有功能 看门 public void lookDoor(){ System.out.println("看门"); } public String getKind() { return kind; } public void setKind(String kind) { this.kind = kind; } } /* * 自定义类型 家 * * 地址 * * 行为 * 在家吃饭 */ public class Home { private String address; //动物在家吃饭 //在所有使用父类类型的地方均可以传入其子类对象。 public void eatAtHome(Animal a){ //调用Animal的eat方法 //a.eat(); } //狗在在家吃饭 // public void eatAtHome(Dog dog){ // System.out.println("狗在家吃了"); // //调用狗的eat方法 // dog.eat(); // } //猫在家吃饭 // public void eatAtHome(Cat cat){ // System.out.println("猫在假吃了"); // //调用猫的eat方法 // cat.eat(); // } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } /** * 测试家类 */ public class Test { public static void main(String[] args) { // 创建Home对象 Home home = new Home(); // Animal a = new Animal(); // home.eatAtHome(a); //在所有使用父类类型的地方均可以传入其子类对象。 Dog d = new Dog(); home.eatAtHome(d); Cat c = new Cat(); home.eatAtHome(c); } } 当子类继承父类后,拥有了父类的成员并可以直接调用父类非私有方法。如果子类认为父类提供的方法不够强大,子类可以按照子类自身的逻辑重新定义继承过来的父类方法,这个重新定义一个方法的过程叫做方法重写。 (注:在学习完多态和抽象类后我们会对方法重写有更深的理解) 子类中定义与父类一样的方法便将父类的方法重写了。此时,当创建子类对象,调用方法时,会调用子类重写后的方法。如: 子类中定义与父类一样的方法声明即是方法重写。 如: 父类定义: public class Person{ private String name; public void eat(){ System.out.println(“吃饭”); } get/set } 子类定义: public class Chinese extends Person{ @override //@override是用于强制规定当前定义的方法一定为重写的方法 public void eat() { System.out.println(“按照中国的习惯,使用筷子吃”); } } 子类的使用: public class Test{ public static void main(String[] args) { Chinese c = new Chinese(); c.setName(“张大力”); //父类继承方法直接调用 String name = c.getName(); //父类继承方法直接调用 System.out.println(name); //打印结果为张大力 c.eat(); //方法重写后调用的为重写后的方法 //打印结果:按照中国的习惯,使用筷子吃 } } /** * 自定义类型 人类 * * 姓名 年龄 */ public class Person { String address; private String name; private int age; public void eat(){ System.out.println("吃"); } public void sleep(){ System.out.println("睡"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } /* * 定义类型 学生类 * * 姓名 年龄 * * 继承 * 概念1 当定义一个类时,发现现有类与要定义的类类似,并且要定义的类属于现有类的一种时, * 就可以将这个类定义为现有类的子类 * * 子类拥有父类的所有属性与方法,无需重新定义。并且可以直接使用非私有的父类成员。 * * 父类私用的成员变量 可以使用get/set方法 访问 * 父类私有的方法 没有办法直接方法 虽然继承了 但是相当于没有 * * 子类可以有自己特有的属性和方法 * * 方法重写 * 子类继承父类后,可以直接使用父类的非私有成员,但是如果觉得父类的成员方法不够强大,子类可以按照自身的逻辑 * 将继承过来的父类方法,进行重写(方法重写,方法复写,方法覆盖) * * 可以使用@Override来验证你的方法是不是重写方法。 */ public class Student extends Person{ private String number; public void method(){ System.out.println(address); System.out.println(getName()); } //重写父类eat方法 @Override public void eat(){ System.out.println("学生吃学生套餐"); } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } } /* * 测试继承后的Studnet */ public class Test { public static void main(String[] args) { //创建Studnet对象 Student s = new Student(); s.setName("柳柳"); s.setNumber("0900112"); s.eat(); // s.sleep(); String name = s.getName(); System.out.println(name); System.out.println(s.getNumber()); System.out.println("-----------------"); //子类调用自己特有的方法 s.method(); } } 子类重写方法时,在声明前加@Override可检测该方法是否为重写的方法 访问权限相同或子类方法访问权限更大(访问权限顺序public>默认) class Fu{ void show(){} public void method(){} } class Zi extends Fu{ public void show(){} //编译运行没问题 void method(){} //编译错误 } 方法名称必须相同 参数列表必须相同 返回值为基本类型时必须相同 返回值为引用类型时相同或子类小(了解) /* * 方法重写的注意事项 * 子类重写方法时,在声明前加@Override可检测该方法是否为重写的方法 * 访问权限相同或子类方法访问权限更大(访问权限顺序public>默认) * 方法名称必须相同 * 参数列表必须相同 * 返回值为基本类型时必须相同 * 返回值为引用类型时相同或子类小(了解) */ public class Fu { public void method(){ System.out.println(" 父类方法"); } public int sum(){ return 0; } public Person get(){ return null; } } public class Zi extends Fu{ //访问权限相同或子类方法访问权限更大(访问权限顺序public>默认) @Override public void method(){ System.out.println("子类方法"); } //返回值为基本类型时必须相同 @Override public int sum(){ return 100; } //返回值为引用类型时相同或子类小(了解) @Override public Student get(){ return null; } } 抽象类用来描述一种类型应该具备的基本特征与功能, 具体如何去完成这些行为由子类通过方法重写来完成,如: 犬科均会吼叫,但属于犬科的狼与狗其吼叫内容不同。所以犬科规定了有吼叫功能,但并不明确吼叫的细节。吼叫的细节应该由狼与狗这样的犬科子类重写吼叫的方法具体实现。 类似上边犬科中的吼叫功能,并不明确实现细节但又需要声明的方法可以使用抽象方法的方式完成。即抽象方法指只有功能声明,没有功能主体实现的方法。 具有抽象方法的类一定为抽象类。 那么犬科就可以定义为抽象类,吼叫方法为抽象方法,没有方法体。 抽象类定义的格式: abstract在class前修饰类: abstract class 类名 { } 抽象方法定义的格式: abstract在访问权限后,返回值类型前修饰方法,方法没有方法体: public abstract 返回值类型 方法名(参数); 看如下代码: //员工 abstract class Employee{ public abstract void work();//抽象函数。需要abstract修饰,并分号;结束 } //讲师 class Teacher extends Employee { public void work() { System.out.println("正在讲解Java"); } } //助教 class Assistant extends Employee { public void work() { System.out.println("正在辅导学生"); } } //班主任 class Manager extends Employee { public void work() { System.out.println("正在管理班级"); } } 抽象类无法直接创建对象,只能被子类继承后,创建子类对象。 子类需要继承抽象父类并完成最终的方法实现细节(即重写方法,完成方法体)。而此时,方法重写不再是加强父类方法功能,而是父类没有具体实现,子类完成了具体实现,我们将这种方法重写也叫做实现方法。 抽象类的意义 抽象类往往用来表示对问题领域进行分析、设计中得出的抽象概念。其存在的意义在于其设计性、复用性与扩展性。 抽象类方便了具体类的定义。 抽象类仅是对功能和属性的声明,表示这类事物应该具备这些内容。限制程序员不能直接创建该抽象类对象,必须定义其子类才可使用。如我们可以听一只狼的叫声,也可以听一只狗的叫声,但是如果我们听一只犬科的叫声就显然不合适了。 抽象类继承细节 只有覆盖了抽象类中所有的抽象方法后,其子类才可以实例化。如果存留未实现的抽象方法则该子类仍为一个抽象类,无法创建对象。 抽象类不一定包含抽象方法。 抽象类可以有非抽象方法。 /* * 犬科 * * 行为 * 吼叫 */ public abstract class Quan { public abstract void houJiao(); public abstract void sleep(); public void eat(){ System.out.println("吃"); } } /* * 抽象类的注意事项: * 子类必须重写父类的所有抽象方法,如果有抽象方法没有重写,就相当于将抽象方法继承过来,子类里面有抽象方法, * 有抽象方法的类必须是抽象类,子类也得定义为抽象类 * * 抽象类不一定包含抽象方法。 * 抽象类可以有非抽象方法。 */ public class Dog extends Quan{ public void eat(){ System.out.println("狗吃肉"); } @Override public void houJiao() { System.out.println("狗汪汪叫!"); } @Override public void sleep() { System.out.println("趴着睡"); } } public class Test { public static void main(String[] args) { //抽象类不能创建对象 //Quan q = new Quan(); //可以创建子类对象 Dog d = new Dog(); d.houJiao(); } } 题目要求: 设计抽象父类与具体子类。 动物类: 属性:年龄、姓名 方法:吃饭、睡觉 猫科类: 动物类基础上添加产地 添加抓老鼠的方法 波斯猫、巴厘猫、卡通猫与猫科类类似 犬科: 与猫科类类似 对于动物类与犬科猫科,均为抽象的概念,规定了该种类型体系中所应该具备的通用基本功能与属性。而实际生产中不会创建动物、犬科、猫科的对象,而会使用更为具体的实现类对象。 定义抽象类动物、犬科、猫科。 定义该三种类型的具体子类。 在测试类中,测试子类属性与子类方法。 /* * 自定义数据类型 动物类 * * 属性 * 年龄 姓名 * 行为 * 吃饭 睡觉 */ public abstract class Animal { private String name; private int age; //吃 public abstract void eat(); //睡 public abstract void sleep(); public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public abstract class Cat extends Animal{ private String address; public void catchMouse(){ System.out.println("抓老鼠!"); } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } public class KTCat extends Cat{ @Override public void eat() { System.out.println("卡通猫吃卡通鱼!"); } @Override public void sleep() { System.out.println("卡通猫站着睡"); } } public class Test { public static void main(String[] args) { KTCat kt = new KTCat(); kt.setName("kitty"); kt.setAge(2); kt.eat(); kt.sleep(); kt.catchMouse(); System.out.println(kt.getName()); System.out.println(kt.getAge());; } } 继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。 好处: (1)继承关系是传递的。若类C继承类B,类B继承类A(多层继承),则类C既有从类B那里继承下来的属性与方法,也有从类A那里继承下来的属性与方法,还可以有自己新定义的属性和方法。继承来的属性和方法尽管是隐式的,但仍是类C的属性和方法。继承是在一些比较一般的类的基础上构造、建立和扩充新类的最有效的手段。 (2)继承简化了人们对事物的认识和描述,能清晰体现相关类间的层次结构关系。 (3)继承提供了软件复用功能。若类B继承类A,那么建立类B时只需要再描述与基类(类A)不同的少量特征(数据成员和成员方法)即可。这种做法能减小代码和数据的冗余度,大大增加程序的重用性。 (4)继承通过增强一致性来减少模块间的接口和界面,大大增加了程序的易维护性。 (5)提供多重继承机制。从理论上说,一个类可以是多个一般类的特殊类,它可以从多个一般类中继承属性与方法,这便是多重继承。Java出于安全性和可靠性的考虑,仅支持单重继承,而通过使用接口机制来实现多重继承。 1.关于私有成员变量 无论父类中的成员变量是pirvate、public、还是其它类型的,子类都会拥有(继承)父类中的这些成员变量。但是父类中的私有成员变量,无法在子类中直接访问,可以通过从父类中继承得到的protected、public方法(如getter、setter方法)来访问。 个人认为这更好的提现了JAVA特性中的封装,而且符合软件工程的设计思想:低耦合 2. 关于静态成员变量 无论父类中的成员变量是静态的、还是非静态的,子类都会拥有父类中的这些成员变量。 3. 关于被子类覆盖的成员变量 无论父类中的成员变量是否被子类覆盖,子类都会拥有父类中的这些成员变量。 抽象类中的非抽象方法不用重写,其他必须重写,接口的方法必须重写; 接口和抽象类中只有方法名,没有定义的,如果你不定义 也就是空方法,接口就是为了弥补java不能多重继承,接口针对的是对象而不是实现。实现的部分可以交由对象去实现。这就是java中的多态啊。 方法重写与方法重载不同,方法的重载是方法的参数个数或种类或顺序不同,方法名相同。 方法重写是要注意权限的问题,子类中的权限不能小于父类的权限,当父类的权限为private时,子类无法继承。也就无法产生所谓的重写。(修饰符高低:private < 默认修饰符 < public) java中的抽象方法就是以abstract修饰的方法,这种方法只声明返回的数据类型、方法名称和所需的参数,没有方法体,也就是说抽象方法只需要声明而不需要实现。 格式: abstract在访问权限后,返回值类型前修饰方法,方法没有方法体: public abstract 返回值类型 方法名(参数); 作用: 抽象方法的作用是,你在此类里不必实现它,只是一个虚方法,所有的实现可以到继承此类的子类里面去做.你也可以理解为,抽象方法就是用来被重载的方法.你可以在子类里对它进行重载,也可以不进行重载.举个例就象定义了一个电器类,其中一个虚方法是显示图象.你用电视机继承电器类的时候就把显示图象的方法实现为电视机的显示方法,用mp4继承电器类时就把这个虚方法实现为mp4自己的显示方法.如果是用收音机类去继承电器,你就可以不用实现这个方法. 抽象类的意义 抽象类往往用来表示对问题领域进行分析、设计中得出的抽象概念。其存在的意义在于其设计性、复用性与扩展性。 抽象类方便了具体类的定义。 抽象类仅是对功能和属性的声明,表示这类事物应该具备这些内容。限制程序员不能直接创建该抽象类对象,必须定义其子类才可使用。如我们可以听一只狼的叫声,也可以听一只狗的叫声,但是如果我们听一只犬科的叫声就显然不合适了。 经理类: 姓名、年龄、薪水、奖金 吃饭、工作内容为管理员工,计算绩效 人类: 姓名、年龄 吃饭 厨师类: 姓名、年龄、薪水、奖金 吃饭、工作内容炒菜 员工类 姓名、年龄、薪水 吃饭、工作 服务员类: 姓名、年龄、薪水、奖金 吃饭、工作内容端盘子 构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。 .//设有下面两个类的定义。 class Person { long id; // 身份证号 String name; // 姓名 } class Student extends Person{ int score; int getScore(){ return score; } } 则类Person和类Student的关系是? A.包含关系 B.继承关系 C.关联关系 D.上述类定义有语法错误 答案:B A.子类继承父类的所有属性和方法 B.子类可以继承父类的私有的属性和方法 C子类可以继承父类的公有的属性和方法 D.创建子类对象时,父类的构造方法都要被执行 答案:C A.一个子类可有多个父类 B.父类派生出子类 C.子类继承父类 D.子类只能有一个父类 答案:A 要求: 编写代码,实现如下功能: (1)定义一个门类, 包含3个属性:宽度width 和 高度height ,颜色color 包含2个方法:开门和关门 开门方法:输出“门已经打开,请进!” 关门方法:输出“门已经关闭,禁止进入!” (2)定义一个木头门WOOD,继承Door 重写父类开门方法:输出门的高度 宽度 颜色 +“门已经打开,请进!” 重写父类关门方法:输出门的高度 宽度 颜色 +“门已经关闭,禁止进入!” (3)定义一个测试类,测试类定义一个main方法 分别创建 门对象 和 木头门对象, 为创建的木头门对象属性赋值, 调用开门和关门两个方法。 答案: public class Demo { public static void main(String[] args) { //创建门对象 Door door = new Door(); // 调用方法 door.openDoor(); door.closeDoor(); // 创建木头门对象 WoodDoor woodDoor = new WoodDoor(); //调用set方法对属性赋值 woodDoor.setWidth(1.8); woodDoor.setHight(2.5); woodDoor.setColor("红色"); // 调用方法 woodDoor.openDoor(); woodDoor.closeDoor(); } } class Door { // 定义属性 private double width; private double hight; private String color; public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHight() { return hight; } public void setHight(double hight) { this.hight = hight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } //开门方法 public void openDoor(){ System.out.println("门已经打开,请进!"); } //关门方法 public void closeDoor(){ System.out.println("门已经关闭,禁止进入!"); } } class WoodDoor extends Door{ //重写开门方法 public void openDoor(){ System.out.println("门的宽度为:"+ getWidth() +",高度为:"+getHight()+",颜色:"+getColor()+",门已经打开,请进!"); } //关门方法 public void closeDoor(){ System.out.println("门的宽度为:"+ getWidth() +",高度为:"+getHight()+",颜色:"+getColor()+",门已经关闭,禁止进入!"); } } 要求: 1.定义动物类,有名称和年龄两个属性,且属性私有化,提供相应的getXxx与setXxx方法,提供无参数的无返回值的吃饭方法,内容为:“吃饭...”; 2.定义猫类,继承动物类,重写父类中的吃饭方法,内容为:“猫吃鱼...” 3.定义狗类,继承动物类,重写父类中的吃饭方法,内容为:“狗吃骨头...” 4.定义测试类,分别创建猫对象和狗对象,并分别给父类对象中的名称和年龄属性赋值; 5.分别使用猫对象和狗对象获取名称和年龄的属性值并打印在控制台上; 6.分别使用猫对象和狗对象调用吃饭方法; 答案: /* * 动物类: * 属性: * 名称 * 年龄 * 方法: * 吃饭方法 * getXxx与setXxx */ public class DongWu { //属性 private String name; private int age; //getXxx与setXxx public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //吃饭方法 public void chiFan(){ System.out.println("吃饭..."); } } /* * 狗类继承动物类 */ public class Gou extends DongWu{ public void chiFan() { System.out.println("狗吃骨头..."); } } /* * 猫类继承动物类: */ public class Mao extends DongWu{ //重写吃饭的方法; public void chiFan() { System.out.println("猫吃鱼..."); } } /* * 测试类 */ public class Test { public static void main(String[] args) { //1、分别创建猫对象和狗对象,并分别给父类对象中的名称和年龄属性赋值 Mao m = new Mao(); m.setName("波斯猫"); m.setAge(3); Gou g = new Gou(); g.setName("哈巴狗"); g.setAge(2); //2、分别使用猫对象和狗对象获取名称和年龄的属性值并打印在控制台上; String name = m.getName(); int age = m.getAge(); String name2 = g.getName(); int age2 = g.getAge(); System.out.println("猫的名字为:"+name+";年龄为:"+age); System.out.println("狗的名字为:"+name2+";年龄为:"+age2); //3、分别使用猫对象和狗对象调用吃饭方法 m.chiFan(); g.chiFan(); } } |
|