一、继承
1.1 概述
① 继承是多态的前提,如果没有继承,就没有多态。
② 继承主要解决的问题就是:共性抽取。
父类也可以成为基类、超类;子类也可以成为派生类。
① 子类可以拥有父类的内容。
② 子类还可以拥有自己专有的内容。
1.2 格式
在继承关系中,“子类就是一个父类”。也就是说,子类可以被当作父类看待。例如父类是员工,子类是讲师,那么“讲师就是一个员工”,是 is-a 关系。
//父类就是一个普通的类定义
public class 父类名称{
//...
}
//定义子类
public class 子类名称 extends 父类名称{
//...
}
1.3 成员变量重名
在继承关系中,如果成员变量重名,则创建子类对象时,访问有两种方式。
① 直接通过子类对象访问成员变量
等号左边是哪个类(即定义的是哪个类的对象),就优先用哪个类中的那个同名变量,没有则向上找。
② 间接通过成员方法访问成员变量
该方法在哪个类中定义,就优先用哪个类中的同名变量,没有则向上找 。
父类:
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() {
// 使用的是本类当中的,不会向下找子类的
System.out.println(num);
}
}
子类:
public class Zi extends Fu {
int numZi = 20;
int num = 200;
public void methodZi() {
// 因为本类当中有num,所以这里用的是本类的num
System.out.println(num);
}
}
主类:
public class Demo01ExtendsField {
public static void main(String[] args) {
Fu fu = new Fu(); // 创建父类对象
System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容
System.out.println("===========");
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
System.out.println("===========");
// 等号左边是谁,就优先用谁
System.out.println(zi.num); // 优先子类,200
// System.out.println(zi.abc); // 到处都没有,编译报错!
System.out.println("===========");
// 这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi(); // 200
// 这个方法是在父类当中定义的,
zi.methodFu(); // 100
}
}
三种重名
局部变量 直接写变量名
本类的成员变量 this.变量名
父类的成员变量 super.变量名
1.4 成员方法重名
如果方法重名,创建的是谁(即new的是谁)就优先用谁,如果没有则向上找。
注意: 无论是成员方法还是成员变量,如果没有都是向上找父类,不会向下找子类的。
1.5 方法的重写
1.5.1 重写与重载
重写(Override): 方法的名称一样,参数列表也一样。也叫覆盖、覆写。
重载(Overload): 方法的名称一样,参数列表不一样。
- 创建的是子类对象(即new的是谁),则优先用子类方法。
注意: 如果在某个类中有同名方法,如果他们不是重载方法,就会报错。
1.5.2 重写的判断条件
① @Override:写在方法前面,用来检测是否是有效的正确的覆盖重写。这是一种【注解】。(它并非必须要写)
② 子类方法的返回值必须【小于等于】父类方法的返回值范围(小于即为其子类)。
java.lang.Object
类是所有类的公共最高父类(祖宗类)
③ 子类方法的权限必须【大于等于】父类方法的权限修饰符。
public>protected>(default)>private
1.6 继承的设计原则
对于已经投入使用的类,尽量不要进行修改,推荐定义一个新的类来重复利用其中的共性内容,并且添加新的内容或者重写旧的内容。
可以在子类的覆盖重写方法中利用 super.方法名() 来调用父类的相应方法,再加上自己要添加的内容。
// 本来的老款手机
public class Phone {
public void call() {
System.out.println("打电话");
}
public void send() {
System.out.println("发短信");
}
public void show() {
System.out.println("显示号码");
}
}
// 定义一个新手机,使用老手机作为父类
public class NewPhone extends Phone {
@Override
public void show() {
super.show(); // 把父类的show方法拿过来重复利用
// 自己子类再来添加更多内容
System.out.println("显示姓名");
System.out.println("显示头像");
}
}
1.7 构造方法的访问
① 子类构造方法当中有一个默认隐含的 super()
(父类的无参构造方法)调用,所以一定是先调用父类构造方法,再调用子类构造方法。
② 子类构造方法可以自己传参调用父类的重载构造方法
③ 只有子类构造方法可以调用父类构造方法,子类的其他方法不可以调用。
④ super()必须写在子类构造方法的第一个语句,因此不能写多个父类构造语句。
⑤ 如果父类没有无参构造方法,且子类构造方法没有调用父类的有参构造方法,则会报错。
1.8 继承的特点
① Java语言是单继承的,一个类的直接父类只能有一个。
② Java语言可以多级继承(父类的父类也称作父类)。
③ 一个父类可以有多个子类。
二、super和this总结
2.1 super
① 在子类的成员方法中,访问父类的成员变量。
② 在子类的成员方法中,访问父类的成员方法。
③ 在子类的构造方法中,访问父类的构造方法。
2.2 this
① 在本类的成员方法中,访问本类的成员变量。
② 在本类的成员方法中,访问本类的另一个成员方法(可以强调该方法来自本类)。
③ 在本类的构造方法中,访问本类的另一个构造方法。
this()
必须是第一个语句,只能有一个。super()
和this()
不能同时使用。- 若不写有参的
super()
,这里仍然会默认调用无参的super()
。 - 不可以在多个构造方法中循环调用,程序会报错。
//不可以在多个构造方法中循环调用,程序会报错。
public Zi() {
this(123);
}
public Zi(int n) {
this(1, 2);
}
public Zi(int n, int m) {
this();
}
2.3 super与this图解
三、抽象类
如果父类的方法不确定如何进行方法体编写,那么这就可以定义为一个抽象方法。
3.1 定义
public abstract class Animal{
public abstract void eat();
public void method(){}
}
抽象方法的格式: 修饰符 abstract 返回值类型 方法名称(参数列表);
注意: 这里的修饰符不能是 private,可以是其他类型。
① 抽象方法所在的类必须是抽象类。
② 抽象类中可以有普通的方法,抽象类中不一定含有抽象方法。
3.2 使用
① 无法直接创建抽象类对象。
② 必须用一个子类来继承抽象父类。
③ 子类必须覆盖重写(也叫实现)抽象父类中的所有抽象方法(要去掉abstract关键字,并写上方法体)。
3.3 注意
① 抽象类中可以有构造方法。
② 抽象类的子类,必须重写抽象父类中的所有的抽象方法,除非该子类也是抽象类。
四、接口
4.1 概念
接口就是多个类的公共规范。
接口是一种引用类型数据,最重要的内容就是其中的抽象方法。
public interface 接口名{
//接口
}
jdk7 接口中包含 1.常量 2.抽象方法
jdk8 接口中增加了 3.默认方法 4.静态方法
jdk9 接口中增加了 5.私有方法
注意: 换成了关键字interface之后,编译生成的字节码文件仍然是:.java
—> .class
。
4.2 抽象方法
必须用两个固定的关键字:public 和 abstract(可以两个都写,也可以只写一个,也可以都不写)
4.3 使用
① 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
//一般接口的实现类命名为接口名称末尾添加上Impl
public class 实现类名称 implements 接口名称{
//...
}
② 实现类必须覆盖重写接口的所有抽象方法。(除非这个实现类是抽象类)
③ 创建实现类的对象,进行使用。
④ 接口和类一样,都可以作为成员变量类型、方法的参数类型和返回值类型
4.4 默认方法
//public可以省略,default不可以省略
public default 返回值类型 方法名称(参数列表){
方法体
}
用于解决接口升级问题:原接口需要增加方法,如果用抽象方法,需要修改实现类,这样工作量太大,所以用默认方法。
① 接口的默认方法,可以通过接口实现类对象,直接调用。
② 接口的默认方法,也可以被接口实现类进行覆盖重写。
4.5 静态方法
//public可以省略,static不可以省略
public static 返回值类型 方法名称(参数列表){
方法体
}
直接用接口名来调用,不能通过接口实现类的对象来调用接口当中的静态方法。
4.6 私有方法
私有方法只能被接口自己调用,不能被实现类或其他类调用。
① 普通私有方法,解决多个默认方法之间重复代码问题
private 返回值类型 方法名称(参数列表){
方法体
}
② 静态私有方法,解决多个静态代码之间重复代码问题
private static 返回值类型 方法名称(参数列表){
方法体
}
4.7 常量
//这三个关键字可以随意省略
//常量名称完全用大写字母,多个单词用下划线隔开
public static final 数据类型 常量名称= 数据值;
一旦赋值,不可修改!且定义时必须赋值!
//调用
接口名称.常量名称;
4.8 注意事项
① 接口中不可以定义静态代码块,也不可以定义构造方法。
② 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{
//覆盖重写两个接口的所有抽象方法
}
③ 如果实现类实现的多个接口中存在同名同返回值类型的方法抽象方法,那只需要覆盖重写一次即可。
④ 如果实现类没有覆盖重写所有的抽象方法,那么它必须是一个抽象类。
⑤ 如果实现类实现的多个接口中,存在同名同返回值类型的方法,那么实现类一定要对冲突的默认方法进行覆盖重写。(去掉default关键字)
⑥ 一个类如果直接父类当中的方法和接口当中的方法产生了冲突,会优先使用父类中的方法。
⑦ 如果实现类实现的多个接口存在同名但不同返回值类型的方法,实现类无法全部实现这些方法,会报错;所以定义接口时要注意。
4.9 接口的多继承
① 接口之间可以进行多继承
② 子接口的多个父接口的抽象方法重复,只进行一次重写。
③ 子接口的多个父接口的默认方法重复,则要在子接口中进行默认方法的重写。(在子接口中仍然是默认方法,即要带着default关键字)
五、多态
5.1 概念
extends 和 implements 是多态性的前提。
5.2 使用
即 父类引用指向子类对象。
父类名称 对象名 = new 子类名称();
接口名称 对象名 = new 实现类名称();
//成员方法看new的是谁,就运行谁的,否则向上找
5.3 成员变量
① 直接通过对象名称访问成员变量
看等号左边是谁,就用谁的成员变量,没有则向上找
② 间接通过对象名称访问成员变量
看调用的是谁的方法,就用谁的成员变量,没有则向上找
口诀:编译看左边,运行看左边。(访问成员变量时)
5.4 成员方法
new的是谁,则访问谁,没有就向上寻找
口诀:编译看左边,运行看右边。(访问成员方法时)
//Animal类
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
//Dog类
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃屎");
}
public void watchHouse(){
System.out.println("狗会看家");
}
}
//主类
public class MainClass {
public static void main(String[] args) {
Animal animal = new Dog();
animal.watchHouse();//会编译报错(编译看左边)
animal.eat();
}
}
5.5 向上转型
//类似于自动类型转换
父类名称 对象名 = new 子类名称();
向上转型一定是安全的。但是有个弊端,就是没办法访问子类的专有的方法。
5.6 向下转型
其实是一个还原的动作,还原成本来的子类对象(如果还原成其他子类,编译没有错误,运行会报错)。
//类似于强制类型转换
子类名称 对象名 = (子类名称)父类对象;
Animal animal = new Cat();//本来是猫,向上转型成为动物
Cat cat = (Cat) animal;//还原成为本来的猫
5.7 instanceof
对象 instanceof 类名称;//返回一个布尔值,返回前面的对象是不是后面类型的实例
public class Demo02Instanceof {
public static void main(String[] args) {
Animal animal = new Dog(); // 本来是一只狗
animal.eat(); // 狗吃SHIT
// 如果希望掉用子类特有方法,需要向下转型
// 判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
// 判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
giveMeAPet(new Dog());
}
public static void giveMeAPet(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
}
六、笔记本案例
进行描述笔记本类,实现笔记本使用USB鼠标、USB键盘:
- USB 接口,包含开启功能、关闭功能
- 笔记本类,包含运行功能、关机功能、使用 USB设备功能
- 鼠标类,要实现 USB接口,并具备点击的方法
- 键盘类,要实现 USB接口,具备敲击的方法
USB接口
public interface USB {
public abstract void open();
public abstract void close();
}
笔记本类
public class Laptop {
public void powerOn(){
System.out.println("打开笔记本电脑");
}
public void powerOff(){
System.out.println("关闭笔记本电脑");
}
public void useDevice(USB usb){
if(usb instanceof Mouse){
Mouse mouse = new Mouse();
mouse.click();
}else if(usb instanceof Keyboard){
Keyboard keyboard = new Keyboard();
keyboard.type();
}
}
}
鼠标类
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click(){
System.out.println("点击鼠标");
}
}
键盘类
public class Keyboard implements USB {
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void type(){
System.out.println("键盘输入");
}
}
主类
public class MainClass {
public static void main(String[] args) {
Laptop laptop = new Laptop();
laptop.powerOn();//笔记本开机
USB usbMouse = new Mouse();//向上转型
laptop.useDevice(usbMouse);//使用鼠标
Keyboard keyboard = new Keyboard();
laptop.useDevice(keyboard);//向上转型,类似于基本数据类型的自动转换
laptop.powerOff();//笔记本关机
}
}
七、final
代表最终的、不可变的。
7.1 修饰类
这个类不能有子类(但有父类)
public final class 类名称{
//...
}
7.2 修饰方法
修饰方法时,这个方法为最终方法,不能被覆盖重写。
修饰符 final 返回值类型 方法名称(参数列表){
//方法体
}
注意: abstract
和 final
不能同时修饰类,也不能同时修饰方法。
7.3 修饰局部变量
一旦使用final来修饰局部变量,那么这个变量就不能进行更改。
final int num;
num = 30
final int num1 = 30;
注意:对于基本类型来说,不可变说的是变量当中的数据不可变;而对于引用类型来说,不可变说的是变量当中的地址值不可改变(但是其中的内容可以变)
7.4 修饰成员变量
使用final关键字修饰成员变量,则这个变量不可改变
注意:
1.成员变量声明了final之后要赋值,不会再有默认值了。
2.对于final的成员变量,那么以下选其一(不能选两个):要么使用直接赋值,要么通过构造方法赋值(所有的构造方法都要对final成员变量赋值,否则报错)。
八、权限修饰符
java中有四种权限修饰符:
public > protected > (default) > private
public | protected | (default) | private | |
---|---|---|---|---|
同一个类 | √ | √ | √ | √ |
同一个包 | √ | √ | √ | |
不同包子类 | √ | √ | ||
不同包非子类 | √ |
九、内部类
一个类内部包含的另一个类。例如:身体和心脏的关系。
9.1 成员内部类
修饰符 class 外部类名称{
修饰符 class 内部类名称{
//...
}
//...
}
注意: 内用外,随意访问;外用内,需要内部类对象。
调用:
① 间接方式:在外部类的方法中,创建内部类对象,然后main只是调用外部类的方法。
② 直接方式:
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
9.2 局部内部类
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//...
}
}
}
9.3 修饰符
① 外部类:public / (default)
② 成员内部类:public / protected / (defaul) / private
③ 局部内部类:什么都不写
9.4 局部内部类的final
如果希望在局部内部类访问所在方法的局部变量,那么这个局部变量必须定义final,但也可以省略(但不能修改值)。
① new出来的对象在堆内存中
② 局部变量是跟着方法走的,在栈内存当中
③ 方法运行结束之后,立刻出栈,局部变量立刻消失,
④ 但是new出来的对象会在堆内存当中持续存在,直到垃圾回收消失。(此时局部内部类仍然想要使用局部变量,但是局部变量已经消失,所以就会需要拷贝之前的局部变量,同时保证之前的值不可更改)
public class MyOuter {
public void methodOuter() {
int num = 10; // 所在方法的局部变量
num = 20;//报错,值不可更改
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}
}
9.5 匿名内部类
匿名内部类是局部内部类的一种。
如果接口的实现类(或者父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用匿名内部类。
接口名/父类名 对象名 = new 接口名/父类名(){
//覆盖重写所有 抽象方法
};//末尾有个分号
/*
① new 代表创建对象的动作
② 接口名称就是匿名内部类需要实现哪个接口
③ {...}这才是匿名内部类的内容
*/
注意:
① 匿名内部类在创建对象时只能使用唯一一次,如果希望多次创建对象,只能再次写一遍匿名内部类。
② 所谓的匿名是,是类名称为匿名,不是对象匿名。
9.6 重名问题
内部类成员变量、内部类局部变量和外部类成员变量重名:
//在内部类的方法中
变量名//内部类局部变量
this.变量名//内部类成员变量
外部类名.this.变量名//外部类成员变量
//局部内部类
public class Outer {
int num = 10;
public void methodOuter(){
int num = 20;
class Inner {
int num = 30;
public void methodInner() {
int num = 40;
System.out.println(Outer.this.num);//10
System.out.println(this.num);//30
System.out.println(num);//40
}
}
new Inner().methodInner();
}
}
这里无法访问值为20的num,有一解释为,由于局部内部类会复制一份外部类成员方法的局部变量。而这里又在内部类的局部变量与其同名,所以直接把外部类的局部变量覆盖掉了。(该说法有待考证)