Java三兄弟之继承
Java的三大特征:封装、继承、多态
继承
继承的概念
就像我们实际生活中的继承,孩子继承了爸爸的才华,继承了妈妈的样貌。我们Java中也同样有继承的关系,子类继承父类的一些成员变量和方法。
多个类中存在相同属性和行为,那么会显得代码的冗余。这时我们可以将这些相同的部分进行抽取,放到一个单独的类中。那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。
这些被抽取的多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承的优点
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,多态的前提。
注意:同时也增加了一些弊端,那就是累与泪之间的耦合度增高了。
继承的格式
可以通过关键字extends
继承一个父类(超类、基类)。
class 父类{}class 子类 extends 父类{}
例如:
class Pet{String petType;
}class Dog extends Pet{String name;public void eat(){System.out.println(petType+":"+name+"在吃东西");}
}public class Test{public static void main(String[] args) {// 创建一个狗类对象Dog dog = new Dog ();//为狗类对象的petType属性赋值dog.petType = "狗";// 为该狗类对象的name属性进行赋值dog .name = "Tom";// 调用该狗的eat()方法dog.eat();}
}结果:
狗:Tom在吃东西
继承的特点
1、子类会继承父类所有的成员变量和方法
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
- 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
- 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。
2、子类无法直接使用父类的私有成员
子类会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问(也可以理解为私有的属性没有被继承,官方文档中的说法),可通过继承的公共的get/set方法进行访问。如图所示:
父类代码:
public class Person {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
子类代码:
public class Student extends Person {private int score;public int getScore() {return score;}public void setScore(int score) {this.score = score;}public String getInfo(){//在子类中不能直接使用父类私有的namereturn "姓名:" + getName() + ",分数:" + getScore();}
}
测试类代码:
public class TestStudent {public static void main(String[] args) {Student student = new Student();student.setName("张三");student.setScore(89);System.out.println(student.getInfo());}
}
3、Java只支持单继承,但可以被多个类继承
单继承
public class A{}
class B extends A{}//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B{} //error
被多个子类继承
class A{}
class B extends A{}
class D extends A{}
...
4、Java支持多层继承
类似于传递性
class A{}class B extends A{}class C extends B{}
注意:所有的类默认继承Object,作为父类。
super调用父类构造器
在继承关系中,子类不会继承父类的构造器,子类构造器可以通过super(【实参列表】)
来调用父类构造器。
- 子类的每个构造器中默认隐藏
super()
,即默认调用父类的无参构造器。 - 子类构造器中可以显示使用
super(【实参列表】)
来调用父类无参或有参构造器,那么默认隐藏的super()
不再存在。 - 如果父类没有无参构造,则必须在子类构造器中显示使用
super(实参列表)
来调用父类构造器。 super(【实参列表】)
只能出现在子类构造器的首行
总之:使用子类任意构造器创建对象时,必须要直接或间接通过super(【实参列表】)
先调用执行父类构造器。
public class Employee {private String name;private int age;private double salary;public Employee() {System.out.println("父类Employee无参构造");}public Employee(String name, int age, double salary) {this.name = name;this.age = age;this.salary = salary;System.out.println("父类Employee有参构造");}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 double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}public String getInfo(){return "姓名:" + name + ",年龄:" + age +",薪资:" + salary;}
}
public class Manager extends Employee{private double bonusRate;public Manager() {super();//可以省略}public Manager(String name, int age, double salary, double bonusRate) {super(name, age, salary);//调用父类的有参构造this.bonusRate = bonusRate;}public double getBonusRate() {return bonusRate;}public void setBonusRate(double bonusRate) {this.bonusRate = bonusRate;}@Overridepublic String getInfo() {return super.getInfo() +",奖金比例:" + bonusRate;}
}
public class TestEmployee {public static void main(String[] args) {Manager m1 = new Manager();System.out.println(m1.getInfo());Manager m2 = new Manager("张三",23,15000,0.3);System.out.println(m2.getInfo());}
}
方法重写(Override)
有的时候我们继承完父类的方法,但是我们的需求和父类这个方法实现的功能有些出入,这个时候我们就可以重写父类的方法。重写后相当于父类的方法就被覆盖了。
方法重写的概念
子类方法中还需要使用父类的某个方法,但是原来父类的方法的功能已经不足以实现我们想要的功能。而子类的方法的名字和原来父类的方法名字相同,参数也相同,这个方法就是父类方法的重写方法。
重写方法的规定:
父子类之间重写方法的名称必须相同。
父子类之间重写方法的参数列表完全相同。
子类方法的返回值类型必须
小于等于
父类方法的返回值类型(小于其实就是是它的子类)。- 如果返回值类型是基本数据类型和void,那么必须是相同
子类方法的权限必须【
大于等于
】父类方法的权限修饰符。权限修饰符大小:public > protected > 缺省 > private
父类私有方法不能重写
跨包的父类缺省的方法也不能重写
静态方法不能被重写,方法重写指的是实例方法重写,静态方法属于类的方法不能被重写,而是隐藏。
final修饰的方法不能被重写
子类中定义与父类中相同的方法,一般方法体不同,用于改造并覆盖父类的方法。
代码示例:比如新的手机增加来电显示头像的功能
class Phone {public void sendMessage(){System.out.println("发短信");}public void call(){System.out.println("打电话");}public void showNum(){System.out.println("来电显示号码");}
}//智能手机类
class NewPhone extends Phone {//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能public void showNum(){//调用父类已经存在的功能使用supersuper.showNum();//增加自己特有显示姓名和图片功能System.out.println("显示来电姓名");System.out.println("显示头像");}
}public class ExtendsDemo06 {public static void main(String[] args) {// 创建子类对象NewPhone np = new NewPhone();// 调用父类继承而来的方法np.call();// 调用子类重写的方法np.showNum();}
}
小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
Idea中进行重写方法可以使用ctrl+o进行快速生成
@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
练习案例
(1)父类Graphic图形
包含属性:name(图形名),属性私有化
包含求面积getArea():返回0.0
求周长getPerimeter()方法:返回0.0
显示信息getInfo()方法:返回图形名称、面积、周长
(2)子类Circle圆继承Graphic图形
包含属性:radius
重写求面积getArea()和求周长getPerimeter()方法,显示信息getInfo()加半径信息
(3)子类矩形Rectange继承Graphic图形
包含属性:length、width
重写求面积getArea()和求周长getPerimeter()方法
public class Graphic {private String name;public Graphic(String name) {super();this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getArea() {return 0.0;}public double getPerimeter() {return 0.0;}public String getInfo() {return "图形:" + name + ",面积:" + getArea() + ",周长:" + getPerimeter();}
}
public class Circle extends Graphic {private double radius;public Circle(String name, double radius) {super(name);//super访问父类的构造器this.radius = radius;}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}@Override//表示这个方法是重写的方法public double getArea() {return Math.PI * radius * radius;}@Override//表示这个方法是重写的方法public double getPerimeter() {return Math.PI * radius * 2;}}
public class Rectangle extends Graphic {private double length;private double width;public Rectangle(String name, double length, double width) {super(name);this.length = length;this.width = width;}public double getLength() {return length;}public void setLength(double length) {this.length = length;}public double getWidth() {return width;}public void setWidth(double width) {this.width = width;}@Overridepublic double getArea() {return length*width;}@Overridepublic double getPerimeter() {return 2*(length + width);}
}
public class TestGraphicExer3 {public static void main(String[] args) {Graphic g = new Graphic("通用图形");System.out.println(g.getInfo());Circle c = new Circle("圆",3.4);System.out.println(c.getInfo());//调用getInfo()方法的对象是cRectangle r = new Rectangle("矩形", 5, 9);System.out.println(r.getInfo());}
}
this和super关键字
this和super的应用场景与作用
this:表示当前对象
使用在构造器和非静态代码块中,表示正在new的对象
使用在实例方法中,表示调用当前方法,对象
super:表示引用父类声明的成员
- 在构造器和实例方法中
无论是this和super都是和对象有关的(不能出现在静态域内)。
this和super的使用格式
- this
- this.成员变量:表示当前对象的某个成员变量,而不是局部变量
- this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
- this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
- super
- super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
- super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
- super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
父、子类声明重名成员变量的问题
特别说明:应该避免子类声明和父类重名的成员变量
因为,子类会继承父类所有的成员变量,所以:
如果重名的成员变量表示相同的意义,就无需重复声明
如果重名的成员变量表示不同的意义,会引起歧义
解决成员变量重名问题:
- 如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
- 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量
- 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
class Father{int a = 10;int b = 11;
}
class Son extends Father{int a = 20;public void test(){//子类与父类的属性同名,子类对象中就有两个aSystem.out.println("子类的a:" + a);//20 先找局部变量找,没有再从本类成员变量找System.out.println("子类的a:" + this.a);//20 先从本类成员变量找System.out.println("父类的a:" + super.a);//10 直接从父类成员变量找//子类与父类的属性不同名,是同一个bSystem.out.println("b = " + b);//11 先找局部变量找,没有再从本类成员变量找,没有再从父类找System.out.println("b = " + this.b);//11 先从本类成员变量找,没有再从父类找System.out.println("b = " + super.b);//11 直接从父类局部变量找}public void method(int a, int b){//子类与父类的属性同名,子类对象中就有两个成员变量a,此时方法中还有一个局部变量a System.out.println("局部变量的a:" + a);//30 先找局部变量System.out.println("子类的a:" + this.a);//20 先从本类成员变量找System.out.println("父类的a:" + super.a);//10 直接从父类成员变量找System.out.println("b = " + b);//13 先找局部变量System.out.println("b = " + this.b);//11 先从本类成员变量找System.out.println("b = " + super.b);//11 直接从父类局部变量找}
}
class Test{public static void main(String[] args){Son son = new Son();son.test();son.method(30,13); }
}
总结:起点不同(就近原则)
变量前面没有super.和this.
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量,
- 如果不是局部变量,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
变量前面有this.
- 通过this找成员变量时,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
变量前面super.
- 通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
- 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
父、子类中方法重写后调用问题
- 如果子类没有重写父类的方法,只要权限修饰符允许,在子类中完全可以直接调用父类的方法;
- 如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法
public class Test1{public static void main(String[] args){Son s = new Son();s.test();Daughter d = new Daughter();d.test();}
}
class Father{protected int num = 10;public int getNum(){return num;}
}
class Son extends Father{private int num = 20;public void test(){System.out.println(getNum());//10 本类没有找父类,执行父类中的getNum()System.out.println(this.getNum());//10 本类没有找父类,执行父类中的getNum()System.out.println(super.getNum());//10 本类没有找父类,执行父类中的getNum()}
}
class Daughter extends Father{private int num = 20;@Overridepublic int getNum(){return num;}public void test(){System.out.println(getNum());//20 先找本类,执行本类的getNum()System.out.println(this.getNum());//20 先找本类,执行本类的getNum()System.out.println(super.getNum());//10 直接找父类,执行父类中的getNum()}
}
总结:
方法前面没有super.和this.
- 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
方法前面有this.
- 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
方法前面有super.
- 从当前子类的直接父类找,如果没有,继续往上追溯
Java三兄弟之继承相关推荐
- JAVA中用于处理字符串的 三兄弟
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! JAVA ...
- 黑马毕向东Java课程笔记(day07):面向对象(第三部分)继承+抽象类+模板方法设计模式+接口+final+继承补充(就业班)
在这一部分中,我们将讲解有关继承的相关内容,包括继承的概述.继承的特点.super关键字.函数覆盖.子类的实例化过程.final关键字这几个部分的内容. 1.继承的概述以及特点 1.1.概述 ...
- Akka in JAVA(三)
2019独角兽企业重金招聘Python工程师标准>>> Akka in JAVA(三) 上两个部分讲了Akka的基本知识和常见的用法.接下来讲一讲Akka的远程调用以及集群的使用.因 ...
- JAVA常用基础知识点[继承,抽象,接口,静态,枚举,反射,泛型,多线程...]
类的继承 Java只支持单继承,不允许多重继承 - 一个子类只能有一个父类 - 一个父类可以派生出多个子类 这里写图片描述 子类继承了父类,就继承了父类的方法和属性. 在子类中,可以使用父类中定义的方 ...
- Java基础小常识-继承-(10)
一.继承的好处 1.好处: 1)提高了代码的复用性 2)让类与类之间产生了关系,给第三个特征多态提供了前提 2.Java中支持单继承,不直接支持多继承,但对c++中的多继承机制进行了改良. 3.单继承 ...
- 【JavaSE_08】Java中static、继承、重写
#一.Static 学习静态这个关键字,关键是抓住两个点: 1,关注其语法特点 2,关注其使用场景 案例讲解: 我们先从静态变量开始,当一个成员变量加上static修饰之后,会是一个什么效果? 比如创 ...
- 《Java程序设计》实验报告——Java的接口与继承
浙江理工大学 <Java程序设计> 实验报告 20 19-20 20学年第 1学期 学 院 信息学院 班 级 计算机科学与技术18(3) 姓 名 申屠志刚 学 号 2 ...
- JAVA 三种线程实现创建方式
JAVA 三种线程实现/创建方式 方式一:继承Thread类 通过继承Thread类来创建一个自定义线程类.Thread类本质上就继承了Runable接口,代表一个线程类.启动线程的唯一办法就是通过T ...
- Java提高篇 —— Java三大特性之继承
一.前言 在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事 ...
最新文章
- 八、进程调度的时机、切换与过程,调度方式
- C#之windows桌面软件第三课:完整的串口调试助手
- FileChannel应用实例——拷贝文件transferFrom方法
- 《系统集成项目管理工程师》必背100个知识点-33常见的工作分解结构表现形式...
- 知识表示学习神器OpenKE:快速获取KG表示
- sql2000 中 存储过程 相关
- 【教程】Edraw Max使用教程:如何打印大流程图?
- Spring整合mybatis 1 查询
- python数据分析准备_使用Python进行数据分析I 环境准备
- 分支程序与循环程序设计-汇编实验二
- matlab 大括号
- c语言定义int 输出4386,大学C语言第五章课后习题参考程序
- 【Flink】Flink SQL 开源UI平台 flink-streaming-platform-web
- python数据结构剑指offer-重建二叉树
- Android应用内嵌cocos2dx游戏项目
- html如何消除空格字符串,jquery如何去除字符串的空格
- Matlab实现的数学模型(2020新整理)
- html5 retina 1像素,7种方法解决移动端Retina屏幕1px边框问题
- 人工智能热卖榜图书《人工智能怎么学》
- Anaconda conda常用命令:从入门到精通