25、Java 编程中的包、访问修饰符与事件处理

Java 编程中的包、访问修饰符与事件处理

1. 包内代码的使用

有时收到邮件是件令人开心的事,可能会收到有用的东西。比如有人从 Burd Brain Consulting 发来了 Drawing 类的子类 DrawingWideBB ,代码如下:

package com.burdbrain.drawings;
import java.awt.Graphics;
public class DrawingWideBB extends Drawing {
    int width = 100, height = 30;
    public void paint(Graphics g) {
        g.drawOval(x, y, width, height);
    }
}

当运行这个类和 Drawing 类时,一切正常,因为它们在同一个包中。 DrawingWideBB 类有权使用 Drawing 类中默认访问权限的 x y 字段。

若要使用 DrawingWideBB 类,需要对代码做两处修改:
1. 将导入声明改为 import com.burdbrain.drawings.DrawingWideBB
2. 修改 ArtFrame 对象的构造函数调用为 new ArtFrame(new DrawingWideBB())

2. 受保护的访问权限

起初,很多人认为 Java 中 protected 意味着安全、难以访问,但事实并非如此。在 Java 中,受保护的成员比默认访问权限的成员更易使用。

默认访问权限的字段只能在其所在的包内访问,而加上 protected 后,该字段的子类(无论是否在同一包中)都可以访问它。例如:

// Listing 13-8
package com.burdbrain.drawings;
import java.awt.Graphics;
public class Drawing {
    protected int x = 40, y = 40, width = 40, height = 40;
    public void paint(Graphics g) {
        g.drawOval(x, y, width, height);
    }
}

// Listing 13-9
import java.awt.Graphics;
import com.burdbrain.drawings.Drawing;
public class DrawingWide extends Drawing {
    int width = 100, height = 30;
    public void paint(Graphics g) {
        g.drawOval(x, y, width, height);
    }
}

Drawing 类中, x y width height 字段是受保护的。 DrawingWide 类虽然不在同一包中,但可以引用 Drawing 类中的 x y 字段。

受保护的访问权限在团队协作中很有用,当外部团队使用你的代码并创建子类时,他们可以直接引用你代码中的受保护字段或方法。

3. 非子类在同一包中的情况

Burd Brain Consulting 又发来了 ShowFrame 类的替代类 ShowFrameWideBB ,代码如下:

package com.burdbrain.drawings;
import com.burdbrain.frames.ArtFrame;
class ShowFrameWideBB {
    public static void main(String args[]) {
        Drawing drawing = new Drawing();
        drawing.width = 100;
        drawing.height = 30;
        ArtFrame artFrame = new ArtFrame(drawing);
        artFrame.setSize(200, 100);
        artFrame.setVisible(true);
    }
}

ShowFrameWideBB 类与 Drawing 类在同一包中,但它不是 Drawing 类的子类。当编译 ShowFrameWideBB 类和包含受保护字段的 Drawing 类时,一切正常,因为受保护的成员在其所在包内的代码(无论是否为子类)中可用。

如果从命令行运行程序,可能需要输入全限定类名,例如运行 ShowFrameWideBB 类的代码时,需要输入 java com.burdbrain.drawings.ShowFrameWideBB

4. Java 类的访问修饰符

Java 类的访问修饰符相对简单,类可以是 public 或非 public 的。
- 公共类 :如果一个类是 public 的,你可以在代码的任何地方引用它,但需要遵循目录结构规则并正确引用打包的类。例如:

import com.burdbrain.drawings.Drawing;
import com.burdbrain.frames.ArtFrame;
...
ArtFrame artFrame = new ArtFrame(new Drawing());

或者不使用导入声明:

com.burdbrain.frames.ArtFrame artFrame = 
    new com.burdbrain.frames.ArtFrame
        (new com.burdbrain.drawings.Drawing());
  • 非公共类 :如果一个类不是 public 的,你只能在该类所在的包内引用它。例如,将 Drawing 类的 public 关键字删除后:
package com.burdbrain.drawings;
import java.awt.Graphics;
class Drawing {
    public int x = 40, y = 40, width = 40, height = 40;
    public void paint(Graphics g) {
        g.drawOval(x, y, width, height);
    }
}

在同一包中的 DrawingWideBB 类可以正常访问它,但不在同一包中的代码引用时会报错。

不过,Java 中的内部类遵循不同的规则,但对于初学者来说,接触内部类的机会较少。

下面是一个简单的总结表格:
| 访问修饰符 | 类的访问范围 |
| ---- | ---- |
| public | 代码的任何地方(需遵循目录结构和引用规则) |
| 非 public | 所在包内 |

5. 响应按键和鼠标点击

在图形用户界面(GUI)中,用户的操作(如按键、移动鼠标、点击鼠标等)称为事件,响应这些操作的代码称为事件处理代码。

