一、OOP 特征二:继承

1.1 继承的由来

Snipaste_2022-04-05_00-33-31.png

  • 多个类中存在相同属性和行为的时候,将这些内容抽取到单独的一个类中,那么多个类中无需再定义这些属性和行为,只需要继承那个类即可。

image.png

  • 此处的多个类称为 子类(派生类) ,单独的这个类称为 父类(基类或超类)
  • 继承描述的是事物之间的所属关系,这种关系是: is...a 的关系。例如:图中的猫属于动物,狗也属于动物。由此可见,父类更通用,子类更具体。通过继承,可以使得多种事物之间形成一种关系体系。

    1.2 继承语法格式

  • 在 Java 中,继承是通过 extends 关键字,声明一个子类继承一个父类。产生父子关系后,子类则可以使用父类中非私有的成员。

  • 语法:

    1. 修饰符 class 父类{
    2. ...
    3. }
    4. 修饰符 class 子类 extends 父类 {
    5. ...
    6. }
  • 示例:

    1. public class Animal {
    2. /**
    3. * 姓名
    4. */
    5. String name;
    6. /**
    7. * 年龄
    8. */
    9. int age;
    10. /**
    11. * 吃
    12. */
    13. public void eat() {
    14. System.out.println(this.name + "在吃饭,年龄是:" + this.age);
    15. }
    16. }

    ```java public class Cat extends Animal { /**

    • 抓老鼠 */ public void catchMouse() { System.out.println(“抓老鼠”); }

}

  1. ```java
  2. public class Dog extends Animal {
  3. /**
  4. * 看家
  5. */
  6. public void lookHome() {
  7. System.out.println("看家护院");
  8. }
  9. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Cat cat = new Cat();
  4. cat.name = "Jerry";
  5. cat.age = 3;
  6. cat.eat();
  7. cat.catchMouse();
  8. System.out.println("********************");
  9. Dog dog = new Dog();
  10. dog.name = "Tom";
  11. dog.age = 4;
  12. dog.eat();
  13. dog.lookHome();
  14. }
  15. }

1.3 继承的好处和弊端

继承好处:

  • ① 继承的出现,减少了代码的冗余(重复),提供了代码的复用性。
  • ② 继承的出现,有利于功能的扩展。
  • ③ 继承的出现,让类和类之间产生了关系,提供了多态的前提。

    注意:不要为了获取其它类中的某个功能而去继承。

继承弊端:

  • ① 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性

1.4 继承的特点之一:成员变量

在子类方法中访问一个变量,采用的是就近原则

  1. 子类局部范围找
  2. 子类成员范围找
  3. 父类成员范围找
  4. 如果都没有就报错(不考虑父亲的父亲…)

示例代码

  1. class Fu {
  2. int num = 10;
  3. }
  4. class Zi {
  5. int num = 20;
  6. public void show(){
  7. int num = 30;
  8. System.out.println(num);
  9. }
  10. }
  11. public class Demo1 {
  12. public static void main(String[] args) {
  13. Zi z = new Zi();
  14. z.show(); // 输出show方法中的局部变量30
  15. }
  16. }

1.5 继承的特点之二:成员方法

1.5.1 继承中成员方法的访问特点

通过子类对象访问一个方法

  1. 子类成员范围找
  2. 父类成员范围找
  3. 如果都没有就报错(不考虑父亲的父亲…)

1.5.2 方法重写( override )

1.5.2.1 方法重写概念

  • 子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样; 重写也会称为方法的 重置覆盖

    1.5.2.2 方法重写的应用场景

  • 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容

    1.5.2.3 Override注解

  • 用来检测当前的方法,是否是重写的方法,起到【校验】的作用
    PS:注解是给计算机看的

1.5.2.4 方法重写的注意事项:

  • 私有方法final修饰的方法不能被重写(父类私有成员子类是不能继承的)
  • ② 子类重写的方法的返回值类型 不能大于 父类被重写的方法的返回值类型(这对于返回值类型为引用数据类型来说的,如果返回值类型是基本数据类型和 void 类型必须相同,换言之,只有继承,才会有父类和子类)。
  • ③ 子类重写父类方法的时候,访问权限需要大于等于父类 (public > 默认 > 私有)。
  • ④ 子类方法抛出的异常不能大于父类被重写方法的异常。
  • 静态方法不能被重写, 因为 static 方法是属于类的,子类无法覆盖父类的方法。

