java初探

18 阅读23分钟

1. java 初探

1.1. 基础

1.1.1. 存储

  • 计算机以二进制的形式对数据进行存储
  • 计算机中最小的存储单元是字节(Byte), 一个字节由8个二进制位组成, 即1Byte=8bit

1.1.2. 数据类型

1.1.2.1. 基本数据类型
  • 整数类型

    • byte 1字节, 取值范围 -128127, 即 -2727-1

    • short 2字节, 取值范围 -3276832767, 即 -215215-1

    • int 4字节

    • long 8字节

      • 定义时末尾需要添加L或者l
  • 浮点数类型

    • float 4字节

      • 定义时末尾需要添加F或者f
    • double 8字节

  • 字符类型

    • char 2字节

      • 定义时需要使用单引号
  • 布尔类型

    • boolean 1字节
  • 空类型

    • null 类型
1.1.2.2. 引用数据类型

1.1.3. 关键字

1.1.3.1. class
  • 定义一个类
  • 类名首字母大写
  • 在大括号中书写代码

1.1.4. 标识符

  • 只能是数字、字母、下划线或$构成
  • 不能以数字开头
  • 不能是关键字

1.1.5. 运算符

1.1.5.1. 算术运算符
  • + 加法运算

  • - 减法运算

  • * 乘法运算

  • / 除法运算

    • 整数相除为整数, 抛弃余数
  • % 取余运算

1.1.5.2. 自增自减运算符
  • ++ 自增运算

    • ++a表示先自增再做其他运算
    • a++表示先做其他运算再自增
  • -- 自减运算

    • --a表示先自减再做其他运算
    • a--表示先做其他运算再自减
1.1.5.3. 赋值运算符
  • =
  • +=
  • -=
  • *=
  • /=
  • %=
1.1.5.4. 关系运算符
  • == 等于
  • != 不等于
  • > 大于
  • < 小于
  • >= 大于等于
  • <= 小于等于
  • instanceof 判断是否为某个类的实例
1.1.5.5. 逻辑运算符
  • & 逻辑与
  • | 逻辑或
  • ! 逻辑非
  • ^ 逻辑异或
  • && 短路与, 左侧为假则右侧不执行
  • || 短路或, 左侧为真则右侧不执行
1.1.5.6. 三元运算符
  • ? :
1.1.5.7. 运算符优先级
  • ()括号优先级最高

小数运算时结果可能不精确

1.1.5.8. 类型转换
1.1.5.8.1. 数字运算
  • 隐式类型转换

    • 小范围类型自动转换为大范围类型
    • 转换顺序为byte->short->int->long->float->double
    • byteshort运算时, 会自动转换为int类型
  • 强制类型转换

    • 将大范围类型转换为小范围类型
    • double->float->long->int->short->byte
    • 转换时可能会丢失精度
    • 转换时需要使用小括号将转换类型括起来
1.1.5.8.2. 字符运算
  • char类型可以与整数进行运算
  • char类型可以与整数进行运算时, 会将char类型转换为int类型
  • 字符会根据ASCII表中的值进行运算
1.1.5.8.3. 字符串运算
  • 只要有字符串参与, 则直接拼接

1.1.6. 流程控制

1.1.6.1. 判断语句
  • if

  • if-else

  • if else-if else

    • 一般地, 将范围最小的写在最前面
  • switch-case-default

    • switch中的表达式可以是byteshortcharintStringenum

    • case中的值不能重复

    • default可以在任意位置

    • break用于跳出switch-case-default语句

      • 不写break会继续执行下一个case语句

switch语句特殊说明

  • 箭头标签
 public static Void main(String[] args) {
     switch (x) {
         case 1 -> {
             // 代码块 不会有case穿透
         }
         default -> {
             // 代码块
         }
     }
 }
  • case可用逗号分隔多个值
 public static Void main(String[] args) {
     switch (x) {
         case 1, 2, 3 -> {
             // 代码块 不会有case穿透
         }
         default -> {
             // 代码块
         }
     }
 }
  • switch可以有返回值

    • 如果箭头后的代码块只有一行, 则可以省略{}yield
 public static Void main(String[] args) {
     String res = switch (x) {
         case 1, 2, 3 -> {
             // 代码块 不会有case穿透
             yield "一";
         }
         default -> {
             // 代码块
         }
     };
 }
1.1.6.2. 循环语句
  • for循环

    • for(初始化表达式; 条件判断语句; 条件控制语句) { 循环体 }
    • 一般地, 知道初始条件与结束条件之间的范围时使用
  • while循环

    • 一般地, 如果知道结束条件时使用
  • do-while循环

    • 至少会执行一次循环体
