Java 中的封装

本文详细介绍了封装在面向对象编程中的核心地位,包括封装的概念、访问修饰符的应用、包的组织与管理,以及static关键字在静态变量、静态方法和静态代码块中的作用。此外,还讨论了代码块的不同类型及其执行顺序。
摘要由CSDN通过智能技术生成

目录

前言

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)的核心思想,以及类和对象的基本概念。这些概念是现代软件应用程序构建的基石。然而,更进一步的理解需要我们深入研究面向对象编程的三大关键特性,即封装继承多态。这些这些特性不仅仅是理论概念,它们是实际编写高质量、可维护和可扩展代码的关键。在本期,我们将重点介绍封装这一重要特性。


1. 封装

  1.1 什么是封装

        封装(Encapsulation)是面向对象编程(OOP)的一个核心概念,它指的是将数据(成员变量/属性)和操作数据的方法(成员方法)封装在一个单元内部的能力。具体来说,封装有以下关键特点:

  1. 数据隐藏:封装通过将对象的内部状态(数据成员)隐藏起来,使其不能被直接访问或修改,而只能通过对象的公共接口来进行操作。这样可以有效地保护数据的完整性和安全性,防止未经授权的访问和不当修改。

  2. 公共接口:封装通过提供公共方法或函数,也称为访问器方法(getter)和修改器方法(setter),允许外部代码与对象进行交互。这些方法定义了对象的行为和如何访问其内部数据,同时隐藏了内部实现细节。

  3. 实现细节隐蔽:封装将对象的内部实现细节隐藏起来,使对象的使用者无需关心对象是如何存储和处理数据的。这有助于降低代码的复杂性,提高代码的可维护性,因为内部实现可以更改而不会影响外部代码。

  4. 数据封装:封装还可以通过限制对数据成员的直接访问,强制使用访问器和修改器方法来控制数据的读取和写入。这有助于在数据被访问或修改时执行额外的逻辑,例如验证数据的有效性或触发事件。

        封装的主要目标是实现信息隐藏和代码隔离,使得对象能够以一种更加安全、可控和可维护的方式进行使用。它是面向对象编程的重要特性之一,有助于构建模块化、可重用和可扩展的软件系统。

例:        

        生活中有很多封装的概念。下面以一个简单的例子来说明:

        汽车就是一个典型的封装示例。,汽车的引擎是一个对象,通过它我们可以更简易的了解封装的具体特征。

  1. 数据隐藏:汽车引擎的内部工作原理对于驾驶汽车的人来说是不可见的。我们不需要知道引擎的每个细节,例如它的每个活塞如何工作,点火系统的细节,或者燃烧室内的精确温度。这些细节都被隐藏在引擎的外部。

  2. 公共接口:汽车的引擎提供了一个公共接口,通常是通过启动按钮、油门和刹车等控制装置来访问。这些控制装置是我们与引擎进行交互的方式,而不是直接操作引擎的内部组件。

  3. 实现细节隐蔽:我们不需要了解引擎的内部实现细节,例如它是如何将燃料与空气混合并在燃烧室中点燃的。这一切都是引擎的内部工作,我们只需要知道如何使用控制装置来控制汽车。

  4. 数据封装:引擎内部可能有各种传感器来监测温度、油压和排放等数据,但这些数据不是公开的,而是通过仪表盘上的显示器来展示给驾驶者。这种封装方式确保了数据的安全性和可访问性。

        总之,汽车引擎是一个生活中常见的封装示例。它能够形象的为我们演示如何隐藏复杂的内部细节,提供一个简单的公共接口供用户操作,同时确保数据的安全性和可访问性。


  1.2  访问修饰限定符

        在Java中,封装是通过类和访问权限这两个关键概念来实现的。类允许我们将数据以及操作数据的方法组合在一起,这种方式更符合人们对事物的自然认知。而访问权限则用于控制哪些方法或字段可以在类的外部直接使用。

        Java中有四种主要的访问修饰符(Access Modifiers),用于管理类和类的成员的可见性和访问权限。这些修饰符提供了不同的访问级别,从最广泛到最受限制分别是:public、protected、default(默认,无修饰符),和 private。

  1. public

    • 具有最高的访问级别,可以从任何类访问。
    • 一个类、字段、方法或内部类被声明为public时,它们对所有其他类都是可见的。
    • 例如,如果一个类被声明为public,则可以从任何其他类创建该类的对象。
  2. protected

    • protected修饰符指定的成员对于同一包中的其他类和所有子类都是可见的。
    • 在不同包中的类只能访问其他类的protected成员,如果它们是该类的子类。
    • 通常用于实现继承相关的逻辑,允许子类访问父类的受保护成员。
  3. default(默认无修饰符)

    • 如果没有明确指定访问修饰符(即没有public、protected、private修饰符),则默认访问级别是包私有(package-private)。
    • 在同一包内的其他类可以访问包私有成员,但在不同包中的类无法访问它们。
    • 这个访问级别通常用于隐藏实现细节,以避免外部类直接访问。
  4. private

    • 具有最低的访问级别,只能在声明它们的类内部访问。
    • private修饰符通常用于隐藏类的内部实现细节,确保外部类无法直接访问这些成员。