示例代码

  1. public class Fu {
  2. private void show() {
  3. System.out.println("Fu中show()方法被调用");
  4. }
  5. void method() {
  6. System.out.println("Fu中method()方法被调用");
  7. }
  8. }
  9. public class Zi extends Fu {
  10. /* 编译【出错】,子类不能重写父类私有的方法*/
  11. @Override
  12. private void show() {
  13. System.out.println("Zi中show()方法被调用");
  14. }
  15. /* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */
  16. @Override
  17. private void method() {
  18. System.out.println("Zi中method()方法被调用");
  19. }
  20. /* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */
  21. @Override
  22. public void method() {
  23. System.out.println("Zi中method()方法被调用");
  24. }
  25. }

1.6 继承的特点之三:单继承

Java中继承的特点

  1. Java中类只支持单
  2. 继承,不支持多继承,也就是说一个类只能有一个直接父类
    • 错误范例:class A extends B, C { }
  3. Java中类支持多层继承
    • class B extends A{}
    • class C extends B{}
  4. 多个类可以继承一个父类
  5. 在java中子类和父类是一种相对概念

    注意:顶层父类是 Object 类,所有类默认继承 Object 类作为父类。

多层继承示例代码:

  1. public class Granddad {
  2. public void drink() {
  3. System.out.println("爷爷爱喝酒");
  4. }
  5. }
  6. public class Father extends Granddad {
  7. public void smoke() {
  8. System.out.println("爸爸爱抽烟");
  9. }
  10. }
  11. public class Mother {
  12. public void dance() {
  13. System.out.println("妈妈爱跳舞");
  14. }
  15. }
  16. public class Son extends Father {
  17. // 此时,Son类中就同时拥有drink方法以及smoke方法
  18. }注意:顶层父类是 Object 类,所有类默认继承 Object 类作为父类。

1.7 继承的特点之四:构造方法

1.7.1 概述

  • 子类是无法 继承 父类的构造方法的。
  • 子类中所有的构造方法默认都会访问父类中无参的构造方法
    • 子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化(子类的构造方法中默认有一个 super()),表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用),才能执行自己的初始化动作

1.7.2 如果父类中没有无参构造方法,只有带参构造方法?

  1. 通过使用super关键字去显示的调用父类的带参构造方法
  2. 子类通过this去调用本类的其他构造方法,本类其他构造方法再通过super去手动调用父类的带参的构造方法

    注意: this(…)super(…) 必须放在构造方法的第一行有效语句,并且二者不能共存

二、关键字 super

2.1 概述

  • 在 Java 类中使用super来调用父类中的指定操作:
    • super 可用于访问父类中定义的属性。
    • super 可用于调用父类中定义的成员方法。
    • super 可用于在子类构造器中调用父类的构造器。

注意:

  • 当子类和父类出现同名成员的时候,可以用 super 表明调用的是父类的成员。
  • super 的追溯不仅限于直接父类
  • super 和 this 的用法很像,this 代表的是本类对象的引用,super 代表的是父类的内存空间的标识。

2.2 调用父类的构造器

  • ① 子类中所有的构造器 默认 都会访问父类中的 无参 构造器。
  • ② 当父类中没有无参构造器的时候,子类的构造器必须通过 this(参数列表)super(参数列表) 语句指定调用本类或父类中相应的构造器。同时,只能 二选一 ,并且放在构造器的首行。
  • ③ 如果子类构造器既没有显示调用父类或本类的构造器,且父类中又没有无参构造器,则 编译报错

    2.3 应用示例

    2.3.1 super 调用方法

  1. public class Person {
  2. protected String name = "张三";
  3. protected int age;
  4. public String getInfo() {
  5. return "姓名:" + this.name + ",年龄" + this.age;
  6. }
  7. }
  1. public class Student extends Person {
  2. private final String school = "中国社会大学";
  3. protected String name = "李四";
  4. @Override
  5. public String getInfo() {
  6. this.age = 18;
  7. return super.getInfo() + ",学校:" + this.school;
  8. }
  9. }
public class StudentTest {
    public static void main(String[] args) {
        Student student = new Student();
        System.out.println("student.getInfo() = " + student.getInfo());
    }
}