下面是一个处理鼠标点击事件的示例代码:

// Listing 14-1
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
class GameFrame extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;
    int randomNumber = new Random().nextInt(10) + 1;
    int numGuesses = 0;
    JTextField textField = new JTextField(5);
    JButton button = new JButton("Guess");
    JLabel label = new JLabel(numGuesses + " guesses");
    public GameFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());
        add(textField);
        add(button);
        add(label);
        button.addActionListener(this);
        pack();
        setVisible(true);
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        String textFieldText = textField.getText();
        if (Integer.parseInt(textFieldText)==randomNumber) {
            button.setEnabled(false);
            textField.setText(textField.getText() + " Yes!");
            textField.setEnabled(false);
        } else {
            textField.setText("");
            textField.requestFocus();
        }
        numGuesses++;
        String guessWord = 
            (numGuesses == 1) ? " guess" : " guesses";
        label.setText(numGuesses + guessWord);
    }
}

// Listing 14-2
class ShowGameFrame {
    public static void main(String args[]) {
        new GameFrame();
    }
}

这个示例实现了一个猜数字游戏,用户在文本框中输入数字并点击按钮,程序会根据输入给出相应的反馈。

6. 事件处理的实现

Listing 14-1 通过以下三个部分处理按钮点击事件:
1. GameFrame 类声明实现 ActionListener 接口。
2. GameFrame 类的构造函数将 this 添加到按钮的动作监听器列表中。
3. GameFrame 类有一个 actionPerformed 方法。

这三个部分共同使 GameFrame 类能够处理按钮点击事件。

7. Java 接口

在 Java 中,一个类只能继承一个父类,但可以实现多个接口。接口类似于类,但有以下特点:
- 接口的方法没有具体的实现体,称为抽象方法。例如 ActionListener 接口:

package java.awt.event;
import java.util.EventListener;
public interface ActionListener 
                        extends EventListener {
    public void actionPerformed(ActionEvent e);
}
  • 当一个类实现接口时,必须为接口的所有方法提供具体的实现。例如 GameFrame 类实现 ActionListener 接口,就必须实现 actionPerformed 方法。
8. 线程执行

Java 程序是多线程的,当运行 Java 程序时,多个线程会同时执行。例如,Java 有一个事件处理线程,它在后台监听鼠标点击事件,并在用户点击鼠标时调用相应的 actionPerformed 方法。

当用户点击按钮时,事件处理线程会查找要调用的 actionPerformed 方法,这就是为什么要调用 addActionListener 方法将 actionPerformed 方法添加到待调用列表中。

9. this 关键字

this 关键字在 GameFrame 类中代表实例本身。当 GameFrame 类的实例调用 button.addActionListener(this) 时,就是将该实例的 actionPerformed 方法添加到按钮的动作监听器列表中。

10. actionPerformed 方法内部

actionPerformed 方法使用了 Java API 中的一些技巧:
- JTextField JLabel 实例有自己的 getText setText 方法,用于获取和设置组件中的文本。
- javax.swing 包中的组件有 setEnabled 方法,用于启用或禁用组件。
- javax.swing 包中的组件有 requestFocus 方法,用于获取用户的下一次输入焦点。

下面是一个简单的流程图,展示了事件处理的流程:

graph TD;
    A[用户点击按钮] --> B[事件处理线程检测到点击];
    B --> C[查找要调用的actionPerformed方法];
    C --> D[调用actionPerformed方法];
    D --> E{输入是否正确};
    E -- 是 --> F[禁用按钮和文本框,显示Yes!];
    E -- 否 --> G[清空文本框,获取焦点,增加猜测次数];

综上所述,Java 中的包、访问修饰符、事件处理和接口等概念是 Java 编程中的重要组成部分,掌握这些概念可以帮助开发者更好地编写和组织代码。

Java 编程中的包、访问修饰符与事件处理

11. 事件处理的详细流程分析

为了更清晰地理解事件处理的过程,我们可以将其拆分为更详细的步骤。以下是事件处理流程的详细列表说明:
1. 用户操作触发事件 :用户在图形用户界面上进行操作,如点击按钮、按下键盘按键等,这些操作会触发相应的事件。
2. 事件被捕获 :Java 的事件处理线程在后台持续监听各种事件,当用户操作触发事件时,事件处理线程会捕获该事件。
3. 查找监听器 :事件处理线程会查找与该事件相关的监听器。在我们的猜数字游戏示例中,按钮被点击后,事件处理线程会查找按钮的动作监听器列表。
4. 调用监听器方法 :找到相应的监听器后,事件处理线程会调用监听器的 actionPerformed 方法。在 GameFrame 类中,就是调用 actionPerformed 方法来处理按钮点击事件。
5. 处理事件逻辑 :在 actionPerformed 方法中,编写了处理事件的具体逻辑。例如,在猜数字游戏中,会比较用户输入的数字和随机生成的数字,根据比较结果进行相应的操作。
6. 更新界面 :根据事件处理的结果,更新图形用户界面。如猜对时禁用按钮和文本框,显示 “Yes!”;猜错时清空文本框,获取焦点并更新猜测次数显示。

