JavaSE 基础 (6) 面向对象基础
面向过程和面向对象思想对比
面向对象和面向过程的思想对比
-
面向过程编程 (Procedure Oriented Programming)
是一种以 过程 为中心的编程思想,实现功能的每一步,都是 自己实现 的 -
面向对象编程 (Object Oriented Programming)
是一种以 对象 为中心的编程思想,通过 指挥对象实现 具体的功能
|
|
对象:指客观存在的事物

图1
)类就是表格结构, 对象就是里边的内容

图2
)一个完整的对象 = 静态的属性 + 动态动作

图3
)新增的列 “雇员动作说明”,显然是对所有的雇员都有用,每个雇员都有这个动作。在类中就是定义成方法:

图4
)- 面向过程和面向对象的区别
都是解决问题的思维方式,都是代码组织的方式
面向过程 是一种 “执行者思维”,解决简单问题可以使用面向过程
面向对象 是一种 “设计者思维”,解决复杂,需要协作的问题可以使用面向对象
面向对象离不开面向过程:
宏观上:通过面向对象进行整体设计
微观上:执行和处理数据,仍然是面向过程
- 面向对象思想小结:
客观存在的任何一种事物,都可以看做为程序中的对象
使用面向对象思想可以将复杂的问题简单化
将我们从执行者的位置,变成了指挥者
类和对象的关系
类和对象的概念
先有一个具体的事物,才能抽象出对象的概念
真实的物体 → 雇员类 → 程序中的对象
认识 → 编码 → 运行
类可以看作是一个模板,或者图纸,系统根据类的定义来造出对象。我们要造一个汽车,怎么样造? 类就是这个图纸,规定了汽车的详细信息,然后根据图纸将汽车造出来。
类:我们叫做 class。 对象:我们叫做 Object, instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。
总结
- 类可以看成一类对象的模板,对象可以看成该类的一个具体实例。
- 类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。
类的组成
-
属性
该事物的各种特征
例如学生事物的属性:姓名、年龄、毕业院校… -
行为
该事物存在的功能(能够做的事情)
例如学生事物行为:学习、Java 编程开发
类:类是对现实生活中一类具有共同属性和行为的事物的抽象
对象:是能够看得到摸的着的真实存在的实体
类是对象的描述,对象是类的实体
类的定义
类的组成:属性 和 行为
- 属性:在代码中通过 成员变量 来体现(类中方法外的变量)
- 行为:在代码中通过 成员方法 来体现 (和前面的方法相比去掉 static 关键字即可)
① 类的定义方式
|
|
类中一般有三种常见的成员:属性 field
,方法 method
,构造器(构造方法)constructor
② 属性(field 成员变量)
[修饰符] 属性类型 属性名 = [默认值];
属性用于定义该类或该类对象包含的数据或者说是静态特征。属性作用范围是整个类体。在定义成员变量时可以对其初始化,如果不对其初始化,Java 使用默认的值对其初始化

成员变量的默认值
)③ 方法(动态的东西)
方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。方法类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
构造方法,方法名和类名保持一致
Public static void main(String[ ] args){ }
Main 方法是所有的程序的入口,Main 方法,形式上属于当前的类,精神上(实际上)是独立的
|
|

图6
)Stu s1 = new Stu( );
Stu( ) 调用的是 class Stu 中的 Stu( ) 构造器,通过构造器,根据 Class Stu( ) 作为模板,创建一个新的对象,新的对象的地址是 0x10, 变量 s1 存放的是新对象的地址 0x10
创建第二个对象

图7
)第一个对象和第二个对象的 study 方法,指向同一个地址空间
这个图记住两个,变量和对象,s1 是变量,存储的是 0x10 对象的地址
案例: 手机类的创健和使用
需求:定义一个手机类,然后定义一个手机测试类,在手机测试类中通过对像完成成员变量和成员方法的使用
分析:
成员变量:品牌,价格…
成员方法:打电话,发短信。…
|
|
JAVA虚拟机内存模型

图8
)从属于线程的内存区域
JVM 的内存划分中,有部分区域是线程私有的,有部分是属于整个 JVM 进程;我们将这部分归为一类

