【Java篇】一气化三清:类的实例化与封装的智慧之道

【Java篇】一气化三清:类的实例化与封装的智慧之道

文章目录

类和对象(中)

💬 欢迎讨论:如果你对本篇内容有任何疑问或想深入探讨,欢迎在评论区留言交流!

👍 点赞、收藏与分享:觉得内容有帮助就请点赞、收藏并分享给更多学习Java的小伙伴!

🚀 继续学习之旅:本篇文章将详细讲解对象的构造与初始化、封装、static成员、代码块以及内部类等高级内容,让你进一步掌握面向对象编程的精髓。

五、对象的构造及初始化

5.1 如何初始化对象

通过前面知识点的学习我们知道,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。

publicstaticvoidmain(String[] args){int a;System.out.println(a);}// Error:(26, 28) java: 可能尚未初始化变量a

要让上述代码通过编译,非常简单,只需在正式使用变量之前给它设置初始值即可。

publicstaticvoidmain(String[] args){Date d =newDate(); d.printDate(); d.setDate(2021,6,9); d.printDate();}// 代码可以正常通过编译

如果是对象,就需要调用之前写的 setDate 方法将具体的日期设置到对象中。

通过上述例子我们发现两个问题:

  • 问题1:每次对象创建好后调用 setDate 方法设置具体日期显得比较麻烦,那么对象该如何初始化?
  • 问题2:局部变量必须初始化才能使用,而字段声明之后没有给值依然可以使用,这是因为字段具有默认初始值。

为了解决问题1,Java引入了 构造方法,使得对象在创建时就能完成初始化操作。


5.2 构造方法

构造方法(也称为构造器)是一种特殊的成员方法,其主要作用是初始化对象。