下面是一个更详细的 mermaid 流程图,展示事件处理的详细流程:

graph LR
    A[用户操作(点击按钮等)] --> B[事件处理线程捕获事件]
    B --> C[查找事件监听器]
    C --> D{是否找到监听器}
    D -- 是 --> E[调用监听器的actionPerformed方法]
    D -- 否 --> F[事件忽略]
    E --> G[执行actionPerformed方法中的逻辑]
    G --> H{输入是否正确}
    H -- 是 --> I[禁用按钮和文本框,显示Yes!]
    H -- 否 --> J[清空文本框,获取焦点,增加猜测次数]
    I --> K[界面更新完成]
    J --> K
12. 接口的实际应用拓展

在实际编程中,接口的应用非常广泛。除了处理事件,接口还可以用于实现多态性和代码的模块化。例如,我们可以定义一个 Shape 接口,用于表示各种形状:

public interface Shape {
    double getArea();
    double getPerimeter();
}

然后,不同的形状类可以实现这个接口:

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double getArea() {
        return length * width;
    }

    @Override
    public double getPerimeter() {
        return 2 * (length + width);
    }
}

这样,我们可以通过接口类型来引用不同的形状对象,实现多态性:

public class ShapeTest {
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(3, 4);

        System.out.println("Circle area: " + circle.getArea());
        System.out.println("Rectangle area: " + rectangle.getArea());
    }
}
13. 包和访问修饰符的最佳实践

在使用包和访问修饰符时,遵循一些最佳实践可以提高代码的可维护性和安全性。以下是一些建议:
- 合理使用包 :将相关的类放在同一个包中,便于管理和组织代码。例如,将所有与绘图相关的类放在 com.burdbrain.drawings 包中。
- 使用公共类 :对于需要在多个地方引用的类,将其声明为 public 类。但要注意遵循目录结构和引用规则。
- 使用受保护的访问权限 :当希望外部团队或子类能够直接访问某些字段或方法时,使用 protected 访问权限。但要谨慎使用,避免过度暴露内部实现。
- 使用默认访问权限 :对于只在同一个包内使用的类和成员,使用默认访问权限,提高代码的封装性。

下面是一个包和访问修饰符使用的总结表格:
| 场景 | 建议的访问修饰符 |
| ---- | ---- |
| 公共类库 | public |
| 内部工具类 | 默认访问权限 |
| 可被子类扩展的类成员 | protected |
| 不希望外部访问的类成员 | private |

14. 多线程编程的注意事项

虽然 Java 程序默认是多线程的,但在多线程编程中需要注意一些问题,以避免出现线程安全问题。
- 共享资源的同步 :当多个线程访问共享资源时,可能会出现数据不一致的问题。可以使用 synchronized 关键字来保证线程安全。例如:

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
  • 避免死锁 :死锁是指两个或多个线程相互等待对方释放资源,导致程序无法继续执行。要避免死锁,需要合理设计线程的执行顺序和资源分配。
  • 线程的生命周期管理 :了解线程的生命周期,包括创建、启动、运行、阻塞和终止等状态,合理管理线程的生命周期可以提高程序的性能和稳定性。
15. this 关键字的更多应用场景

this 关键字除了在事件处理中代表实例本身外,还有其他应用场景。
- 区分成员变量和局部变量 :当方法中的局部变量和类的成员变量同名时,可以使用 this 关键字来区分。例如:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
  • 调用本类的其他构造方法 :在一个构造方法中,可以使用 this 关键字调用本类的其他构造方法。例如:
public class Rectangle {
    private int width;
    private int height;

    public Rectangle() {
        this(0, 0);
    }

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
}
16. 总结与回顾

通过以上内容的学习,我们深入了解了 Java 编程中的包、访问修饰符、事件处理、接口、线程执行和 this 关键字等重要概念。
- 包和访问修饰符帮助我们组织和管理代码,提高代码的可维护性和安全性。
- 事件处理和接口使我们能够实现交互式的图形用户界面,处理用户的各种操作。
- 多线程编程让 Java 程序能够同时处理多个任务,提高程序的性能。
- this 关键字在不同场景下有不同的用途,帮助我们更好地编写和理解代码。

在实际编程中,要灵活运用这些概念,根据具体需求选择合适的方法和技术,不断提高自己的编程能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值