1.1.6.3. 循环控制语句
  • break跳出循环
  • continue跳过本次循环

1.1.7. 转义字符

1.1.7.1. \t制表符
  • 制表符的原理是: 相邻的前项字符串如果长度不是4的倍数,那么在该字符串之后补充1~4个空格,使之凑成4的倍数

1.1.8. 常用api

  • 生成随机数

    • Math.random()生成[0,1)之间的随机数

    • Random

      • ran.nextInt()

        • 不传参数表示默认生成整个int范围内的随机整数
        • 只传一个参数表示生成[0,n-1)之间的整数
        • 传递两个参数表示生成[m,n-1)之间的整数

1.1.9. 数组

  • 数组是引用数据类型
  • 数组用来存储相同的数据类型
  • 数组一旦创建, 就不能改变长度
  • 数组在内存中是连续存储的
1.1.9.1. 静态数组初始化
  • 静态数组指的是在定义数组时, 明确知道数组中的内容

  • 完整写法

    • 数据类型[] 数组名 = new 数据类型[]{元素1,元素2,...};
    • int[] arr = new int[]{1,2,3,4,5};
  • 简写

    • 数据类型[] 数组名 = {元素1,元素2,...};
    • int[] arr = {1,2,3,4};
1.1.9.2. 数组的元素访问
  • 通过索引访问数组中的元素
  • 索引从0开始
  • 索引越界会报错
  • arr[索引]
1.1.9.3. 数组的遍历
  • 使用循环遍历数组

    • 数组的长度为arr.length
1.1.9.4. 动态数组初始化
  • 动态数组指的是在定义数组时, 不明确知道数组中的内容, 仅指定数组的长度
  • 数组的默认值如下
数据类型默认值
int0
double0.0
char'\u0000'
booleanfalse
Stringnull
Objectnull
  • 完整写法

    • 数据类型[] 数组名 = new 数据类型[长度值]
    • int[] arr = new int[10]

1.1.10. 方法

1.1.10.1. 方法的定义
  • 方法是一段具有特定功能的代码片段, 可以被反复调用
  • 方法是程序中的独立功能, 也是最小的执行单元
  • 方法可以提高代码的复用性和可维护性
  • 方法之间是同级的, 不允许相互嵌套
  • 调用方法时参数类型与个数需要一一对应
 // 定义格式 public static 返回值类型 方法名(参数列表){方法体 return 返回值;}
 public static int foo(int bar) {
     // 方法体
     return 0;
 }
 // `public`表示方法可以被任意类访问
 // `static`表示方法属于类, 而不是某个对象
1.1.10.2. 方法重载
  • 方法重载是指在同一个类中, 可以定义多个同名的方法, 这些方法的参数列表不同

    • 在使用方法时, 根据传入的参数类型和个数来确定具体调用哪个方法
  • 方法重载不关注返回值类型

img0.png

  • 一般地, 上图中的第四种传参方式一般不推荐使用, 因为这样会导致编译器在对参数进行同等提升时, 无法确定是调用哪个方法

1.2. java的运行机制

1.2.1. java分为编译与运行两个阶段

  • 编译阶段: 将.java文件编译成.class文件

  • 运行阶段: 将.class文件加载到内存中, 并执行其中的代码

    • 运行时, java虚拟机会将.class文件中的字节码解释成机器码, 并执行其中的指令
  • java是运行在虚拟机中的, 利用虚拟机, 可以实现跨平台运行

1.2.2. 内存与内存地址

  • 内存是计算机中用来存储临时数据的地方, 包括寄存器、缓存、内存条等

    • 内存中的数据会随着程序的结束而消失
    • 内存中的数据是易失性的, 断电后数据会丢失, 不能长期保存
    • 内存中的数据读写速度快, 但容量小
    • 内存中的空间是连续的, 且以字节为单位划分空间, 每个字节都有一个唯一的地址
  • 内存地址是内存中每个字节的位置, 可以通过内存地址来访问内存中的数据

  • 存储是计算机中用来存储永久数据的地方, 包括硬盘、U盘、光盘等

  • 在32位操作系统中, 最多可以存储2^32-1个字节, 内存地址的范围是0x00000000~0xFFFFFFFF, 共4GB

  • 在64位操作系统中, 最多可以存储2^64-1个字节, 内存地址的范围是0x0000000000000000~0xFFFFFFFFFFFFFFFF, 共16EB

