七、类与对象

文章目录

  • 类与对象
    • 1.1 自定义类
    • 1.2 第一个类
    • 1.3 private变量
    • 1.4 变量默认值
    • 1.5 构造方法
    • 1.6 类和对象的生命周期

类与对象

本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记

将客观世界中存在的一切可以描述的事物称为对象(实体),即“万物皆对象”。例如,楼底下卖烧饼的大姐、楼下KFC服务员、楼下停的共享单车,桌子上的水杯,中午的外卖订单等,都可以称为对象。这些众多的对象有些是有相同的属性和功能(或行为)的,按照属性和功能(或行为)等对它们进行分类、抽象,就形成了概念世界中的类。类实际上就是根据生活经验总结抽取出来的概念产物,相当于一组对象的模型。类和对象关系如下图所示。

如上图,类和对象我们可以理解为类等于抽象概念的人,对象等于实实在在的某个人。类是抽象的,对象是具体的,类相当于创建对象的蓝图。例如汽车,类与对象的关系就如汽车设计图与汽车实物的关系。面向对象的程序实现需要通过类创建对应的实例化对象,来对应客观世界中的实体。

1.1 自定义类

一个类由其包含的属性以及该类可以进行的操作组成,属性又可以分为是类本身具有的属性,还是一个具体实例具有的属性,同样,操作也可以分为是类型本身可以进行的操作,还是一个具体实例可以进行的操作。这样,一个类就主要由 4 4 4部分组成:

  1. 类本身具有的属性,通过类变量体现。
  2. 类本身可以进行的操作,通过类方法体现。
  3. 类实例具有的属性,通过实例变量体现。
  4. 类型实例可以进行的操作,通过实例方法体现。

类变量和实例变量都叫成员变量,也就是类的成员,类变量也叫静态变量或静态成员变量。类方法和实例方法都叫成员方法,也都是类的成员,类方法也叫静态方法。

1、类变量类方法

类型本身具有的属性通过类变量体现,经常用于表示一个类型中的常量。比如Math类,定义了两个数学中常用的常量,如下所示:

   public static final double E = 2.718281828459045;public static final double PI = 3.141592653589793;

E E E表示数学中自然对数的底数,自然对数在很多学科中有重要的意义; P I PI PI表示数学中的圆周率 π \pi π。要使用类变量,可以直接通过类名访问,如Math.PI。这两个变量的修饰符也都有public staticpublic表示外部可以访问,static表示是类变量。与public相对的是private,表示变量只能在类内被访问。与static相对的是实例变量,没有static修饰符。这里多了一个修饰符final final在修饰变量的时候表示常量,即变量赋值后就不能再修改了。使用final可以避免误操作,比如,如果有人不小心将Math.PI的值改了,那么很多相关的计算就会出错。另外,Java编译器可以对final变量进行一些特别的优化。所以,如果数据赋值后就不应该再变了,就加final修饰符。表示类变量的时候,static修饰符是必需的,但publicfinal都不是必需的。

2、类方法

类型本身的方法通过类方法体现,经常用于表示一个类型中的方法。比如Math中定义的一些数学函数,要使用这些函数,直接在前面加Math.即可,例如Math.abs(-1)返回 1 1 1​。这些函数都有相同的修饰符:public staticstatic表示类方法,也叫静态方法,与类方法相对的是实例方法。实例方法没有static修饰符,必须通过实例或者对象调用,而类方法可以直接通过类名进行调用,不需要创建实例。public表示这些函数是公开的,可以在任何地方被外部调用。与public相对的是private。如果是private,则表示私有,这个函数只能在同一个类内被别的函数调用,而不能被外部的类调用。在Math类中,有一个函数Random initRNG就是private的,这个函数被public的方法random调用以生成随机数,但不能在Math类以外的地方被调用。将函数声明为private可以避免该函数被外部类误用,调用者可以清楚地知道哪些函数是可以调用的,哪些是不可以调用的。类实现者通过private函数封装和隐藏内部实现细节,而调用者只需要关心public就可以了。可以说,通过private封装和隐藏内部实现细节,避免被误操作,是计算机程序的一种基本思维方式。