图9
)1.程序计数器 (程序是一步一步执行的,我们需要一个计数器,记录程序执行到那一步,这样,下一次执行的时候知道从哪一步开始,有时候线程会暂停),在 JVM 规范中,每个线程都有自己的程序计数器。这是一块比较小的内存空间,存储当前线程正在执行的 Java 方法的 JVM 指令地址,即字节码的行号。如果正在执行 Native 方法,则这个计数器为空
2.Java 虚拟机栈 同样也是属于线程私有区域,每个线程在创建的时候都会创建一个虚拟机栈,生命周期与线程一致,线程退出时,线程的虚拟机栈也回收。虚拟机栈内部保持一个个的栈帧(stack frame),每次方法调用都会进行压栈,JVM 对栈帧的操作只有出栈和压栈两种,方法调用结束时会进行出栈操作。该区域存储着局部变量表,编译时期可知的各种基本类型数据,对象引用,方法出口等信息
3.本地方法栈 (调用本地的方法)与虚拟机栈类似,本地方法栈是在调用本地方法 (调用操作系统 API 或访问硬件接口) 时使用的栈,每个线程都有一个本地方法栈
4.堆(Heap)
堆,几乎所有创建的 Java 对象实例,都是被直接分配到堆上的。堆被所有的线程所共享,在堆上的区域,会被垃圾回收器作进一步划分,例如新生代,老年代的划分。Java 虚拟机在启动的时候,可以使用 “Xmx” 之类的参数指定堆区域的大小
5.方法区(存放不变的数据)
方法区与堆一样,也是所有的线程所共享,存储被虚拟机加载的元(Meta)数据,包括类信息,常量,静态变量,即时编译器编译后的代码等数据。
方法区是一种 Java 虚拟机的规范。由于方法区存储的数据和堆中存储的数据一致,实质上也是堆,因此,在不同的 JDK 版本中方法区的实现方式不一样。
JDK 7 以前,方法区就是堆中的 “永久代”
JDK 7 开始去 “永久代”,把静态变量、字符串常量池(string 常量池)都挪到了堆内存中

图10
)元空间(Metaspace)就是方法区, 位于本地内存
直接内存(Direct Memory)
|
|
运行时常量池(方法区中)
这是方法区的一部分。常量池主要存放两大类常量:
- 1.字面量,如文本字符串,声明为final的常量值等。
- 2.符号引用,存放了与编译相关的一些常量,因为 Java 不像 C++ 那样有连接的过程,因此字段方法这些符号引用在运行期就需要进行转换,以便得到真正地内存入口地址。
直接内存
直接内存并不属于 Java 规范规定的属于 Java 虚拟机运行时数据区的一部分。Java 的 NIO 可以使用 Native 方法直接在 java 堆外分配内存,使用 DirectByteBuffer 对象作为这个堆外内存的引用
程序执行的内存分析
Java 虚拟机的内存可以简单分为三个区域:虚拟机栈 stack,堆 heap,方法区 method area
虚拟机栈(简称:栈)的特点如下
- 1.栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
- 2.JVM 为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
- 3.栈属于线程私有,不能实现线程间的共享!
- 4.栈的存储特性是 “先进后出,后进先出”
- 5.栈是由系统自动分配,速度快!栈是一个连续的内存空间!
方法出口:也就是返回值
堆的特点如下
- 1.堆用于存储创建好的对象和数组(数组也是对象)
- 2.JVM只有一个堆,被所有线程共享
- 3.堆是一个不连续的内存空间,分配灵活,速度慢!
方法区(又叫静态区,也是堆)特点如下
1.方法区是 JAVA 虚拟机规范,可以有不同的实现。
- JDK7以前是 “永久代”
- JDK7部分去除 “永久代”,静态变量、字符串常量池都挪到了堆内存中
- JDK8是 “元数据空间” 和堆结合起来。
2.JVM 只有一个方法区,被所有线程共享!
3.方法区实际也是堆,只是用于存储类、常量相关的信息!
4.用来存放程序中永远不变或唯一的内容。(类信息【Clss对象,反射机制中会重点讲授】、静态变量、字符串常量等)
对象内存图
编写 Person 类
|
|
创建 Person 类对象并使用
|
|
Peron 类图示分析

