九、面向对象之继承
2. 继承
2.1 概念:
关键字extends建立一个类与另一个类的父子关系
2.2 写法:
public class B extends A {
}
A被称为父类、基类、超类
B被称为子类、派生类
2.4 特点:
继承父类非私有成员(变量、方法)
2.5 继承后对象的创建:
子类的对象由子类、父类共同完成
2.6 继承的执行原理:
子类会把子类自己的变量和父类的变量在new的时候都在堆内存中开辟存储空间,只不过public的可以直接用.访问,其他private要通过get、set方法。
2.7 继承的好处:
减少重复代码的编写
Test.java
public class Test {
public static void main(String[] args) {
Teacher t1 = new Teacher();
t1.setName("xx");
t1.setSkill("Math");
t1.printInfo();
}
}
People.java
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Teacher.java
public class Teacher extends People{
private String skill;
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
public void printInfo() {
System.out.println(getName() + "的技能是" + getSkill());
}
}
2.8 权限修饰符
限制类中的成员能够被访问的范围
private:本类中
缺省:本类中,同一个包下的其他类
protected:本类中,同一个包下的其他类,其他包下的子类(不是子类对象)
public:本类中,同一个包下的其他类,其他包下的子类,任意包下的任意类
2.9 单继承、Object类
Java是单继承的,Java中的类不支持多继承(不同的父类相同的方法不知道调用谁),但是支持多层继承
Object类是Java所有类的祖宗
2.10 方法重写
子类为满足自己的需求,定义一个与父类方法名称相同、参数列表一样的方法,覆盖父类的方法
重写后,方法的访问,Java遵循就近原则
重载一般方法功能接近类似,重写会扩展功能
重载的参数列表不同,重写的参数列表相同
重写的注意事项:——按格式顺序思考一遍
使用@Override注解,可以指定java编译器,检查重写的格式是否正确,并且增加代码的可读性
访问权限子类重写时应比父类大(public > protected > 缺省)
返回值子类重写时应比父类小
私有和静态方法不能被重写,否则会报错
技巧:申明不变,重新实现
应用场景:
子类重写Object类的toString()方法,以便返回对象的内容
子类访问成员的特点:
就近原则
访问局部变量:变量名称访问
访问该对象的变量:this关键字
访问该对象的父类中的变量:super关键字
问题:如何访问父类的父类中的变量或方法——其实父类已经完全继承下来了,所以super已经够用了
2.11 子类构造器的特点
全部构造器都会先调用父类的无参数构造器,再执行自己。——子类的构造器会有一行默认代码super(),调用父类的无参构造器;如果父类没有无参构造器,需要手写super(……),指定去调用父类的构造器
应用场景:
数据被拆分在多个类中,要通过各自数据所在类的构造器进行初始化
注意:
子类的无参构造器不使用的话不定义不会报错,如果默认调用父类的无参构造器,父类就必须写了
this(……)调用该类的其他构造器;不能同时使用supper();他俩都应该在第一行
3. 多态
3.1 概念:
在继承/实现情况下的一种现象,表现为对象多态、行为多态(不存在变量多态)
实例: 一个人既可以是学生、也可以是孩子(也就是多重身份),每种身份下的同一种行为可能会存在差异(后续这个例子需要完善)——这个例子有点不确切,能说一个大类下可以分很多小类,每个小类的方法可能存在差别,是大类不同方向的扩充。
前提: 有继承/实现关系;存在父类引用子类对象;存在方法重写(一定)——编译看左边,运行看右边
3.2 好处:
- 右边对象的随意解耦合
- 父类类型的范围更大,可以使用父类类型的变量作为形参,可以接收一切子类对象——多态下不能使用子类的独有功能——>多态下的类型转换
3.3 强制类型转换
- 概念: 子类 变量名 = (子类)父类变量 Teacher t = (Teacher) p;
- 前提: 存在继承/实现关系
- 注意: 如果对象的真实类型与强转后的类型不同,会报类型转化异常(ClassCastException)
- 解决: 使用instanceof关键字判断当前对象的这怎是类型,再进行强转
4. final关键字
可以修饰类、方法、变量
修饰类:该类被称为最终类,不能被继承了——场景:工具类
修饰方法:该方法被称为最终方法,不能被重写了——但是可以重载
修饰变量:该变量只能被赋值一次,定义是时候就必须进行赋值(局部变量,修饰静态成员变量相当常量——通常来记录配置信息,修饰实例成员变量没有意义——每个对象成员变量都不相同)
常量记录系统配置信息的优势、执行原理:常量+宏定义——出现常量的地方在程序编译之后会全部替换成字面量,保证性能
5. 抽象类
abstract修饰
抽象方法: 必须用abstract修饰,只有方法签名,一定不能有方法体——也就是没有具体实现所以是抽线的
抽象类: abstract修饰的类
注意:
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类——只要用abstract修饰的类就是抽象类,即使内部都不是抽象方法,也不能创建对象;不使用abstract修饰,内部就不能定义抽象方法
- 抽象类中有全部类该有的成员
- 主要特点是:不能创建对象,只能作为一种特殊的父类,让子类继承并实现
- 一个类继承了抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义为抽象类
好处:
更好地支持多态——不用类型转换就能实现调用子类方法
场景:模板方法 设计模式
解决方法中存在重复代码的问题
抽象类的话两个子类的方法没有相同的地方;重写的话可以保留相同的地方
**建议使用final修饰模板方法!!**防止被子类重写
6. 接口
6.1 概念
接口是一种特殊结构,用interface关键字修饰,不能创建对象(有抽象方法的类是抽象类,不能创建对象)
public interface 接口名{
// 成员变量(常量)——通过类名访问
// 成员方法(抽象方法)——其他方法存在方法体,与抽象方法是违背的
}
6.2 实现
Test被称为实现类
class Test implements A, B{
实现A,B接口的全部抽象方法 / 把Test类设置成抽象类
}
6.3 好处
- 弥补了单继承的不足,一个类可以同时实现多个接口
- 让程序面向接口遍程,可以灵活切换各种业务实现
一个人有多重身份,通过接口就知道有哪些身份
之后添加功能方便,总代码简单不需要定义很多变量就可以产生不同的效果。——灵活切换实现方案
6.4 细节
JDK8开始,接口中新增的三种方法:
public interface A {
// 1. 默认方法——实例方法
// 使用实现类的对象访问,通过实例对象就可以访问
// 如果之后每个实现类都要新增一个方法,可以在接口中新增一个default方法就行
default void test1() {
System.out.println("=====默认方法=====");
test2();
}
// JDK 9之后
// 私有方法:必须使用private修饰,类内访问
private void test2() {
System.out.println("=====私有方法=====");
}
// 静态方法:static修饰,默认会被public修饰,通过接口名访问
static void test3() {
System.out.println("=====静态方法=====");
}
}
6.5 接口的多继承
一个接口可以同时继承多个接口,可以把多个接口合并成一个接口,便于实现类实现
public interface C extends B, A {
}
7. 内部类
概念: 如果一个类定义在另一个类的内部,这个类就是内部类
格式:
public class Car {
// 内部类
public class Engine {
}
}
场景:
一个类的内部包含一个完整事物,但这个事物没有必要单独设计
分类:
成员内部类:都可以定义类中的东西,但是静态成员是从JDK16之后才可以定义的。Outer.Inner in = new Outer().new Inner();
静态内部类:类有的静态内部类都可以。Outer.Inner in = new Outer.Inner();
局部内部类:
**匿名内部类:**特殊的局部内部类,不需要有名字
7.1 匿名内部类
本质是一个子类,并会立即创建出一个子类对象。不是自己主动要用,而是用别人的需要用。
格式:
new 类或接口 (参数值) {
类体(一般是方法重写);
}
应用场景:
通常作为一个参数传输传输给方法
实例1:
public class Test {
public static void main(String[] args) {
Swim s = new Swim() {
@Override
public void swim() {
System.out.println("这个是小狗");
}
};
go(new Swim() {
@Override
public void swim() {
System.out.println("这个是小猫");
}
});
}
public static void go(Swim s) {
s.swim();
System.out.println("go wayong!");
}
}
interface Swim {
public void swim();
}
GUI界面场景:
import javax.swing.*;
import java.awt.event.ActionEvent;
public class Test {
public static void main(String[] args) {
// GUI编程
// 1. 创建窗口
JFrame win = new JFrame("登录界面");
JPanel panel = new JPanel(); // 自适应布局
win.add(panel);
JButton btn = new JButton("登录");
panel.add(btn);
// 给按钮绑定单击事件监听器
// btn.addActionListener(new AbstractAction() {
// @Override
// public void actionPerformed(ActionEvent e) {
// JOptionPane.showMessageDialog(win, "登陆啦!!");
// }
// });
// 最终目的是简化代码
btn.addActionListener(a -> JOptionPane.showMessageDialog(win, "这样也能登录呀~~"));
win.setSize(400, 400);
// 居中
win.setLocationRelativeTo(null);
// 关闭窗口退出程序
win.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
win.setVisible(true);
}
}
8. 枚举
8.1 概念
枚举是一种特殊的类
8.2 格式
修饰符 enum 枚举类名{
名称1,名称2 ,……;
其他成员……
}
public enum A {
X,
Y,
Z;
private String name;
private A() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
- 枚举类的构造器是私有的,不能对外创建对象
- 枚举类的第一行都是常量,记住的是枚举类的对象
- 枚举类提供了一些额外的API:拿到全部对象
8.3 抽象枚举
构建对象的时候要重写就行
8.4 应用场景
用来表示一组信息,然后作为参数进行传输
public class Test {
public static void main(String[] args) {
// 枚举的应用场景——信息标志和分类
check(Constant2.GIRL);
// check(Constant.BOY);
}
public static void check(Constant2 sex) {
switch (sex) {
case BOY:
System.out.println("男孩子嗷");
break;
case GIRL:
System.out.println("女孩子嗷");
break;
}
}
// public static void check(int flag) {
// switch (flag) {
// case Constant.BOY:
// System.out.println("男孩子嗷");
// break;
// case Constant.GIRL:
// System.out.println("女孩子嗷");
// break;
// }
// }
}
public class Constant {
public static final int BOY = 0;
public static final int GIRL = 1;
}
public enum Constant2 {
BOY, GIRL;
}
9. 泛型
9.1 概念
定义类、接口、方法时,同时声明了一个或者多个类型变量(),称为泛型类、泛型接口、泛型方法,他们统称为泛型。
9.2 作用
在编译阶段约束所能操作的数据类型,并自动进行检查!这样可以避免强制类型转换、及可能出现的异常。
本质: 把具体的数据类型作为参数传给了类型变量
9.3 自定义
泛型类格式:
修饰符 class 类名<类型变量, 类型变量, ……> {
}
public class MyClass2<E, T> {
public void put(E e, T t) {
}
}
public class Test {
public static void main(String[] args) {
MyClass2<Cat, String> c2 = new MyClass2<>();
}
}
泛型方法格式:
修饰符 <类型变量, 类型变量, ……> 返回值类型 方法名(形参列表) {
}
使用已有泛型的时候可以使用<?>表示任意类型,要想给他增加限制,例如:上限<? extends 类名>,只能访问本类及子类;下限<? super 类名>,只能访问本类及父类。
为什么通常用E, T, K, V关键字
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
9.4 注意事项
泛型擦除: 泛型工作是在编译阶段,一旦编译完成,class文件中就不存在泛型了——底层还是Object类型的,只不过多了强制类型转换
泛型不支持基本数据类型,只能支持对象类型(引用数据类型)——要使用其对应的类
public class Test {
public static void main(String[] args) {
String rs = test("java");
System.out.println(rs);
ArrayList<Integer> list1 = new ArrayList<>();
}
// 泛型方法
public static <T> String test(T t) {
// return t;
return "泛型";
}
}