05【继承、抽象、权限修饰符、final】

一、继承

1.1 继承概述

继承是面向对象三大特征之一,继承就是子类继承父类的特征(属性)和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。

  • 总结:

1)子类继承父类可以获得父类的功能,提高代码的复用性

2)子类可以重写(覆盖)某些父类的功能,我们一般称为增强

3)子类除了可以继承父类的功能之外,还可以额外添加子类独有的功能,一般来说,子类要比父类强大(你的是我的,我的还是我的);

在上图中,有4类动物,分别为狮子、老虎、牛、羊,他们都是动物,都具备一些动物的基本属性和行为,例如眼睛、耳朵、鼻子等,所以他们的顶层父类是动物(Aniaml);接着狮子与老虎属于食肉动物,他们具备食肉动物的一些基本属性和行为,他们都继承与食肉动物(Carnivorous);而牛与羊属于食草动物,他们具备食草动物的一些基本属性和行为所以他们都继承与食草动物(Herbivorous)

1.2 继承的格式

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

class 父类 {
    ...
}
class 子类 extends 父类 {
    ...
}

继承演示,代码如下:

package com.dfbz.demo01_继承的语法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {

    public static void main(String[] args) {
        Student student = new Student();
        student.name = "小灰";
        student.age = 20;
        student.eat();
        System.out.println("---------------");

        Teacher teacher = new Teacher();
        teacher.name = "小红";
        teacher.age = 30;
        teacher.eat();
        System.out.println("---------------");

        Worker worker = new Worker();
        worker.name = "小刚";
        worker.age = 40;
        worker.eat();
    }
}

class Person {
    String name;
    int age;

    public void eat() {
        System.out.println("年龄为【" + age + "】岁的【" + name + "】正在吃饭");
    }
}

// 继承Person类
class Student extends Person {
}

// 继承Person类
class Teacher extends Person {
}

// 继承Person类
class Worker extends Person {
}

1.3 继承案例

1.3.1 需求

请使用继承定义以下类:

  1. 程序员(Coder)
    1. 成员变量:姓名、年龄
    2. 成员方法:吃饭、睡觉、敲代码
  2. 老师(Teacher)
    1. 成员变量: 姓名、年龄
    2. 成员方法: 吃饭、睡觉、上课

1.3.2 分析

1.3.3 代码实现

1)定义父类,抽取共性属性、行为。

package com.dfbz.demo02_继承的小案例;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person {
    // 共性的东西
    String name;
    int age;

    /**
     * 吃饭行为
     */
    public void eat() {
        System.out.println(name + "吃饭");
    }

    /**
     * 睡觉行为
     */
    public void sleep() {
        System.out.println(name + "睡觉");
    }
}

2)定义程序员类,继承父类的name,age等属性,继承eat,sleep等行为,再添加特有的敲代码方法

package com.dfbz.demo02_继承的小案例;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
class Coder extends Person {
    public void coding() {
        // 继承父类的name属性
        System.out.println(name + "敲代码");
    }
}

3)定义程序员类,继承父类的name,age等属性,继承eat,sleep等行为,再添加特有的上课方法

package com.dfbz.demo02_继承的小案例;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
class Teacher extends Person {
    public void teach() {
        // 继承父类的name属性
        System.out.println(name + "上课");
    }
}

4)测试类:

package com.dfbz.demo02_继承的小案例;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Teacher t = new Teacher();
        t.name = "小灰";
        t.age = 20;
        t.eat();
        t.sleep();
        t.teach();              //上课方法

        Coder coder = new Coder();
        coder.name = "小蓝";
        coder.age = 18;
        coder.eat();
        coder.sleep();
        coder.coding();         //敲代码方法
    }
}

1.4 父类不可被继承的内容

并不是父类的所有内容都可以给子类继承的,以下2个内容不能被子类继承:

  1. 被private修饰的
  2. 构造方法不能继承

Tips:虽然被private修饰的成员不能被继承下来,但是通过getter/setter方法访问父类的private成员变量

测试案例:

package com.dfbz.demo03_父类不能被继承的内容;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        Zi zi = new Zi();
        zi.num = 10;
