目录
前言
1. 封装
1.1 什么是封装
1.2 访问修饰限定符
1.3 封装中的包
1.3.1 什么是包
1.3.2 包的导入
1.3.3 包的定义
1.3.4 Java 常见包
2.static 关键字
2.1 静态变量
2.2 静态方法
2.3 静态代码块
3. 代码块
3.1 普通代码块
3.2 构造代码块
3.3 静态代码块
3.4 代码执行顺序
4. 总结
在之前的学习中,我们已经探讨了面向对象编程(OOP)的核心思想,以及类和对象的基本概念。这些概念是现代软件应用程序构建的基石。然而,更进一步的理解需要我们深入研究面向对象编程的三大关键特性,即封装、继承和多态。这些这些特性不仅仅是理论概念,它们是实际编写高质量、可维护和可扩展代码的关键。在本期,我们将重点介绍封装这一重要特性。
封装(Encapsulation)是面向对象编程(OOP)的一个核心概念,它指的是将数据(成员变量/属性)和操作数据的方法(成员方法)封装在一个单元内部的能力。具体来说,封装有以下关键特点:
数据隐藏:封装通过将对象的内部状态(数据成员)隐藏起来,使其不能被直接访问或修改,而只能通过对象的公共接口来进行操作。这样可以有效地保护数据的完整性和安全性,防止未经授权的访问和不当修改。
公共接口:封装通过提供公共方法或函数,也称为访问器方法(getter)和修改器方法(setter),允许外部代码与对象进行交互。这些方法定义了对象的行为和如何访问其内部数据,同时隐藏了内部实现细节。
实现细节隐蔽:封装将对象的内部实现细节隐藏起来,使对象的使用者无需关心对象是如何存储和处理数据的。这有助于降低代码的复杂性,提高代码的可维护性,因为内部实现可以更改而不会影响外部代码。
数据封装:封装还可以通过限制对数据成员的直接访问,强制使用访问器和修改器方法来控制数据的读取和写入。这有助于在数据被访问或修改时执行额外的逻辑,例如验证数据的有效性或触发事件。
封装的主要目标是实现信息隐藏和代码隔离,使得对象能够以一种更加安全、可控和可维护的方式进行使用。它是面向对象编程的重要特性之一,有助于构建模块化、可重用和可扩展的软件系统。
例:
生活中有很多封装的概念。下面以一个简单的例子来说明:
汽车就是一个典型的封装示例。,汽车的引擎是一个对象,通过它我们可以更简易的了解封装的具体特征。
数据隐藏:汽车引擎的内部工作原理对于驾驶汽车的人来说是不可见的。我们不需要知道引擎的每个细节,例如它的每个活塞如何工作,点火系统的细节,或者燃烧室内的精确温度。这些细节都被隐藏在引擎的外部。
公共接口:汽车的引擎提供了一个公共接口,通常是通过启动按钮、油门和刹车等控制装置来访问。这些控制装置是我们与引擎进行交互的方式,而不是直接操作引擎的内部组件。
实现细节隐蔽:我们不需要了解引擎的内部实现细节,例如它是如何将燃料与空气混合并在燃烧室中点燃的。这一切都是引擎的内部工作,我们只需要知道如何使用控制装置来控制汽车。
数据封装:引擎内部可能有各种传感器来监测温度、油压和排放等数据,但这些数据不是公开的,而是通过仪表盘上的显示器来展示给驾驶者。这种封装方式确保了数据的安全性和可访问性。
总之,汽车引擎是一个生活中常见的封装示例。它能够形象的为我们演示如何隐藏复杂的内部细节,提供一个简单的公共接口供用户操作,同时确保数据的安全性和可访问性。
在Java中,封装是通过类和访问权限这两个关键概念来实现的。类允许我们将数据以及操作数据的方法组合在一起,这种方式更符合人们对事物的自然认知。而访问权限则用于控制哪些方法或字段可以在类的外部直接使用。
Java中有四种主要的访问修饰符(Access Modifiers),用于管理类和类的成员的可见性和访问权限。这些修饰符提供了不同的访问级别,从最广泛到最受限制分别是:public、protected、default(默认,无修饰符),和 private。
public:
protected:
default(默认无修饰符):
private:
范围 | private | 默认 | protected | public |
---|---|---|---|---|
同一类中 | ✓ | ✓ | ✓ | ✓ |
同一包中 | ✕ | ✓ | ✓ | ✓ |
不同包中子类 | ✕ | ✕ | ✓ | ✓ |
不同包中非子类 | ✕ | ✕ | ✕ | ✓ |
根据访问修饰限定符的不同权限我们得知,在Java中,封装不仅限于类内部的数据和方法的组织,还涉及包(Package)的概念。包也是一种封装机制。包用于组织和管理相关类和接口,将它们分组在一起,提供了更高级别的封装和组织代码的方式。
在Java中,包可以类比于电脑中的文件夹或目录结构。它们的作用是为了更有效地组织和管理类,就像我们在电脑上将文件按照特定的分类放在文件夹中一样。包可以理解为一种目录,它将多个相关的类和接口收集在一起,使得我们可以更清晰地组织和访问代码。
举例来说,就像为了更好地管理电脑上的音乐,我们可以创建一个文件夹,并在其中按照艺术家、专辑或音乐类型等属性对音乐进行分类一样,包可以让我们在软件开发中更好地组织和分类代码,使代码更易于维护和管理。
另外,在同一个工程中不同的包中允许存在相同名称的类。
Java 提供了大量现成的类供开发者使用,比如Date类、ArrayList类等等。这些类里提供了丰富的方法,当我们需要使用到这些方法时就需要用到 import 关键字。
在Java中,我们可以使用 import 关键字来导入其他包中的类,这使得我们可以在当前类中使用这些类。例如,要使用Date类,我们可以使用 import java.util.Date ; 导入 java.util 包中的 Date 类。这样,我们就可以在我们的代码中创建和操作Date对象,而不必从头开始编写日期处理的功能,虽然导入操作本身并不是封装,但它对代码的可重用性有着重要作用。
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
//得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果我们需要用到 java.util 中的其他类, 可以使用 import java.util. ⁎ 来导入;
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
//得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是使用 “ import 包 . ⁎ ” 来导入类名时,如果导入的多个包中包含了同名的类,使用通配符 ⁎ 导入可能会导致命名冲突。这会使编译器无法确定要使用哪个类而报错;
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error: java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
此时需要使用完整的包名来消除歧义。
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
总之,使用包是一种组织和管理Java代码的重要方式,可以确保代码的唯一性、可维护性和可读性。遵循这些基本规则有助于更好地组织和管理自定义包,并使代码更具结构化和可维护性。
以下是一些常见的Java标准库包:
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包。
7. java.math:包含用于执行大数运算的类,如BigInteger和BigDecimal,用于处理大数字和高精度的数学运算。
static 是Java中的一个关键字,它可以用于不同的上下文中,具有不同的含义和用途:
静态变量(Static Variables): 使用 static 关键字声明的变量称为静态变量,也叫类变量。静态变量属于类而不是实例,因此它们对于类的所有实例都是共享的。
静态成员变量的特性:
public class MyClass {
static int count = 0; // 静态变量,所有实例共享
String name;
int age;
public MyClass() {
}
public MyClass(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
// 静态成员变量可以直接通过类名访问
System.out.println(MyClass.count);
MyClass c1 = new MyClass();
MyClass c2 = new MyClass("brz", 8);
MyClass c3 = new MyClass("wrx", 12);
// 也可以通过对象访问:但是classRoom是三个对象共享的
System.out.println(c1.count);
System.out.println(c2.count);
System.out.println(c3.count);
}
}
最终输出结果均为:0
静态方法(Static Methods): 使用 static 关键字声明的方法称为静态方法。静态方法不依赖于类的实例,可以直接通过类名调用,而不需要创建对象。静态方法通常用于执行与类相关的操作,而不涉及实例特定的数据。例如:
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}
调用静态方法:int result = MathUtils.add(3, 5);
注意:
静态方法中不能访问任何非静态成员变量: 静态方法不依赖于类的实例,因此无法访问非静态成员变量,因为非静态成员变量属于类的实例,而静态方法不关联到任何特定的实例。
静态方法中不能调用任何非静态方法: 这是因为非静态方法需要通过对象的引用(
this
)来调用,而静态方法没有与特定对象关联,因此无法传递this
引用。非静态方法是与对象的状态相关的,而静态方法是与类相关的。静态方法无法重写,不能用来实现多态: 静态方法与类相关,而不是与对象相关。因为多态是基于对象的动态绑定,所以只有实例方法(非静态方法)才能被重写和用于实现多态。静态方法在子类中可以被隐藏,但不能被重写。(后续多态介绍)
静态代码块(Static Initialization Blocks): 静态代码块是一个在类加载时执行的代码块,通常用于初始化静态变量或执行一次性的初始化操作。静态代码块用 static 关键字和大括号包围起来,如下所示:
public class MyClass {
static {
// 静态代码块中的初始化操作
}
}
当涉及到代码块时,我们通常指的是在程序中用大括号 { } 包围的代码区域。代码块根据其定义的位置和关键字可以分为不同类型,下面进行详细介绍。
代码块就是在程序中用大括号 { } 包围的代码区域。根据代码块定义的位置以及关键字,又可分为以下四种:
普通代码块是在方法内部定义的,它用于创建局部作用域,其中的变量在块内部有效。普通代码块通常用于控制变量的作用范围和生命周期。
public static void main(String[] args) {
{ //直接使用{}定义,普通方法块
int x = 5 ; // 这个变量只在块内部有效
System.out.println("x1 * 2 = " + x);
}
int x = 10 ;
System.out.println("x2 * 2 = " +x);
}
输出:
x1*2 = 10
x2*2 = 20
构造代码块,也叫实例代码块,用于在每次创建对象时执行一些初始化操作。与普通代码块不同,构造块定义在类中也没有关键字,它们会在每次对象创建时都执行一次。
public class Subaru{
private String name;
private int age;
public Subaru() {
System.out.println("Driven by What’s Inside");
}
//实例代码块
{
this.name = "impreza";
this.age = 12;
System.out.println("Confidence in Motion");
}
public void show(){
System.out.println("name: " + name + " age: " + age);
}
}
public class Main {
public static void main(String[] args) {
Subaru sti = new Subaru();
stu.show();
}
}
运行结果:
Confidence in Motion
Driven by What’s Inside
name: impreza age: 12
使用 static 定义的代码块称为静态代码块。只在类加载时执行一次,通常用于初始化静态变量或执行一次性的初始化操作。
public class Subaru{
private String name;
private int age;
public Subaru() {
System.out.println("Driven by What’s Inside");
}
//实例代码块
{
this.name = "impreza";
this.age = 12;
System.out.println("Confidence in Motion");
}
//静态代码块
static{
String producer = Subaru;
System.out.println("FHI——SUBARU");
}
public void show(){
System.out.println("name: " + name + " age: " + age);
}
}
public class Main {
public static void main(String[] args) {
Subaru sti = new Subaru();
stu.show();
}
}
运行结果:
FHI——SUBARU
Confidence in Motion
Driven by What’s Inside
name: impreza age: 12
注意:
静态优先,同类中按定义位置先后顺序执行。
成员变量 → 静态代码块 → 静态方法 → 构造代码块 → 构造方法 → 成员方法
首先,执行成员变量的初始化,按照定义位置先后顺序执行。
接下来,执行静态代码块,这是在类加载时执行的,只会执行一次。
然后,执行静态方法。静态方法是可以被直接调用的,所以可以在任何时候执行。
构造代码块会在每次创建类的对象时执行,先于构造方法执行。
构造方法是在对象创建时执行的,可以有多个构造方法,根据实例化对象时的构造方法不同而执行不同的构造方法。
在本文中,我们深入研究了封装这一关键的面向对象编程概念,以及与之相关的访问修饰符、包的概念,以及静态关键字的用法。封装是编写清晰、安全、可维护代码的基础,它允许我们将数据和操作封装在一起,隐藏内部细节,提供一个简单的接口供其他代码使用。
我们还了解了不同类型的代码块,如普通代码块、构造块和静态块,以及它们在程序中的执行顺序。这些概念有助于我们更好地组织和控制代码的行为。