一、继承

1.继承概述

为什么会有继承?

现实生活中,为什么要有继承?

JAVA 让类不继承的方法 java 类 继承_System


程序中,为什么要有继承?

JAVA 让类不继承的方法 java 类 继承_抽象类_02

继承的含义

继承:在java中指的是“一个类”可以“继承自”“另一个类”。 "被继承的类"叫做: 父类/超类/基类,"继承其他类的类"叫做:子类。继承后,“子类”中就“拥有”了“父类”中所有的成员(成员变量、成员方法)。 “子类就不需要再定义了”。

继承的好处

  1. 提高代码的复用性(减少代码冗余,相同代码重复利用)。
  2. 使类与类之间产生了关系。

2.继承的格式

格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}

需要注意:Java是单继承的,一个类只能继承一个直接父类(只能有一个亲爹),并且满足is-a的关系,例如:Dog is a Animal, Student is a Person

代码演示

人类:
public class Person {
    // 成员变量
    String name;
    int age;
    
    // 功能方法
    public void eat(){
        System.out.println("吃东西...");
    }

    public void sleep(){
        System.out.println("睡觉...");
    }
}
老师类: extends 人类
public class Teacher extends Person {

}
测试:
public class Test {
    public static void main(String[] args) {
        Teacher t = new Teacher();
        System.out.println(t.name);
        System.out.println(t.age);
        t.eat();
        t.sleep();
    }
}

3.继承后成员访问规则

构造方法访问规则

  • 构造方法不能被继承
public class Fu{
   public Fu(){
   }
   public Fu(String name,int age){
   }
}
public class Zi extends Fu{
}
public class Demo {
   public static void main(String[] args) {
       Zi z = new Zi("张三", 17);//编译错误,Zi类没有全参构造
   }
}

私有成员访问规则

  • 父类的私有成员可以被子类继承,但是子类不能直接访问
public class Fu{
   private int num = 100;//私有成员,只能在父类内部使用。
   private void method(){
       System.out.println("私有成员方法");
   }
}
public class Zi extends Fu{

}
public class Demo {
   public static void main(String[] args) {
       Zi z = new Zi();
       System.out.println(z.num);// 编译错误
       z.method();// 编译错误
   }
}

非私有成员的访问规则

  • 当通过子类访问非私有成员时,先从子类自身中找,如果找到就使用子类自己的,找不到就去父类中找。比如自己要买房,如果自己有钱就用自己的钱交首付,自己没钱,找父母帮忙交首付。
public class Fu{
   int money = 100;
   public void method(){
       System.out.println("Fu 类中的成员方法method");
   }
}
public class Zi extends Fu{
   int money = 1;
    public void method(){
       System.out.println("Zi 类中的成员方法method");
   }
}
public class Demo{
   public static void main(String[] args){
       Zi z = new Zi();
       System.out.println(z.money);//1
       z.method();// Zi 类中的成员方法method
   }
}

4.方法重写

方法重写的概念

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

class Fu{
    public void method(){
        System.out.println("Fu method方法");
    }

    // 重写使用场景
    public void sport(){
        System.out.println("运动的方式为游泳");
    }

    public void run(){
        System.out.println("第1圈");
        System.out.println("第2圈");
        System.out.println("第3圈");
        System.out.println("第4圈");
    }
}

class Zi extends Fu{
    @Override
    public void method(){
        System.out.println("Zi method方法");
    }

    @Override
    public void sport() {
        System.out.println("运动的方式为跑步");
    }

    @Override
    public void run() {
        // 调用父类的方法来执行一下
        super.run();// 调用父类的run方法

        System.out.println("第5圈");
        System.out.println("第6圈");
        System.out.println("第7圈");
    }
}