2.3.2 super 调用父类的构造器

public class Person {
    private final String name;

    private final int age;

    private final Date birthDate;

    public Person(String name, int age, Date birthDate) {
        this.name = name;
        this.age = age;
        this.birthDate = birthDate;
    }

    public Person(String name, int age) {
        this(name, age, null);
    }

    public Person(String name, Date birthDate) {
        this(name, 0, birthDate);
    }

    public Person(String name) {
        this(name, 0, null);
    }

    @Override
    public String toString() {
        return "name='" + this.name + '\'' + ", age=" + this.age + ", birthDate=" + this.birthDate;
    }
}
public class Student extends Person {

    private final String school;

    public Student(String name, int age, String school) {
        super(name, age);
        this.school = school;
    }

    @Override
    public String toString() {
        return super.toString() + ", school='" + this.school;
    }
}
public class StudentTest {
    public static void main(String[] args) {
        Student student = new Student("张三", 18, "社会大学");
        System.out.println(student);
    }
}

2.4 super内存图(理解)

  • 对象在堆内存中,会单独存在一块super区域,用来存放父类的数据

01_super内存图.png

2.5 this 和 super 的区别

区别点 this super
访问属性 访问本类中的属性,如果本类中没有找到此属性,则从父类中继续查找 直接访问父类中的属性
访问方法 访问本类中的方法,如果本类中没有找到此方法,则从父类中继续查找 直接访问父类中的方法
调用构造器 调用本类构造器,必须放在构造器的首行 调用父类构造器,必须放在子类构造器首行

三、关键字 static

3.1 概述

  • 当我们在编写一个类的时候,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少个对象的情况下,某些特定的数据在内存空间里只有一份 ,例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

image.png

3.2 类属性、类方法的设计思想

  • 类属性作为该类各个对象之间的共享的变量。在设计类的时候,分析哪些属性不会因为对象的不同而改变,将这些属性设置为类属性,相应的方法设置为类方法。
  • 如果方法和调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

3.3 static 的使用范围

  • 在 Java 中,static 可以修饰 属性方法代码块内部类

3.4 被 static 修饰的成员的特点

  • ① 随着类的加载而加载。
  • ② 优先于对象存在。
  • ③ 修饰的成员,被所有对象所共享。譬如类方法,类成员变量
  • ④ 访问权限允许的情况下,可以不创建对象,直接被类调用(推荐)。

6.4 static关键字注意事项

  • 一个静态方法中只能访问静态的成员,静态存在的时候非静态可能还不存在
  • 非静态方法可以访问静态的成员,也可以访问非静态的成员
  • 静态方法中是没有this关键字(this:当前对象的引用,this需要在创建对象时候,才会存在,静态存在的时候,对象可能还没有创建)
  • 只能用于修饰成员变量,不能用于修饰局部变量

    3.5 单例模式

    3.7.1 概述

  • 设计模式是 在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式 。设计模式免去了我们再思考和摸索,就像经典的棋谱,不同的棋局,采用不同的棋谱。

  • 所谓的类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对 某个类有且仅有存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须 将类的构造器的访问权限设置为private ,这样,就不能使用 new 关键字在类的外部产生类的对象了,但是类内部依然可以产生该类的对象。因为类的外部开始还无法得到类的对象,只能 调用该类的某个静态方法 返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的 该类对象的变量也必须定义为静态的

3.7.2 单例模式

  • 示例:饿汉式

    public class Singleton {
      /**
       * 内部提供一个当前类的实例 此实例也必须是静态化的
       */
      private static final Singleton singleton = new Singleton();
    
      // 私有化构造器
      private Singleton() {
    
      }
    
      /**
       * 提供公共的静态方法,返回当前类的对象
       *
       * @return {@link Singleton}
       */
      public static Singleton getInstance() {
          return singleton;
      }
    }
    
  • 示例:懒汉式

public class Singleton {
    /**
     * 内部提供一个当前类的实例 此实例也必须是静态化的
     */
    private static Singleton singleton;

    // 私有化构造器
    private Singleton() {

    }

    /**
     * 提供公共的静态方法,返回当前类的对象
     *
     * @return {@link Singleton}
     */
    public static Singleton getInstance() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

3.7.3 单例模式的好处

