第三知识单元

面向对象程序设计

Java程序设计 第4讲,主讲人:李欣

Created: 2023-09-12 Tue 00:24

0.1. 互动课堂

Click to host the seminar.

0.2. 签到

https://xin.blue/tool/attendance/

0.3. 本次课的目标

  • 修饰符与访问控制
    • 理解类、域、方法的修饰符与访问控制
  • 类的设计
    • 理解如何使用Java语言设计合理的类

1. 类的修饰符

1.1. 抽象类

abstract 修饰符 修饰的类被称为 抽象类 。 抽象类就是 没有具体对象概念类

abstract class PhoneCard {
    double balance;
    void performDial() {
        // ...
    }
}
public class Main {
    public static void main(String [] args) {
        PhoneCard card = new PhoneCard();
    }
}

abstract class PhoneCard {
    double balance;
    void performDial() {
        // ...
    }
}
javac Main.java
javac Main.java
# Main.java:3: error: PhoneCard is abstract; cannot be instantiated
#         PhoneCard card = new PhoneCard();
#                          ^
# 1 error

1.2. 最终类

如果一个类被 final 修饰符 所修饰和限定, 说明这个类 不能 再有 子类

final class Campus201Card extends PhoneCard {
    String campusName;
}
abstract class PhoneCard {
    double balance;
    void performCharge() { balance += 100.0; }
}
class SuperCampus201Card extends Campus201Card {
    String superCampusName;
}
final class Campus201Card extends PhoneCard {
    String campusName;
}
abstract class PhoneCard {
    double balance;
    void performCharge() {
        balance += 100.0;
    }
}
javac Main.java
javac Main.java
# Main.java:8: error: cannot inherit from final Main.Campus201Card
#     class SuperCampus201Card extends Campus201Card {
#                                      ^
# 1 error
public class Main {
    public static void main(String [] args) {
        Campus201Card my201Card = new Campus201Card();
        my201Card.setCampusName();
        my201Card.performCharge();
        System.out.println(my201Card.toString());
    }
}
final class Campus201Card extends PhoneCard {
    String campusName;
    void setCampusName() {
        campusName = "NCUT";
    }
    public String toString() {
        String s = "CampusName: " + campusName + ", Balance: " + balance;
        return s;
    }
}
abstract class PhoneCard {
    double balance;
    void performCharge() {
        balance += 100.0;
    }
}
CampusName: NCUT, Balance: 100.0

2.

2.1. 域的定义

class Employee { // Employee类定义
    String name;
    int age;
    float salary;
    Employee(String n, int a, float s) { //构造函数
        name = n;
        age = a;
        salary = s;
    }
    void upSalary(float inc) { // 方法:提薪
        salary = salary + inc;
    }
    String getInfo() { // 方法:取信息
        return ", Age:" + age + ", Salary:" + salary;
    }
}

2.2. 静态域

class Employee { // Employee类定义
    String name;
    int age;
    float salary;
    static float minSalary = 500; // 静态属性,职工最低工资
    Employee(String n, int a, float s) { // 构造函数
        name = n;
        age = a;
        salary = s;
    }
    void upSalary(float inc) { // 方法:提薪
        salary = salary + inc;
    }
    String getInfo() { // 方法:取信息
        return ", Age:" + age + ", Salary:" + salary;
    }
}

2.3. 静态初始化器

public class Main {
    public static void main(String [] args) {
        Employee richard = new Employee("Richard", 42, 13000);
        Employee lucy = new Employee("Lucy", 28, 8000);
        System.out.print(richard.name + "'s ID: " + richard.employeeID);
        System.out.println(richard.getInfo());
        System.out.print(lucy.name + "'s ID: " + lucy.employeeID);
        System.out.println(lucy.getInfo());
    }
}
class Employee { // Employee类定义
    long employeeID;
    String name;
    int age;
    float salary;
    static float minSalary; // 静态属性,职工最低工资
    static long nextEmployeeID;
    static {
        minSalary = 500;
        nextEmployeeID = 1008600001;
    }
    Employee(String n, int a, float s) { // 构造函数
        employeeID = nextEmployeeID++;
        name = n;
        age = a;
        salary = s;
    }
    void upSalary(float inc) { // 方法:提薪
        salary = salary + inc;
    }
    String getInfo() { // 方法:取信息
        return ", Age:" + age + ", Salary:" + salary;
    }
}
Richard's ID: 1008600001, Age:42, Salary:13000.0
Lucy's ID: 1008600002, Age:28, Salary:8000.0

2.4. 最终域

final 修饰符 说明 常量 时,需要注意以下几点:

  • 需要说明 常量数据类型
  • 需要同时指出 常量具体数值
  • 为节省空间, 常量 通常声明为 static

https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/Integer.html#field-detail

2.5. 易失域

参考教材

  • 4.4.4 p.78

如果一个 volatile 修饰符 所修饰, 说明这个域可能 同时被几个线程控制修改 。 通常, volatile 用来修饰 接受外部输入

3. 方法

3.1. 方法的定义