5.2.1 构造方法的概念
publicclassDate{publicint year;publicint month;publicint day;// 构造方法:// 名字与类名相同,没有返回值类型,设置为void也不行// 一般情况下使用public修饰// 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次publicDate(int year,int month,int day){this.year = year;this.month = month;this.day = day;System.out.println("Date(int,int,int)方法被调用了");}publicvoidprintDate(){System.out.println(year +"-"+ month +"-"+ day);}publicstaticvoidmain(String[] args){// 此处创建了一个Date类型的对象,并没有显式调用构造方法Date d =newDate(2021,6,9);// 输出Date(int,int,int)方法被调用了 d.printDate();// 2021-6-9}}

构造方法的特点是:

  • 名字必须与类名相同,且没有返回值类型(连void都不行)。
  • 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次。
    注意:构造方法的作用是对对象中的成员进行初始化,并不负责给对象开辟内存空间。
5.2.2 构造方法的特性

构造方法具有如下特性:

  1. 名字必须与类名完全相同
  2. 没有返回值类型,即使设置为void也不行
  3. 创建对象时由编译器自动调用,且在对象生命周期内只调用一次(就像人的出生,每个人只能出生一次)
  4. 支持重载:同一个类中可以定义多个构造方法,只要参数列表不同即可

示例代码1:带参构造方法

publicclassDate{publicint year;publicint month;publicint day;// 构造方法:名字与类名相同,没有返回值类型,使用public修饰// 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次publicDate(int year,int month,int day){this.year = year;this.month = month;this.day = day;System.out.println("Date(int, int, int)方法被调用了");}publicvoidprintDate(){System.out.println(year +"-"+ month +"-"+ day);}publicstaticvoidmain(String[] args){// 此处创建了一个Date类型的对象,并没有显式调用构造方法Date d =newDate(2021,6,9);// 输出:Date(int, int, int)方法被调用了 d.printDate();// 输出:2021-6-9}}

示例代码2:无参构造方法

publicclassDate{publicint year;publicint month;publicint day;// 无参构造方法:给成员变量设置默认初始值publicDate(){this.year =1900;this.month =1;this.day =1;}}

上述两个构造方法名字相同但参数列表不同,构成了方法的重载。

  1. 如果用户没有显式定义构造方法,编译器会生成一个默认的无参构造方法。

注意:一旦用户显式定义了构造方法,编译器就不会再生成默认构造方法了

示例代码3:仅定义带参构造方法时默认构造方法不会生成

// 带有三个参数的构造方法publicDate(int year,int month,int day){this.year = year;this.month = month;this.day = day;}publicvoidprintDate(){System.out.println(year +"-"+ month +"-"+ day);}publicstaticvoidmain(String[] args){Date d =newDate();// 编译期报错,因为没有无参构造方法 d.printDate();}

示例代码4:只有无参构造方法的情况

publicclassDate{publicint year;publicint month;publicint day;publicvoidprintDate(){System.out.println(year +"-"+ month +"-"+ day);}publicstaticvoidmain(String[] args){Date d =newDate(); d.printDate();}}

示例代码5:只有带参构造方法的情况

publicclassDate{publicint year;publicint month;publicint day;publicDate(int year,int month,int day){this.year = year;this.month = month;this.day = day;}}
  1. 构造方法中可以通过 this(…) 调用其他构造方法来简化代码。
    注意:this(…) 必须是构造方法中的第一条语句,否则编译器会报错。

示例代码6:正确使用this(…)实现构造器链

publicclassDate{publicint year;publicint month;publicint day;// 无参构造方法 -- 内部调用带参构造方法实现初始化// 注意:this(1900, 1, 1);必须是构造方法中的第一条语句publicDate(){// System.out.println(year); // 若取消注释则编译会失败this(1900,1,1);// 以下赋值代码被省略,因为已在带参构造方法中完成初始化// this.year = 1900;// this.month = 1;// this.day = 1;}// 带有三个参数的构造方法publicDate(int year,int month,int day){this.year = year;this.month = month;this.day = day;}}
注意:构造方法中的 this(…) 调用不能形成循环,否则会导致编译错误。
publicDate(){this(1900,1,1);}publicDate(int year,int month,int day){this();}/* 无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用 编译报错:Error:(19, 12) java: 递归构造器调用 */
  1. 在大多数情况下,我们使用 public 来修饰构造方法,但在特殊场景下(如实现单例模式)可能会使用 private 修饰构造方法。

5.3 默认初始化

在上文中提到的第二个问题:为什么局部变量在使用前必须初始化,而成员变量可以不初始化?

要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:

Date d =newDate(2021,6,9);

在程序员看来只是一句简单的语句,但 JVM 层面需要做好多事情。下面简单介绍下:

  1. 检测对象对应的类是否被加载,如果没有则加载
  2. 为对象分配内存空间并先默认初始化
  3. 处理并执行类中的 init 方法
  4. 初始化分配好的空间 (说明:多个线程同时申请资源,JVM 要保证分配给对象的空间内干净。)

即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:Java中的 成员变量 会由 JVM 自动赋予默认值(如int类型为0,boolean类型为false等),而局部变量则需要显式初始化才能使用。

Java为不同的数据类型提供了默认值,具体如下所示:

数据类型默认值
byte0
char‘\u0000’
short0
int0
long0L
booleanfalse
float0.0f
double0.0d
referencenull

这些默认值确保了即使对象的成员变量没有显式初始化,也不会发生错误,成员变量会有一个初始的稳定状态。

  1. 设置对象头信息(关于对象内存模型后面会介绍)
  2. 调用构造方法,给对象中各个成员赋值

5.4 就地初始化

就地初始化是指在成员变量声明时直接为它们赋初值。这种方法在代码的简洁性上具有优势,可以避免每次创建对象时重复设置成员变量的值。

代码示例:

publicclassDate{publicint year =1900;// 就地初始化publicint month =1;// 就地初始化publicint day =1;// 就地初始化publicDate(){// 构造方法在此处不需要再次初始化year、month、day,它们已经有默认值}publicvoidprintDate(){System.out.println(year +"-"+ month +"-"+ day);}publicstaticvoidmain(String[] args){Date d =newDate(); d.printDate();// 输出:1900-1-1}}

在这个例子中,yearmonthday 的值在声明时就被初始化为1900、1和1,确保了每次创建对象时这些值已经存在。


六、封装

6.1 封装的概念

封装是面向对象编程的三大特性之一,其核心思想是将数据和操作数据的方法结合在一起,并对外隐藏实现细节。
例如,电脑作为一个复杂的设备,用户只需通过开关、键盘和鼠标等接口进行交互,而不必关心内部CPU、显卡等工作原理。

在这里插入图片描述

简单来说就是套壳屏蔽细节。


6.2 访问限定符

访问修饰符说明

  • public:可以理解为一个人的外部接口,能被外部访问。
  • protected:主要是给继承使用,子类继承后就能访问到。
  • default(不写修饰符时即为默认访问修饰符):对于同一包内的类可见,不同包则不可见。
  • private:只能在当前类中访问。

(这部分要介绍完继承后才能完全理解)

在这里插入图片描述
【说明】protected 主要是给继承使用;default 只能给同一个包内使用;按照自己的理解去记忆。

示例代码:

publicclassComputer{privateString cpu;privateString brand;privateString memory;privateString screen;publicComputer(String brand,String cpu,String memory,String screen){this.brand = brand;this.cpu = cpu;this.memory = memory;this.screen = screen;}publicvoidboot(){System.out.println("开机");}publicvoidshutDown(){System.out.println("关机");}}
publicclassTestComputer{publicstaticvoidmain(String[] args){Computer c =newComputer("华为","i9","16G","4K"); c.boot(); c.shutDown();}}
注意:一般情况下,成员变量通常设置为 private,成员方法设置为 public

6.3 封装扩展之包

6.3.1 包的概念

包(Package)用于对类进行分组管理,有助于解决类名冲突并提高代码的组织性。例如,将相似功能的类归为同一包。

为了更好的管理类,把多个类收集在一起成为一组,称为软件包

在这里插入图片描述
6.3.3导入包

在Java中,如果我们需要使用不在默认 java.lang 包中的类或接口,就需要使用 import 关键字来导入。
例如,我们想使用 java.util.Date 这个类,就可以这样做:

importjava.util.Date;publicclassTestImport{publicstaticvoidmain(String[] args){Date d =newDate();System.out.println(d);}}

这样就可以正常创建 Date 对象并使用。


6.3.3全类名

当不同包中存在同名的类时(如 java.util.Datejava.sql.Date 都叫 Date),可能会引发冲突。
这时,可以使用 全类名(Fully Qualified Name)来指定使用哪个类:

publicclassTestFullName{publicstaticvoidmain(String[] args){// 使用全类名来区分两个Date类java.util.Date d1 =newjava.util.Date();java.sql.Date d2 =newjava.sql.Date(System.currentTimeMillis());System.out.println(d1);System.out.println(d2);}}

通过在创建对象时加上包名,就可以区分来自 java.utiljava.sqlDate 类。


6.3.4 静态导入

从 Java 5 开始,支持使用 静态导入import static)的方式将某个类中的 静态成员(常量或方法) 导入到当前类中,从而在调用时可以省略类名。

示例代码:

importstaticjava.lang.Math.PI;importstaticjava.lang.Math.random;publicclassTestStaticImport{publicstaticvoidmain(String[] args){System.out.println(PI);// 直接使用PI常量System.out.println(random());// 直接使用random()方法}}

如果不使用静态导入,则需要写成:

System.out.println(Math.PI);System.out.println(Math.random());

6.3.5 IDE工具中的包结构

当我们使用 IntelliJ IDEA 或 Eclipse 等 IDE 工具时,会在项目结构中直观地看到包名与文件夹一一对应。

  • 包名一般使用 小写 的域名反写形式(如 com.example.project),在 IDE 中会对应层级文件夹结构。
  • 在同一个包下,可以放置多个类文件,便于组织与管理。

在实际开发中,合理划分包结构能让项目更易于维护和理解。


6.3.6 包的访问权限控制举例

Computer类位于com.bit.demo1包中,TestComputer位于com.bit.demo2包中:

packagecom.bit.demo1;publicclassComputer{privateString cpu;// cpuprivateString memory;// 内存publicString screen;// 屏幕String brand;// 品牌publicComputer(String brand,String cpu,String memory,String screen){this.brand = brand;this.cpu = cpu;this.memory = memory;this.screen = screen;}publicvoidPowerOff(){System.out.println("关机~~~");}publicvoidSurfInternet(){System.out.println("上网~~~");}}
////////////////////////////////////packagecom.bit.demo2;importcom.bit.demo1.Computer;publicclassTestComputer{publicstaticvoidmain(String[] args){Computer p =newComputer("HW","i7","8G","13*14");System.out.println(p.screen);// 公有属性,可以被其他包访问// System.out.println(p.cpu); // 私有属性,不能被其他包访问// System.out.println(p.brand); // brand是default,不允许被其他包中的类访问}}

注意:
如果去掉前面的Computer类中的public修饰符,代码也会编译失败。


6.3.7 常见的包

  1. java.lang:系统常用基础类(String,Object),此包从JDK1.1后自动导入。
  2. java.lang.reflect:Java反射机制包;
  3. java.net:进行网络编程开发包;
  4. java.sql:进行数据库开发的包;
  5. javax.util:Java提供的工具程序包(集合类等)非常重要;
  6. javalio.io:编程程序包。

注意事项: import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要.import 只是为了写代码的时候更方便.

区别如下:

  • Java 的 import
    仅在编译时为你提供类或接口的简写路径,使你可以直接使用类名而不用写出完整的包名。实际上,编译器会在编译过程中通过类路径(classpath)去查找相应的类文件,而不会把代码插入到当前文件中。
  • C/C++ 的 #include
    预处理器会在编译之前将头文件的内容直接拷贝进源代码文件中,这种方式实际上是将文件的内容“粘贴”到包含它的文件里。

因此,Java 的 import 只是一种简化引用的机制,并不涉及代码的复制。


七、总结与展望

在本篇文章中,我们围绕对象的构造与初始化、封装、包的管理以及访问权限等内容展开了详细讲解,帮助大家深入理解Java面向对象编程的核心概念。

7.1 总结

  • 对象的构造与初始化
    • 构造方法:通过构造方法,我们可以在创建对象时立即为其成员赋值,保证对象在使用前处于有效状态。
    • 构造器重载与this调用:支持多个构造方法以及构造器链,让对象初始化更加灵活和简洁。
    • 默认初始化与就地初始化:成员变量会自动获得默认值,同时也可以在声明时直接赋值,避免重复代码。
  • 封装
    • 核心思想:将数据与操作数据的方法绑定在一起,通过访问限定符(public、private、protected、default)隐藏内部实现细节。
    • 访问控制:合理使用访问修饰符保护数据安全,提供公开接口供外部使用,增强了程序的健壮性和安全性。
  • 包的管理与导入
    • 包的概念:通过将相关类归为一组,实现代码的模块化管理和命名空间隔离,避免类名冲突。
    • import语句:在编译时提供类名简写路径,与C/C++的#include机制不同,import不进行代码复制,仅起到标识和简化引用的作用。

7.2 总结

  • 深入static成员
    在后续的内容中,我们将更详细地探讨static关键字的使用,包括静态变量、静态方法及其在类中的意义,帮助大家更好地理解类级别的共享特性。
  • 代码块与初始化块
    将介绍类中的初始化块和静态代码块,讨论它们在对象创建过程中的执行顺序和作用,为进一步掌握对象生命周期奠定基础。
  • 内部类与匿名类
    内部类是一种特殊的类定义方式,它能够更紧密地绑定外部类的成员,将在未来篇章中深入剖析其用法和设计思想。
  • 面向对象的设计原则
    随着对类和对象理解的深入,我们也将探讨更多面向对象设计的原则和模式,帮助大家构建更健壮、可维护的Java应用程序。

本篇文章为大家构建了坚实的面向对象编程基础,希望通过系统的总结和展望,能够激发你对Java编程更深层次的兴趣。欢迎在评论区分享你的见解或提问,共同进步!


以上就是关于【Java篇】一气化三清:类的实例化与封装的智慧之道内存中的桥梁:Java数组与引用的灵动操作内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

Read more

【基础算法总结】哈希表/set/map篇

【基础算法总结】哈希表/set/map篇

目录 * 一,哈希表简介 * 二,算法原理和代码实现 * 1.两数之和 * 349.两个数组的交集 * 面试题01.02.判断是否互为字符重排 * 217.存在重复元素 * 219.存在重复元素II * 692.前k个高频单词 * 45.字母异位词分组 * 三,算法总结 一,哈希表简介 哈希思想是算法中一个十分重要的思想,体现的是一种映射关系,而哈希表就是基于哈希思想实现的存储数据的容器。哈希表的作用是快速查找某个元素,时间复杂度为O(1),时间复杂度为O(n)。 使用哈希一般有两种方式: (1)STL中的 unordered系列容器。 (2)用数组模拟简易哈希表。这种情况一般用于处理字符串中的"字符"或是当数据范围很小的时候使用。 下面将通过若干道题目进一步体会哈希表的使用。 二,算法原理和代码实现 1.两数之和 算法原理: (1)

By Ne0inhk
数据结构—顺序表

数据结构—顺序表

数据结构—顺序表 * 线性表 * 顺序表 * 概念与结构 * 顺序表和数组区别 * 分类 * 静态顺序表 * 动态顺序表 * 动态顺序表模拟实现 * 定义动态顺序表结构 * 顺序表初始化 * 顺序表销毁 * 顺序表打印 * 顺序表动态扩容 * 尾插 * 头插 * 尾删 * 头删 * 查找 * 指定位置之前插入 * 删除pos位置的数据 * 竞赛中的静态顺序表 * 静态申请数组 * 封装静态顺序表 * 动态顺序表--vector * 创建vector * size / empty * begin / end * push_back / pop_back * front / back * resize * clear * insert / erase * 仓库—代码总结 线性表 线性表(linear list)是

By Ne0inhk
Flutter 组件 vnlunar 适配鸿蒙 HarmonyOS 实战:高精度农历算法,构建民俗文化日期与节气治理架构

Flutter 组件 vnlunar 适配鸿蒙 HarmonyOS 实战:高精度农历算法,构建民俗文化日期与节气治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 vnlunar 适配鸿蒙 HarmonyOS 实战:高精度农历算法,构建民俗文化日期与节气治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全球化部署、涉及多语言本地化(L10n)及深层文化特性适配的背景下,如何实现准确的阴阳历(农历)转换、二十四节气计算及民俗节日提醒,已成为提升应用“人文温度”与本地化竞争力的核心要素。在鸿蒙设备这类强调分布式时间同步与低功耗常驻显示(AOD)的环境下,如果应用依然依赖简单的查表法或通过网络接口获取农历信息,由于由于闰月计算的复杂性或离线环境限制,极易由于由于计算偏移导致传统节日提醒的误报。 我们需要一种能够实现天文级算法推演、支持高精度节气定位且具备纯 Dart 离线运作能力的历法治理方案。 vnlunar 为 Flutter 开发者引入了标准化的阴阳历转换协议。它不仅支持对天干地支、生肖及闰月的精确解构,更针对东南亚等地区的历法细微差异提供了专项适配。在适配到鸿蒙 HarmonyOS 流程

By Ne0inhk
Flutter 三方库 matcher 的鸿蒙化适配指南 - 实现具备语义化断言与自定义匹配算法的测试契约框架、支持端侧质量验证的强力抽象实战

Flutter 三方库 matcher 的鸿蒙化适配指南 - 实现具备语义化断言与自定义匹配算法的测试契约框架、支持端侧质量验证的强力抽象实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 matcher 的鸿蒙化适配指南 - 实现具备语义化断言与自定义匹配算法的测试契约框架、支持端侧质量验证的强力抽象实战 前言 在进行 Flutter for OpenHarmony 开发时,当编写单元测试时,我们经常使用 expect(actual, matcher) 这种语法。你是否想过,如何让断言读起来像自然语言一样?或者,如何自定义一套专门针对鸿蒙原生组件状态的对比逻辑?matcher 是 Dart 官方维护的断言库扩展,它定义了测试中所有“匹配逻辑”的底层协议。本文将探讨如何在鸿蒙端构建极致、严谨的质量契约体系。 一、原直观解析 / 概念介绍 1.1 基础原理 该库建立在“谓词逻辑(Predicate Logic)”之上。它通过将复杂的 Object

By Ne0inhk