跳至主要內容

访问控制权限(封装)

晨光-向大约 12 分钟JavaJava语言

访问控制权限(封装)

访问控制权限又称为封装,它是面向对象三大特性中的一种,我之前在学习过程中经常会忽略封装, 心想这不就是一个访问修饰符么,怎么就是三大特性的必要条件了?后来我才知道,如果你信任的下属 对你隐瞒bug,你是根本不知道的

访问控制权限其实最核心就是一点:只对需要的类可见。

Java中成员的访问权限共有四种,分别是publicprotecteds、defaultprivate,它们的可见性如下

privatedefaultprotectedpublic
同一类
同一包中的类
子类
其他包中的类

√ 表示的是可以进行访问。

1. 继承

继承是所有OOP(Object Oriented Programming)语言和Java语言都不可或缺的一部分。只要我们创建了一个类,就隐式的继承自Object父类,只不过没有指定。如果你显示指定了父类,那么你继承于父类,而你的父类继承于Object类。

image-20230226082304209

,如上图所示,如果使用了 extends显示指定了继承,那么我们可以说 Father是父类,而Son是子类,用代码表示如下

1.1 继承的实现

1. 继承的概念

继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法

  • 实现继承的格式

​ 继承的关键字是extends;

格式:class 子类 extends 父类 { };

  • 继承带来的好处

继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。

  • 示例
public class Fu {
    public void show() {
        System.out.println("show方法被调用");
    }
}
// Zi继承Fu
public class Zi extends Fu {
    public void method() {
        System.out.println("method方法被调用");
    }
}
public class Demo {
    public static void main(String[] args) {
		//创建对象,调用方法
        Fu f = new Fu();
        f.show();
        Zi z = new Zi();
        z.method();
        z.show();
    }
}

2. 继承的好处和弊端

  • 继承好处

提高了代码的复用性(多个类相同的成员可以放到同一个类中)。

提高了代码的维护性(如果方法的代码需要修改,修改一处即可)。

  • 继承弊端

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

  • 继承的应用场景:

使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承。

is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类

1.2 继承中的成员访问特点

1. 继承中变量的访问特点

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

  • 子类局部范围找

  • 子类成员范围找

  • 父类成员范围找

  • 如果都没有就报错(不考虑父亲的父亲…)

示例代码

class Fu {
    int num = 10;
}
class Zi {
    int num = 20;
    public void show(){
        int num = 30;
        System.out.println(num);
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show(); // 输出show方法中的局部变量30
    }
}

2. super

  • this&super关键字:

this:代表本类对象的引用

super:代表父类存储空间的标识(可以理解为父类对象引用)

  • this和super的使用分别

    • 成员变量:

      this.成员变量 - 访问本类成员变量

      super.成员变量 - 访问父类成员变量

    • 成员方法:

      this.成员方法 - 访问本类成员方法

      super.成员方法 - 访问父类成员方法

  • 构造方法:

this(…) - 访问本类构造方法

super(…) - 访问父类构造方法

3. 继承中构造方法的访问特点

注意:子类中所有的构造方法默认都会访问父类中无参的构造方法。

子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()。

问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?

  1. 通过使用super关键字去显示的调用父类的带参构造方法。

  2. 在父类中自己提供一个无参构造方法。【推荐】

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

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

  • 子类成员范围找

  • 父类成员范围找

  • 如果都没有就报错(不考虑父亲的父亲…)

5. super内存图

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

6. 方法重写

  • 方法重写概念

子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)。

  • 方法重写的应用场景

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

  • Override注解

用来检测当前的方法,是否是重写的方法,起到【校验】的作用

  • 方法重写的注意事项
    • 私有方法不能被重写(父类私有成员子类是不能继承的)
    • 子类方法访问权限不能更低(public > 默认 > 私有)
public class Fu {
    private void show() {
        System.out.println("Fu中show()方法被调用");
    }
    void method() {
        System.out.println("Fu中method()方法被调用");
    }
}
public class Zi extends Fu {
    /* 编译【出错】,子类不能重写父类私有的方法*/
    @Override
    private void show() {
        System.out.println("Zi中show()方法被调用");
    }
    /* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */
    @Override
    private void method() {
        System.out.println("Zi中method()方法被调用");
    }
    /* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */
    @Override
    public void method() {
        System.out.println("Zi中method()方法被调用");
    }
}

1.3. 继承的注意事项

  • Java中类只支持单继承,不支持多继承

错误范例:class A extends B, C { }

  • Java中类支持多层继承
public class Granddad {
    public void drink() {
        System.out.println("爷爷爱喝酒");
    }
}
public class Father extends Granddad {
    public void smoke() {
        System.out.println("爸爸爱抽烟");
    }
}
public class Mother {
    public void dance() {
        System.out.println("妈妈爱跳舞");
    }
}
public class Son extends Father {
// 此时,Son类中就同时拥有drink方法以及smoke方法
}

2. 多态

2.1 多态的概述

1. 什么是多态?

同一个对象,在不同时刻表现出来的不同形态

多态指的是同一个行为具有多个不同表现形式。是指一个类实例(对象)的相同方法在不同情形下具有 不同表现形式。封装和继承是多态的基础,也就是说,多态只是一种表现形式而已。

2. 多态的前提

  • 要有继承或实现关系

  • 要有方法的重写(父类方法)

  • 要有父类引用指向子类对象

2.2 多态中的成员访问特点

1. 成员访问特点

  • 成员变量

编译看父类,运行看父类

  • 成员方法

编译看父类,运行看子类

2. 示例

动物类

public class Animal {
    public int age = 40;
    public void eat() {
        System.out.println("动物吃东西");
    }
}

public class Cat extends Animal {
    public int age = 20;
    public int weight = 10;
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void playGame() {
        System.out.println("猫捉迷藏");
    }
}

测试类

public class AnimalDemo {
    public static void main(String[] args) {
        //有父类引用指向子类对象
        Animal a = new Cat();
        System.out.println(a.age);
        // System.out.println(a.weight);
        a.eat();
        // a.playGame();
    }
}

2.3 多态的好处和弊端

好处

提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作

弊端

不能使用子类的特有成员。

2.4 多态中的转型

1. 向上转型

父类引用指向子类对象就是向上转型

2. 向下转型

格式:子类型 对象名 = (子类型)父类引用;

3. 示例

动物类

public class Animal {
    public int age = 40;
    public void eat() {
        System.out.println("动物吃东西");
    }
}

public class Cat extends Animal {
    public int age = 20;
    public int weight = 10;
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void playGame() {
        System.out.println("猫捉迷藏");
    }
}

测试类

public class AnimalDemo {
    public static void main(String[] args) {
        //多态
        //向上转型
        Animal a = new Cat();
        a.eat();
        // a.playGame();
        //向下转型
        Cat c = (Cat)a;
        c.eat();
        c.playGame();
    }
}

2.5 多态的案例

请采用多态的思想实现猫和狗的案例,并在测试类中进行测试。

动物类

public class Animal {
    private String name;
    private int age;
    public Animal() {
    }
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void eat() {
        System.out.println("动物吃东西");
    }
}

public class Cat extends Animal {
    public Cat() {
    }
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

public class Dog extends Animal {
    public Dog() {
    }

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

    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}

测试类

public class AnimalDemo {
    public static void main(String[] args) {
        //创建猫类对象进行测试
        Animal a = new Cat();
        a.setName("加菲");
        a.setAge(5);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();
        a = new Cat("加菲", 5);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();
    }
}

3. 组合

组合其实不难理解,就是将对象引用置于新类中即可。组合也是一种提高类的复用性的一种方式。如果 你想让类具有更多的扩展功能,你需要记住一句话多用组合,少用继承

public class SoccerPlayer {

    private String name;
    private Soccer soccer;

}
public class Soccer {

    private String soccerName;
}

代码中SoccerPlayer引用了 Soccer类,通过引用Soccer类,来达到调用soccer中的属性和方法。

组合和继承是有区别的,它们的主要区别如下。

特征组合继承
关系组合是一种has-a的关系,可以理解为有一 个继承是一种is-a的关系,可以理解为是一 个
耦合性组合的双方是一种松耦合的关系继承双方紧耦合
是否具有多 态组合不具备多态和向上转型继承是多态的基础,可以实现向上转型
时期组合是运行期绑定继承是编译期绑定

关于继承和组合孰优孰劣的争论没有结果,只要发挥各自的长处和优点即可,一般情况下,组合和继承 也是一对可以连用的好兄弟。

4. 代理

除了继承和组合外,另外一种值得探讨的关系模型称为代理。代理的大致描述是,A想要调用B类 的方法,A不直接调用,A会在自己的类中创建一个B对象的代理,再由代理调用B的方法。例如如下代码

public class Destination {
    public void todo(){
        System.out.println("control...");
    }
}
public class Device {
    private String name;
    private Destination destination;
    private DeviceController deviceController;
    public void control(Destination destination){
        destination.todo();
    }
}
public class DeviceController {
    private Device name;
    private Destination destination;
    public void control(Destination destination){
        destination.todo();
    }
}

5. 向上转型

向上转型代表了父类与子类之间的关系,其实父类和子类之间不仅仅有向上转型,还有向下转型,它们 的转型后的范围不一样

  • 向上转型:通过子类对象(小范围)转化为父类对象(大范围),这种转换是自动完成的,不用强制。

  • 向下转型:通过父类对象(大范围)实例化子类对象(小范围),这种转换不是自动完成的,需要强制指定。

6. 修饰符

6.1 package

1. 包的概念

包就是文件夹,用来管理类文件的

2. 包的定义格式

package 包名; (多级包用.分开)

例如:package com.heima.demo;

3. 带包编译&带包运行

带包编译:javac –d . 类名.java

  • 例如:javac -d . com.heima.demo.HelloWorld.java

带包运行:java 包名+类名

  • 例如:java com.heima.demo.HelloWorld

6.2 import

  • 导包的意义

使用不同包下的类时,使用的时候要写类的全路径,写起来太麻烦了。为了简化带包的操作,Java就提供了导包的功能。

  • 导包的格式

格式:import 包名;

范例:import java.util.Scanner;

  • 示例

没有使用导包,创建的Scanner对象

package com.heima;

public class Demo {
	public static void main(String[] args) {
        // 1. 没有导包,创建Scnaner对象
        java.util.Scanner sc = new java.util.Scanner(System.in);
	}
}

使用导包后,创建的Scanner对象

package com.heima;
import java.util.Scanner;

public class Demo {
    public static void main(String[] args) {
        // 1. 没有导包,创建Scnaner对象
        Scanner sc = new Scanner(System.in);
	}
}

6.3 final

final的意思是最后的、最终的,它可以修饰类、属性和方法。

  • final修饰类时,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注 意final类中的所有成员方法都会被隐式地指定为final方法。

  • final修饰方法时,表明这个方法不能被任何子类重写,因此,如果只有在想明确禁止该方法在子 类中被覆盖的情况下才将方法设置为final。

  • final修饰变量分为两种情况,一种是修饰基本数据类型,表示数据类型的值不能被修改;一种是 修饰引用类型,表示对其初始化之后便不能再让其指向另一个对象。

6.4 static

1. 修饰属性和方法

static是Java中的关键字,它的意思是静态的,static可以用来修饰成员变量和方法,static用在没有创建对象的情况下调用方法/变量。

  • 用static声明的成员变量为静态成员变量,也成为类变量。类变量的生命周期和类相同,在整个应 用程序执行期间都有效。
static String name = "cxuan"; 
  • 使用static修饰的方法称为静态方法,静态方法能够直接使用类名.方法名进行调用。由于静态方 法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this关键字的,实例变量 都会有this关键字。在静态方法中不能访问类的非静态成员变量和非静态方法,
static void printMessage(){
 System.out.println("cxuan is writing the article");
}

2. 静态代码块

static除了修饰属性和方法外,还有静态代码块的功能,可用于类的初始化操作。进而提升程序的性 能。

public class StaicBlock {
 static{
 System.out.println("I'm A static code block");
 }
}

由于静态代码块随着类的加载而执行,因此,很多时候会将只需要进行一次的初始化操作放在static代 码块中进行。