- 参考书:《Java语言程序设计与数据结构(基础篇)》—— 梁勇
- 参考视频教程:java教程
文章目录
- 一、类和对象相关概念
- 1. 面向过程的程序设计
- 2. 面向对象的程序设计
- 二、java中类和对象的使用及存储
- 1. 创建和使用类
- (1)创建类
- (2)使用类
- (3)说明
- 2. 内存中的存储情况
- (1)堆/栈/方法区
- (2)静态成员和非静态成员
- (3)静态代码块
- 3. 示例
- (1)单一实例的情况
- (2)两个同类实例的情况
- (3)两个引用变量引用到同一实例的情况
- 三、类和方法
- 1. 类作为方法的参数
- 2. 类作为方法的返回值
- 四、成员变量和局部变量的区别
- 五、构造方法
- 六、一个标准的类(Java Bean)
- 1. private关键字
- 2. this关键字
- 3. Java Bean
- 七、不可变类
一、类和对象相关概念
1. 面向过程的程序设计
- 面向过程的程序 = 算法+数据结构
- 程序由
全局变量
和函数
构成,用函数操作数据结构 - 不足:
- 函数和其操作的数据结构没有直观的联系,程序长了之后,难以直接看出
- 某个数据结构有哪些函数可以对它操作操作
- 某个函数到底是操作哪些数据结构的
- 任意两个函数间存在怎样的调用关系?
- 没有“封装”和“隐藏”的概念,要访问某个数据结构中的某个变量,可以直接访问。当变量定义变化时,就必须找到所有调用处修改,难以检查哪个函数导致错误
- 难以进行代码重用
- C语言就是典型的面向过程编程语言,只能用面向过程的思想进行开发
2. 面向对象的程序设计
- 面向对象的程序 = 类+类+…+类
- 设计方法:
- 把某类事物的客观属性(共同特点)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性)
- 把这类事物能进行的行为也归纳出来,形成一个个函数,用于操作数据结构(这一步叫
抽象
) - 然后通过某种语法形式,把数据结构和操作此结构的函数捆绑到一起,形成一个
类
,从而将数据结构和函数紧密联系起来(这步叫封装
) - 类相当于一个
模板
,对象
是类的实例,可以从一个类构造多个实例(对象)
- 抽象的结果:成员变量和成员方法
-
成员变量
:描述对象的状态/属性,由数据域及其值表示 -
成员方法
:描述对象的行为,由方法定义
- 基本特点:抽象、封装、继承、多态
- java是典型的面向对象编程语言,支持面向对象的程序开发
二、java中类和对象的使用及存储
1. 创建和使用类
(1)创建类
- java中通过
class
关键字创建类,在类中直接定义成员变量和成员方法,注意成员变量放在成员方法外边 - 类可以嵌套定义
-
内部类
:在一个类中定义的类,称为内部类(参考:浅谈Java内部类) -
外部类
:和内部类相对应的,不在其他类内部定义的类称为外部类
- 一个
.java
源码文件中可以定义多个类,但只能有一个public
类,且文件名必须和此public
类同名
- 如
Demo.java
中必须有且只有一个public
公共类,且它叫Demo
- 编译时,java编译器把每一个类都单独生成一个
.class
文件
- 通常,我们使用IDE开发java程序时,会把每个类单独放在一个同名
.java
文件中 - 默认值:创建类后,类的成员变量会有一个默认值,规则和数组一样
(2)使用类
使用类的一般步骤:
导包,指出需要使用的类在什么位置,对于和当前类在同一个包的情况,可以省略导包语句
创建实例(对象):
使用
类对象是一种引用数据类型,类似C的指针指针类型,可以进行以下操作
有时候,对象创建后不需要引用访问,这时可以不把他赋给某个引用变量,这样创建的对象叫
匿名对象
匿名数组就是匿名对象的一种,使用匿名对象的策略仅仅就是创建、初始化、应用,因为它没有任何名字因此没法重用它
(3)说明
- 关于引用变量赋值和垃圾回收
- 假设有两个引用变量
C1
、C2
,C1 = C2;
会使C1
指向C2
指向的对象,这时如果原来C1
指向的对象没有其他引用变量引用了,那个对象就会成为不可访问的,会自动被java垃圾回收机制回收。关于对象的存储详见下文 - 如果要消除某个对象,可以给引用它的引用变量赋值
null
,这样它就会被自动回收
2. 内存中的存储情况
- 和
String
或数组相同,类对象是一种引用数据类型,这类似C中的指针类型,其存储的是一个地址值。创建类对象时,在堆区创建类对象,在栈区创建类对象变量,对象变量本质上存储着堆区对象的地址。如果堆区的某个类对象没有任何引用变量指向它,就会被垃圾回收机制自动回收
(1)堆/栈/方法区
- JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)
- 堆区:
- 存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
- jvm只有一个堆区,被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
- 通过
new
关键字,在堆区创建对象 - 栈区:
- 每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象/引用数据类型的引用。例如
String str = "12345";
这行代码,它在栈区创建了一个引用str
,在堆区创建了一个String对象"12345"
,并且把str
引用到它 - 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
- 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
- 方法区:
- 又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
- JDK8之前,由永久代实现,主要存放类的信息、常量池、方法数据、方法代码等;JDK8之后,取消了永久代,提出了元空间,并且常量池、静态成员变量等迁移到了堆中;元空间不在虚拟机内存中,而是放在本地内存中。
(2)静态成员和非静态成员
- 通过
static
关键字可以设置类成员为静态的 成员变量
- 非静态成员变量(实例变量):是属于类的实例的,在jvm加载类的字节码文件到方法区时不创建,只有在类实例创建时才在堆区一起创建。每个类实例中只能直接访问到自己的成员变量,不能直接访问其他同类实例中的同名变量。在使用时,只能通过
实例名.变量名
的方式使用 - 静态成员变量(类变量):是属于类的,在jvm加载类的字节码文件到方法区时即创建,早期(jdk8之前)是放在方法区的,后来改为存放在堆区。所有类的实例中都可以直接访问到类的非静态成员变量,并能对其进行修改。使用时,可以
实例名.变量名
这样访问,也可以通过类名.变量名
的方式使用它,而不需要创建对象。
- 非静态成员变量(实例变量):是属于类的实例的,在jvm加载类的字节码文件到方法区时不创建,只有在类实例创建时才在堆区一起创建。每个类实例中只能直接访问到自己的成员变量,不能直接访问其他同类实例中的同名变量。在使用时,只能通过
成员方法
- 非静态成员方法(实例方法):在jvm加载类的字节码文件到方法区时,类的实例方法并不分配入口地址,只有当我们创建对象时,类的实例方法才会被分配访问地址。在创建类的第一个对象时,类的实例方法的入口地址就会被分配,之后再创建此对象时不再给实例方法的分配新的地址,而是共享第一个对象被创建时分配的实例方法的入口地址,并且,只有当类的最后一个实例对象被销毁时才会将地址回收。类似实例变量,只能用
实例名.方法名()
形式使用 - 静态成员方法(类方法):在jvm加载类的字节码文件到方法区时,类方法就会被分配入口地址,所以类方法的入口地址分配比实例方法要早。类似实例变量,可以用
实例名.方法名()
或类名.方法名()
两种形式使用
- 非静态成员方法(实例方法):在jvm加载类的字节码文件到方法区时,类的实例方法并不分配入口地址,只有当我们创建对象时,类的实例方法才会被分配访问地址。在创建类的第一个对象时,类的实例方法的入口地址就会被分配,之后再创建此对象时不再给实例方法的分配新的地址,而是共享第一个对象被创建时分配的实例方法的入口地址,并且,只有当类的最后一个实例对象被销毁时才会将地址回收。类似实例变量,只能用
(3)静态代码块
- 静态代码块是类中使用
static
关键字修饰的代码块。 - 格式
- 特点:
- 第一次用到本类时,静态代码块执行唯一的一次
- 静态内容总是优先于非静态内容,所以静态代码块优先于构造方法执行
- 主要用途:一次性地对静态成员变量进行赋值,在涉及jdbc时,静态代码块很有用
3. 示例
(1)单一实例的情况
在package
cn.itcast.day01.demo02
中建立一个Phone
类在同一个package
cn.itcast.day01.demo02
中建立一个DemoPhone
类,其中包含main
方法。由于和Phone
类在同一个package中,所以不需要导包- 内存示意图如下
- 可见
- 类信息存储于方法区;类实例存储于堆区;
- 在运行过程中,每次方法调用会在栈区建立一个栈帧,程序转至此栈帧运行,方法运行完毕后其栈帧弹出
- 类的实例变量保存的是堆中类对象的地址,从而可以通过实例变量引用到对象
(2)两个同类实例的情况
修改
DemoPhone
,增加一个同类实例,如下- 此时内存示意图如下
- 可见,同一个类的多个实例,在第一个实例被创建时给实例方法分配入口地址,之后的其他实例共用同一个入口地址
(3)两个引用变量引用到同一实例的情况
- 再修改
DemoPhone
,two
不指向和one
相同的对象 - 此时内存示意图如下
1. 类作为方法的参数
- java中只有一种传参数方法:
值传递
- 对于引用型变量(String/数组/类对象…)等,传递的是地址值
- 示例
- 这个示例中,我们假设堆中类对象的地址为
0x666
,在main方法中创建的对象变量one
保存了0x666
这个地址,从而可以引用到堆中的对象。当one
作为实参传入方法method
时,由于是值传递,形参param
也保存了0x666
,从而可以访问到堆中的对象
2. 类作为方法的返回值
- java中只有一种传返回值方法:
值传递
- 对于引用型变量(String/数组/类对象…)等,传递的是地址值
- 示例
- 这个示例中,我们假设堆中类对象的地址为
0x666
,在getPhone
方法中创建的对象变量one
保存了0x666
这个地址,从而可以引用到堆中的对象。当one
作为返回值赋值给main方法中的对象变量two
时,由于是值传递,two
也保存了0x666
,从而可以访问到堆中的对象
定义的位置不同
- 局部变量:在方法内部
- 成员变量:在方法外部,直接写在类中。类变量只能声明一次,但在类方法中可以出现和类变量同名的局部变量(可以是多个),且方法中局部变量优先
作用范围不一样
- 局部变量:只有方法内部可以使用,出了方法就不能用了
- 成员变量:整个类全部可以通用
默认值不一样
- 局部变量:没有默认值,如果要想使用,必须手动赋值
- 成员变量:有默认值,规则和数组一样
内存位置不一样
- 局部变量:栈内存
- 成员变量:堆内存
生命周期不一样
- 局部变量:随着方法进栈而诞生,随着方法出栈而消失
- 成员变量:随着对象创建而诞生,随着对象回收而消失
- 构造方法是专门用来创建对象的方法,在用
new
创建对象时,就是在调用构造方法 - 格式:
- 注意:
- 构造方法的名称必须和所在类的名称完全一样(包括大小写)
- 构造方法不要写返回值类型(void也不要写),且方法中不能有
return
- 如果没有手动写出构造方法,编译器会隐式定义并使用空构造方法,称为
默认构造方法
,形如public 类名称(){}
,它什么也不做 - 一旦编写了至少一个构造方法,编译器不会生成使用空构造方法了
- 构造方法可以进行重载
1. private关键字
使用
private
关键字修饰的成员成为类的私有成员,在本类中可以随意访问,但在类外不能通过示例名,成员名
的形式直接访问了对于每个私有成员变量,定义一对
setter
和getter
方法,间接对其进行设置和访问。- 通过这种方式,可以对数据进行一些检查和预处理,可以提高代码的安全性
- 一般将
setter
和getter
方法命名为getXxx()
和setXxx()
。对于boolean
,其getter
方法一般命名为isXxx()
示例
定义一个person类
在main方法中,通过
setter
和getter
处理私有成员变量
2. this关键字
-
this
是一个对象用来引用自身的引用名,可以引用对象的属性和方法 - 通过谁调用的方法,谁就是
this
。比如在main
中有Person
类实例student
,执行student.setAge()
调用时,setAge()
方法内部的this
就指代student
(他们保存了同一个堆中Person
类对象的地址) -
this
的使用场景
this
通常可以省略当方法的局部变量和类的成员变量同名时,根据 “就近原则” ,优先使用局部变量。如果要在方法中访问同名的类成员变量,可以通过
this.成员变量名
的形式
this
用来调用同一个类的其他构造方法当一个类有多个构造方法时,推荐手动实现参数最多的构造,其他参数少的构造用this调用参数最多的来实现
3. Java Bean
一个标准的类应该包含以下四个部分
- 所有成员变量使用
private
关键字修饰 - 每个成员变量编写一对
setter
和getter
方法 - 编写一个无参数构造方法
- 编写一个全参数构造方法
- 所有成员变量使用
这样的一个标准类称为
Java Bean
在IntelliJ IDEA环境中,写完成员变量后,可以通过
菜单栏code -> Generate
自动生成getter
和setter
以及构造方法Constructor
- 通常创建一个对象,其内容是允许改变的
- 可以利用 “不可变类” 构造 “不可变对象”, 其内容不允许改变。例如
String
类就是不可变的(String
相关操作中,都是根据初始化字符串返回新字符串,不能修改String
的初始化值) - 一个不可变类具有以下特点
- 其所有数据域都是
private
的 - 没有可以设置数据域的方法(没有
setter
方法) - 没有返回一个指向数据域的引用的访问器方法