  • 由于单例模式只生成一个实例, 减少了系统性能的开销 ,当一个对象的产生需要比较多的资源的时候,如:读取配置、产生其他依赖对象时,则可以通过在应用启动的时候直接产生一个实例对象,然后永久驻留内存的方式来解决。 ```java package java.lang;

import java.io.*; import java.util.StringTokenizer; import sun.reflect.CallerSensitive; import sun.reflect.Reflection;

public class Runtime { private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
    return currentRuntime;
}

private Runtime() {}

// 其他略

}

<a name="68e137f2"></a>
### 3.7.4 单例模式的应用场景

- ① `应用程序的日志应用` ,一般使用单例模式实现,由于共享日志的文件一直处于打开状态,所以只能有一个实例去操作,否则内容不好追加。
- ② `数据库连接池` ,一般使用单例模式实现,因为数据库连接是一种数据库资源。
- ③ `读取资源文件的类` ,一般使用单例模式实现,没有必要每次使用配置文件数据的时候,都生成一个对象去读取。
- ……
- ……

<a name="525c2130"></a>
## 3.6 理解 main 方法的语法

- 由于 Java 虚拟机需要调用类的 `main()` 方法,所以该方法的访问权限必须是 `public` 。
- 因为 Java 虚拟机执行 `main()` 方法的时候不需要创建对象,所以该方法必须是 `static` 的。
- `main()` 接收一个 String 类型的数组参数,该数组汇总保存执行 Java 命令时传递给所运行的类的参数。
- 因为 `main()` 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。

<a name="8da97007"></a>
# 四、代码块
<a name="WWP7P"></a>
## 4.1代码块概述 (理解)

在Java中,使用 { } 括起来的代码被称为代码块


<a name="Olzlh"></a>
## 4.2代码块分类 (理解) 

<a name="eZCbP"></a>
### 4.2.1 局部代码块

-  位置: 方法中定义 
-  作用: 限定变量的生命周期,及早释放,提高内存利用率 
-  示例代码 
```java
public class Test {
    /*
        局部代码块
            位置:方法中定义
            作用:限定变量的生命周期,及早释放,提高内存利用率
     */
    public static void main(String[] args) {
        {
            int a = 10;
            System.out.println(a);
        }

       // System.out.println(a);
    }
}

4.2.2 构造代码块

  • 位置: 类中方法外定义
  • 特点: 每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
  • 作用: 将多个构造方法中相同的代码,抽取到构造代码块中中
  • 示例代码 ```java public class Test { /*
     构造代码块:
         位置:类中方法外定义
         特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
         作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
    
    */ public static void main(String[] args) {
     Student stu1 = new Student();
     Student stu2 = new Student(10);
    
    } }

class Student {

{
    System.out.println("好好学习");
}

public Student(){
    System.out.println("空参数构造方法");
}

public Student(int a){
    System.out.println("带参数构造方法...........");
}

}

<a name="q1hDu"></a>
### 4.2.3 静态代码块

-  位置: 类中方法外定义 
-  特点: 需要通过static关键字修饰,随着类的加载而加载,并且只执行一次 
-  作用: 在类加载的时候做一些数据初始化的操作 
-  示例代码 
```java
public class Test {
    /*
        静态代码块:
            位置:类中方法外定义
            特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
            作用:在类加载的时候做一些数据初始化的操作
     */
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person(10);
    }
}

class Person {
    static {
        System.out.println("我是静态代码块, 我执行了");
    }

    public Person(){
        System.out.println("我是Person类的空参数构造方法");
    }

    public Person(int a){
        System.out.println("我是Person类的带...........参数构造方法");
    }
}

五、抽象类

5.1抽象类的概述