图11
)String[] args
是形参,是局部变量,也需要存放。只是 args 的值为 null 而已
局部变量 p1,此时的值为 null

图12
)在执行 main 方法的时候,首先要加载类信息

图13
)Person( )构造方法执行完毕,出栈

图14
)String 不是基本数据类型,char 才是,String 在 java 中既是类也是一个对象,需要我们自定义,对象通过引用地址来操作,将常量 “张三” 存放到方法区 Person 类里的常量池里,“张三” 有个地址,地址传给 0x11 里的 name

图15
)P1.show 是个方法,在栈里创建一个空间,p1.show 栈帧,栈帧里会创建一个默认的 this (我们称为隐式参数),this 的地址是 p1 的地址,p1.show 的意思是:我是个对象(p1)调用了自己的方法(show)
|
|
This 的地址 0x11,this.name 指的是 0x11.name,this 就是当前对象,谁调我,我就是谁
System.out.println("姓名: " + name + ",年龄: " + age);
里的 this 可写可不写,不写默认编译器加 this

图16
)P1.show 执行完毕后,show( ) 方法完成了使命,执行出栈操作
P2 执行相同的操作

图17
)
图18
)Person( ) 构造方法是根据方法区里的 person 类信息的模板来执行的

图19
)
图20
)
图21
)P2.show 执行完毕,show 方法出栈,此时,main( ) 方法也执行完毕,main( ) 方法出栈

图22
)main( ) 方法出栈后,其余的也不存在了,也要删除,删除完毕后,程序退出
整体图

图23
)问: 如何理解方法区是一种规范(规范是标准),可以有不同的实现(实现是执行)?JDK 7 之前、JDK 7、JDK 8 以后都是怎么实现的?
方法区规范和实现是有区别的,规范是标准,实现是执行
- JDK 6 永久代
- JDK 7 一部分永久代,一部分堆
- JDK 8 元空间,本地内存
两个引用指向同一对象内存图

图24
)垃圾回收机制(Garbage Collection)
垃圾回收机制(一般指的是堆中的对象)
堆的内存空间是有限的,当空间不足时,需要把一些对象干掉,什么时候把什么对象干掉,这个对象变为垃圾的时候,什么时候变为垃圾呢,没有人用的时候,而把垃圾清理掉的过程,就是垃圾回收的过程。把空间空出来,给新的对象。
垃圾回收具有两种方式
会有一个线程 GC,只要虚拟机启动,这个线程就会启动,它负责监管那些对象被变量引用了,那些没被引用,没被引用的就是垃圾,就把他销毁掉,把空间释放
Java 是自动回收垃圾的
垃圾回收主要是回收堆里边的
在 C 语言中,自己创建对象,自己回收,在 Java 中,系统自动回收
垃圾回收原理和算法
内存管理
- Java 的内存管理很大程度指的就是:堆中对象的管理,其中包括对象空间的分配和释放。
- 对象空间的分配:使用 new 关键字创建对象即可
- 对象空间的释放:将引用的变量赋值为 null 即可。(将栈中变量指向对象的地址赋值为 null,即 p1:null)。垃圾回收器将负责回收所有 “不可达” 对象的内存空间。
栈中的栈帧用完会自动结束,而方法区中加载的类信息是一直需要的,所以垃圾回收主要用于堆中的对象
将引用的变量赋值 null 即可,赋值 null 就没有对象指向那个变量
“不可达”:没有变量去引用
垃圾回收过程
任何一种垃圾回收算法一般要做两件基本事情:
- 1.发现无用的对象
- 2.回收无用对象占用的内存空间
垃圾回收机制保证可以将 “无用的对象” 进行回收。无用的对象指的就是没有任何变量引用该对象。Java 的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。
垃圾回收算法
1.引用计数法
堆中的每个对象都对应一个引用计数器,当有引用指向这个对象时,引用计数器加 1,而当指向该对象的引用失效时(引用变为 null),引用计数器减 1,最后如果该对象的引用计算器的值为 0 时,则 Java 垃圾回收器会认为该对象是无用对象并对其进行回收。优点是算法简单,缺点是 “循环引用的无用对象” 无法别识别。