1.2.3. 内存分配

  • 栈内存: 每个线程都有独立的栈, 方法调用时会在栈上开辟空间, 方法执行完毕后, 栈上的空间会被释放
  • 堆内存: 所有线程共享, 存储对象、数组、字符串常量池 => 通常使用new关键字创建的对象都会存储在堆内存中
  • 方法区: JDK7以前是永久代实现的, JDK8以后是元空间实现的, 从虚拟机内部迁移到本地内存, 存储字节码信息、常量与静态变量
  • 本地方法栈: 存储本地Native方法的信息
  • 程序计数器: 每个线程都独立, 存储当前线程正在执行的指令的地址

1.2.4. 从内存中看基本数据类型

  • 变量里记录的是真实的数据, 传递的也是真实的数据
  • 形參的改变不会影响实参的值, 除非形参是引用类型或者返回了值, 另外的函数中由此对值进行相应修改

img2.png

1.2.5. 从内存中看引用数据类型

  • 引用数据类型在内存中存储的是地址, 传递的也是地址

  • 形參的改变会影响实参的值, 因为形參和实参指向同一个地址

  • 将零散的数据作为一个整体进行操作

img1.png

1.3. 对象

  • 类名 对象名 = new 类名();

1.3.1. javabean

  • 描述一类事物的类被称为javabean
  • 不作为主函数的入口
  • javabean类中可以书写属性和行为
  • javabean类中的方法不添加static

1.3.2. 测试类

  • 带有main方法的类被称为测试类

1.3.3. 面向对象中数据的安全问题

  • private关键字

    • 权限修饰符, 可以修饰变量与成员方法
    • 一旦某个变量或者方法使用了它, 那么只能在本类中访问, 在外界无法被访问
  • gettersetter方法

    • 对每一个私有属性, 提供对应的读取与设置方法

1.3.4. this关键字

  • this关键字代表当前对象
  • 在类中, 访问变量时遵循就近原则

1.3.5. 构造方法

  • 构造方法是一种特殊的方法

  • 构造方法的名称必须与类名保持一致, 并且大小写也必须严格相同

  • 构造方法没有返回值类型, 也不需要写void

  • 构造方法中不需要写return, 因为构造方法的任务就是创建对象

  • 构造方法也可以重载

  • 在创建对象时, 系统会自动调用构造方法来完成对象的初始化工作

  • 一般地, 在创建构造方法时, 会写一个空參方法与一个全参方法

  • 执行时机

    • 构造方法会在创建对象的时候自动调用, 无法被手动调用
    • 构造方法可以重载, 也可以重写
  • 注意事项

    • 如果没有定义构造方法, 那么系统会自动生成一个空參构造方法
    • 如果定义了任意构造方法, 那么系统将不再自动生成空參构造方法

在定义类时 我们始终都会定义一个空参构造方法与一个全参构造方法

1.3.6. 对象在内存中的分配

 Student stu = new Student();
  • 1.将字节码.class文件加载到内存中
  • 2.对应的main方法被调用, 入栈, 在栈中创建stu变量
  • 3.在堆中开辟空间, 创建Student对象
  • 4.默认初始化对象中的属性
  • 5.显示初始化对象中的属性
  • 6.调用构造方法, 对对象进行初始化
  • 7.将对象的地址值赋值给stu变量

当方法出栈之后, 方法中的变量会消失 当没有任何地方使用到堆中的对象时, 堆中的对象也会消失 字节码信息不会消失, 知道虚拟机关闭

1.3.7. 对象在方法中的内存分配

  • 当对象作为参数传递给方法时, 传递的是对象的地址
  • 在方法中修改对象中的属性时, 原对象的属性也会发生改变, 因为它们都指向同一个地址
  • 表示所在方法调用者的内存地址

1.3.8. this关键字

  • javascript中的this关键字类似, 谁调用, this就代表谁

NOTICE: 在方法中, 会记录调用者的内存地址

img3.png

1.4. 对象进阶

1.4.1. static关键字

  • 表示静态的意思,它可以修饰成员方法、成员变量。
1.4.1.1. 修饰成员变量
  • 修饰后的变量叫做静态变量, 该变量被所有对象共享
  • 使用类名进行调用
  • 使用实例进行调用
1.4.1.2. 静态变量在内存中的存储
  • 静态变量是随着类的加载而加载的, 优先于对象出现
  • JDK8之前, 静态变量是存储在方法区中的, 从JDK8开始, 静态变量被存储到堆内存中
  • 静态变量不会随着对象的销毁而销毁, 会一直存在于内存中直到程序结束
  • 静态变量是被所有对象所共享的, 所以在一个对象中修改了静态变量的值, 其他对象获取到的就是修改后的值