public class Main {
    public static void main(String [] args) {
        int count = 1;
        PerfectNum pn = new PerfectNum();
        for(int i = 1; i < 10000; i++) {
            if(pn.isPerfect(i)) {
                System.out.print(i + String.valueOf(' '));
                count++;
            }
        }
    }
}
class PerfectNum {
    boolean isPerfect(int x) {
        int y = 0;
        for(int i = 1; i < x; i++)
            if(x % i == 0) y += i;
        if(y == x)
            return true;
        else
            return false;
    }
}
6 28 496 8128 

3.2. 抽象方法

参考教材

  • 4.5.2 pp.79-83

3.3. 静态方法

public class PerfectNum {
    public static void main(String [] args) {
        int count = 1;
        for(int i = 1; i < 10000; i++) {
            if(isPerfect(i)) {
                System.out.print(i + String.valueOf(' '));
                count++;
            }
        }
    }
    static boolean isPerfect(int x) {
        int y = 0;
        for(int i = 1; i < x; i++)
            if(x % i == 0) y += i;
        if(y == x)
            return true;
        else
            return false;
    }
}
6 28 496 8128 

3.4. 其他方法

最终方法

final 修饰符所修饰的方法,是功能和内部语句 不能更改 的最终方法, 即是 不能 被当前类的子类 重新定义 的方法。

本地方法

native 修饰符一般用来声明用 其他语言 书写方法体具体实现方法功能 的特殊的方法, 所有的 native 方法都 没有 方法体 ,而用一个分号代替。

同步方法

synchronized 修饰符主要用于 多线程共存 的程序中的 协调同步

4. 访问控制符

访问控制符 是一组限定 属性方法 是否可以被 程序中的其他部分 访问调用修饰符 。 这里的 其他部分 是指程序里这个类之外的 其他类

4.1. 类的访问控制

  默认(无访问控制符) 公共类(有 public 修饰符)
同一包中的类 可以访问 可以访问
不同包中的类 不可访问 可以访问

参考教材

  • 4.6.1 p.86

4.2. 类成员的访问控制

公共类
类成员访问控制符 public protected default private
可以访问类成员的区域 所有类 包中的类、所有子类 包中的类 本类
默认类
类成员访问控制符 public protected default private
可以访问类成员的区域 包中的类 包中的类 包中的类 本类

参考教材

  • 4.6.2 pp.86-90

4.3. 实验

4.3.1. 包外访问 public 类中非 public 成员的情况

// ./perfect/Perfect.java
package perfect;
public class Perfect {
    static boolean isPerfect(int x) {
        int y = 0;
        for(int i = 1; i < x; i++)
            if(x % i == 0) y += i;
        if(y == x)
            return true;
        else
            return false;
    }
}
// ./main/Main.java
package main;
import perfect.Perfect;
class Main {
    public static void main(String [] args) {
        int count = 1;
        for(int i = 1; i < 10000; i++) {
            if(Perfect.isPerfect(i)) {
                System.out.print(i + String.valueOf(' '));
                count++;
            }
        }
    }
}
tree
# .
# ├── main
# │   ├── Main.class
# │   └── Main.java
# └── perfect
#     ├── Perfect.class
#     └── Perfect.java
# 
# 2 directories, 4 files
javac perfect/Perfect.java
javac main/Main.java
# main/Main.java:8: error: isPerfect(int) is not public in Perfect; cannot be accessed from outside package
#             if(Perfect.isPerfect(i)) {
#                       ^
# 1 error

4.3.2. 包外访问 public 类中 public 成员的情况

// ./perfect/Perfect.java
package perfect;
public class Perfect {
    public static boolean isPerfect(int x) {
        int y = 0;
        for(int i = 1; i < x; i++)
            if(x % i == 0) y += i;
        if(y == x)
            return true;
        else
            return false;
    }
}
// ./main/Main.java
package main;
import perfect.Perfect;
class Main {
    public static void main(String [] args) {
        int count = 1;
        for(int i = 1; i < 10000; i++) {
            if(Perfect.isPerfect(i)) {
                System.out.print(i + String.valueOf(' '));
                count++;
            }
        }
    }
}
javac perfect/Perfect.java
javac main/Main.java
java main/Main
# 6 28 496 8128

4.3.3. 包外访问非 public 类中 public 成员的情况

// ./perfect/Perfect.java
package perfect;
class Perfect {
    public static boolean isPerfect(int x) {
        int y = 0;
        for(int i = 1; i < x; i++)
            if(x % i == 0) y += i;
        if(y == x)
            return true;
        else
            return false;
    }
}
// ./main/Main.java
package main;
import perfect.Perfect;
class Main {
    public static void main(String [] args) {
        int count = 1;
        for(int i = 1; i < 10000; i++) {
            if(Perfect.isPerfect(i)) {
                System.out.print(i + String.valueOf(' '));
                count++;
            }
        }
    }
}
javac perfect/Perfect.java
javac main/Main.java
# main/Main.java:3: error: Perfect is not public in perfect; cannot be accessed from outside package
# import perfect.Perfect;
#               ^
# main/Main.java:8: error: Perfect is not public in perfect; cannot be accessed from outside package
#             if(Perfect.isPerfect(i)) {
#                ^
# 2 errors