3、实例变量和实例方法

实例变量表示具体的实例所具有的属性,实例方法表示具体的实例可以进行的操作。如果将微信订阅号看作一个类型,那“人民日报”订阅号就是一个实例,订阅号的头像、功能介绍、发布的文章可以看作实例变量,而修改头像、修改功能介绍、发布新文章可以看作实例方法。实例变量和实例方法是每个实例独有的,虽然可能和其它类似的实例一样,但是不是同一个东西。比如,一个人,都有头发,但是每个人的头发都是不一样的。

实例方法和类方法的主要区别如下:

  1. 类方法只能访问类变量,不能访问实例变量,可以调用其他的类方法,不能调用实例方法。
  2. 实例方法既能访问实例变量,也能访问类变量,既可以调用实例方法,也可以调用类方法。

1.2 第一个类

我们定义一个简单的类来表示直角坐标系中的一个点,代码如下:

package com.ieening;public class ClassTest {public static void main(String[] args) {Point point; // 注释4point = new Point(); // 注释5System.out.println("Point:x=" + point.x + ",y=" + point.y); // 注释9point.x = 3; // 注释6point.y = 4; // 注释6System.out.println("Point:x=" + point.x + ",y=" + point.y); // 注释7System.out.println(point.distance()); // 注释8}
}/*** Point*/
class Point { // 注释1public int x; // 注释2public int y; // 注释2public double distance() { // 注释3return Math.sqrt(x * x + y * y);}
}
  1. 注释1:定义了名为Point的类(class Point)。
  2. 注释2:类中定义了两个两个实例变量(public int x; public int y;)表示。
  3. 注释3:类中定义一个实例方法(public double distance)。
  4. 注释4:Point p声明了一个变量,这个变量叫p,是Point类型的,这个变量和数组变量是类似的,都有两块内存:一块存放实际内容,一块存放实际内容的位置。声明变量本身只会分配存放位置的内存空间,这块空间还没有指向任何实际内容,像这种只保存实际内容位置的变量,也叫做引用类型的变量。
  5. 注释5:创建了一个实例或对象,然后赋值给了Point类型的变量p,它至少做了两件事:
    1. 分配内存,以存储新对象的数据,对象数据包括这个对象的属性,具体包括其实例变量xy
    2. 给实例变量设置默认值,int类型默认值为 0 0 0
  6. 注释6:给public属性赋值;
  7. 注释7:使用public属性,格式为:<对象变量名>.<成员名>;
  8. 注释8:调用实例方法distance,并输出结果,语法形式是:<对象变量名>.<方法名>;
  9. 注释9:实例变量都会有默认值,int默认值为0

本例中,我们通过对象直接操作了其内部数据xy,这是一个不好的习惯,一般而言,不应该将实例变量声明为public,而是private,并且只应该通过对象的方法对实例变量进行操作。这也是为了减少误操作,直接访问变量没有办法进行参数检查和控制,而通过方法修改,可以在方法中进行检查。

1.3 private变量

一般不应该将实例变量声明为public,下面我们修改一下类的定义,将实例变量定义为private,然后通过实例方法来操作变量,比如获取变量值以及修改变量值。修改后,代码如下:

package com.ieening;public class ClassTest {public static void main(String[] args) {Point point;point = new Point();System.out.println("Point:x=" + point.getX() + ",y=" + point.getY());point.setX(3);point.setY(4);System.out.println("Point:x=" + point.getX() + ",y=" + point.getY());System.out.println(point.distance());}
}/*** Point*/
class Point {private int x;private int y;public int getX() {return x;}public void setX(int x) {this.x = x;}public int getY() {return y;}public void setY(int y) {this.y = y;}public double distance() {return Math.sqrt(x * x + y * y);}
}

上面代码中,我们加了 4 4 4个方法,setⅩ/setY用于设置实例变量的值,getⅩ/getY用于获取实例变量的值,这就是私有属性的SetterGetter方法。

这里面需要介绍的是this这个关键字。this表示当前实例,在语句this.x=x;中,this.x表示实例变量x,而右边的x表示方法参数中的x。在实例方法中,有一个隐含的参数,这个参数就是this,没有歧义的情况下,可以直接访问实例变量,在这个例子两个变量setter方法中,因为实例属性和方法参数相同,所以需要通过加上this来消除歧义。这 4 4 4个方法看上去是非常多余的,直接访问变量不是更简洁吗?而且函数调用是有成本的。在这个例子中,意义确实不太大,实际上,Java编译器一般也会将对这几个方法的调用转换为直接访问实例变量,而避免函数调用的开销。但在很多情况下,通过函数调用可以封装内部数据,避免误操作,我们一般还是不将成员变量定义为public

1.4 变量默认值

实例变量都有默认值,但是如果我们想修改该默认值该怎么办呢?如下面代码,如果希望修改这个默认值,有两种方法:

  1. 注释1:可以在定义变量的同时就赋值;
  2. 注释2:将代码放入初始化代码块中,代码块用{}包围;
package com.ieening;public class ClassTest {public static void main(String[] args) {......}
}/*** Point*/
class Point {private int x=6; // 注释1private int y;{y=8; // 注释2}......
}

上述代码中,x的默认值设为了 1 1 1, y的默认值设为了 2 2 2​。在新建一个对象的时候,会先调用这个初始化,然后才会执行构造方法中的代码,关于构造方法,我们稍后介绍。静态变量也可以这样初始化:

    static int STATIC_ONE = 1;static int STATIC_TWO;static {STATIC_TWO = 2;}

语句外面包了一个static {},这叫静态初始化代码块。静态初始化代码块在类加载的时候执行,这是在任何对象创建之前,且只执行一次。

1.5 构造方法

在初始化对象的时候,前面我们都是直接对每个变量赋值,有一个更简单的方式对实例变量赋初值,就是构造方法,先看下面代码。在Point类定义中增加如下代码:

package com.ieening;public class ClassTest {public static void main(String[] args) {......}
}/*** Point*/
class Point {private int x;private int y;Point() { // 注释1this(0, 0);}Point(int x, int y) { // 注释2this.x = x;this.y = y;}public int getX() {return x;}......}

注释1和注释2,这两个就是构造方法,构造方法可以有多个。不同于一般方法,构造方法有一些特殊的地方:

  1. 名称是固定的,必须与类名相同。这也容易理解,靠这个用户和Java系统就都能容易地知道哪些是构造方法。
  2. 没有返回值,也不能有返回值。构造方法隐含的返回值就是实例本身。
  3. 与普通方法一样,构造方法也可以重载。第二个构造方法是比较容易理解的,使用this对实例变量赋值。我们解释下第一个构造方法,this(0,0)的意思是调用第二个构造方法,并传递参数0,0,我们前面解释说this表示当前实例,可以通过this访问实例变量,这是this的第二个用法,用于在构造方法中调用其他构造方法。这个this调用必须放在第一行,这个规定也是为了避免误操作。构造方法是用于初始化对象的,如果要调用别的构造方法,先调别的,然后根据情况自己再做调整,而如果自己先初始化了一部分,再调别的,自己的修改可能就被覆盖了。

这个例子中,不带参数的构造方法通过this(0,0)又调用了第二个构造方法,这个调用是多余的,因为xy的默认值就是 0 0 0,不需要再单独赋值,我们这里主要是演示其语法。

我们来看下如何使用构造方法,代码如下:

public class ClassTest {public static void main(String[] args) {Point point;point = new Point(3, 4); // 注释1System.out.println("Point:x=" + point.getX() + ",y=" + point.getY());System.out.println(point.distance());}
}

注释1中调用构造方法,就可以将实例变量xy的值设为 3 3 3 4 4 4。前面我们介绍new Point()的时候说,它至少做了两件事,一件是分配内存,另一件是给实例变量设置默认值,这里我们需要加上一件事,就是调用构造方法。调用构造方法是new操作的一部分。通过构造方法,可以更为简洁地对实例变量进行赋值。

关于构造方法,下面我们讨论两个细节概念:一个是默认构造方法;另一个是私有构造方法。

1、默认构造方法

默认构造方法每个类都至少要有一个构造方法,在通过new创建对象的过程中会被调用。但构造方法如果没什么操作要做,可以省略。Java编译器会自动生成一个默认构造方法,也没有具体操作。但一旦定义了构造方法,Java就不会再自动生成默认的,具体什么意思呢?在这个例子中,如果我们只定义了第二个构造方法(带参数的),则下面语句:Point p = new Point();就会报错,因为找不到不带参数的构造方法。为什么Java有时候自动生成,有时候不生成呢?在没有定义任何构造方法的时候,Java认为用户不需要,所以就生成一个空的以被new过程调用;定义了构造方法的时候,Java认为用户知道自己在干什么,认为用户是有意不想要不带参数的构造方法,所以不会自动生成。

2、私有构造方法

构造方法可以是私有方法,即修饰符可以为private,为什么需要私有构造方法呢?大致可能有这么几种场景:

  1. 不能创建类的实例,类只能被静态访问,如MathArrays类,它们的构造方法就是私有的。
  2. 能创建类的实例,但只能被类的静态方法调用。有一种常见的场景:类的对象有但是只能有一个,即单例(单个实例)。在这种场景中,对象是通过静态方法获取的,而静态方法调用私有构造方法创建一个对象,如果对象已经创建过了,就重用这个对象。
  3. 只是用来被其他多个构造方法调用,用于减少重复代码。

1.6 类和对象的生命周期

了解了类和对象的定义与使用,下面我们再从程序运行的角度理解下类和对象的生命周期。

在程序运行的时候,当第一次通过new创建一个类的对象时,或者直接通过类名访问类变量和类方法时,Java会将类加载进内存,为这个类分配一块空间,这个空间会包括类的定义、它的变量和方法信息,同时还有类的静态变量,并对静态变量赋初始值。类加载进内存后,直到程序结束一般都不会释放。一般情况下,类只会加载一次,所以静态变量在内存中只有一份。当通过new创建一个对象的时候,对象产生,在内存中,会存储这个对象的实例变量值,每做new操作一次,就会产生一个对象,就会有一份独立的实例变量。每个对象除了保存实例变量的值外,可以理解为还保存着对应类型即类的地址,这样,通过对象能知道它的类,访问到类的变量和方法代码。实例方法可以理解为一个静态方法,只是多了一个参数this。通过对象调用方法,可以理解为就是调用这个静态方法,并将对象作为参数传给this。对象的释放是被Java用垃圾回收机制管理的,大部分情况下,我们不用太操心,当对象不再被使用的时候会被自动释放。

具体来说,对象和数组一样,有两块内存,保存地址的部分分配在栈中,而保存实际内容的部分分配在堆中。栈中的内存是自动管理的,函数调用入栈就会分配,而出栈就会释放。堆中的内存是被垃圾回收机制管理的,当没有活跃变量指向对象的时候,对应的堆空间就可能被释放,具体释放时间是Java虚拟机自己决定的。活跃变量就是已加载的类的类变量,以及栈中所有的变量。


  1. 马俊昌.Java编程的逻辑[M].北京:机械工业出版社,2018. ↩︎

  2. 尚硅谷教育.剑指Java:核心原理与应用实践[M].北京:电子工业出版社,2023. ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/455107.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity3D判断屏幕中某个坐标点的位置是否在指定UI区域内

系列文章目录 unity工具 文章目录 系列文章目录前言一、使用rect.Contains()判断1-1、转换坐标1-2、代码如下&#xff1a;1-3、注意事项1-3、测试效果如下 二、使用坐标计算在不在区域内2-1、方法如下&#xff1a;2-2、注意事项 三、使用RectTransformUtility.ScreenPointToLo…

【PTA函数题】6-2 约瑟夫环之循环链表

n个人围成一圈&#xff08;编号依次为&#xff1a;0,1,2...n-1&#xff09;,从第一个人开始报数&#xff0c;1&#xff0c;2&#xff0c;……数到m者出列&#xff0c;再从下一个开始重新报数&#xff0c;数到m者再出列……。 下面的程序中&#xff0c;用不带附加表头的循环单链…

在 MacOS 上虚拟化 x86Linux 的最佳方法(通过 Rosetta)

categories: [VM] tags: MacOS VM 写在前面 买了 ARM 的 mac, 就注定了要折腾一下虚拟机了… 之前写过一篇文章是通过 utm 虚拟化archlinux, 其实本质上还是调用了 qemu-system-x86_64, 所以速度并不快, 后来想着能不能借用 Rosetta 的优势即原生转译, 来虚拟化 Intel 的 Linu…

正点原子-STM32定时器学习笔记(1)未完待续

1. 通用定时器简介&#xff08;F1为例&#xff09; F1系列通用定时器有4个&#xff0c;TIM2/TIM3/TIM4/TIM5 主要特性&#xff1a; 16位递增、递减、中心对齐计数器&#xff08;计数值&#xff1a;0~65535&#xff09;&#xff1b; 16位预分频器&#xff08;分频系数&#xff…

Mybatis中的sql-xml延迟加载机制

Mybatis中的sql-xml延迟加载机制 hi&#xff0c;我是阿昌&#xff0c;今天记录一下关于Mybatis中的sql-xml延迟加载机制 一、前言 首先mybatis技术本身就不多介绍&#xff0c;说延迟加载机制之前&#xff0c;那要先知道2个概念&#xff1a; 主查询对象关联对象 假设咱们现…

架构整洁之道-软件架构-展示器和谦卑对象、不完全边界、层次与边界、Main组件、服务

6 软件架构 6.9 展示器和谦卑对象 在《架构整洁之道-软件架构-策略与层次、业务逻辑、尖叫的软件架构、整洁架构》有我们提到了展示器&#xff08;presenter&#xff09;&#xff0c;展示器实际上是采用谦卑对象&#xff08;humble object&#xff09;模式的一种形式&#xff…

Spring Boot整合MyBatis Plus实现基本CRUD与高级功能

文章目录 1. 引言2. 项目搭建与依赖配置2.1 添加MyBatis Plus依赖2.2 配置数据源与MyBatis Plus 3. 实现基本CRUD功能3.1 创建实体类3.2 创建Mapper接口3.3 实现Service层3.4 控制器实现 4. 高级功能实现4.1 自动填充功能4.2 乐观锁功能4.3 逻辑删除功能 5. 拓展&#xff1a;My…

数据结构高级算法

目录 最小生成树 Kruskal(克鲁斯卡尔)(以边为核心) 9) 不相交集合(并查集合) 基础 Union By Size 图-相关题目 4.2 Greedy Algorithm 1) 贪心例子 Dijkstra Prim Kruskal 最优解(零钱兑换)- 穷举法 Leetcode 322 最优解(零钱兑换)- 贪心法 Leetcode 322 3)…

在Linux下搭建自己的私有maven库并部署和发布自定义jar依赖和自定义maven插件(三)开发和发布自己开发的maven插件

系列文章目录 在Linux下搭建自己的私有maven库并部署和发布自定义jar依赖和自定义maven插件(二)发布自己开发的jar包 文章目录 系列文章目录在Linux下搭建自己的私有maven库并部署和发布自定义jar依赖和自定义maven插件(二)发布自己开发的jar包 前言一、插件需求二、maven自定…

neo4j查询id为null

今天在neo4j里执行一条查询语句时&#xff0c;发现id属性查询不出来显示为null 后来了解到&#xff0c;Neo4j 默认情况下并不提供一个名为 id 的属性。通常情况下&#xff0c;Neo4j 中的节点都有一个内部的唯一标识符&#xff0c;但是这个标识符并不以 id 的形式暴露给用户。 …

LLMs之miqu-1-70b:miqu-1-70b的简介、安装和使用方法、案例应用之详细攻略

LLMs之miqu-1-70b&#xff1a;miqu-1-70b的简介、安装和使用方法、案例应用之详细攻略 目录 miqu-1-70b的简介 miqu-1-70b的安装和使用方法 1、安装 2、使用方法 miqu-1-70b的案例应用 miqu-1-70b的简介 2024年1月28日&#xff0c;发布了miqu 70b&#xff0c;潜在系列中的…

Google MobileDiffusion: 移动端设备上的快速文字到图片生成技术

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…