访问权限
范围 private     默认    protectedpublic  
同一类中
同一包中
不同包中子类
不同包中非子类

  1.3 封装中的包

        根据访问修饰限定符的不同权限我们得知,在Java中,封装不仅限于类内部的数据和方法的组织,还涉及包(Package)的概念。包也是一种封装机制。包用于组织和管理相关类和接口,将它们分组在一起,提供了更高级别的封装和组织代码的方式。

    1.3.1 什么是包        

        在Java中,包可以类比于电脑中的文件夹或目录结构。它们的作用是为了更有效地组织和管理类,就像我们在电脑上将文件按照特定的分类放在文件夹中一样。包可以理解为一种目录,它将多个相关的类和接口收集在一起,使得我们可以更清晰地组织和访问代码。

        举例来说,就像为了更好地管理电脑上的音乐,我们可以创建一个文件夹,并在其中按照艺术家、专辑或音乐类型等属性对音乐进行分类一样,包可以让我们在软件开发中更好地组织和分类代码,使代码更易于维护和管理。
 

        另外,在同一个工程中不同的包中允许存在相同名称的类。


    1.3.2 包的导入

      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());
    }
}

    1.3.3 包的定义

  1. 包语句: 在Java代码文件的顶部,使用 package 语句来指定该代码所属的包。例如:package com.example.mypackage;
  2. 唯一性和命名规则: 包名应尽量保持唯一,通常使用公司的域名的颠倒形式来确保唯一性,如 com.example.myproject。包名应使用小写字母,并可以包含数字和下划线,但不应包含特殊字符或空格。
  3. 代码路径: 包名应与代码存储路径相匹配。例如,如果你的包名是 com.example.mypackage,则代码文件应该存储在相应的目录结构中,例如 com/example/mypackage。
  4. 默认包: 如果一个类没有指定 package 语句,它将被放置在默认包 src 中。然而,最佳实践是始终为你的类指定一个明确的包,以避免潜在的命名冲突和组织问题。

        总之,使用包是一种组织和管理Java代码的重要方式,可以确保代码的唯一性、可维护性和可读性。遵循这些基本规则有助于更好地组织和管理自定义包,并使代码更具结构化和可维护性。


    1.3.4 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,用于处理大数字和高精度的数学运算。


2.static 关键字

static 是Java中的一个关键字,它可以用于不同的上下文中,具有不同的含义和用途:

  2.1 静态变量

        静态变量(Static Variables): 使用 static 关键字声明的变量称为静态变量,也叫类变量。静态变量属于类而不是实例,因此它们对于类的所有实例都是共享的。

静态成员变量的特性:

  1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。
  2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问,因为它们属于类而不是对象。
  3. 类变量存储在方法区(Method Area)中,而不是对象的堆内存中。方法区是用于存储类的结构信息、静态变量等的内存区域。
  4. 生命周期伴随类的一生,即随着类的加载而创建,在整个程序运行期间存在,随着类的卸载而销毁。
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

  2.2 静态方法

        静态方法(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 引用。非静态方法是与对象的状态相关的,而静态方法是与类相关的。

  • 静态方法无法重写,不能用来实现多态: 静态方法与类相关,而不是与对象相关。因为多态是基于对象的动态绑定,所以只有实例方法(非静态方法)才能被重写和用于实现多态。静态方法在子类中可以被隐藏,但不能被重写。(后续多态介绍)


  2.3 静态代码块

        静态代码块(Static Initialization Blocks): 静态代码块是一个在类加载时执行的代码块,通常用于初始化静态变量或执行一次性的初始化操作。静态代码块用 static 关键字和大括号包围起来,如下所示:

public class MyClass {
    static {
        // 静态代码块中的初始化操作
    }
}

        当涉及到代码块时,我们通常指的是在程序中用大括号 { } 包围的代码区域。代码块根据其定义的位置和关键字可以分为不同类型,下面进行详细介绍。


3. 代码块

        代码块就是在程序中用大括号 { } 包围的代码区域。根据代码块定义的位置以及关键字,又可分为以下四种:

  • 普通代码块
  • 构造块
  • 静态块
  • 同步代码块(暂时不解释,后期介绍)

  3.1 普通代码块

        普通代码块是在方法内部定义的,它用于创建局部作用域,其中的变量在块内部有效。普通代码块通常用于控制变量的作用范围和生命周期。

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

  3.2 构造代码块

        构造代码块,也叫实例代码块,用于在每次创建对象时执行一些初始化操作。与普通代码块不同,构造块定义在类中也没有关键字,它们会在每次对象创建时都执行一次。

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

  3.3 静态代码块

 使用 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

注意:

  • 静态代码块只会执行一次,与对象多少无关;
  • 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的;
  • 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并);
  • 实例代码块只有在创建对象时才会执行;

  3.4 代码执行顺序 

        静态优先,同类中按定义位置先后顺序执行。

        成员变量 → 静态代码块 → 静态方法 → 构造代码块 → 构造方法 → 成员方法

  1. 首先,执行成员变量的初始化,按照定义位置先后顺序执行。

  2. 接下来,执行静态代码块,这是在类加载时执行的,只会执行一次。

  3. 然后,执行静态方法。静态方法是可以被直接调用的,所以可以在任何时候执行。

  4. 构造代码块会在每次创建类的对象时执行,先于构造方法执行。

  5. 构造方法是在对象创建时执行的,可以有多个构造方法,根据实例化对象时的构造方法不同而执行不同的构造方法。

  6. 最后,执行成员方法。成员方法是可以被对象调用的。

4. 总结

        在本文中,我们深入研究了封装这一关键的面向对象编程概念,以及与之相关的访问修饰符、包的概念,以及静态关键字的用法。封装是编写清晰、安全、可维护代码的基础,它允许我们将数据和操作封装在一起,隐藏内部细节,提供一个简单的接口供其他代码使用。

        我们还了解了不同类型的代码块,如普通代码块、构造块和静态块,以及它们在程序中的执行顺序。这些概念有助于我们更好地组织和控制代码的行为。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值