JavaSE 基础语法 - 继承 - ① 例如我们可以定义一个学生类publicclassStudent{privateStringname;privateintage;publicvoidstudy(){System.out.println(学生正在学习...);}}也可以定义一个教师类publicclassTeacher{privateStringname;privateintage;publicvoidteach(){System.out.println(老师正在讲课...);}}仔细观察这两个类我们会发现Student学生类中有姓名、年龄Teacher教师类中有姓名、年龄它们都拥有name姓名age年龄只是各自拥有不同的方法。如果以后还有Worker工人Doctor医生Police警察是不是每个类都要重新写privateStringname;privateintage;如果以后需要增加privateStringphone;那么所有类都要修改。这样的代码重复率高难维护容易出错那么有没有一种方式能够把公共代码提取出来让多个类共同使用答案就是继承Inheritance。继承是 Java 面向对象三大特性之一封装、继承、多态。它最大的作用就是提高代码复用性建立类之间的层次关系。1、为什么需要继承1.1 代码复用思想在软件开发中有一个非常重要的思想不要重复编写相同的代码。例如现在要开发一个学校管理系统。首先定义学生类publicclassStudent{Stringname;intage;publicvoideat(){System.out.println(吃饭);}publicvoidsleep(){System.out.println(睡觉);}publicvoidstudy(){System.out.println(学习);}}然后定义教师类publicclassTeacher{Stringname;intage;publicvoideat(){System.out.println(吃饭);}publicvoidsleep(){System.out.println(睡觉);}publicvoidteach(){System.out.println(讲课);}}继续定义医生publicclassDoctor{Stringname;intage;publicvoideat(){System.out.println(吃饭);}publicvoidsleep(){System.out.println(睡觉);}publicvoidcure(){System.out.println(看病);}}这时会发现有很多代码几乎完全一样。例如Stringname;intage;eat();sleep();这些代码在每一个类中都重复出现。这就是重复代码。重复代码会带来很多问题。1.2 不使用继承存在的问题假设现在学校要求所有人都需要增加电话privateStringphone;那么 Student、Teacher、Doctor、Worker、Police都需要修改。……如果有100 个类。就要修改100 次。如果 eat() 方法需要修改所有类也要一起修改。例如以前publicvoideat(){System.out.println(吃饭);}现在改成publicvoideat(){System.out.println(食堂吃饭);}需要修改所有类。这样不仅工作量巨大。而且非常容易遗漏。例如Teacher 忘记修改。Student 修改了。那么程序行为就不一致。这样一来维护的成本就变高了。在大型项目中重复代码越多维护成本越高随之Bug也越容易出现。因此软件开发追求一次编写到处复用。1.3 现实生活中的继承继承思想它来源于现实生活。例如我们人都有共同特点姓名、年龄、吃饭、睡觉等等。学生除了具有人的特点还具有学习。老师除了具有人的特点还有讲课。医生除了具有人的特点还有治病。可以看到学生是一种is-a人。老师也是一种is-a人。医生也是一种is-a人。因此完全可以把姓名、年龄、吃饭、睡觉放到 Person 。然后 Student 直接拥有Teacher 直接拥有Doctor 直接拥有。这就是继承思想。为什么叫继承现实生活中父母拥有房子、车子、财富孩子可以继承。Java 中也是类似父类拥有成员变量、成员方法子类可以直接拥有。所以Java 使用继承来描述这种关系。需要注意的是Java 中的继承并不是简单地复制代码而是建立父类与子类之间的关系。子类可以直接访问和使用父类中允许继承的成员从而实现代码复用。2、什么是继承2.1 继承的概念Java 官方对继承的描述可以概括为继承Inheritance是一种类与类之间的关系子类可以继承父类的属性和行为从而实现代码复用和功能扩展。简单来说子类可以拥有父类中可以继承的成员。例如Person类中有以下变量和方法 ↓ name ageeat()sleep()StudentStudent类中有以下方法 ↓study()如果 Student 继承 Person 。那么 Student 最终拥有name ageeat()sleep()study()是不是几乎不用重复写代码这就是继承最大的作用。2.2 父类Superclass被继承的类称为父类Parent Class也称Superclass例如publicclassPerson{Stringname;intage;publicvoideat(){System.out.println(吃饭);}}这里Person就是父类。它负责存放所有公共成员。例如姓名、年龄、吃饭、睡觉。2.3 子类Subclass继承别人的类叫子类Child Class也叫Subclass例如publicclassStudentextendsPerson{publicvoidstudy(){System.out.println(学习);}}Student 就是 Person 的子类。Student 除了自己的 study() 还能拥有name、age、eat()这些成员。所以 Student 拥有 Person 所有可继承成员 自己新增成员。3、继承的基本语法3.1 extends 关键字Java 中使用extends关键字表示继承。基本语法如下class子类extends父类{}例如publicclassPerson{Stringname;intage;publicvoideat(){System.out.println(吃饭);}}StudentpublicclassStudentextendsPerson{publicvoidstudy(){System.out.println(学习);}}这里extends表示 Student 继承 Person 。此时 Student 自动拥有name、age、eat()、study()可以直接创建对象进行测试publicclassTest{publicstaticvoidmain(String[]args){StudentstunewStudent();stu.name张三;stu.age18;stu.eat();stu.study();}}运行结果吃饭 学习虽然 name、age 和 eat() 都定义在 Person 中但 Student 继承了 Person因此可以直接使用这些成员前提是访问权限允许。在设计类之间的关系时要遵循is-a是一个原则例如Student is a Person、Teacher is a Person。3.2 定义父类父类通常用于抽取多个类共同拥有的属性和行为。例如publicclassPerson{Stringname;intage;publicvoideat(){System.out.println(吃饭);}publicvoidsleep(){System.out.println(睡觉);}}Person描述所有人共同拥有的信息。包括姓名、年龄、吃饭、睡觉以后学生、老师、医生都可以继承 Person 。3.3 定义子类StudentpublicclassStudentextendsPerson{Stringschool;publicvoidstudy(){System.out.println(学习);}}TeacherpublicclassTeacherextendsPerson{Stringsubject;publicvoidteach(){System.out.println(讲课);}}这样 Person 负责公共成员。Student 负责学生独有成员。Teacher 负责教师独有成员。职责更加清晰。3.4 创建子类对象创建方式与普通对象完全一致。例如StudentstunewStudent();对象创建成功后不仅可以访问Student 自己的成员也可以访问 Person 继承下来的成员。例如stu.name张三;stu.age18;stu.eat();stu.sleep();stu.study();说明 Student 对象里面包含 Person 所有可继承成员。3.5 简单案例定义PersonpublicclassPerson{Stringname;intage;publicvoidintroduce(){System.out.println(name age);}}定义StudentpublicclassStudentextendsPerson{doublescore;publicvoidexam(){System.out.println(考试);}}测试publicclassTest{publicstaticvoidmain(String[]args){StudentstunewStudent();stu.name张三;stu.age18;stu.score95;stu.introduce();stu.exam();}}输出张三18考试4、子类可以继承哪些成员父类中的所有内容子类都可以继承吗答案是不是。Java 中能够继承的成员主要包括成员变量成员方法不能继承构造方法另外private 成员虽然属于父类对象的一部分但是不能直接访问。4.1 成员变量成员变量可以继承。例如publicclassPerson{Stringname;intage;}StudentpublicclassStudentextendsPerson{}测试StudentstunewStudent();stu.name张三;stu.age18;System.out.println(stu.name);System.out.println(stu.age);输出张三18说明 Student 已经拥有name、age两个成员变量。4.2 成员方法成员方法同样可以继承。例如publicclassPerson{publicvoideat(){System.out.println(吃饭);}}StudentpublicclassStudentextendsPerson{}测试StudentstunewStudent();stu.eat();输出吃饭说明 eat() 已经属于Student。4.3 private 成员很多同学最容易误解private。例如publicclassPerson{privateStringname;}StudentpublicclassStudentextendsPerson{}测试StudentstunewStudent();stu.name张三;编直接报错。为什么是不是 private 不能继承其实严格来说private 成员会被子类继承但是不能被子类直接访问。可以理解为父类对象里面仍然存在 name 只是 Student 不能直接操作。如果父类提供publicvoidsetName(Stringname){this.namename;}publicStringgetName(){returnname;}那么 Student 可以StudentstunewStudent();stu.setName(张三);System.out.println(stu.getName());所以应该理解成private 限制的是访问权限而不是是否存在于对象中。4.4 构造方法为什么不能继承很多同学都会认为构造方法也应该继承。实际上不是。例如PersonpublicclassPerson{publicPerson(){System.out.println(Person);}}StudentpublicclassStudentextendsPerson{}Student 并没有 Person() 这个构造方法。为什么原因构造方法作用是初始化当前类对象。最终既要初始化 Person 又要初始化 Student 。因此Java不会继承构造方法。而是通过 super() 调用父类构造方法后面讲。5、成员访问规则5.1 成员变量查找顺序例如PersonpublicclassPerson{Stringname父类;}StudentpublicclassStudentextendsPerson{}测试StudentstunewStudent();System.out.println(stu.name);输出父类为什么因为 Student 自己没有 name所以Java继续去父类查找。查找顺序Student-Person-Object-再没有的话就会编译错误也就是说先找自己再找父类最后一直向上查找。成员变量隐藏如果Student它自己也有Stringname子类;此时System.out.println(stu.name);输出子类说明Java优先访问子类成员。这种现象叫成员变量隐藏。需要注意的是成员变量并不存在真正意义上的“重写”只是子类定义了一个同名变量隐藏了父类变量。5.2 成员方法查找顺序例如PersonpublicclassPerson{publicvoideat(){System.out.println(Person eat);}}StudentpublicclassStudentextendsPerson{}调用StudentstunewStudent();stu.eat();输出Personeat因为 Student 没有 eat() 继续去 Person 查找。如果Student自己定义publicvoideat(){System.out.println(Student eat);}那么输出Studenteat这里实际上已经体现了方法重写的效果。6、Java 继承特点6.1 Java 单继承什么是单继承例如classPerson{}classStudentextendsPerson{}这里 Student 只有一个直接父类Student 继承了 Person这就是单继承。所谓单继承就是一个类只能直接继承一个父类。例如classAnimal{}classDogextendsAnimal{}这是合法的。但是classAnimal{}classPet{}classDogextendsAnimal,Pet{}编译会直接报错。Java 不允许一个类同时继承多个父类。为什么采用单继承原因主要有两个1、避免成员冲突例如classA{voidtest(){System.out.println(A);}}classB{voidtest(){System.out.println(B);}}如果classCextendsA,B{}那么newC().test();到底应该调用A 的 test()还是 B 的 test()Java 无法判断。因此Java 从语言设计上直接禁止了多继承。2、降低系统复杂度继承关系越复杂代码越难维护。Java 希望开发者更多地使用组合Composition而不是无限制地使用继承。因此Java 采用单继承 接口Interface来代替C 的多继承。6.2 多层继承虽然Java 不支持一个类继承多个父类但是支持多层继承Multilevel Inheritance。例如classAnimal{publicvoideat(){System.out.println(吃饭);}}classDogextendsAnimal{}classTeddyextendsDog{}继承关系Teddy - Dog - Animal此时Teddy 拥有 eat() 。原因Dog 继承 Animal。Teddy 继承 Dog。所以最终 Teddy 拥有 Animal 所有可继承成员。测试publicclassTest{publicstaticvoidmain(String[]args){TeddyteddynewTeddy();teddy.eat();}}输出吃饭说明成员可以沿着继承链不断向下传递。6.3 is-a 思想继承不是为了节省代码而节省代码。更重要的是描述类之间的关系。在面向对象设计中有一个非常重要的原则is-a是一个例如Studentis aPersonDogis anAnimalTeacheris aPerson这些都满足is-a。因此可以使用继承。反过来例如CarEngine汽车不是发动机。发动机也不是汽车。它们之间不是“is-a”。而是Carhas anEngine汽车拥有发动机这种关系叫has-a拥有通常使用组合Composition。因此设计类的时候先问自己它们是不是is-a关系如果不是那么通常不要使用继承。6.4 综合案例下面来看一下这个案例classPerson{Stringname;intage;publicvoideat(){System.out.println(吃饭);}}classStudentextendsPerson{doublescore;publicvoidstudy(){System.out.println(学习);}}classTeacherextendsPerson{Stringsubject;publicvoidteach(){System.out.println(讲课);}}测试publicclassTest{publicstaticvoidmain(String[]args){StudentstunewStudent();stu.name张三;stu.age18;stu.score95;stu.eat();stu.study();TeacherteachernewTeacher();teacher.name李老师;teacher.age35;teacher.subjectJava;teacher.eat();teacher.teach();}}运行结果吃饭 学习 吃饭 讲课从这个案例可以看出Student 和 Teacher 都继承了 Person。两个子类都拥有 name、age 和 eat()避免了重复编写代码。子类在继承父类成员的基础上还可以扩展自己的属性和行为例如 score、study()、subject 和 teach()。7、JVM 内存分析理解继承不能只停留在语法层面。更重要的是理解子类对象在内存中到底是什么样子的。很多初学者认为 Student 对象里面只有 Student 自己的成员。实际上不是。来看下面这个例子classPerson{Stringname;intage;}classStudentextendsPerson{doublescore;}创建对象StudentstunewStudent();很多人认为对象长这样Studentscore其实真正内存应该是也就是说子类对象实际上由两部分组成父类部分 子类部分。因此 stu.name能够访问。因为对象里面真的存在name并不是复制了一份。