public class Test {
    public static void main(String[] args) {
        /*
            - 方法重载: 在同一个类中,有多个同名的方法,参数列表不同,这些方法就是重载的方法

            - 方法重写的概念:在父子类中,出现一模一样的方法时(返回值类型,方法名,参数列表),这就是方法重写

            - 重写的注意事项:
                1.方法重写一定要是父子类关系
                2.方法重写要求返回值类型,方法名,参数列表要相同
                3.子类重写父类的方法,要求子类重写方法的访问权限大于或者等于父类方法的权限
                   权限修饰符: public  >    protected   >  默认(空)   >  private
                4.如果是重写的方法,可以使用@Override注解来标识,如果不是重写的方法,就不可以

            - 重写的使用场景:
                父类的方法无法满足子类的需要,才会去重写
         */

        Zi zi = new Zi();
        zi.sport();
        zi.run();
    }
}

重写注意事项

  • 方法重写是发生在子父类之间的关系。
  • 子类方法重写父类方法,返回值类型、方法名和参数列表都要一模一样。
  • 子类方法重写父类方法,必须要保证权限大于等于父类权限。
  • 访问权限从大到小: public protected (默认) private
  • 使用@Override注解,检验是否重写成功,重写注解校验!
  • 建议重写方法都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

方法重写和方法重载的区别

  • 方法重写的概念:在父子类中,出现一模一样的方法时(返回值类型,方法名,参数列表),这就是方法重写
  • 方法重载的概念:在同一个类中,有多个同名的方法,**参数列表不同,**这些方法就是重载的方法

5.this和super关键字

this和super关键字的介绍

  • this:存储的“当前对象”的引用;
  • this可以访问:本类的成员属性、成员方法、构造方法;
  • super:存储的“父类对象”的引用;
  • super可以访问:父类的成员属性、成员方法、构造方法;

this关键字的三种用法

  • this访问本类成员变量:this.成员变量
public class Student{
    String name = "张三";
    public void show(){
        String name = "李四";
        System.out.println("name = " + name);// 李四
        System.out.println("name = " + this.name);// 张三
    }
}
  • this访问本类成员方法:this.成员方法名()
public class Student{
    public void show(){
        System.out.println("show方法...");
        this.eat();
    }
    public void eat(){
        System.out.println("eat方法...");
    }
}
  • this访问本类构造方法:this()可以在本类的一个构造方法中,调用另一个构造方法
public class Student{
    public Student(){
        System.out.println("空参构造方法...");
    }

    public Student(String name) {
        this();//当使用this()调用另一个构造方法时,此代码必须是此构造方法的第一句有效代码。
        System.out.println("有参构造方法...");
    }
}
public class Demo {
    public static void main(String[] args) {
        Student stu2 = new Student();
    }
}

super关键字的三种用法

  • super访问父类的成员变量:super.父类成员变量名
public class Fu{
    int money = 100;
}
public class Zi extends Fu{
    int money = 10;
    public void show(){
        int monet = 1;
        System.out.println(“money : “ + money);//1
        System.out.println(“this.money : “ + this.money);//10
        System.out.println(“super.money:” + super.money);//100  直接去父类中找
    }
}
  • super访问父类的成员方法:super.成员方法名()
public class Fu{
    public void show(){
        System.out.println("父类的show方法...");
    }
}
public class Zi extends Fu{
   public void show(){
        super.show();
        System.out.println("子类的show方法...");
    }
}
public class Demo {
    public static void main(String[] args) {
       Zi zi = new Zi();
       zi.show();
    }
}
  • super访问父类的构造方法:super()
public class Fu{
    public Fu(){
        System.out.println("Fu 类的空参构造方法..");
    }
    public Fu(String name, int age) {
        System.out.println("Fu 类的有参构造方法..");
    }
}
public class Zi extends Fu{
    public Zi(){
        super();// 调用父类的空参构造方法
        System.out.println("Zi 类的空参构造方法..");
    }
    public Zi(String name,int age){
        super(name,age);// 调用父类的有参构造方法
         System.out.println("Zi 类的有参构造方法..");
    }
}
public class Demo {
    public static void main(String[] args) {
        Zi zi = new Zi();
        System.out.println("----------------------");
        Zi z2 = new Zi("刘德华", 17);
    }
}

6.super的注意事项

  • super访问成员变量和成员方法:优先去父类中找,如果有就直接使用,如果没有就爷爷类中找,以此类推。