图25
)S2 的地址给了s1.friend,s1 的地址给了s2.friend
虽然外部变量的引用置为 null,外部引用取消掉了,但是内部互相引用,永远释放不了
2.引用可达法(根搜索算法)
程序把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
把变量和对象之间的关系用一张数据结构图来表示,垃圾回收器一启动,就遍历这张图,这张图那些不可达的节点,就证明已经没有了,就把那些对象干掉
通用的分代垃圾回收机制
分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代、持久代。同时,将处于不同状态的对象放到堆中不同的区域。JVM 将堆内存划分为 Eden、Survivor 和 Tenured/Old 空间。
Eden、Survivor 属于年轻代,Tenured/Old 属于年老代
1.年轻代
所有新生成的对象首先都是放在 Eden 区。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是 Minor GC, 每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当 “年轻代‘ 区域存放满对象后,就将对象存放到年老代区域。

图26
)所有新生成的对象都在 Eden 区,Eden 区满的时候,Minor GC 就会去清理 Eden 区,已经用完的对象清理掉,而没有用完的对象,把他们放到 Survivor 1 区,当 Survivor 1 区满的时候,Minor GC 就去清理 Survivor 1 区,把已经用完的对象清理掉,没有用完的对象放到 Survivor 2 区里,当 Survivor 2 区满的时候,Minor GC 就去清理 Survivor 2 区,把已经用完的对象清理掉,没有用完的对象放到 Survivor 1 区,经过来回清理,短期的对象都被清理掉了,累计清理15次后,这个对象依然在,那么这个对象就进入年老代
2.年老代
在年轻代中经历了 N (默认15) 次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,我们就需要启动 Major GC 和 Full GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。

图27
)当对象进入年老代后,此时,就不归 Minor GC 管了,而是 Major GC 管理,而 Major GC 每次启动都要耗费大量资源,所以,他主要用来处理年老代,因为年老代里都是周期较长的对象
这种算法可以充分的利用空间,快速区分小的,大的,以空间换时间,虽然从 Eden 里复制对象到 Survivor 中,看着浪费空间,但是效率很高,Major GC 的范围小于 Full GC, Major GC 用于清理年老代,而 Full GC 范围广,可以来一次大扫除,清理她管辖内所有东西,也就是全面清理年轻代和年老代区域和永久代区域
3.永久代
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响。JDK 7 以前就是 “方法区” 的一种实现。JDK 8 以后已经没有 “永久代” 了,使用 metaspace 元数据空间和堆替代。
相当于,饭店开张我就来,晚上饭店关门我还在,与饭店共存亡,jvm 宕机了,也就不在了,只要 jvm 在运行,就一直在
永久代,不管什么时候来,会一直存在到 java 虚拟机执行完毕
静态文件如静态变量,字符串常量……
JDK 8 以后就把这些东西分开了,静态变量,字符串常量……放到堆里,代码之类的放到 metapace 里