img4.png

1.4.1.3. 修饰成员方法
  • 修饰后的方法叫做静态方法
  • 该方法多用在测试类和工具类中,
  • javabean类中很少使用
  • 使用类名调用
  • 使用实例调用
1.4.1.4. 工具类
  • 在开发中, 我们经常会看到一些工具类, 这些工具类都是用来提供给别人使用的, 它们里面的方法都是静态方法
  • 调用时, 直接使用类名进行调用
  • 在定义工具类时, 我们一般会将构造方法私有化, 这样就不能创建对象了
  • 所以在定义方法时, 会将方法设置为静态方法, 这样就可以直接使用类名进行调用了
 public class ArrayUtils {
     private ArrayUtils() {
     }
 ​
     public static void methods() {
     }
     // ...
 }

1.4.2. 静态关键字的注意事项

  • 静态方法只能访问静态变量或者静态方法
  • 非静态方法可以访问非静态变量或者非静态方法, 也可以访问静态变量与静态方法
  • 静态方法中不能使用this关键字

img5.png

为什么静态方法只能访问静态变量或者静态方法?

1.静态变量或者静态方法会优先于对象的创建而创建, 在堆内存中只要调用类就会创建相应的静态变量或者静态方法\ 2.此时, 对象还没有在堆内存中创建, 所以如果在静态变量或者静态方法中访问非静态变量或者方法是访问不到的

 public class Student {
     private String name;
     public static String schoolName;
 ​
     public static void foo() {
         System.out.println(name); // ❌, 非静态方法不能访问非静态变量
         System.out.println(this); // ❌, 静态方法中不能使用this关键字
         System.out.println(schoolName); // ✅, 静态方法可以访问静态变量
     }
 ​
     public void bar() {
         System.out.println(name); // ✅
         System.out.println(schoolName); // ✅
     }
 }

1.4.3. 重新认识main方法

 public class Test {
     public static void main(String[] args) {
         // ...
     }
 }
  • public表示被虚拟机调用, 访问权限最大
  • static表示被虚拟机调用, 只能通过类名访问, 不能通过实例访问, 所以在main方法中的其他方法也都必须是静态的( 因为静态方法只能够使用静态变量与静态方法)
  • void表示没有返回值
  • main是方法名, 是一个固定的名字, 不能修改
  • String[] args表示传递给main方法的参数, 是一个字符串数组, 可以通过args获取到传递的参数

1.4.4. final关键字

  • 表示最终、不可变的意思, 用来修饰变量、方法和类
  • 类似于javascript中的const关键字
  • final修饰基本数据类型时, 一旦修饰后, 该变量的值将被锁定无法改变
  • final修饰引用数据类型时, 一旦修饰后, 该引用的地址将被锁定无法改变, 但是该引用指向的对象的内容是可以改变的

1.4.5. 枚举

  • 枚举是一个特殊的javabean类, 它表示只能存在有限个实例
  • 所有的枚举项默认是public static final
  • 在使用枚举项时, 直接使用类名调用即可
public enum Status {
    // 在枚举类的首行代码需要完整写出枚举项
    // 格式: 枚举名称(枚举属性值)
    // 这里相当于是调用了Status类来创建实例
    SUCCESS("成功"),
    FAIL("失败"),
    PENDING("待处理");

    // 将枚举类的变量设为私有
    private String orderStatus;

    // 枚举类的构造函数默认为私有, 防止外部使用时创建对象
    Status() {
    }

    Status(String orderStatus) {
        this.orderStatus = orderStatus;
    }

    public String getOrderStatus() {
        return this.orderStatus;
    }

}

每一个枚举项, 都是该枚举类的对象实例\ 枚举项在底层其实就是常量, 所以默认是public static final的\ 枚举项的首行必须是枚举项, 枚举项之间使用逗号进行分隔, 使用分号结尾\ 枚举项的构造方法必须是private的, 防止外部使用时创建对象\ 编译器会自动为枚举类增加valueOfvalues方法, 可以通过类名调用 values方法以数组的形式返回该类中的所有枚举项 valueOf方法返回指定枚举项名称的值

1.5. 面向对象三大特点

  • 封装
  • 继承
  • 多态