class Ye{
    int a = 10;
    public void eat(){
        System.out.println("Ye");
    }
}
class Fu extends Ye{
    int a = 20;
    public void eat(){
        System.out.println("Fu");
    }
}
 class Zi extends Fu{
   int a = 30;
   public void show(){
       System.out.println(super.a);
       super.eat();
   }
}
public class Test2 {
    public static void main(String[] args) {
       Zi zi = new Zi();
       zi.show();
    }
}
  • 子类的构造方法默认会调用父类的空参构造方法,如果父类中没有空参构造方法,只定义有参构造方法,则会编译报错。所哟在编写类的时候最好把空参和有参构造方法都定义好。
class Fu {
    public Fu(int a){

    }
}
 class Zi extends Fu{// 编译报错
  
}

7.继承体系对象的内存图

  1. 继承体系内存图原理—父类空间优先于子类对象产生。
  2. 在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。
  3. 案例
public class Fu {
    int num = 10;
    int numFu = 100;

    public void method(){
        System.out.println("父类中的method方法");
    }
}
public class Zi extends Fu {

    int num = 20;

    public void show(){
        int num = 30;
        System.out.println("访问局部变量num:"+num);
        System.out.println("访问本类成员变量num:"+this.num);
        System.out.println("访问父类成员变量num:"+super.num);
    }

    @Override
    public void method() {
        super.method();
        System.out.println("子类中的method方法");
    }
}
public class ExtendsDemo1 {
    public static void main(String[] args) {
        // 创建一个子类对象
        Zi zi = new Zi();

        // 使用子类对象调用show方法
        zi.show();

        // 使用子类对象调用method方法
        zi.method();
    }
}

JAVA 让类不继承的方法 java 类 继承_java_03

8.继承的特点

  1. Java只支持单继承,不支持多继承
// 一个类只能有一个父类,不可以有多个父类。
class A {
   
}
class B {
   
}
class C1 extends A {// ok
   
} 
class C2 extends A, B {// error
   
}
  1. 一个类只能有一个父类,但可以有多个子类
// A可以有多个子类
class A {
   
}
class C1 extends A {
   
}
class C2 extends  A {
   
}
  1. 可以多层继承
class A /*extends Object*/{// 爷爷   默认继承Object类
   
}
class B extends A {// 父亲
   
}
class C extends B {// 儿子
   
}

二、抽象类

1.抽象类的概述和定义

抽象类的概述

  • 概述: 使用abstract关键字修饰的类就是抽象类
  • 特点: 这种类不能被创建对象,它就是用来做父类的,被子类继承的

抽象类的定义

  • 格式:
修饰符 abstract class 类名{
    
}
  • 例如:
public abstract class Person{

}

抽象类中的成员

  • 成员变量
  • 成员方法
  • 构造方法
  • 抽象方法

2.抽象方法的概述和定义

抽象方法的概述

  • 没有方法体,使用abstract修饰的方法就是抽象方法

抽象方法的定义

修饰符 abstract 返回值类型 方法名(形参列表);
例如:
	public abstract void work();

3.抽象类的特点

  • 抽象类不能被创建对象,就是用来做“父类”,被子类继承的。
  • 抽象类不能被创建对象,但可以有“构造方法”——为成员属性初始化。
  • 抽象类中可以没有抽象方法,但抽象方法必须定义在抽象类中。
  • 子类继承抽象类后,必须重写抽象类中所有的抽象方法,否则子类必须也是一个抽象类。
public abstract class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public Person() {
    }

    public String getName() {
        return name;
    }

    // 抽象方法
    public abstract void eat();
    public abstract void drink();
}

public class Student extends Person {
    // alt+insert
    public Student(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println("学生吃快餐...");
    }

    @Override
    public void drink() {
        System.out.println("学生喝冰红茶");
    }
}

public class Teacher extends Person {
    @Override
    public void eat() {
        System.out.println("老师吃小炒...");
    }

    @Override
    public void drink() {
        System.out.println("学生喝矿泉水");
    }
}