//        zi.count = 20;         // 语法错误: 子类无法继承父类私有的成员

        zi.setCount(20);         // 可以通过set方法来设置值(前提是set方法没有被私有)

        zi.method();
//        zi.show();               // 语法错误: 子类无法继承父类私有的成员
    }
}

class Fu {
    int num;
    private int count;

    public Fu() {
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public void method() {
        System.out.println("method...");
    }

    private void show() {
        System.out.println("show...");
    }
}

class Zi extends Fu {
    //构造方法不能继承,因为构造方法和类名相同,父类和子类的名称肯定不相同,无法继承
/*
    public Fu(){

    }
    */
}

1.5 成员变量的继承

当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?

1.5.1 成员变量不重名

  • 当子类与父类成员变量不重名时:

Tips:当子类父类中出现不重名的成员变量,这时的访问是没有影响的;

测试代码:

package com.dfbz.demo04_成员变量继承的特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.method1();
    }
}

class Fu {
    int num1 = 10;
}

class Zi extends Fu {
    int num2 = 20;

    public void method1() {
        System.out.println("父类的: " + num1);
        System.out.println("子类的: " + num2);
    }
}

运行结果:

父类的: 10
子类的: 20

1.5.2 成员变量重名

  • 当子类与父类成员变量重名时:

当子类父类中出现重名的成员变量,这时的访问是有影响的,默认取子类的(就近原则);

测试代码:

package com.dfbz.demo04_成员变量继承的特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.method1();
    }
}

class Fu {
    int num = 10;
}

class Zi extends Fu {
    int num = 20;

    public void method1() {
        System.out.println("父类的: " + super.num);
        System.out.println("子类的: " + num);
    }
}

运行结果:

父类的: 10
子类的: 20

1.6 成员方法的继承

当类之间产生了关系,其中各类中的成员方法,又产生了哪些影响呢?

1.6.1 成员方法不重名

如果子类和父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法;

示例代码:

package com.dfbz.demo05_成员方法的继承特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.method1();       //子类没有,但是父类有
        zi.method2();
    }
}

class Fu {
    public void method1() {
        System.out.println("Fu---Method1");
    }
}

class Zi extends Fu {
    public void method2() {
        System.out.println("Zi---Method2");
    }
}

1.6.2 成员方法重名(方法的重写)

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)

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

示例代码:

package com.dfbz.demo05_成员方法的继承特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Zi zi=new Zi();
        //执行重写后的method方法
        zi.method();            //Zi---Method
    }
}

class Fu{
    public void method(){
        System.out.println("Fu---Method");
    }
}
class Zi extends Fu{
    //子类重写了父类的method方法(相当于重新实现此方法的功能)
    public void method(){
        System.out.println("Zi---Method");
    }
}

1.6.3 重写的案例

1) 体现子类具体性

产生继承关系后,由于父类描述的是整个继承体系的共性功能,因此父类的描述往往会趋于抽象的表达,而子类往往比父类更加具体、直接;

我们观察下面案例:

需求:

  • 猫类:
    • 行为:喵喵的叫、吃鱼
  • 狗类:
    • 行为:汪汪的叫、吃骨头

图解:

代码实现:

1)定义一个父类(Animal),提供共性功能;

/**
 * 动物类
 */
class Animal {
    public void eat() {
        System.out.println("吃饭");
    }

    public void speak() {
        System.out.println("说话");
    }
}

2)定义猫类,重写父类提供的说话、吃饭方法:

/**
 * 猫类
 */
class Cat extends Animal{

    // 重写吃饭方法
    public void eat(){
        System.out.println("吃鱼~");
    }

    // 重写说话方法
    public void speak(){
        System.out.println("喵喵的叫~");
    }

}

3)定义狗类,重写父类提供的说话、吃饭方法:

/**
 * 狗类
 */
class Dog extends Animal{

    // 重写吃饭方法
    public void eat(){
        System.out.println("吃骨头~");
    }

    // 重写说话方法
    public void speak(){
        System.out.println("汪汪的叫~");
    }
}

4)测试类:

package com.dfbz.demo06_重写的特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_体现子类具体性 {
    public static void main(String[] args) {

        Cat cat=new Cat();
        cat.eat();

        System.out.println("------------");

        Dog dog=new Dog();
        dog.eat();
    }
}

2) 体现子类强大性

父类的功能被子类继承后,子类除了有父类的功能,还能包含子类独有的功能。并且子类可以对父类已有的方法进行重写,这无疑也是一种对旧方法的增强;

我们观察下面案例

需求:

  • PupilStudent:
    • 方法:背古诗(只会小学生古诗词)
  • JuniorStudent:
    • 方法:背古诗,除了小学生古诗词,还会初中生古诗词
  • SeniorStudent:
    • 方法:背古诗,除了小学生古诗词,还会初中生古诗词,还会高中生古诗词

代码实现:

1)PupilStudent

class PupilStudent{
    public void read(){
        System.out.println("春眠不觉晓、处处闻啼鸟..");
    }
}

2)JuniorStudent:

class JuniorStudent extends PupilStudent{
    public void read(){

        // 获取父类的方法
        super.read();
        System.out.println("古藤老树昏鸦,小桥流水人家");
    }
}

3)SeniorStudent:

class SeniorStudent extends JuniorStudent{
    public void read(){

        // 获取父类的方法
        super.read();
        System.out.println("蜀道之难,难于上青天");
    }
}

4)测试类:

package com.dfbz.demo06_重写的特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_体现子类强大性 {
    public static void main(String[] args) {
        PupilStudent ps=new PupilStudent();
        ps.read();
        System.out.println("-------");

        JuniorStudent js=new SeniorStudent();
        js.read();
        System.out.println("------------");

        SeniorStudent ss=new SeniorStudent();
        ss.read();
    }
}

输出结果:

春眠不觉晓、处处闻啼鸟..
-------
春眠不觉晓、处处闻啼鸟..
古藤老树昏鸦,小桥流水人家
蜀道之难,难于上青天
------------
春眠不觉晓、处处闻啼鸟..
古藤老树昏鸦,小桥流水人家
蜀道之难,难于上青天

1.7 继承后构造方法的特点

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?

首先我们要回顾两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的
  2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super()表示调用父类的无参构造方法,父类成员变量初始化后,才可以给子类使用。

继承后子类构造方法特点:子类所有构造方法都会调用父类的无参构造

示例代码:

package com.dfbz.demo07_构造方法继承的特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        Zi zi = new Zi();
        System.out.println("----------");
        Zi zi2 = new Zi(20);
    }
}

class Fu {
    private int num = 10;

    public Fu() {
        System.out.println("Fu无参构造");
    }

    public Fu(int num) {
        System.out.println("Fu有参构造");
    }
}

class Zi extends Fu {
    private int num = 20;

    public Zi() {
        // 子类在创建会先把父类加载进内存,子类的所有构造方法中都有一句默认的super();
        System.out.println("Zi无参构造");
    }

    public Zi(int num) {
        System.out.println("Zi有参构造");
    }
}

运行结果:

Tips:子类在继承父类时,必须保证父类有无参构造方法,否则编译报错;

1.8 super关键字

1.8.1 super的作用

  • 问题1):在子类继承父类时,如果子类中的成员变量名称和父类的冲突时,那么默认情况下是取子类的成员变量;但是继承自父类的成员变量该怎么获取呢?
  • 问题2):在子类继承父类时,子类方法名和父类方法名冲突了,我们知道会出现重写(Override),但此时如果需要调用父类的成员方法该怎么办呢?
  • 问题3):子类在初始化时,默认会初始化父类,执行的是父类的无参构造方法来初始化,如果想要调用父类的其他构造方法来初始化父类,怎么办?

super关键字:是父类的引用,类似于之前学过的 thisthis代表的是本类对象,而super代表的是父类对象;使用super我们可以调用父类的成员(属性和行为),注意super关键字不能访问父类私有(private修饰)的成员

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量(不是private修饰的)时,需要使用 super 关键字,修饰父类成员变量,类似于之前学过的 this

关于权限修饰符知识我们后面再讲解;

  • super的关键字的作用如下:
    • 1)用于访问父类中定义的属性
    • 2)用于调用父类中定义的成员方法
    • 3)用于在子类构造方法中调用父类的构造器

1.8.2 this和super图解

  • 1)main方法进栈执行;
  • 2)执行new Zi(),首先现将Fu、Zi两个类加载到内存(方法区)
  • 3)初始化Fu类,再初始化子类;子类中保留父类的引用super;可以通过super来获取父类的非私有内容;
  • 4)子类调用method方法,调用的是this区中的方法,因此输出的是Zi method...

示例代码:

package com.dfbz.demo08_super和this的内存图;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Zi zi = new Zi();
        System.out.println(zi.num);
        zi.method();
    }
}
class Fu {
    private int num = 10;
    public void method() {
        System.out.println("Fu method...");
    }
}
class Zi extends Fu {
    int num = 20;
    public void method() {
        System.out.println("Zi method...");
    }
}

1.8.3 使用super获取父类成员

使用格式:

super.父类成员变量名

子类方法需要修改,代码如下:

class Zi extends Fu {    
    int num = 20;        
    public void method() {                
        System.out.println("Zi method...");                       
        System.out.println(super.num);        // 获取父类的属性(语法错误)				    
        super.method();    					// 获取父类的方法    
    }
}

Tips:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,但是可以在父类中提供公共的getXxx方法和setXxx方法。

在父类提供get/set方法:

class Fu {    
    private int num = 10;    // 提给get/set方法    
    public int getNum() {        
        return num;    
    }   
    public void setNum(int num) {        
        this.num = num;    
    }    
    public void method() {       
        System.out.println("Fu method...");    
    }
}

子类通过get/set方法来访问父类私有成员:

class Zi extends Fu {    
    int num = 20;        
    public void method() {        
        System.out.println("Zi method...");        // 获取父类的属性
        //        System.out.println(super.num);        // 父类私有成员不可以通过super获取        
        
        // 通过提供的get/set方法来访问        
        System.out.println(super.getNum());        // 获取父类的方法        
        super.method();    
    }
}

内存图解:

1.8.4 this和super访问构造方法

我们知道子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super();super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

定义一个Person类(父类):

package com.dfbz.demo10_this和super访问构造方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
class Person {

    private String name;
    private int age;

    public Person() {
    }

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

    public void eat() {
        System.out.println("人吃饭");
    }
}

定义一个Coder类(子类):

package com.dfbz.demo10_this和super访问构造方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
class Coder extends Person {
    public Coder() {
        // this();           // 不能自己调用自己
        this("程序员-小张", 20);        // 代表调用本类的其他构造方法
//        super();          // 写了super就不能写this了(本类的其他构造方法上第一句已经有了super())
    }

    public Coder(String name, int age) {
        super(name, age);
    }

    public void eat() {
        System.out.println("程序员吃饭");
    }

    public void method() {
        super.eat();
        this.eat();
    }
}

示例代码:

package com.dfbz.demo10_this和super访问构造方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Coder c = new Coder();
        c.method();
    }
}

运行结果:

人吃饭
程序员吃饭

1.9 继承的特点

1.9.1 继承问题

在Java中,不支持多继承,只支持单继承,但支持多重继承;即A继承B,B继承C,这样下来C间接继承与A;

  1. Java只支持单继承,不支持多继承。
class A{}
class B{}
class C extends A{}
//class C extends A extends B{}     // 错误
  1. 一个类可以有多个子类。
// A可以有多个子类
class A {}
class B extends A {}
class C extends A {}
  1. Java支持多层继承。
class A {}
class B extends A {}
class C extends B {}

1.9.2 钻石问题

钻石问题(diamond problem)指的是在多继承中,多个父类拥有同名方法时,子类继承时的问题;

  • 多继承示例代码:
// 祖父类
class A {
    public void method() {
        System.out.println("aaa");
    }
}

// 父类1
class B extends A{

}

class C extends A{

}

// 编译报错
class D extends B, C {}
  • 多层继承:
class A {
    public void method() {
        System.out.println("aaa");
    }
}

class B extends A{
    public void method() {
        System.out.println("bbb");
    }
}

// C间接继承与A
class C extends B{
    
}

1.9.3 static 继承的特点

我们知道被static修饰的成员是属于类的,而不是对象的;那么被static修饰的成员能否被继承到子类,供子类使用呢?

答:被static修饰的成员是可以被继承到子类的;

测试代码:

package com.dfbz.demo11_static继承的特点;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        System.out.println(A.abc);
        System.out.println(B.abc);

        System.out.println(new A().abc);            // 使用匿名对象方法
        System.out.println(new B().abc);            // 使用匿名对象方法


        A.method();             // aa
        B.method();             // bb

        new A().method();       // aa
        new B().method();       // bb 
    }
}

class A{
    static int abc=10;
    public static void method(){
        System.out.println("aa");
    }
}

class B extends A{
    // 重写static方法必须使用static修饰该方法
    public static void method(){
        System.out.println("bb");
    }
}

二、抽象类(Abstract Class)

2.1 抽象类概述

在继承体系中,由于父类的设计应该保证继承体系中所有子类的共性,子类往往比父类要描述的更加清晰、具体;因此我们有时需要将父类设计的抽象化;即方法只声明方法体,而没有方法具体功能,我们把没有方法主体的方法称为抽象方法包含抽象方法的类就是抽象类

2.1.1 举例

1. 厨师    成员变量:工号,姓名,工资    成员方法:工作(炒菜)
2. 保安    成员变量:工号,姓名,工资    成员方法:工作(检查外来人员)
3. 保洁    成员变量:工号,姓名,工资    成员方法:工作(负责公司的清洁)

图解:

2.1.2 定义

  • 抽象方法 :没有方法体的方法。
  • 抽象类:包含抽象方法的类。

2.2  使用格式

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

定义格式:

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

代码举例:

public abstract void work();

2.2.1 抽象类

如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式:

abstract class 类名字 { 
	
}

代码举例:

public abstract class Animal {
	public abstract void run();
}

2.2.2 抽象的使用

1)继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。

2)必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

抽象类是不可以进行实例化的,抽象类本就是包含有无法实例化的抽象方法,或者说这个方法是没有任何意义的,他存在的意义就是让子类去实现它;因此抽象类是不可以实例化的,也就是不能创建对象;

代码举例:

package com.dfbz.demo01_抽象类小案例;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        // 抽象类不可以实例化
//        Employee employee=new Employee();
//        employee.work();

        Cook cook = new Cook();
        cook.setId("001");
        cook.setName("小红");
        cook.setSalary(20000.0D);
        cook.work();

        System.out.println("----------------");

        Security security = new Security();
        security.setId("002");
        security.setName("小刚");
        security.setSalary(30000.0D);
        security.work();

    }
}

// 员工类
abstract class Employee {
    private String id;
    private String name;
    private double salary;

    // 抽象方法,每个员工都有方法,那么具体的方法内容是什么,父类不能说清楚,留给子类说(这个方法是留给子类来重写的)
    public abstract void work();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

// 含有抽象方法的类必须是抽象类
class Cook extends Employee {
    public void work() {
        System.out.println("炒菜...");
    }
}

class Security extends Employee {
    public void work() {
        System.out.println("检查外来人员..");
    }
}

class Cleaner extends Employee {
    public void work() {
        System.out.println("负责公司清洁");
    }
}

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

2.3 抽象类注意事项

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

理解:我们知道对象是具体的,而抽象类含有抽象方法,抽象方法不是具体的(抽象的),即使实例化成对象了,也不是具体的;因此抽象类不能实例化(不能创建对象)

  1. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的

理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  1. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计

  1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

三、权限修饰符

3.1 概述

在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,

  • public:公共的
  • protected:受保护的
  • default:默认的
  • private:私有的

public > protected > 默认 > private

3.2 不同权限的访问能力

public(公共) protected(保护) default(默认) private(私有)
本类中
同一包中(子类或任意类)
不同包的子类(通过super访问)
不同包的任意类

public具有最大权限。private则是最小权限。

Tips:Java中存在default关键字,但它并不是权限修饰符,这其中存在一定的歧义。不加权限修饰符,就是default权限

3.2.1 private

private是最小的权限,只能在本类中访问,其他类均不可访问private修饰的成员;(即使是子类也不行)

  • 定义Fu类:
package com.dfbz.demo;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Fu {
    private String name;				// 使用private修饰name成员
}
  • 测试类:
package com.dfbz.demo;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Fu fu = new Fu();
        
        // private修饰的成员只能在本类(Fu类)中访问,编译报错
        System.out.println(fu.name);            
    }
}

即使是子类也不能访问父类中private修饰的成员:

package com.dfbz.demo;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Zi extends Fu{
    public void show(){
        System.out.println(super.name);
    }
}

3.2.2 default

default是默认的权限修饰符,使用时不用显式的加上**default**关键字,什么修饰符都不加默认是default权限;default权限允许同一个包下是可以访问的,如果是不同包,那就访问不了了;

我们接着上述案例,权限修饰符改为defalut之后:

Tips:Zi和Fu在同一个包,因此Zi中可以访问成功

但是Fu和Demo01并不在同一个包,因此Demo01中还是不能访问成功:

我们可以将Fu移动到demo01包下。此时Demo01就可以访问成功了,但是Zi与Fu不在同一个包,因此Zi不能访问成功:

3.2.3 protected

protected修饰符的范围是不同包下的子类可以访问,只要是子类,都可以访问的到(通过super访问),但是如果不是子类就不能访问了;

将修饰符改为protected,发现Zi可以访问(但必须使用super访问):

当我们尝试将Fu切换到demo包,此时Demo01将不能访问失败(因为Demo01并不是Fu的子类,也没有使用super访问):

3.2.4 public

public是最高的权限修饰符了,他的权限是任意包的任意类中都可以访问;

我们将Fu中的修饰符改为public,发现在任何地方都可以访问成功了

3.3 权限修饰符和方法的重写

在继承那一章节我们学习过方法的重写,当子类继承父类时,我们可以重新父类的方法,使其更加强大;

但是方法的重写必须保证子类方法的权限修饰符>=父类方法的权限修饰符

换句话说,如果子类方法的权限修饰符小于父类方法的权限修饰符那么方法将不能被重写(语法报错);

  • 父类:
class Fu {
    // 私有
    private void method1() {
    }

    // 默认
    void method2() {
    }

    // 保护
    protected void method3() {
    }

    // 公开
    public void method4() {
    }
}

class Zi{
    private void method1(){}
    
    // 默认
    void method2() {
    }

    // 保护
    protected void method3() {
    }

    // 公开
    public void method4() {
    }
    
}

四、final关键字

fianl是Java中的一个关键字,中文含义是"最终的",fianl可以修饰类、方法、变量等;

4.1 fianl关键字的特点

4.1.1 修饰类

final修饰的类不能被继承。提高安全性,提高程序的可读性;

package com.dfbz.demo01_修饰类;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_final修饰类 {


}

final class Fu {}

// 语法错误: 被final修饰的类不可以被继承
class Zi extends Fu{}

4.1.2 修饰方法

final修饰的方法不能被子类重写;

package com.dfbz.demo02_修饰方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

    }
}

class Fu {
    public final void method(){}
}

class Zi extends Fu {

    // 语法错误: 被final修饰的方法不可以被重写
    public void method(){}
}

4.1.3 修饰变量

fianl可修饰局部变量和成员变量,被fianl修饰的变量不可被修改。另外,在使用fianl修饰成员变量时,必须赋值。

package com.dfbz.demo03_修饰变量;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_修饰基本数据类型 {
    public static void main(String[] args) {
        final int COUNT = 10;

        // 语法错误: 被final修饰的变量不可以被更改
        COUNT = 20;
    }
}

class A {

    // 被final饰的成员变量声明的时候必须要赋值
    final int NUM = 100;

    public void show() {
        // 语法错误: 不可修改
        NUM = 200;
    }
}

Tips:final修饰的变量(成员变量或局部变量)只能被赋值一次;因此被final修饰的变量我们称为常量,通常全大写命名;

posted @ 2023-02-09 13:43  绿水长流*z  阅读(116)  评论(0)    收藏  举报