1.5.1. 封装

  • 将属性与行为封装到类中, 并通过访问修饰符来控制访问权限

  • 封装的好处

    • 隐藏内部实现细节
    • 提高代码的复用性
    • 提高代码的安全性
    • 提高代码的可维护性
    • 提高代码的可扩展性
  • 封装的注意事项

    • 在定义类时, 一般会将属性设置为私有, 然后提供对应的读取与设置方法

1.5.2. 继承

  • 继承指的是类与类之间的一种父子关系
  • 使用extends关键字表示继承
  • 继承可以把多个子类中重复的代码抽取出来提取到公共的父类中
  • 子类可以添加自身的特有属性或者方法
public class Father {
    private String name;
    private int age;

    public void eating() {
    }
}

public class Son extends Father {
    // 子类的独有属性
    private String grade;

    public Father() {
    }

    public Father(String name, int age, String grade) {
        super(name, gae); // 调用父类的构造方法
        this.grade = grade;
    }
}
1.5.2.1. 如何设计继承结构
  • 当类与类之间存在共性, 并且子类是父类的一种
1.5.2.2. 继承的特点
  • java中, 继承只支持单继承, 可以支持多层继承, 这与javascript是一样的
  • 顶级父类是Object类, 所有的类都继承自Object
1.5.2.3. 继承中成员的特点
1.5.2.3.1. 成员变量
  • 父类: 把所有子类的共有变量抽离出来
  • 调用规则: 使用就近原则
1.5.2.3.2. 成员方法
  • 方法重写
  • 重写的核心前提是:这个方法必须能够被子类继承和访问。

注意与方法重载的区别

  • 方法重写是子类需要使用父类的同名方法
  • 方法重写时需要将方法的所有内容与父类保持一致
  • 在重写的方法前添加@Override注解, 可以让编译器检查方法是否重写成功
  • final修饰的类为最终类, 里面的所有方法都不能重写

  • private私有方法, static静态方法, final最终方法都不能被重写

    • private 方法不会被继承到子类
    • static 方法属于类,不属于对象实例,而重写是基于对象实例的多态行为。
    • final 方法不能被重写, 它是最终方法
  • 子类中的权限修饰符要大于等于父类(不写<protected<public)

  • 子类中的返回值类型必须小于等于父类

1.5.2.3.3. 构造方法
  • 父类的构造方法不会直接继承给子类, 子类可以使用super方法调用父类的构造方法
  • 如果子类的方法没有添加super, 那么JVM会默认添加一个, 访问父类的无参构造方法
  • 在创建对象时, 执行顺序是先执行父类的构造方法, 再执行子类的构造方法
1.5.2.4. super关键字
  • 一般地, 我们将super调用方法会写在子类的首行

  • 表示父类, 与javascript中的类似

  • java中, 也存在继承链

    • 属性访问的就近原则, 方法中访问某个变量时, 会优先在方法局部中查找, 如果局部不存在, 会在本类中查找, 如果本类中不存在, 会逐级向父类查找

特别地, 在方法中使用this关键字访问某个变量时, 也会执行上述的继承链查找流程

this()表示调用本类中的其他构造方法

  • 如果在子类中存在其他子类, 多个子类不能互相使用this()调用, 必须要预留一个调用父类的构造方法
  • 一般地, 我们将this()调用方法会写在子类的首行

img6.png

public class Student {
    // 这里可以赋默认值
    private String name;
    private int age;

    public Student() {
        // 这里可以使用this()调用本类中的其他构造方法
        this("张三", 18); // 表示调用有参构造, 并赋默认值
        // super(); // ❌, super与this调用无法并存
    }

    public Student(String name, int age) {
        // this(); // ❌, this不能在构造函数中互相调用
        this.name = name;
        this.age = age;
    }
}
1.5.2.5. 继承的内存结构与字节码详解

父类中什么样的属性或者方法能够被子类继承?\

  1. 构造方法\

无法被子类继承, 但是可以使用super关键字调用父类的构造方法

  1. 成员变量\

非静态的会被子类继承, private修饰的无法直接被子类调用, 需要使用get或者set方法访问\ 静态成员变量因为保存在静态区, 不是随着对象进行保存, 所以不会继承

  1. 成员方法\

虚方法可以被继承(非下述关键字修饰的成员方法) final修饰的方法无法被继承, 但是可以被子类调用\ private修饰的方法无法被继承, 无法被子类调用\ static修饰的方法无法被继承, 但是可以被子类调用

属性构造方法成员变量成员方法
finalstaticprivate
继承
直接访问