图28
)- Minor GC:用于清理年轻代区域。Eden 区满了就会触发一次 Minor GC。清理无用对象,将有用对象复制到 “Survivor1”、“Survivor2” 区中。
- Major GC:(耗资源)用于清理老年代区域。
- Full GC:用于清理年轻代、年老代区域。成本较高,会对系统性能产生影响。
JVM调优和Full GC
在对 JVM 调优的过程中,很大一部分工作就是对于 Full GC 的调节。有如下原因可能调用 Full GC:
- 年老代(Tenured)被写满
- 永久代(Perm)被写满
- System.gc( )被调用
System.gc( ) 的作用是向 JVM 提出建议,建议你清理一下,而不是直接调用,只有建议权,没有执行权,它可以不听你的
Finalize 方法,用来释放对象或资源的方法,构造方法用来构造,finalize 用来释放,用不到,java 有自动释放机制
上一次 GC 之后 Heap 的各区域分配策略动态变化
其他要点
- 程序员无权调用垃圾回收器。
- 程序员可以调用 System.gc( ), 该方法只是通知 JVM, 并不是运行垃圾回收器。尽量少用,会申请启动 Full GC, 成本高,影响系统性能。
- finalize( ) 方法,是 Java 提供给程序员用来释放对象或资源的方法,但是尽量少用
为啥方法区里也有对象的解释
方法区中也可能存储着对象。在 Java 虚拟机中,方法区(或永久代)是一种特殊的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。
在某些情况下,对象可能会被存储在方法区中。例如,Java 中的字符串常量池位于方法区,当创建一个字符串常量时,如 String s = “abc”;,这个字符串常量 “abc” 就会被存储在方法区中。
然而,请注意,Java 8 之后,永久代被元空间(Metaspace)所取代,元空间是专为类元数据设计的内存区域,它扩展了原有的方法区概念。因此,在 Java 8 之后,方法区通常指的是元空间。
在 Java 中,字符串常量可以被视为一个 String 类的对象。在 Java 中,所有的字符串常量都被存储在字符串常量池中,这个池子是 Java 堆内存中的一个特殊区域。当你创建一个字符串常量时,例如 String s = “abc”;,这个字符串常量 “abc” 就会被存储在字符串常量池中。
在这个意义上,你可以说字符串常量 “abc” 是一个对象,因为它是 String 类的一个实例。然而,需要注意的是,这并不意味着你可以像操作其他对象那样操作字符串常量。例如,你不能改变字符串常量的值,因为它们是不可变的。
总的来说,虽然字符串常量可以被视为一个对象,但它们有一些特殊的性质,比如不可变性。
开发中容易造成内存泄露的操作
- 创建大量无用对象
比如,我们在需要大量拼接字符串时,使用了 String 而不是 String Builder。
|
|
- 静态集合类的使用
像 HashMap、Vector.、List 等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象 Object 也不能被释放。
- 各种连接对象 (IO 流对象、数据库连接对象、网络连接对像) 未关闭
IO 流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网络连接,不使用的时候一定要关闭。
- 监听器的使用
释放对象时,没有删除相应的监听器
总之就是用完了没释放
封装
- 面向对象三大特征之一 (封装、继承和多态)
- 隐藏实现细节, 仅对外暴露公共的访问方式
- 封装常见的体现:
1.私有成员变量,提供 setXxx 和 getXxx 方法
2.将代码抽取到方法中,这是对代码的一种封装
3.将属性抽取到类当中,这是对数据的一种封装 - 封装的好处:
1.提高了代码的宝全性
2.提高了代码的复用性
封装的作用和含义
封装是面向对象三大特征之一。对于程序合理的封装让外部调用更加方便,更加利于写作。同时,对于实现者来说也更加容易修正和改版代码。
封装:把一些细节封装起来,不让别人看到,做到六个字 “高内聚,低耦合”。高内聚(内部代码封装起来不让别人看)就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合(别人调用的时候方便一点)是仅暴露少量的方法给外部使用,尽量方便外部调用。一般做到高内聚也就做到了低耦合。
编程中封装的具体优点:
- 提高代码的安全性
- 提高代码的复用性
- “高内聚”:封装细节,便于修改内部代码,提高可维护性
- “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作
未进行封装代码演示
|
|
我们都知道,年龄不可能是负数,也不可能超过130岁,但是如果没有使用封装的话,便可以给年龄赋值成任意的整数,这显然不符合我们的正常逻辑思维。执行结果如图所示

图29
)再比如说,如果哪天我们需要将 Person 类中的 age 属性修改为 String 类型的,你会怎么办?你只有一处使用了这个类的话那还比较幸运,但如果你有几十处甚至上百处都用到了,那你岂不是要改到崩溃。而封装恰恰能解决这样的问题。如果使用封装,我们只需要稍微修改下 Person 类的 setAge( ) 方法即可,而无需修改使用了该类的客户代码。
封装的实现-使用访问控制符
Java 是使用 “访问控制符” 来 控制哪些细节需要封装,哪些细节需要暴露。Java 中4种 “访问控制符” 分别为 private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。