当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,!

  • 随着继承层次一个个新的子类的定义,类变得越来越具体,而父类则更一般,更通用。
  • 类的设计应该保证父类和子类能够共享特征。
  • 这些公共特征(共性特征)应该抽取到一个公共的父类中,而这些方法在父类中又无法给出具体的体现,这个时候就需要抽象类了,具体体现就交给子类各自去实现。
  • 在父类中声明这些方法的时候,就只有方法签名,没有方法体,我们将这些没有方法体的方法称为 抽象方法
  • 在 Java 中,将包含抽象方法类称为 抽象类

    5.2 语法格式

  • 抽象类

    • 定义:使用 abstract 关键字修饰的类。
    • 语法:
      权限修饰符 abstract class 类名 {}
      
  • 抽象方法

    • 定义:没有方法体的方法。
    • 语法:

      权限修饰符 abstract 返回值类型 方法名(参数列表);
      

      示例: ```java /**

      • 抽象类 Animal */ public abstract class Animal {

      /**

      • 吃饭的方法 */ public abstract void eat();

}

//猫类继承动物 public class Cat extends Animal { @Override public void eat() { System.out.println(“猫吃老鼠”); }

//狗类继承动物 public class Dog extends Animal { @Override public void eat() { System.out.println(“狗吃肉”); } }

//测试类 public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.eat(); // 狗吃肉

    Cat cat = new Cat();
    cat.eat(); // 猫吃老鼠
}

}

<a name="TsQnx"></a>
## 5.3 抽象类的特点

1. 抽象类和抽象方法必须使用 abstract 关键字修饰
1.  抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类 
> 没有包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计

3. 抽象类不能创建对象
> 假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

4. 抽象类可以有构造方法 ,是供子类创建对象时,初始化父类成员变量使用的。
> 子类的构造方法中,有默认的 super() 或手动的 super(实参列表) ,需要访问父类的构造方法。

5.  抽象类的子类 
   1. 要么重写抽象类中的所有抽象方法 
   1.  **要么是抽象类** 
> 假设不重写所有的抽象方法,则类中可能包含抽象方法,那么在创建对象后,调用抽象的方法,没有意义。

6. 不能用 `abstract` 修饰变量、代码块、构造器。
6. 不能用 `abstract` 修饰私有方法、静态方法、final 修饰的方法和 final 修饰的类。

<a name="bGGCq"></a>
# 六、final关键字
<a name="FJkIZ"></a>
## 4.1 含义

- final是java语言中的一个关键字
- final关键字代表最终的、不可改变的
- `final修饰的类不能被继承` ,目的是为了提高安全性,提高程序的可读性。比如:`String类` 、`System类` 、`StringBuilder类` 。
- `final修饰的方法不能被子类重写` 。比如:Object 类的 `getClass()` 方法。
- `final修饰的变量(成员变量或局部变量)即为常量。名称大写,且只能被赋值一次` 。`final修饰的成员变量必须在声明时或每个构造器中或代码块中显示赋值,然后才能使用` 

<a name="ebcFy"></a>
## 4.2 fianl关键字的作用

    它可以修饰非抽象类、非抽象类成员方法和变量

<a name="rqwxB"></a>
## 4.3 fianl修饰类
```java
final class A{
    //A 是没有子孙的
}
class B extends A{
    //错误的, 无法从最终类A进行继承
    //B类继承A类 相当于对A类功能进行扩展
    //如果不希望别人对A类进行扩展,可以给A类加final关键字,如String类
}

4.4 final修饰成员方法

  • 这个方法就是最终方法,不能被覆盖重写

  • 使用场景:如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。 ```java class C{ public final void doSome(){

     System.out.println("C's doSome...")
    

    } } class D extends C{ //错误,无法覆盖 / public void doSome(){ System.out.println(“D’s doSome…”) } / }

<a name="P75TP"></a>
## 4.5 final修饰成员变量
表明该变量是一个常量,不能再次赋值,系统不负责赋默认值,要求程序员必须手动赋值,只能赋一次,这个手动赋值,在变量后面赋值可以,在构造方法中赋值也可以

**注意:**

- 变量是基本类型,不能改变的是值
- 变量是引用类型,不能改变的是地址值,但地址里面的内容是可以改变的

```java
/**
*final 修饰成员变量:
*系统不负责赋默认值,要求程序员必须手动赋值,只能赋一次,
*这个手动赋值,在变量后面赋值可以,在构造方法中赋值也可以
*/
class User{
    //成员变量


    //final int age;  错误: 变量age未在默认构造器中初始化
    final int age=10;

    //在构造方法中赋值 weight只赋一次值
    final double weight;
    //构造方法
    public User(){   
        this.weight=80;
        //系统赋默认值在这个时候,final修饰后,系统不会赋值
        //this.weight=0;

    }
}
public static void main(String[] args){
    final Student s = new Student(23);
      s = new Student(24);  // 错误
     s.setAge(24);  // 正确
}