1.5.2.5.1. 继承中其他方法的说明
  • final

    • 编译期: 逐级向上查找, 遍历整个继承链, 确定调用的方法在某个类中, 记录该方法的地址
    • 运行期: 直接运行编译时确定的方法
  • static

    • 编译期: 逐级向上查找, 遍历整个继承链, 确定调用的方法在某个类中, 将对象调用方法直接修改为类名调用方法
    • 运行期: 直接运行
1.5.2.5.2. 继承中成员方法的特点
  • 重写时, 要保证方法的名称、形參列表必须与父类中的一致, 方法体按照实际编写
  • 子类重写父类方法时, 访问权限需要大于等于父类
  • 子类重写父类方法时, 返回值类型必须小雨等于父类
  • 建议: 重写时方法申明与父类保持一致
  • private方法不能被重写, static静态方法独属于父类, 无法被继承, final静态方法无法被重写
1.5.2.6. 权限修饰符
修饰符同类同包异包子类异包异类
private
缺省
protected
public

1.5.3. 多态

1.5.3.1. 何为多态?
  • 多态指的是同一个行为具有多个不同表现形式或形态的能力
1.5.3.2. 表现形式
  • 多态的表现形式: 父类类型 对象名称 = 子类对象
1.5.3.3. 多态前提
  • 存在继承/实现关系 ---必定
  • 有父类引用指向子类对象 ---必定
  • 有方法重写 ---可选
1.5.3.4. 多态调用成员的特点
  • 调用成员变量: 编译看左边, 运行看左边

    • 当把java文件编译称为class文件时, 会看父类有没有对应的成员属性, 如果有, 编译通过, 如果没有, 编译失败
    • 当实际访问时, 取出的数据也是父类的数据
  • 调用成员方法: 编译看左边, 运行看右边

    • 当把java文件编译称为class文件时, 会看父类有没有对应的成员方法, 如果有, 编译通过, 如果没有, 编译失败
    • 当实际访问时, 使用的是子类重写之后的方法; 如果子类没有重写, 则使用父类的方法
1.5.3.4.1. 弊端
  • 多态无法访问子类中独有的方法
1.5.3.4.2. 类型转换
  • 从父到子: 父类类型变量赋值给子类类型变量

    • 强制类型转换, 需要手动实现
    • 子类类型 变量名称 = (子类类型) 父类变量
  • 从子到父: 子类类型变量赋值给父类类型变量

    • 自动类型转换, 自动实现的
    • 父类类型 变量名称 = 子类变量
  • 在将父类类型转换为子类类型时, 一般使用instanceof关键字进行判断

    • 类型一致执行强制类型转换, 否则抛出错误
public class GrandFather {
}

public class Father extends GrandFather {
}

public class Son extends Father {
}
public static void main(String[] args) {
    // 子转父
    GrandFather gf = new Father();
    // 父转子
    if (gf instanceof Father) {
        Father f = (Father) gf; // 只能转为自己的真实类型
    } else {
        System.out.println("错误的类型转换");
    }
    // ❌错误的父转子
    Son son = (Son) gf;
}
1.5.3.5. 抽象类
  • 当父类的各个子类中含有共有的方法时, 父类中针对改方法的方法体无实际意义时, 往往可以将该方法定义称为抽象方法

    • 含有抽象方法的类称为抽象类
    • 在父类中定义类抽象方法后, 子类在定义同名方法时会自动重写
  • 父类中抽象方法的定义:

// 含有抽象方法的父类也必须添加 abstract 关键字
public abstract class Father {
    // ...私有变量不变

    // 定义抽象方法, 不需要再添加大括号
    public abstract void foo();
}
  • 抽象类不能被实例化

    • Father f = new Father();, 防止实例对象调用无意义的抽象方法
  • 抽象类可以不包含抽象方法; 但是含有抽象方法的类一定是抽象类

    • 实际开发中, 如果某个抽象父类有很多抽象方法, 而在某个子类中只想要重写其中的某一个方法时, 为了避免在子类中进行不必要的重写
    • 往往需要增加一个adaptor转换层, 在该转换层中重写所有的抽象方法为空方法, 然后将该转换层定义为抽象类
    • 这样在创建子类时, 直接继承该转换层即可, 并可以只重写自己所需要使用的方法
  • 抽象类中可以包含构造方法

    • 构造方法的作用是初始化成员变量
  • 抽象类的子类可以是抽象类或者是重写重写抽象类抽象方法的类

    • 一般地, 我们在使用抽象类时不会将继承的子类也定义为抽象类, 这样必须要再次创建子类才能够实例化