继承与多态
第一节、继承
一、继承的概念、格式、特点
(一)继承的概念:
1、继承是多态的前提,如果没有继承,就没有多态。
2、继承解决的主要问题是:共性抽取。
3、面向对象的三大特征:封装性、继承性、多态性。
(二)继承的格式
父类的格式:(即普通类)
public class 父类名称 {
// ...
}
子类的格式:
public class 子类名称 extends 父类名称 {
// ...
}
(三)继承的三个特点(单继承、多层继承、相对继承)
1、Java只支持单继承,不支持多继承。
2、Java支持多层继承(继承体系)。
3、子类和父类是一种相对的概念。
二、继承中的变量
(一)成员变量的访问(直接、间接)
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
1、直接通过子类对象访问成员变量:
等号【左边】是谁,就优先用谁,没有则向上找。
2、间接通过成员方法访问成员变量:
该方法【属于】谁,就优先用谁,没有则向上找。
(二)子类方法中重名的三种变量区分(局部、本成、父成)
1、局部变量: 直接写局员变量名
2、本类的成员变量: this.成员变量名
3、父类的成员变量: super.成员变量名
三、继承中的方法
(一)成员方法-覆盖重写
1、访问:在父子类的继承关系当中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先用谁,如果没有则向上找。
注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。
重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写。
重载(Overload):方法的名称一样,参数列表【不一样】。
2、重写(Override)
概念:重写要求两同两小一大原则: 方法名相同,参数类型相同,子类返回类型小于等于父类方法返回类型, 子类抛出异常小于等于父类方法抛出异常, 子类访问权限大于等于父类方法访问权限。
(1)必须保证父子类之间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
(2)子类方法的返回值类型必须【小于等于】父类方法的返回值类型。
小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。
(3)子类方法的抛出异常【小于等于】父类方法的抛出异常
(4)子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空。
(二)构造方法的访问
1、继承关系中,父子类构造方法的访问特点:
(1) 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造。
(2)子类构造可以通过super关键字来调用父类重载构造。
(3)super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。
(4)子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。
(5)父类的构造方法是不被子类继承的,只能从子类的构造方法中用super关键字调用。
四、super与this关键字
(一)super三用法
1、在子类的成员方法中,访问父类的成员变量。
2、在子类的成员方法中,访问父类的成员方法。
3、在子类的构造方法中,访问父类的构造方法。
(二)this三用法
1、在本类的成员方法中,访问本类的成员变量。
2、在本类的成员方法中,访问本类的另一个成员方法。
3、在本类的构造方法中,访问本类的另一个构造方法。
在第3种用法当中要注意:
A. this( )调用也必须是构造方法的第一个语句,唯一一个。
B. super和this两种构造调用,不能同时使用。
(三)super与this的内存图解
五、继承综合案例:发红包
(一)案例分析
(二)案例实现
第二节、抽象类
一、抽象的概念、格式
(一)概念
1、父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
2、被abstract修饰的内容都是暂未被实现的,比如类、方法。
属性之所以不能被abstract修饰,是因为属性不存在"尚未被实现"的状态。
比如你可能会联想到int age; 或是String name; 但可惜的是,在申明变量时,int会默认给age赋予初始值0,String会默认给name赋予初始值""。因此属性达不到"尚未被实现的状态",从而不能被abstract修饰。
(二)抽象方法和抽象类的格式
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
二、抽象的使用、注意事项
(一)如何使用抽象类和抽象方法:
1、不能直接创建new抽象类对象。
2、必须用一个子类来继承抽象父类。
3、子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
4、创建子类对象进行使用。
(二)抽象方法和抽象类的注意事项
1、抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
2、抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
3、抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
4、抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
第三节、接口
一、接口的概念、格式、特点
(一)接口的概念
1、接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么 接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。 接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并 不是类,而是另外一种【引用数据类型】。
2、接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
3、接口是一种特殊的抽象类。
(二)接口的格式
public interface 接口名称 {
// 接口内容
}
(三)接口的特点
1、内容特点:
抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)
在Java9+版本中,接口的内容可以有:
(1)成员变量其实是常量,格式:
[public] [static] [final] 数据类型 常量名称=数据值;
注意:常量必须进行赋值,而且一旦赋值不能改变。常量名称完全大写,用下划线进行分隔。
(2)接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型 方法名称(参数列表);
注意:实现类必须覆盖重写接口所有的抽象方法,除非实现类是抽象类。
(3)从Java8开始,接口里允许定义默认方法,格式:
[public] default 返回值类型 方法名称(参数列表){方法体}
注意:默认方法也可以被覆盖重写
(4)从Java8开始,接口里允许定义静态方法,格式:
[public] static 返回值类型 方法名称(参数列表){方法体}
注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法
(5)从Java9开始,接口里允许定义私有方法,格式:
普通私有方法:private 返回值类型 方法名称(参数列表){方法体}
静态私有方法:private static 返回值类型 方法名称(参数列表){方法体}
注意:private的方法只有接口自己才能调用,不能被实现类或别人使用。
2、实现特点(多实现)
(1)类与类之间是单继承的。直接父类只有一个。
(2)类与接口之间是多实现的。一个类可以实现多个接口。
(3)接口与接口之间是多继承的。
注意事项:
①多个父接口当中的抽象方法如果重复,没关系,不冲突因为抽象方法没有方法体。
②多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。
二、接口的常量
(一)格式
public static final 数据类型 常量名称 = 数据值;
备注:一旦使用final关键字进行修饰,说明不可改变。
(二)特点
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。
注意事项:
1、接口当中的常量,可以省略public static final,注意:不写也照样是这样。
2、接口当中的常量,必须进行赋值;不能不赋值。
3、接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
三、接口的四类方法(抽、默、静、私)
(一)抽象方法abstract
1、接口抽象方法定义
在任何版本的Java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
(1)接口当中的抽象方法,修饰符必须是两个固定的关键字:【public abstract】
(2)这两个关键字修饰符,可以选择性地省略。
(3)方法的三要素(返回值类型,方法名称,参数列表),可以随意定义。
2、接口抽象方法使用
接口使用步骤:
(1)接口不能直接使用,必须有一个“实现类”来“实现”该接口。
格式:
public class 实现类名称 implements 接口名称 {
// ...
}
(2)接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
实现:去掉abstract关键字,加上方法体大括号。
(3)创建实现类的对象,进行使用。
注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
(二)默认方法default (接口升级)
1、接口默认方法定义
从Java 8开始,接口里允许定义默认方法。
格式:
public default 返回值类型 方法名称(参数列表) {
方法体
}
备注:接口当中的默认方法,可以解决【接口升级】的问题。
2、接口默认方法使用
(1)接口的默认方法,可以通过接口【实现类对象】,直接【调用】。
(2)接口的默认方法,也可以被接口【实现类】进行覆盖【重写】。
(三)静态方法static
1、接口静态方法定义
从Java 8开始,接口当中允许定义静态方法。
格式:
public static 返回值类型 方法名称(参数列表) {
方法体
}
提示:就是将abstract或者default换成static即可,带上方法体。
2、接口静态方法使用
通过接口名称,直接调用其中的静态方法。
注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
格式:
接口名称.静态方法名(参数);
(四)私有方法private / private static
1、接囗私有方法定义
从Java 9开始,接口当中允许定义私有方法:
(1)普通私有方法,解决多个【默认方法】之间【重复代码】问题
格式:
private 返回值类型 方法名称(参数列表) {
方法体
}
(2)静态私有方法,解决多个【静态方法】之间【重复代码】问题
格式:
private static 返回值类型 方法名称(参数列表) {
方法体
}
四、继承父类并实现多个接口
(一)接口是没有静态代码块或者构造方法的。
(二)一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
(三)如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
(四)如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个【抽象类】。
(五)如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
(六)一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用【父类】当中的方法。
第四节、多态
一、多态的概念、格式、特点
(一)多态的概念
1、多态: 是指同一行为,具有多个不同表现形式。父类引用指向子类对象,引用变量所调用的方法总是表现出子类方法的行为特征。
2、多态的前提【重点】
(1)继承或者实现【二选一】
(2)方法的重写【意义体现:不重写,无意义】
(3)父类引用指向子类对象【格式体现】
(二)多态的格式
代码当中体现多态性,其实就是一句话:父类引用指向子类对象。(一只猫被看做成一只动物)
格式:
父类名称 对象名 = new 子类名称();
或者:
接口名称 对象名 = new 实现类名称();
(三)多态中的特点
1、成员变量(编译运行全看父类)
成员变量不能覆盖重写
成员变量不具备多态性,通过引用变量来访问其包含的实例变量,系统总是试图访问它编译时类型所定义的成员变量,而不是运行时类型所定义的成员变量。
什么是编译时类型和运行时类型?
Java中的许多对象(一般都是具有父子类关系的父类对象)在运行时都会出现两种类型:编译时类型和运行时类型,例如:Person person = new Student();这行代码将会生成一个person变量,该变量的编译时类型是Person,运行时类型是Student。
Java的引用变量有两个类型,一个是编译时类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,会出现所谓的多态。因为子类其实是一种特殊的父类,因此java允许把一个子类对象直接赋值给一个父类引用变量,无须任何类型转换,或者被称为向上转型,由系统自动完成。
有一些父类,他允许被继承,但是他本身有一些属于自己的函数,这些函数也允许覆盖,但是函数中有一些自己必须要执行的步骤,如果不执行就会导致类错误,所以我们在覆盖这些函数的时候,必须调用super.xxx。让父函数正常执行,才能执行我们覆盖后添加的内容。
2、成员方法(编译看父类,运行看子类)
在多态的代码当中,成员方法的访问规则是:
看new的是谁,就优先用谁,没有则向上找。
口诀:编译看左边,运行看右边。
对比一下:
成员变量:编译看左边,运行还看左边。***
成员方法:编译看左边,运行看右边。
3、多态的好处
二、多态的转型(向上、向下)
(一)对象的向上转型
向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
解决方案:用对象的向下转型【还原】。
(二)对象的向下转型
三、用instanceof关键字进行父子类判断
如何才能知道一个父类引用的对象,本来是什么子类?
格式:
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
四、多态案例笔记本USB
(一)笔记本USB接口案例分析
(二)笔记本USB接口案例实现
第五节、fina关键字
一、fina关键字概念
(一)final关键字概念
学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承 API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字, 用于修饰不可改变内容。
final: 不可改变。可以用于修饰类、方法和变量。
类:被修饰的类,不能被继承。
方法:被修饰的方法,不能被重写。
变量:被修饰的变量,不能被重新赋值
(二)常见四种用法:
1. 可以用来修饰一个类
2. 可以用来修饰一个方法
3. 还可以用来修饰一个局部变量
4. 还可以用来修饰一个成员变量
二、final的4种用法(类、局变、成变、成方)
(一)final修饰类
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
// ...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)
(二)final修饰局部变量
(三)final修饰成员变量
1、由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2、对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3、必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
(四)final修饰成员方法
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表) {
// 方法体
}
注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
第六节、权限修饰符
编写代码时,如果没有特殊的考虑,建议这样使用权限:
成员变量使用 private ,隐藏细节。
构造方法使用 public ,方便创建对象。
成员方法使用 public ,方便调用方法。
第七节、内部类
一、内部类的概念
(一)内部类的概念
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。
(二)分类:
1、成员内部类
2、局部内部类(包含匿名内部类)
二、成员内部类
(一)成员内部类的格式
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// ...
}
// ...
}
注意:内用外,随意访问;外用内,需要内部类对象。
(二)成员内部类的访问特点
1. 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2. 直接方式,
类名称 对象名 = new 类名称();
【 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称(); 】
(三)成员内部类的同名变量访问
如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
三、局部内部类
(一)局部内部类的格式
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表) {
class 局部内部类名称 {
// ...
}
}
}
类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1、外部类:public / (default)
2、成员内部类:public / protected / (default) / private
3、局部内部类:什么都不能写
(二)局部内部类的final问题
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。
原因:
1、new出来的对象在堆内存当中。
2、局部变量是跟着方法走的,在栈内存当中。
3、方法运行结束之后,立刻出栈,局部变量就会立刻消失。
4、但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
(三)局部内部类重点:【匿名内部类】
1、匿名内部类的格式
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
.....
};
2、匿名内部类的注意事项
对格式“new 接口名称() {...}”进行解析:
(1)new代表创建对象的动作
(2)接口名称就是匿名内部类需要实现哪个接口
(3) {...}这才是匿名内部类的内容
另外还要注意几点问题:
(1)匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
(2)匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
(3)匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!
第八节、引用类型用法总结
一、calss作为成员变量类型
二、interface作为成员变量、方法参数、返回值类型