public abstract class Doctor extends Person{
    @Override
    public void eat() {
        System.out.println("医生吃麻辣烫");
    }
}
public class Test {
    public static void main(String[] args) {
        /*
            1.有抽象方法的类一定是抽象类,抽象类中不一定有抽象方法
            2.抽象类不能创建对象,只用来作为父类
            3.抽象方法主要是供子类重写

            4.抽象类的子类,如果是普通类,那么必须重写父类中的所有抽象方法
            5.抽象类的子类,如果是抽象类,那么可以不重写父类中的抽象方法
            6.抽象类中可以有构造方法,但不能创建对象,抽象类中的构造方法有什么用?
                主要是供子类初始化从父类继承过来的属性
            7.什么时候会定义抽象方法:
                当父类中的某个方法,所有的子类都有不同的实现的时候,父类中的这个方法就定义成抽象方法
             */
        Student stu = new Student("张三");
        System.out.println(stu.getName());
    }
}

4. 模板设计模式

设计模式概述

  • 设计模式就是解决一些问题时的固定思路,也就是代码设计思路经验的总结。

模板设计模式概述

  • 针对某些情况,在父类中指定一个模板,然后根据具体情况,在子类中灵活的具体实现该模板
// 人类: 父类  学生类,老师类: 子类
// 场景: 学生类,老师类都有睡觉的功能,睡觉功能的实现都是一样的     
// 场景: 学生类,老师类都有吃东西的功能,吃东西的功能的实现不一样的
// 场景: 一个父类的多个子类,有些功能是相同的,有些功能是不相同的,但都有这些功能
public abstract class Person{
    public void sleep(){
        // 通用功能: 具体实现 
        System.out.println("两眼一闭,睡觉...");
    }
    // 不通用功能: 抽象方法
    public abstract void eat();
}

抽象类体现的就是模板思想模板是将通用的东西在抽象类中具体的实现,而模板中不能决定的东西定义成抽象方法,让使用模板(继承抽象类的类)的类去重写抽象方法实现需求

模板模式的实现步骤

  • 定义抽象父类作为模板。
  • 在父类中定义"模板方法"— 有实现方法(通用模板)+抽象方法(填充模板)
  • 子类继承父类,重写抽象方法(填充父类的模板)。
  • 测试类:
  • 创建子类对象,通过子类调用父类的“实现的方法”+ “子类重写后的方法” 。

案例演示

假如我现在需要定义新司机和老司机类,新司机和老司机都有开车功能,开车的步骤都一样,只是驾驶时的姿势有点不同,新司机:开门,点火,双手紧握方向盘,刹车,熄火老司机:开门,点火,右手握方向盘左手抽烟,刹车,熄火。那么这个时候我们就可以将固定流程写到父类中,不同的地方就定义成抽象方法,让不同的子类去重写。

分析:

  • 定义一个司机抽象父类作为模板
  • 通用模板: 开车功能 (有方法体的方法)
  • 开门
  • 点火
  • 调用开车姿势的方法
  • 刹车
  • 熄火
  • 填充模板: 开车姿势 (抽象方法)
  • 老司机类继承司机类,重写开车姿势
  • 新司机类继承司机类,重写开车姿势
    代码如下:
// 司机开车的模板类
public abstract class Driver {
  public void go() {
      System.out.println("开门");
      System.out.println("点火");
      // 开车姿势不确定?定义为抽象方法
      ziShi();
      System.out.println("刹车");
      System.out.println("熄火");
  }

  public abstract void ziShi();
}

现在定义两个使用模板的司机:

public class NewDriver extends Driver {

   @Override
   public void ziShi() {
       System.out.println("新司机双手紧握方向盘");
   }
}

public class OldDriver extends Driver {
   @Override
   public void ziShi() {
       System.out.println("老司机右手握方向盘左手抽烟...");
   }
}

编写测试类

JAVA 让类不继承的方法 java 类 继承_System_04

public class Demo02 {
    public static void main(String[] args) {
        NewDriver nd = new NewDriver();
        nd.go();

        OldDriver od = new OldDriver();
        od.go();
    }
}

运行效果: