北大慕课Java程序设计【四】类、包、接口(下)

目录

一、访问控制符

1. 修饰符

2. 成员的访问控制符(权限修饰符)

3. 类的访问控制符

4. setter 和 getter 及其优势

二、其他修饰符

1. 三种其他修饰符的含义及应用对象

2. static 的使用

3. final 的使用

4. abstract 的使用

三、接口

1. 接口的概念

2. 接口的作用

3. 接口的定义与类型

4. 枚举(从 JDK 1.5 起)

5. 接口中的常量

四、语法小结

1. 完整的类的定义

2. 完整的接口的定义

3. 三种要求固定声明方式的方法

4. 完整的 Java 源文件


一、访问控制符

1. 修饰符

→ 修饰符(modifiers)分为两类,可以修饰类及其成员(字段、方法)

① 访问修饰符:public,private 等等

② 其他修饰符:abstract

2. 成员的访问控制符(权限修饰符)

在同一个类中在同一个包中不同包中的子类不同包中的非子类
private
默认(包可访问)
protected
public

【举例】下面的例子中文件结构如下:

tree
.
├── Derived1.java
├── Main.java
└── pkg
    ├── Test.java
    └── pkg1
        └── Test1.java

2 directories, 4 files

为清晰起见,绘制 UML 图如下(图中有些类,如 Group 将在后面的笔记中出现):

 代码及注释如下:

→ Main.java 中的 Base 类和 Derived 类:

class Base {
    // 不能写 public class Base 因为文件名不是 base.java
    private int a = 1;
    int b = 2;
    protected int c = 3;
    public int d = 4;

    Base() {
        System.out.println("Base initialized.");
        System.out.println("this.a = " + this.a); // 同一个类中可以访问的字段
    }
}

class Derived extends Base {
    Derived() {
        System.out.println("Derived initialized.");
        // System.out.println(a); [错误]
        System.out.println(b); // 同一个包中可以访问的字段
        System.out.println(c);
        System.out.println(d);
    }
}

→ Main.java 中的 Main 类:

public class Main {
    public static void main(String[] args) {
        Base base = new Base();
        // System.out.println(base.a); [错误]
        System.out.println(base.b);
        System.out.println(base.c);
        System.out.println(base.d);

        Derived derived = new Derived();
        System.out.println(derived);
        Derived1 derived1 = new Derived1();
        System.out.println(derived1.e); // 同包的非子类可以访问的字段
        System.out.println(derived1.f); // 同包的非子类可以访问的字段

        Person person = new Person();
        // System.out.println(person.age); [错误]
        System.out.println(person.getAge());
        Person person1 = new Person(20);
        System.out.println(person1.getAge());

        if (person.setAge(18)) {
            System.out.println("Success!");
        } else {
            System.out.println("Failed!");
        }
        System.out.println(person.getAge());

        if (person1.setAge(300)) {
            System.out.println("Success!");
        } else {
            System.out.println("Failed!");
        }
        System.out.println(person1.getAge());
    }
}

→ 与 Base 类在同一个包中的 Derived1 类:

public class Derived1 extends Base {
    public int e = 10;
    int f = 12;

    Derived1() {
        System.out.println(b); // 同包的子类中可以访问的字段
        System.out.println(c); // 同包的子类中可以访问的字段
    }
}

→ pkg/Test1.java 中的 Test1 和 Test2 类:

package pkg.pkg1;

import pkg.Test;

public class Test1 extends Test {
    public Test1() {
        super();
        System.out.println("test1");
        // System.out.println(pri); [错误]
        // System.out.println(def); [错误]
        System.out.println(pro);
        System.out.println(pub);
    }
}

class Test2 {
    public Test2() {
        Test test = new Test();
        System.out.println("test2");
        // System.out.println(test.pri); [错误]
        // System.out.println(test.def); [错误]
        // System.out.println(test.pro); [错误]
        System.out.println(test.pub);
    }
}

3. 类的访问控制符

→ 定义类时,类的访问控制符要么是 public,要么是默认

① public:该类可以被其他类所访问

② 默认:只能被同包中的类访问

【注意】访问受两层控制:类与字段、方法

4. setter 和 getter 及其优势

① 将字段用 private 修饰,从而更好地将信息封装和隐藏

set 和 get 都用 public 来修饰,分别用 set 和 get 方法对类的属性进行存取

③ setter 和 getter 的优势:

        → 属性用 private 更好地封装和隐藏,外部类不能随意存取和修改

        → 提供方法来存取对象的属性,在方法中可以对给定参数的合法性进行检验

        → 方法可以用来给出计算后的值

        → 方法可以完成其他必要的工作(如清理资源,设定状态等等)

        → 只提供 get 方法,而不提供 set 方法的字段,可以保证其只读的属性

【举例】Java 中的 setter 和 getter

class Person {
    private int age;

    Person() {
        age = 18;
    }

    Person(int age) {
        this.age = age;
    }

    public boolean setAge(int age) {
        if (age > 0 && age < 200) {
            this.age = age;
            return true;
        }
        return false;
    }

    public int getAge() {
        return age;
    }
}

 

二、其他修饰符

1. 三种其他修饰符的含义及应用对象

→ 其他修饰符也即非访问控制符

基本含义修饰类 修饰成员修饰局部变量
static静态的、非实例的、类的可以修饰内部类
final最终的、不可改变的
abstract抽象的、不可实例化的

2. static 的使用

① static 字段(在一定意义上,可以表示全局变量)

        → static 成员不属于任何一个对象的实例,保存在类的内存区域的公共存储单元

        → 类变量可以通过类名直接进行访问,也可以通过实例对象来访问,结果相同

【举例】System 类的 in 和 out 对象属于类的域,可用类名直接访问,即 System.in 和 System.out

② static 方法

        → 静态方法(类方法)属于整个类

        → 静态方法不能操作和处理属于某对象的域,只能处理本类中的静态域或调用静态方法

        → 静态方法中,不能访问实例变量,不能使用 this 或 super

        → 调用这个方法时,应该是用类名直接调用,也可以用某一个具体的对象名

【举例】static 字段及方法的例子如下:

class Group {
    static long totalNum; // 在一定意义上,可以表示全局变量
    static String location = "Shandong";
    int id;
    String name;

    Group() {
        totalNum = 11;
        id = 10;
        name = "Group 1";
    }

    static void func() {
        // 静态方法(类方法)——属于整个类;调用时应该直接用类,也可以用实例来调用
        System.out.println(totalNum);
        // System.out.println(id); [错误] 不能操作和处理属于某个对象的成员变量
        // func1(); [错误] 只能处理本类中的 static 域或 调用 static 方法
        printLocation();
        // System.out.println(this.id); [错误] 不能使用 this 或 super
        Group group = new Group();
        System.out.println(group.id); // [正确]
    }

    static void printLocation() {
        System.out.println(location);
    }

    void func1() {
        // 实例方法——属于某个实例
        System.out.println(id);
    }
}

③ import static(JDK 1.5 之后可以使用)

        → import static java.lang.System.*; 引入之后,可以直接使用 out.println();

【注意】要求类的所有成员都是 static 成员

3. final 的使用

① final 类与 final 方法

        → 被 final 修饰和限定的类:不能被继承,也就是说不能有子类,其方法不能被 Override,编译器可以实现优化

        → 被 final 修饰和限定的方法:不能被子类覆盖

② final 字段与 final 局部变量(方法中的变量)

        → 值一旦给定,就不能被修改;是只读量,能且只能被赋值一次,不能被赋值多次

        → 字段用 static final 修饰:可以表示常量,例如 Integer.MAX_VALUE,Math.PI 等

        → 与 final 字段、局部变量有关的赋值:

                1. 定义 static final 域时,没有给定初始值:按照默认值初始化,数值为 0;boolean 为 false,引用为 null

                2. 定义 final 域(非 static)时,必须且只能赋值一次,不能缺省;可以在定义变量的时候赋值,也可以在每一个构造函数中进行赋值

                3. 定义 final 局部变量,必须且只能赋值一次;它的值可能不是常量,但它的取值在变量存在期间不会改变

【举例】final 关键字的使用

final class TestFinal {
    final int a = 32;
    final int b;
    final static double PI = 3.14159265; // 也可以写成 final static

    TestFinal(int b) {
        System.out.println(PI);
        // PI = 1.1; [错误]
        this.b = b;
        // this.b = 10; [错误]
        final float c = (float) PI;
        System.out.println(c);
        // c = 10; [错误]
        int d = 20;
        final int e = d;
        System.out.println(e); // e 的值在变量存在期间不能改变
        System.out.println(++d);
    }
}

4. abstract 的使用

① abstract 类:抽象类,不能用 new 来实例化,更高度的抽象

② abstract 方法:

        → 为所有子类定义统一的接口,抽象方法只需要声明,无需实现,用分号而不用花括号

        → 抽象类中类包含抽象方法,也可不包含抽象方法

        【注意】但若包含抽象方法,必须声明为抽象类

        → 抽象方法在子类中必须被实现,否则子类仍然为抽象类

【举例】abstract 关键字的使用

abstract class TestAbstract {
    // 一个抽象类一定不能用 final 来修饰
    final int a;

    TestAbstract() {
        this.a = 7;
        System.out.println("TestAbstract initialized.");
    }

    abstract void func(int x, int y, int z);

    static void test() {
        System.out.println("TestAbstract: test");
    }
}

class TestAbstractDerived extends TestAbstract {
    final int b;

    TestAbstractDerived(int b) {
        super();
        this.b = b;
    }

    @Override
    void func(int x, int y, int z) {
        System.out.println("TestAbstractDerived: func");
        System.out.printf("%d %d %d\n", x, y, z);
    }
}

class Test {
    public static void main(String[] args) {
        Group group = new Group();
        System.out.println(group.id);
        Group.func();
        group.func1();

        TestFinal testFinal = new TestFinal(19);
        System.out.println(testFinal.a + " " + testFinal.b);

        // TestAbstract testAbstract = new TestAbstract(); [错误]
        TestAbstract.test();
        TestAbstractDerived testAbstractDerived = new TestAbstractDerived(-8);
        System.out.println(testAbstractDerived.b);
        testAbstractDerived.func(9, 9, 6);
        testAbstractDerived.func(1, 2, 1);

        TestAbstract testAbstract = new TestAbstractDerived(10);
        testAbstract.func(7, 2, 1);
    }
}

 

三、接口

1. 接口的概念

→ 接口是某种特征的约定(抽象)

① 定义接口 interface:所有方法都是 public abstract

② 实现接口 implements:可以实现多继承,与类的继承关系无关

面向接口的编程,不是面向实现;Java 中有大量的接口

Flyable f = new Bird() // 只要能飞就可以啦 ~

→ 接口是引用类型(引用类型主要有三种 class interface 数组)

2. 接口的作用

可以实现不相关类的相同行为,不需要考虑这些类之间的层次关系,从而在一定意义上实现了多重继承(一个类里面实现多个不同的特征)

可以指明多个类需要实现的方法(更加抽象,类似于 C++ 的纯虚的类)

③ 可以了解对象的的交互界面,不需要了解对象对应的类(见 Main 中解释)3. 接口的定义

3. 接口的定义与类型

【举例】接口的示例

// public 修饰的接口——任意类都可以使用
// 缺省情况下——只有与该接口定义在同一包的类才可以访问
interface Collection {
    // 接口 [extends listOfSuperInterfaces] 可以有多个父接口
    // 子接口继承父接口所有的常量和方法
    // 类 [extends Base] 只能继承一个父类

    // 命名往往用 -able 或 -ible 结尾
    void add(Object obj);

    void delete(Object obj);

    Object find(Object obj);

    int size();
    // 这里默认是 public 但在实现的时候要写上 public
}

class FIFOQueue implements Collection {
    // 必须全都实现才行
    @Override
    public void add(Object obj) {
        System.out.println("FIFOQueue: add");
    }

    @Override
    public void delete(Object obj) {
        System.out.println("FIFOQueue: delete");
    }

    @Override
    public Object find(Object obj) {
        System.out.println("FIFOQueue: find");
        return null;
    }

    @Override
    public int size() {
        System.out.println("FIFOQueue: size");
        return 0;
    }
}

public class Main {
    public static void main(String[] args) {
        // 接口可以作为引用类型来使用
        // 任何实现该接口的类的实例都可以存储在该接口类型的变量中
        // 通过这些变量可以访问类所实现的接口中的方法
        // Java 运行时系统动态地确定使用哪个类的方法

        Collection collection = new FIFOQueue();
        collection.add(null); // 使用时不需要了解对象对应的类
        FIFOQueue fifoQueue = new FIFOQueue();
        fifoQueue.size();
        System.out.println("Hello world!");
    }
}

4. 枚举(从 JDK 1.5 起)

【举例】枚举类型及其使用

enum Light {
    // JDK 1.5 之后 可以使用枚举 Java 中的枚举是用类来实现的,可以复杂地使用
    Red, Yellow, Green
}
Light light = Light.Red;
switch (light) {
    case Red -> System.out.println("Red");
    case Yellow -> System.out.println("Yellow");
    case Green -> System.out.println("Green");
    // 这样写不用 break 哦
}

5. 接口中的常量

【举例】Java 接口中的常量

// 接口中的常量——接口体可以包含常量的定义
// 常量的定义格式为 type NAME = value;

interface Printer1 {
    float VERSION1 = 2022.2f;

    // 类似于 C++ 中的 const 通常具有 public static final 的属性
    default void printInt(int n) {
        // 默认方法 Java 8 之后
        System.out.println("Printer1: The integer is " + n + ".");
    }

