什么是继承,有什么用
- 定义:继承是从已有的类中扩展出新的类,新的类具有父类的数据属性和方法,并能扩展新的能力,
- 作用:
- 基本作用:子类继承父类,代码可以得到复用
- 主要作用:有了继承关系,才有后期的方法覆盖和多态机制
如何继承
class 类名 extends 父类名 {
类体;
}
继承的特性
- B类继承A类
- 则称A类:超类、父类、基类
- 而称B类:子类、派生类、扩展类
- 实例
package Day09继承;
public class Test01 {
}
class A{}
class B extends A{}
- java中继承只支持单继承,不支持多继承(体现简单性)
- 实例
package Day09继承;
public class Test02 {
public static void main(String[] args) {
}
}
class C {}
class D {}
class E extends C{}
class F extends D{}
class G extends D,C{} //java: 需要'{' 说明D后面应该跟‘{’ 而不是其他的类名
- java虽然不支持多继承,但是能有产生间接继承的效果
- 实例:
package Day09继承;
public class Test03 {
}
class X{}
class Y extends X {}
class Z extends Y {}
//这样Z也同时继承了X和Y,不过是直接继承Y,间接继承X而已
- java中规定,子类继承父类,除了构造方法以外,其他的都可以继承
- 私有属性无法在子类中直接访问,即父类中使用关键字 private 修饰的属性不能在子类中直接访问,但是可以通过间接手段访问
- 实例
package Day09继承;
public class Test04 {
private String name = "张三";
String id = "001";
public Test04(String name, String id) {
this.name = name;
this.id = id;
}
public Test04(String id) {
this.id = id;
}
public Test04() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
//Student student = new Student("张三");
//原因: 实际参数列表和形式参数列表长度不同 ———— 父类的构造方法无法继承(无论是否有private修饰)
//实例化子类对象
Student student1 = new Student();
//访问父类中使用private修饰的变量
//System.out.println(student1.name);
//java: name 在 Day09继承.Test04 中是 private 访问控制 ———— 使用private修饰的父类变量不能继承
//访问父类中未使用private修饰的变量
System.out.println(student1.id); //001
//通过getName()方法获取private修饰的变量的值
System.out.println(student1.getName()); //张三
}
}
class Student extends Test04{
}
- 所有对象都有Object类型的所有特征 —— 默认继承
- 实例
package Day09继承;
public class Test05 extends Object{
}
//public class Test05{}
//以上两个表达式结果是一样的,因为所有类都会默认继承 Object 类
//Object类 是所有类的超类,类体结构中的根
- 缺点:耦合度会非常高 —— 一旦父类的代码发生了改变,子类都会收到牵连
通过子类对象调用继承过来的方法
- 实例
package Day09继承;
public class Test06 {
public static void main(String[] args) {
//创建子类对象
Cat cat = new Cat();
//表面看起来是子类对象在调用父类的方法,实际上是子类在调用自己的方法。
// 因为继承后这个方法就是子类的了
cat.move();
//通过子类对象访问name
System.out.println(cat.name);
}
}
class Animal{
String name = "汤圆";
//提供一个动物移动的方法
public void move(){
System.out.println(name + "正在跑");
}
}
class Cat extends Animal{
}
什么时候可以用继承
- 满足什么条件的时候可以使用继承
- 凡是采用”is a“能够描述的,都可以
- 例如
- Cat is a Animal
- Dog is a Animal
- 若 A类 和 B类有相同的代码,但是没有 ”is a“的关系,不一定能使用继承
package Day09继承;
import Day07封装.Person;
public class Test07 {}
class Product{
String name;
}
class Customer extends Person {
String name;
}
// Product类和Customer类,虽然有共同的变量,但是他们之间的关系并没有 “is a” 这样的关系,所以这种继承反而不好
方法覆盖
方法覆盖初体验
package Day09继承_覆盖和多态.覆盖;
/*
方法覆盖初体验
* */
public class Test01 {
public static void main(String[] args) {
Chinese chinese = new Chinese();
chinese.speak(); //人类在说话
American american = new American();
american.speak(); //人类在说话
//“人类在说话”显然不符合我们的期望,所以有了方法覆盖
}
}
class Person{
public void speak(){
System.out.println("人类在说话");
}
public void soSome(){
System.out.println("doSome...");
}
}
//子类继承父类之后,有一些行为(方法)不需要改进,而有一些并不满足子类的需求,所以需要改进
class Chinese extends Person{
//Chinese类在调用speak()方法的时候,希望能输出的是“中国人正在说汉语”
}
class American extends Person{
//American类在调用speak()方法的时候,希望能输出的是“美国人正在说英语”
}
什么时候考虑使用方法覆盖
- 子类继承父类后,继承过来的方法满足不了子类的业务需求的时候
- 子类是可以对某个方法进行重新编写,即进行“方法覆盖”
满足方法覆盖的条件
- 两个类之间必须要有继承关系
- 重写的方法和之前的方法具有“三同”
- 相同的返回值类型
- 相同的方法名
- 相同的参数列表(个数、类型、顺序)
- 访问条件不能更低,可以更高(见包机制和访问控制权限【暂未更新】)
- 重写之后的方法不能比之前的方法跑出更多的异常,但是可以更少(见异常篇【暂未更新】)
注:
- 当子类的方法对父类的方法进行覆盖之后,子类对象再调用这个方法的时候,调用的是覆盖之后的方法
- 在进行方法覆盖的时候,最好是将父类中的办法原封不动的复制粘贴过来 or 使用IDEA工具进行方法的覆盖,不建议手写(因为一旦写错就有点麻烦,还不方便找到)
实例:
package Day09继承_覆盖和多态.覆盖;
import java.io.IOException;
/*
方法覆盖初体验
* */
public class Test02 {
public static void main(String[] args) {
American1 american1 = new American1();
Chinese1 chinese1 = new Chinese1();
//不相同的参数列表 ———— 导致子类中的dance(int i)方法并没有覆盖父类中的dance()方法 ————— 覆盖方法不满足条件2
chinese1.dance();
chinese1.dance(1);
}
}
class Person1{
public void speak(){
System.out.println("说话");
}
public void soSome(){
System.out.println("doSome...");
}
public void dance(){
System.out.println("跳舞");
}
public void dress() throws IOException{
System.out.println("穿着");
}
public void drink(){
System.out.println("喝水");
}
}
//子类继承父类之后,有一些行为(方法)不需要改进,而有一些并不满足子类的需求,所以需要改进
class Chinese1 extends Person1{
//Chinese类在调用speak()方法的时候,希望能输出的是“中国人说汉语”
@Override
public void speak() {
System.out.println("中国人说汉语");
}
public void dance(int i) {
System.out.println("有" + i + "个中国人正在跳舞");
}
@Override
// public void dress() throws Exception{ //Chinese1中的dress()无法覆盖Person1中的dress(),被覆盖的方法未抛出java.lang.Exception ———— 覆盖方法不满足条件4
public void dress() {
System.out.println("中国人穿棉袄");
}
// protected void drink() { //Chinese1中的drink()无法覆盖Person1中的drink(),正在尝试分配更低的访问权限; 以前为public ———— 覆盖方法不满足条件3
public void drink(){
System.out.println("中国人喝山泉水");
}
}
class American1 extends Person1{
//American类在调用speak()方法的时候,希望能输出的是“美国人说英语”
@Override
public void speak() {
System.out.println("美国人说英语");
}
@Override
public void dress() {
System.out.println("美国人穿短袖");
}
}
方法覆盖的注意事项
- 方法覆盖只针对于方法,和属性无关
- 私有方法不能进行覆盖
- 因为构造方法不能继承,不满足方法覆盖条件1,所以构造方法也不能覆盖
- 方法覆盖只是针对实例方法,静态方法覆盖没意义
toString()方法的覆盖
- 关于Object类中的toString()方法
- 作用:将java对象转换成字符串的形式
- toString()方法的默认实现是不够用的,所以需要程序员进行方法覆盖
- 默认实现:
//toString()方法的源代码
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 实例:(读者若想看重写toString()方法前后的区别,只需将代码最下方的toString()方法注释即可)
package Day09继承_覆盖和多态.覆盖;
public class Test03 {
public static void main(String[] args) {
MyDate myDate = new MyDate();
//这两种写法是一样的,输出对象的时候,后面隐藏了toString();
System.out.println(myDate); //Day09继承_覆盖和多态.覆盖.MyDate@4554617c
System.out.println(myDate.toString()); //Day09继承_覆盖和多态.覆盖.MyDate@4554617c
//没有重写toString()方法:Day09继承_覆盖和多态.覆盖.MyDate@4554617c
//重写toString方法后:1999年8月22日
}
}
class MyDate{
private int year;
private int month;
private int day;
public MyDate() {
this.year = 1999;
this.month = 8;
this.day = 22;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public void setYear(int year){
this.year = year;
}
public int getYear(){
return year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
//重写toString()方法
@Override
public String toString() {
return this.year + "年" + this.month + "月" + this.day + "日";
}
}
覆盖五问
- 什么时候会用到方法覆盖?
- 什么条件满足的时候构成方法覆盖?
- Object类的toString()方法的作用?
- toString()方法具体如何进行覆盖
- 方法重载和方法覆盖的区别
多态
转型
向上转型
- 子 ----> 父
- 父类型引用指向子类型的对象
- 与“自动类型转换”有异曲同工之妙
向下转型
- 父 ----> 子
- 父类引用可以使用在父类中没有,在子类的“特有”方法
- 需要加强制转换符(与“强制类型转换”相似)
注
- 向上转型、向下转型,不要说成自动类型转换和强制类型转换
- 自动类型转换和强制类型转换是基本数据类型方面的术语,和转型无关
- 只是为了方便理解,所以才引用了“自动类型转换”和“强制类型转换”这两个概念
多态定义
父类型引用指向子类型对象
多态的两个阶段
- 编译阶段:静态绑定的是父类中的方法
- 运行阶段:动态绑定子类型对象的方法
多种形态:编译和运行的时候形态不一样
实例:
package Day09继承_覆盖和多态.多态;
public class Test01 {
public static void main(String[] args) {
//使用多态的方式实例化对象(父类型引用指向子类型对象)
Person person = new Chinese();
Person person1 = new American();
//1、没有继承的两个类之间使用多态
//Chinese chinese = new American(); //java: 不兼容的类型: American无法转换为Chinese
//调用继承的方法
person.dress(); //穿羽绒服
person1.dress(); //穿短袖
//2、通过两个阶段分析多态
/*
分析person.dress():(因为java程序分别有“编译阶段”和“运行阶段”,所以分别从这两个阶段分析)
编译阶段:
编译器看到person.dress()的时候,只知道person(变量)是Person类型(类)
所以会去Person.class的字节码文件中查找dress()方法
找到之后,绑定上move()方法,编译通过
静态绑定成功
(编译阶段属于静态绑定)
运行阶段:
运行阶段在堆内存中的Java对象实际上是Chinese,
所以当调用dress()方法的时候,
调用的是Chinese类的dress()方法
这个过程属于动态绑定
(运行阶段属于动态绑定)
* */
//3、使用多态调用子类中特有的方法
Person person2 = new Chinese();
//person2.education(); //java: 找不到符号
/*
分析:为什么person2不能调用education()方法
编译阶段:
编译器执行到这里的时候只知道person2是Person类
所以去Person.class寻找education()方法
但是没有找到,静态绑定失败,所以报错:找不到符号
就是没有找到education()方法
* */
//4、向下转型
Person person3 = new Chinese();
Chinese person4 = (Chinese)person3;
person4.education(); //九年义务教育
/*
为什么没有报错?
因为person4是由person3向下转型得到,
person4就是Chinese类型,自然而然能调用Chinese类型的专属方法
且:Person和Chinese具有继承关系(这个是前提)
* */
//5、向下转型的风险
Person person5 = new Chinese();
// American american = (American)person5;
// american.gun(); //Exception in thread "main" java.lang.ClassCastException:Chinese cannot be cast to American
/*
为什么报错了?
编译阶段:
编译器检测到person5是一个Person类型
Person类型和American类型具有继承关系,所以向下转型没有问题
所以编译阶段没有问题
运行阶段:
堆内存中实际创建的是Chinese对象,
所以实际操作是要将Chinese对象向下转型成American对象,
因为Chinese类和American类是没有继承关系的
所以出现异常:ClassCastException ———— 类型转换异常
* */
//如何避免情况5? ———— 使用instanceof关键字进行判断
if (person5 instanceof American){
American american1 = (American)person5;
american1.gun();
}
if (person5 instanceof Chinese){
Chinese chinese = (Chinese)person5;
chinese.education();
}
}
}
class Person{
public void speak(){
System.out.println("说话");
}
public void dress(){
System.out.println("穿着");
}
public void drink(){
System.out.println("喝水");
}
}
class Chinese extends Person{
@Override
public void speak() {
System.out.println("说汉语");
}
@Override
public void dress() {
System.out.println("穿羽绒服");
}
@Override
public void drink() {
System.out.println("喝山泉水");
}
public void education(){
System.out.println("九年义务教育");
}
}
class American extends Person{
@Override
public void speak() {
System.out.println("说英语");
}
@Override
public void dress() {
System.out.println("穿短袖");
}
@Override
public void drink() {
System.out.println("喝可乐");
}
public void gun(){
System.out.println("可以合法携带枪支");
}
}
instanceof运算符
- 如何避免ClassCastException?
- instanceof在运行阶段动态判断引用指向对象的类型
- instanceof的语法:
- (引用 instanceof 类型)
注
- (引用 instanceof 类型)
- instanceof运算符的结果只会:true/false
- 在任何时候,进行向下转型操作的时候,一定要用instanceof运算符进行判断,从而避免ClassCastException
实例:
package Day09继承_覆盖和多态.多态;
public class Test02 {
public static void main(String[] args) {
Person1 person1 = new Chinese1();
System.out.println(person1 instanceof Chinese1); //true
System.out.println(person1 instanceof American1); //false
}
}
class Person1{
}
class Chinese1 extends Person1{
}
class American1 extends Person1{
}
为什么要使用instanceof运算符
- main方法由程序员A编写,需要调用B类中的test方法,B类是程序员B写的
- 程序员B只知道test方法传入的参数是一个Person类
- 但是别人调用test方法的时候,可能传入一个Chinese或者American
- 如果不使用向下转型,就会报错:java.lang.ClassCastException
实例:
程序员A写的main方法
package Day09继承_覆盖和多态.多态.Instanceof;
public class Test03 {
public static void main(String[] args) {
Test04 test04 = new Test04();
test04.test(new American());
test04.test(new Chinese());
}
}
程序员B写的test()方法
package Day09继承_覆盖和多态.多态.Instanceof;
/*
将未使用向下转型的代码取消注释,并将使用了向下转型的代码注释后,
再执行main方法就可以看出有无instanceof的区别了
* */
public class Test04 {
public void test(Person p){
//使用instanceof运算符
if (p instanceof American){
American american = (American)p;
american.gun();
} else if (p instanceof Chinese){
Chinese chinese = (Chinese)p;
chinese.education();
}
//直接将传入的参数向下转型(如果传入的是Chinese,就会报错)
/*American american = (American)p;
american.gun();*/
}
}
Person类:
package Day09继承_覆盖和多态.多态.Instanceof;
public class Person {
public void speak(){
System.out.println("说话");
}
public void dress(){
System.out.println("穿着");
}
public void drink(){
System.out.println("喝水");
}
}
Chinese类
package Day09继承_覆盖和多态.多态.Instanceof;
public class Chinese extends Person{
@Override
public void speak() {
System.out.println("说汉语");
}
@Override
public void dress() {
System.out.println("穿羽绒服");
}
@Override
public void drink() {
System.out.println("喝山泉水");
}
public void education(){
System.out.println("九年义务教育");
}
}
American类
package Day09继承_覆盖和多态.多态.Instanceof;
public class American extends Person{
@Override
public void speak() {
System.out.println("说英语");
}
@Override
public void dress() {
System.out.println("穿短袖");
}
@Override
public void drink() {
System.out.println("喝可乐");
}
public void gun(){
System.out.println("可以合法携带枪支");
}
}
回顾多态
-
向上转型和向下转型
- 向上转型:子类型转型成父类型
- 向下转型:父类型转型成子类型
- 需要添加强制类型转换符
- 在调用子类对象特有的方法的时候需要向下转型
- 容易出现ClassCastException(类型转换异常)
- 使用instanceof运算符,可以动态判断
- 向下转型之前一定要使用instanceof运算符
- 注:不管是向上转型还是向下转型,两个类型之间必须要有继承关系
-
多态
- 多种形态,多种状态
- 编译和运行有两种不同的状态
- 编译的时候叫做静态绑定
- 运行的时候叫做动态绑定
- 多种形态,多种状态
在实际开发中的作用
实例:
- 创建Person类
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class Person {
public void escape(){
System.out.println("逃跑");
}
}
- 创建Terminator类
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class Terminator {
/*
* 最开始的时候,终结者只追杀美国人
* */
public void kill(American american){
System.out.print("终结者追杀" + american + ",");
american.escape();
}
/*
* 终结者又想去追杀其他人,就不得不该百年 Terminator 这个类的代码
* */
public void kill(Chinese chinese){
System.out.print("终结者追杀" + chinese + ",");
chinese.escape();
}
/*
* 有没有一种方法就能让终结者追杀所有人呢? ———— 有,使用多态
* */
public void kill(Person person){
System.out.print("终结者追杀" + person + ",");
person.escape();
}
/*
* 编译的时候,编译器发现形参是一个Person类,于是就去Person类中找escape()方法,找到了,就通过(静态绑定)
* 实参的具体的人种是不知道的,只知道一定是人
* 运行的时候,底层对象是什么类型,就调用该类型的escape()方法
* */
}
- 创建Chinese类
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class Chinese extends Person{
private String name = "中国人";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Chinese(String name) {
this.name = name;
}
public Chinese() {
}
@Override
public void escape() {
System.out.println(name + "往地堡中逃");
}
@Override
public String toString() {
return name;
}
}
- 创建American类
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class American extends Person{
private String name = "美国人";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public American(String name) {
this.name = name;
}
public American() {
}
@Override
public void escape() {
System.out.println(name + "往防御工事中逃");
}
@Override
public String toString() {
return name;
}
}
- 创建Japanese类
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class Japanese extends Person{
private String name = "日本人";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Japanese(String name) {
this.name = name;
}
public Japanese() {
}
@Override
public void escape() {
System.out.println(name + "往海底逃");
}
@Override
public String toString() {
return name;
}
}
- 测试
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class Test {
public static void main(String[] args) {
//创建终结者对象
Terminator terminator = new Terminator();
//创建美国人对象
American american = new American();
//创建中国人对象
Chinese chinese = new Chinese();
//终结者追杀美国人
terminator.kill(american); //终结者追杀美国人,美国人往防御工事中逃
//终结者追杀中国人
terminator.kill(chinese); //终结者追杀中国人,中国人往地堡中逃
//使用多态创建日本人对象
Person person = new Japanese();
terminator.kill(person); //终结者追杀日本人,日本人往海底逃
//注:如果直接调用escape()方法,就没有体现出终结者去追杀的效果
person.escape();
}
}
多态在开发中的作用
- 降耦合,升扩展
package Day09继承_覆盖和多态.多态.实际开发中的作用;
public class Terminator {
public void kill(American american){
System.out.print("终结者追杀" + american + ",");
american.escape();
}
public void kill(Chinese chinese){
System.out.print("终结者追杀" + chinese + ",");
chinese.escape();
}
//Terminator和American、Chinese的关系很紧密(耦合度高,导致拓展力低)
public void kill(Person person){
System.out.print("终结者追杀" + person + ",");
person.escape();
}
//Terminator和American、Chinese的关系脱离了,Terminator关注的是Person类
//这样Terminator和American、Chinese的耦合度就降低了,软件的扩展力就提高了
}
遗留问题
- 私有方法不能覆盖
- 方法覆盖只是针对实例方法,静态方法覆盖没有意义
- 在方法覆盖中,关于方法的返回值类型
遗留问题1:
package Day09继承_覆盖和多态.多态;
public class Test03 {
//私有方法
private void doSome(){
System.out.println("Test03 ----> doSome...");
}
public static void main(String[] args) {
Test03 person2 = new Chinese2();
person2.doSome();
/*
* 输出的是Test03 ----> doSome...
* 说明并没有进行方法覆盖
* */
}
}
class Chinese2 extends Test03{
//并没有重写父类中的doSome()方法,因为访问权限不能更低,但是能更高
public void doSome(){
System.out.println("Chinese2 ----> doSome...");
}
}
遗留问题2:
1、方法覆盖要和多态机制联合起来才有意义
2、静态方法不存在方法覆盖
- 静态方法的调用并不需要对象,而多态和对象有紧密关系,所以,一般情况下,静态方法是不会进行方法覆盖的
package Day09继承_覆盖和多态.多态;
public class Test04 {
public static void main(String[] args) {
//虽然静态方法可以使用“引用.”的方式来调用,但是和对象没有关系
Animal animal = new Cat();
animal.doSome(); //这里实际上执行的是:Animal.doSome();
//静态方法的调用方法还是应该用“类名.”
Animal.doSome();
//这个不叫方法覆盖
Cat.doSome();
}
}
class Animal{
//父类的静态方法
public static void doSome(){
System.out.println("Animal ----> doSome...");
}
}
class Cat extends Animal{
//子类“重写”静态方法doSome()
public static void doSome() {
System.out.println("Cat ----> doSome...");
}
}
遗留问题3:
- 在学习了多态机制之后,可以知道
- 对于返回值类型是基本数据类型来讲,必须一致
- 对于返回值类型是引用数据类型来讲,重写之后的返回值类型可以变得更小
package Day09继承_覆盖和多态.多态;
public class Test05 {
public static void main(String[] args) {
}
}
class MyClass{
public double sum(int a, int b){
return a + b;
}
}
class OtherClass extends MyClass{
// public int sum(int a, int b) {
public double sum(int a, int b){
/*
* OtherClass中的sum(int,int)无法覆盖MyClass中的sum(int,int),返回类型int与double不兼容
* */
return a + b;
}
//所以基本数据类型,子类进方法覆盖的返回值和父类的一定要一样
}
class YourClass{
public MyClass doSome(){
return null;
}
}
class HimClass extends YourClass{
public OtherClass doSome() {
return null;
}
//重写时,返回值类型变小了,是可以的
/*public Object doSome(){
return null;
//HimClass中的doSome()无法覆盖YourClass中的doSome(),返回类型java.lang.Object与MyClass不兼容
//重写时,返回值类型变大了,是不可以的
}*/
}