访问权限修饰符
)- private 表示私有,只有自己的类能访问
- default 表示没有修饰符修饰,只有同一个包的类能访问。(int a;啥也不加默认 default)
- protected 表示可以被同一个包的类以及其他包中的子类访问
我自己能用,同一个包中的邻居能用,子类也能用,就算子类与我不在同一个包中,也能用 - public 表示可以被该项目的所有包中的所有类访问
private、default、protected、public 既能修饰属性,也能修饰方法
关于 protected 的两个细节:
- 若父类和子类在同一个包中,子类可访问父类的 protected 成员,也可访问父类对象的 protected 成员。
- 若子类和父类不在同一个包中,子类可访问父类的 protected 成员,不能访问父类对象的 protected 成员。
|
|
封装的使用细节
开发中封装的简单规则:
1.属性一般使用 private 访问权限(不管三七二十一,写类属性用 private)
- 属性私有后,提供相应的 get/set 方法来访问相关属性,这些方法通常是 public 修饰的,以提供对属性的赋值与读取操作(注意:boolean 变量的 get 方法是 is 开头)
2.方法:一些只用于本类的辅助性方法可以用 private 修饰,希望其它类调用的方法用 public 修饰
JavaBean 的封装演示:
|
|
Get/set 方法,键盘敲 get/set 会直接提示相关的方法, 或者 alt + insert
|
|
Get/set 方法里的内容可以自己定义,不至于年龄随便输入个数就可以
Alt+insert 快捷键,选择 generate
上面的 person2 就可以称作一个简单的 JavaBean(java豆)

图31
)加入封装后,就不能直接 p.name 使用了,得 p.setName( )
this关键字
案例
|
|
局部变量和成员变量出现了重名的情况, 在出现重名情况时, Java 会使用就近原则, 谁离我近, 我就使用谁, 在上面的 setName( ) 方法中, name= name; 两个 name 使用的都是局部变量, 那就意味着局部变量自己在给自己赋值, 根本没有触碰到成员变量, 我现在想让局部变量给成员变量赋值, 就需要把 “=” 左边的变量标识为成员变量, 此时, 加上 this 关键字即可, this.name = name;
|
|
我们平时在使用成员变量时, 系统会默认帮我们加上 this. , return age;
等效于 return this.age;
this 关键字的作用
- 可以调用本类的成员(变量,方法)
- 解决局部变量和成员变量的重名问题
方法被哪个对象调用,ths 就代表哪个对象
this内存原理
构造器并不是创建对象,而是初始化对象中的属性,在调用构造器之前已经创建好对象了,构造器相当于房子建好去装修
对象创建的过程和this的本质
构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回该类的对象,但这个对象并不是完全由构造器负责创建。创建一个对象分为如下四步:
- 分配对象空间,并将对象成员变量设置默认值, 设置为 0 或 null
- 执行属性值的显式初始化
- 执行构造方法
- 返回对象的地址给相关的变量
this 的本质就是 “创建好的对象的地址” 由于在构造方法调用前,对象已经创建。因此,在构造方法中也可以使用 this 代表 “当前对象”
构造器创建对象,但是他只负责一部分内容,负责初始化对象中的属性,在调用构造器前,对象已创建,并给了默认值, 而构造器是在已经分配了默认值的基础上再次进行初始化
属性值的显示初始化是说, 假如有一个属性是 int id = 3; 那么他的属性值显式初始化值就为 3

图32
)User u1 = new User();
此时,new 就已经相当于在堆里创建一个空间,并在这个空间里,给属性赋默认值(id:0 name:null pwd:null), 然后调用构造方法(构造方法调用之前, 对象地址也已经有了:0x1)
此时,还未调用构造方法,但是毛胚房已经有了,只不过没按你的要求装修,构造方法就是装修队,JVM 是开发商

图33
)此时,已调用构造方法,构造方法里有三个参数,分别是:隐式参数
、id
、name
,将实参传给形参,此时,隐式参数=0x1
、id = 100
、name = 高小七
,其次,this.id 指的是 0x1 里的 id,将 User 里的 id 值 100 赋值给 0x1.id,name 值高小七赋值给 0x1.name

图34
)构造器用来初始化对象中的属性
快捷键创建构造器,右键 Generate(alt+insert),选 constructor,需要什么选什么