    default void test() {
        System.out.println("Printer1: test");
    }
}

6. Java 8 中的接口

【举例】Java 8 中的接口,扩展了传统的抽象概念

interface Printer2 {
    float VERSION2 = 2022.4f;

    // Java 8 接口成员还可以是 static 方法
    // 具有实现体的方法——default 方法
    //     好处——提供了一个默认的实现,子类在 implements 中可以不用再写了
    static String describe() {
        return "Printer2: describe";
    }

    default void printInt(int n) {
        // 默认方法 Java 8 之后
        System.out.println("Printer2: The integer is " + n + ".");
    }

   /* 产生二义性(Printer 将会 extends 两个同名的方法)
    default void test() {
        System.out.println("Printer2: test");
    } */
}

interface Printer extends Printer1, Printer2 {
    int INFO = 2022;

    void print();

    @Override
    default void printInt(int n) {
        System.out.println("Printer: The integer is " + n + ".");
    }
}

class TestPrinter implements Printer {
    @Override
    public void print() {
        System.out.println(INFO);
    }

    @Override
    public void printInt(int n) {
        System.out.println("TestPrinter: The integer is " + n + ".");
    }
}

class TestPrinter1 implements Printer1 {
    @Override
    public void test() {
        System.out.println("TestPrinter1: test");
    }
}

class TestPrinter1_1 implements Printer1 {
}

class TestPrinter2 implements Printer2 {
}

class Test {
    public static void main(String[] args) {
        Printer printer = new TestPrinter();
        printer.print();
        printer.printInt(99);
        // 默认方法不是抽象方法,不强制被重写

        System.out.println(printer.VERSION1 + " " + printer.VERSION2);
        System.out.println(Printer2.describe());
        // System.out.println(TestPrinter.describe());
        // [注意] 接口静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
        // [注意] public 可以省略,但是 static 不能省略

        TestPrinter1 testPrinter1 = new TestPrinter1();
        testPrinter1.printInt(7);
        testPrinter1.test();
        Printer1 testPrinter1$1 = new TestPrinter1();
        testPrinter1$1.printInt(7);
        testPrinter1$1.test(); // 动态绑定 输出 TestPrinter1: test

        TestPrinter1_1 testPrinter1_1 = new TestPrinter1_1();
        testPrinter1_1.test();
        TestPrinter2 testPrinter2 = new TestPrinter2();
        testPrinter2.printInt(7);
    }
}

 

四、语法小结

1. 完整的类的定义

[public] [abstract | final] class className

[extends superClassName] [implements InterfaceNameList] {

        [public | protected | private] [static] [final] [transient] [volatile] type variableName;

        [public | protected | private] [static] [final | abstract] [native] [synchronized] returnType methodName([parameterList]) [throws exceptionList] { statements }

}

2. 完整的接口的定义

[public] interface InterfaceName [extends superInterfaceList] {
     type constantName = value;
     returnType methodName([parameterList]);
     还可以有 static 方法 和 default 方法
}

3. 三种要求固定声明方式的方法

① 构造方法:className([parameterList]) {}

② main 方法:public static void main(String[] args) {}

③ finalize 方法:protected void finalize() throws throwable {}

4. 完整的 Java 源文件

① 指定文件中的类所在的包,0个或1个:package packageName; 

② 指定引入的类,0个或多个:import packageName[.className | *]; 

③ 属性为 public 的类的定义,0个或1个:public classDefinition

④ 接口或类的定义,0个或多个:interfaceDefinition & classDefinition

【注意】源文件的名字必须与属性为 public 的类的名字完全相同

        → 在一个 java 文件中,package 语句和 public 类最多只能有一个

        → public 接口的名称需要与文件名相同

【举例】一个完整的 Java 源文件

package pkg;

class Test {
    public int a;

    // [1] 构造方法——className([parameterList]) {}
    Test(int a) {
        this.a = a;
    }

    protected void finalize() {
        // [3] 析构方法——编译器使用的方法,编程的时候不写出来
        // Overrides method that is deprecated and marked for removal in 'java.lang.Object'
    }
}

interface TestInterface {
    void func(int a);
}

class TestInterfaceImplementation implements TestInterface {
    @Override
    public void func(int a) {
        System.out.println(a * a);
    }
}

public class Main {
    public static void main(String[] args) {
        // [2] main 方法——public static void main(String[] args) { ... }
        Test test = new Test(20);
        System.out.println(test.a);

        TestInterface testInterface = new TestInterfaceImplementation();
        testInterface.func(7);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值