4.3.4. 控制在类级别上的访问保护

public class Main {
    public static void main(String [] args) {
        Employee e1 = new Employee("Richard");
        Employee e2 = new Employee("Lucy");
        System.out.println(e1.equals(e2));
        e2.setName("Richard");
        System.out.println(e1.equals(e2));
        // System.out.println(e1.name); // 私有域不能直接访问
    }
}
class Employee {
    private String name;
    Employee(String name) {
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public boolean equals(Employee other) {
        return name.equals(other.name);
    }
}
false
true

4.3.5. Combination of modifiers: abstract and final

abstract final class Test {
    abstract void test();
}
javac Test.java
# Test.java:1: error: illegal combination of modifiers: abstract and final
# abstract final class Test {
#                ^
# 1 error

4.3.6. Combination of modifiers: abstract and private

abstract class Test {
    abstract private void test();
}
javac Test.java
# Test.java:2: error: illegal combination of modifiers: abstract and private
#     abstract private void test();
#                           ^
# 1 error

4.3.7. Combination of modifiers: abstract and static

abstract class Test {
    abstract static void test();
}
javac Test.java
# Test.java:2: error: illegal combination of modifiers: abstract and static
#     abstract private void test();
#                           ^
# 1 error

4.3.8. Combination of modifiers: abstract and final

abstract class Test {
    abstract final void test();
}
javac Test.java
# Test.java:2: error: illegal combination of modifiers: abstract and final
#     abstract private void test();
#                           ^
# 1 error

4.3.9. Combination of modifiers: abstract and native

abstract class Test {
    abstract native void test();
}
javac Test.java
# Test.java:2: error: illegal combination of modifiers: abstract and native
#     abstract private void test();
#                           ^
# 1 error

4.3.10. abstract 类中存在 private 成员的情况

public class Test extends SuperAbstractTest {
    public static void main(String [] args) {
        Test test = new Test();
        System.out.println(test.str);
        test.outputTest();
    }
}
abstract class SuperAbstractTest {
    private String str = "Test";
    private void outputTest() {
        System.out.println(str);
    }
    public void setStr(String str) {
        this.str = str;
    }
}
javac Test.java
# Test.java:4: error: str has private access in SuperAbstractTest
#         System.out.println(test.str);
#                                ^
# Test.java:5: error: cannot find symbol
#         test.outputTest();
#             ^
#   symbol:   method outputTest()
#   location: variable test of type Test
# 2 errors
public class Test extends SuperAbstractTest {
    public static void main(String [] args) {
        Test test = new Test();
    }
}
abstract class SuperAbstractTest {
    private String str = "Test";
    private void outputTest() {
        System.out.println(str);
    }
    public void setStr(String str) {
        this.str = str;
    }
}
javac Test.java
javac Test.java
java Test
public class Test extends SuperAbstractTest {
    public static void main(String [] args) {
        Test test = new Test();
        test.outputTest();
        test.setStr("Good job");
        test.outputTest();
    }
}
abstract class SuperAbstractTest {
    private String str = "Test";
    public void outputTest() {
        System.out.println(str);
    }
    public void setStr(String str) {
        this.str = str;
    }
}
javac Test.java
javac Test.java
java Test
# Test
# Good job

4.3.11. 一般类中出现 abstract 方法的情况

class Test {
    public static void main(String [] args) {
        System.out.println("test");
    }
    abstract void test();
}
javac Test.java
javac Test.java
# Test.java:1: error: Test is not abstract and does not override abstract method test() in Test
# class Test {
# ^
# 1 error
public class Test extends SuperAbstractTest {
    void test() {
        System.out.println("test");
    }
    public static void main(String [] args) {
        Test test = new Test();
        test.test();
    }
}
abstract class SuperAbstractTest {
    abstract void test();
}
javac Test.java
java Test
# test

4.3.12. static 方法中处理 static 属性的情况

public class Test {
    static String staticStr = "static";
    public static void main(String [] args) {
        System.out.println(staticStr);
    }
}
javac Test.java
java Test
# static

4.3.13. static 方法中处理非 static 属性的情况

public class Test {
    String normalStr = "normal";
    public static void main(String [] args) {
        System.out.println(normalStr);
    }
}
javac Test.java
# Test.java:4: error: non-static variable normalStr cannot be referenced from a static context
#         System.out.println(normalStr);
#                            ^
# 1 error

5. 课后作业

  1. 阅读教材 例4-10 中的程序,描述该例子中类的设计有什么问题。
  2. 教材 例4-11 中是如何改善以上问题的?这对我们进行类的设计有何启发?
  3. 习题4-3 使用抽象和封装有哪些好处?
  4. 编写 Calc 类,实现加法、减法、乘法、除法运算,并在主类中测试该类,同时给出程序源码及输出。