图35
)
图36
)此时,有三个形参的构造方法,User(int id, String name, String pwd)
与有两个形参的构造方法 User(int id, String name)
的前两行代码是一样的,此时可以省略,使用 this()
,来调用两个形参的构造器
使用 this 调用重载的构造器,一个构造器调用另一个构造器用 this
this 最常的用法:
- 在程序中产生二义性之处,应使用 this 来指明当前对象;普通方法中,this 总是指向调用该方法的对象。构造方法中,this 总是指向正要初始化的对象。
- 使用 this 关键字调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且必须位于构造方法的第一句。
- this 不能用于 static 方法中。
This 不能用于 static 方法中,This 指当前对象,必须是从属于对象的,当 this 在 main( ) 方法中是不可用的,因为 main( ) 不是从属于对象的,属于类
this(id, name)
可以看做 this.id=id;
this.name=name;
this: 代表调用该方法的对象(一般我们是在当前类中使用 this, 所以我们常说 this 代表本类对象的引用)
构造方法 (构造器 constructor)
构造方法基础用法
构造器也叫构造方法,用于对象的初始化。构造器是一个创建对象时被自动调用的特殊方法,目的是对象的初始化。构造器的名称应与类的名称一致。Java 通过 new 关键字来调用构造器,从而返回该类的实例,是一种特殊的方法。
|
|
构造器四个要点:
- 构造器通过 new 关键字调用
- 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用 return 返回某个值 (这里的返回值指的是,使用构造器时, 对象的初始化会返回一个地址给要创建的对象)
- 如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义,则编译器不会自动添加
- 构造器的方法名称必须和类名保持一致
方法名与类名相同, 大小写也要一致
没有返回值类型。连 void 都没有
没有具体的返回值 (不能由 return 带回结果数据)
对象的创建完全是由构造方法实现的吗?
不完全是。构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回了该类对象,但这个对象并不是完全由构造器负责创建的。创建一个对像分为如下四步:
1.分配对象空间,并将对象成员变量初始化为0或空
2.执行属性值的显示初始化
3.执行构造方法
4.返回对象的地址给相关的变量
构造方法作用
作用:用于 给对象 的数据(属性)进行 初始化
案例
定义一个 “点” (Point) 类用来表示二维空间中的点(有两个坐标)。要求如下:
- 可以生成具有特定坐标的点对象。
- 提供可以设置坐标的方法。
- 提供可以计算该 “点” 距另外一点距离的方法。
|
|
getDistance(origin)
是把 (0.0, 0.0)
实际参数传给 getDistance(Point p)
方法里的形式参数 p
,所以 p.x
和 p.y
指的是 origini
的 x
和 y
,而 getDistance()
里的 this.x
和 this.y
指的是 Point(3.0, 4.0)
的 x
和 y
getDistance()
是个方法,开辟一个空间,他的局部变量是 p
,p
指向的地址是 origini
的地址 0x2
p.getDistance(origin)
是对象 p
调的,所以在 getDistance()
方法中 (this.x-p.x)
中的 this.x
指的是 p(3.0, 4.0)
的 x
,那个对象调的,就是那个对象
栈帧:每个方法都开辟个空间,例如:main 方法,这个空间里存放变量
构造方法的重载
构造方法也是方法,只不过有特殊的作用而已。与普通方法一样,构造方法也可以重载。
|
|
上面在初始化时, 把值已经定义好了
如果已定义有参的构造器,而没有定义无参的构造器,这时候,系统不会给你定义默认的无参构造器,只要你定义了构造器,无论是有参还是无参,这时候系统就不会给你自动定义了,所以当你定义了有参构造器,需要用无参构造器时,需要自己去定义
如果方法构造中形参名与属性名相同时,需要使用this关键字区分属性与形参。
this.id
表示 属性id
;
id
表示 形参id
标准类的代码编写和使用
要求
- 必须对成员变量私有化
- 比如提供 get/set 方法
- 必须提供无参构造方法
- 必须提供有参构造方法
补充
- 生成空参构造方法: 在类中空白区域 -> 右键 -> 选择Generate -> 选择Constructor -> 点击Select None按钮
- 生成有参构造方法: 在类中空白区域 -> 右键 -> 选择Generate -> 选择Constructor -> Ctrl + A -> 回车
- 生成 get/set 方法: 在类中空白区域 -> 右键 -> 选择Generate -> 选择Getter And Setter -> Ctrl + A -> 回车