第二章 类与对象的基本概念
一、面向对象的程序设计方法概述
面向对象的程序设计
- 与结构化程序设计相比,更符合人类认识现实世界的思维方式
- 已成为程序设计的主流方式
- 涉及的抽象概念:抽象,封装,继承,多态
1.面向对象的程序设计方法概述
对象
现实世界中,万物皆对象,都具有各自的属性,对世界都呈现各自的行为
程序中,一切都是对象,都具有标识,属性和行为(方法),通过一个或多个变量来保存其状态,通过方法实现他的行为。
类
将属性及行为相同或相似的对象归为一类
类可以看成是对象的抽象,代表了此类对象所具有的共同属性和行为
在面向对象的程序设计中,每一个对象都属于某个特定的类
结构化程序设计
通常由若干个程序模块组成,每个程序模块都可以是子程序或函数
数据和功能分离,代码难于维护和复用
面向对象程序设计
基本组成单位是类
程序在运行时由类生成对象,对象是面向对象程序的核心
对象之间通过发送消息进行通信,互相协作完成相应功能
1.抽象
抽象:忽略问题中与当前目标无关的方面,以便更充分地注意与当前目标有关的方面。
过程抽象:将整个系统的功能划分为若干部分,强调功能完成的过程和步骤,而隐藏其具体的实现。 C语言的函数,Java语言的方法。
数据抽象:面向对象软件开发方法的主要特点。 将系统中需要处理的数据和这些数据上的操作结 合在一起,抽象成不同的抽象数据类型—类。类 既包括数据,也包括对数据的操作。
2.封装
封装:
是一种信息隐蔽技术;
利用抽象数据类型将数据和基于数据的操作封装在一起;
用户只能看到对象的封装界面信息,对象的内部细节对用户是隐蔽的;
封装的目的在于将对象的使用者和设计者分开,使用者不必知道行为实现的细节,只需使用设计者提供的消息来访问对象。
封装的定义:清楚的边界(所有对象的内部信息被限定在这个边界内);接口(对象向外界提供的方法,外界可以通过这些方法与对象进行交互);受保护的内部实现(功能的实现细节,不能从类外访问)。
类的示意图,体现了封装和数据抽象。
3.继承
是指新的类可以获得已有类(称为超类、基类或父类)的属性和行为,称新类为已有类的派生类(也称为子类)
在继承过程中派生类继承了基类的特性,包括方法和实例变量
派生类也可修改继承的方法或增加新的方法,使之 更适合特殊的需要
有助于解决软件的可重用性问题,使程序结构清晰, 降低了编码和维护的工作量
单继承
任何一个派生类都只有单一的直接父类
类层次结构为树状结构
多继承
一个类可以有一个以上的直接父类
类层次结构为网状结构,设计及实现比较复杂
Java语言仅支持单继承
对象的实体构成示意图(假设父类可以实例化,也就是父类不是抽象类)
4.多态
一个程序中同名的不同方法共存
主要通过子类对父类方法地覆盖来实现
接口方法覆盖、方法重载也是实现多态的具体形式。
不同类的对象可以响应同名的消息(方法),具体的实现方法却不同
使语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序方法同名问题
二、类与对象
在程序中,对象是通过一种抽象数据类型来描述的, 这种抽象数据类型称为类(Class)
一个类是对一类对象的描述。类是构造对象的模板
对象是类的具体实例
1.类的声明
声明形式 [public] [abstract] [final] class类名称 [extends 父类名称] [implements 接口名称列表]
{
变量成员声明及初始化;
方法声明及方法体;
}
关键字
class 表明其后声明的是一个类。
extends 如果所声明的类是从某一父类派生而来,那么,父类的名字应写在extends之后
implements 如果所声明的类要实现某些接口,那么,接口的名字应写在implements之后
修饰符
- 可以有多个,用来限定类的方式
- public 表明此类为公有类
- abstract 指明此类为抽象类(只能用做超类,不能实例化;只有当子类实现了抽象超类中的所有抽象方法,子类才不是抽象类,才能产生实例)
- final 指明此类为终结类(终结类不能被继承,终结方法不能被子类覆盖)
- 类声明体
- 变量成员声明及初始化 可以有多个
- 方法声明及方法体 可以有多个
2.对象的声明与引用
变量和对象
变量除了存储基本数据类型的数据,还能存储对象的引用,用来存储对象引用的变量称为引用变量
类的对象也成为类的实例
对象的声明
格式
类名 变量名
声明一个引用变量时并没有对象生成
对象的创建
- 生成实例的格式:
- new <类名>()
- 其作用是:
- 在内存中为此对象分配内存空间
- 返回对象的引用(reference,相当于对象的存储地址)
- 引用变量可以被赋以空值(默认值也是null)aclock = null;
自动装箱拆箱
Java 5新增特性,基本数据类型的自动装箱拆箱
自动装箱
- Java 5之前:Integer i = new Integer(3);
- Java 5:Integer i = 3;
自动拆箱
- Java 5 之前:int j = i.intValue(); //i为Integer类型 的对象
- Java 5:int j = i; //i为Integer类型的对象
- Integer是int的包装类,int则是java的一种基本数据类型
自动装箱拆箱
支持自动装箱和自动拆箱的类型
这些都是基本数据类型的包装类
3.数据成员
表示Java类的状态
声明数据成员必须给出变量名及其所属的类型,同时还可以指定其他特性
在一个类中成员变量名是唯一的
数据成员的类型可以是Java中任意的数据类型(简单类型,类,接口,数组)
声明格式
[public | protected | private] [static] [final] [transient] [volatile] 变量数据类型 变量名1[=变量初值],变量名2[=变量初值],…;
格式说明
- public、protected、private 为访问控制符
- static指明这是一个静态成员变量
- final指明变量的值不能被修改
- transient指明变量是临时状态(不必持久化(serialize))
- volatile指明变量是一个共享变量
实例变量
没有static修饰的变量称为实例变量
用来存储所有实例都需要的属性信息,不同实例的属性值可能会不同
可通过下面的表达式访问实例属性的值 <实例名>.<实例变量名>
实例变量属于特定的对象,在创建对象时存储在堆中。
public class Circle {
int radius;
}
public class ShapeTester {
public static void main(String args[]) {
Circle x;
x = new Circle();
System.out.println(x);
System.out.println("radius = " + x.radius);
}
}
类变量
也成为静态变量,声明时需加static修饰符
不管类的对象有多少,类变量只存在一份,在整个类中只有一个值
类初始化的同时就被赋值
适用情况:类中所有对象都相同的属性;经常需要共享的数据;系统中用到的一些常量
引用格式 <类名 | 实例名>.<类变量名>
public class Circle {
static double PI = 3.14159265;
int radius;
}
public class ClassVariableTester {
public static void main(String args[]) {
Circle x = new Circle();
System.out.println(x.PI);
System.out.println(Circle.PI);
Circle.PI = 3.14;
System.out.println(x.PI);
System.out.println(Circle.PI);
}
}
当我们生成Circle类的实例时,在每一个实例中并 没有存储PI的值,PI的值存储在类中
final修饰符
实例变量和类变量都可以被声明为final
final实例变量必须在每个构造方法结束之前赋初值,以保证使用之前会被初始化
final类变量必须在声明的同时初始化
方法参数也可以被声明为final,防止在方法体中给参数重新赋值
不给参数重新赋值是比较好的编码方式。因为可能会引起误会。
实例变量即使被声明为final,也是保存在堆中。
Review
▫ 抽象,封装,继承,多态
▫ 自动装箱和自动拆箱
▫ static修饰类的成员变量(Java中方法的局部变量不能是static修饰的). ➔类变量
▫ final修饰类的成员。也是实例变量,在new对象的时候会和其他实例变量一起分配空间。
▫ final 修饰方法参数,防止方法内部对其重新赋值而引起误解。
▫ final修饰方法局部变量:作为常量。
▫ 变量的位置:堆和栈。
4.方法成员
定义类的行为。一个对象能够做的事情,我们能够从一个对象中取得的信息
可以没有,也可以有多个;一旦在类中声明了方法,它就成为了类声明的一部分
分为实例方法和类方法
声明格式
[public | protected | private] [static] [final] [abstract] [native] [synchronized]
返回类型 方法名([参数列表]) [throws exceptionList]
{
方法体
}
格式说明
方法修饰
- public、protected、private 为存取控制符
- static指明方法是一个类方法
- final指明方法是一个终结方法
- abstract指明方法是一个抽象方法
- native用来集成java代码和其它语言的代码
- synchronized用来控制多个并发线程对共享数 据的访问
返回类型
- 方法返回值的类型,可以是任意的Java数据类型
- 当不需要返回值时,返回类型为void
参数类型
- 简单数据类型,
- 引用类型(数组、类或接口)
- 可以有多个参数,也可以没有参数,方法声明时的参数称为形式参数
方法体
- 方法的实现
- 包括局部变量的声明以及所有合法的Java指令
- 局部变量的作用域只在该方法内部
throws exceptionList
- 用来处理异常
方法调用
给对象发消息意味者调用对象的某个方法
- 从对象中取得信息
- 修改对象的状态或进行某种操作
- 进行计算及取得结果等
调用格式
<对象名>.<方法名>([参数列表])
称点操作符“.”前面的<对象名>为消息的接收者
参数传递
- 值传递:参数类型为基本数据类型时
- 引用传递:参数类型为引用变量时(比如对象类型或数组时)
实例方法
表示特定对象的行为
声明时前面不需要加上static修饰符
使用时需要发送给一个类实例
关键字this代表此方法的接收者对象
不同的类中可以声明相同方法名的方法 ;使用时,系统会根据接收者对象的类型找到相应类的方法
类方法
也成为静态方法,表示类中对象的共有行为
声明时前面需加static修饰符
不能被声明为抽象的
类方法可以在不建立对象的情况下用类名直接调用,也可用类实例调用。
类方法中不能访问实例变量,否则会发生编译错误
可变长参数
从Java 5开始,可以在方法的参数中使用可变长 参数
可变长参数使用省略号表示,其实质是数组
例如,”String … s”表示”String[] s”
对于可变长参数的方法,传递给可变长参数的实际参数可以是多个对象,也可以是一个对象或者是没有对象
static double maxArea(Circle c, Rectangle... varRec) {
Rectangle[] rec = varRec;
for (Rectangle r : rec) {
}
}
public static void main(String[] args) {
Circle c = new Circle();
Rectangle r1 = new Rectangle();
Rectangle r2 = new Rectangle();
System.out.println("max area of c, r1 and r2 is " + maxArea(c, r1,
r2));
System.out.println("max area of c and r1 is " + maxArea(c, r1));
System.out.println("max area of c and r2 is " + maxArea(c, r2));
System.out.println("max area of only c is " + maxArea(c));
}
5.类的组织
包的概念
是一组类的集合,一个包可以包含若干个类文件,还可以包含若干个包
包的作用:
- 将相关的源代码文件组织在一起
- 类名的空间管理,利用包来划分名字空间,便可以避免类名冲突
- 提供包一级的封装及存取权限
包的命名
- 每个包的名称必须是“独一无二”的
- Java中包名使用小写字母表示
- 命名方式建议
- 将机构的Internet域名反序,作为包名的前导
- 若包名中有任何不可用于标识符的字符,用下划线替代
- 若包名中的任何部分与关键字冲突,后缀下划线
- 若包名中的任何部分以数字或其他不能用作标识符 起始的字符开头,前缀下划线
编译单元与类空间
- 一个Java源代码文件称为一个编译单元,由三部分组成
- 所属包的声明(省略,则属于默认包)
- Import(引入)包的声明,用于导入外部的类
- 类和接口的说明
- 一个编译单元中只能有一个public类,该类名与文件名相同,编译单元中的其他类往往是public类的辅助类,经过编译,每个类产生一个class文件
- 利用包来划分空间,便可以避免类名冲突
包的声明
- 命名的包 例如:
package Mypackage;
- 默认包 不含有包声明的编译单元是默认包的一部分
包与目录
- Java使用文件系统来存储包和类
- 包名就是文件夹名,即目录名
- 目录名并不一定是包名
- 用javac编译源程序时,如遇到当前目录(包)中没有声明的类,就会以环境变量classpath为相对查 找路径,按照包名的结构来查找。因此,要指定 搜寻包的路径,需设置环境变量classpath
引入包
- 为了使用其它包中所提供的类,需要使用import语句引入所需要的类
- Java编译器为所有程序自动引入包java.lang
- import语句的格式
import package1[.package2…]. (classname |*);
- 其中package1[package2…]表明包的层次,它对应于文件目录
- classname则指明所要引入的类名
- 如果引入一个包中的所有类,则可以使用星号(*)来代替类名
静态引入
- Java5新特性
- 在Java 5之前,通过类名使用类的静态成员。例如, Math.PI,Math.sin(double)
- 如果在程序中需要多次使用静态成员,则每次使用都 需要加上类名
- 静态引入分为两种:单一引入,全体引入
- 单一引入是指引入某一个指定的静态成员,例如: import static java.lang.Math.PI;
- 全体引入是指引入类中所有的静态成员,例如: import static java.lang.Math.*;
程序编译方法
javac -d . Person.java `` javac -d . Student.java
这样编译器会自动创建目录并保存class文件
如果当前位置不在上述编译程序的目录下,可以 利用classpath指定JVM搜索类文件的路径。比如: java -classpath cn.edu.nwpu.person.Student
6.访问控制
类的访问控制
体现封装和细节隐藏的程度
类的访问控制只有public及无修饰符(缺省类)两种
访问权限符与访问能力之间的关系如表
类成员的访问控制
公有(public)可以被其他任何对象访问(前提是对类成员所在类有访问权限)
保护(protected)可被同包任何类,同一类及其子类的实例对象访问
私有(private)只能被这个类本身访问,在类外不可见
默认(default)仅允许同一个包内访问:又被称为“包访问权限”
面向对象程序设计为了体现细节隐藏,应该尽可能把数据成员设计为private。
如果要允许其它类访问radius的值,就需要在Circle类中声明 相应的公有方法。通常有两类典型的方法用于访问属性值, get方法及set方法
get方法
功能是取得属性变量的值
get方法名以“get”开头,后面是实例变量的名字
一般具有以下格式: public
set方法
功能是修改属性变量的值
set方法名以“set”开头,后面是实例变量的名字
一般具有以下格式:public void set
{
关键字this的使用
如果形式参数名与实例变量名相同,则需要在实例变量名之前加this关键字,否则系统会将实例变量当成形式参数。
在上面的set方法中,如果形式参数为radius,则需要在成员变量radius之前加上关键字this。
public void setRadius(int radius){ this.radius = radius; }
Review
实例方法,类方法
- 类方法不能访问实例变量
• package
- 类的组织和管理
- classpath 的使用
- import 以及import static
访问控制
类的访问控制: public 默认
类成员的访问控制: public 默认, protected, private
set和get方法:
Package补充
Alpha类的访问控制 为public,则其成员的可见性:
三、对象的初始化和回收
对象的初始化
系统在生成对象时,会为对象分配内存空间,并自动调用构造方法对实例变量进行初始化
对象回收
对象不再使用时,系统会调用垃圾回收程序将其占用的内存回收
1.构造方法
一种和类同名的特殊方法;
用来初始化对象;
Java中每个类都有构造方法,用来初始化该类的一个新的对象;
没有定义构造方法的类,系统自动提供默认的构造方法。
构造方法的特点:
- 方法名与类名相同
- 没有返回类型,修饰符void也不能有
- 通常被声明为公有的(public)
- 可以有任意多个参数
- 主要作用是完成对象的初始化工作
- 不能在程序中显式的调用
- 在生成一个对象时,系统会自动调用该类的构造方法为新生成的对象初始化
默认构造方式
系统提供的默认构造方式
- 如果在类的声明中没有声明构造方法,则Java编译 器会提供一个默认的构造方法
- 默认的构造方法没有参数,其方法体为空
- 使用默认的构造方法初始化对象时,如果在类声明中没有给实例变量赋初值,则对象的属性值为零或空
自定义构造方法
自定义构造方法与方法重载
- 可在生成对象时给构造方法传送初始值,使用希望的值给对象初始化
- 构造方法可以被重载,构造方法的重载和方法的重载一致
- 一个类中有两个及以上同名的方法,但参数表不同,这种情况就被称为方法的重载。在方法调用时,Java可以通过参数列表的不同来辨别应调用哪一个方法。
自定义无参的构造方法
- 无参的构造方法对其子类的声明很重要。如果在一个类中不存在无参的构造方法,则要求其子类声明时必须声明构造方法,否则在子类对象的初始化时会出错。
- 在声明构造方法时,好的声明习惯是
- 不声明构造方法
- 如果声明,至少声明一个无参构造方法
用户在进行类声明时,如果没有声明任何构造方法,系统 会赋给此类一个默认(无参)的构造方法。但是,只要用 户声明了构造方法,即使没有声明无参的构造方法,系统也不再赋默认的构造方法。
this关键字的使用
可以使用this关键字在一个构造方法中调用另外的构造方法;
代码更简洁,维护起来更容易;
通常用参数个数比较少的构造方法调用参数个数最多的构造方法。
2.内存回收技术
内存回收技术
当一个对象在程序中不再被使用时,就成为一个无用对象
- 当前的代码段不属于对象的作用域
- 把对象的引用赋值为空
Java运行时系统通过垃圾收集器周期性地释放无用对象所使用的内存
Java运行时系统会在对对象进行自动垃圾回收前,自动调用对象的finalize()方法
垃圾收集器
自动扫描对象的动态内存区,对不再使用的对象做上标记以进行垃圾回收
作为一个线程运行
- 通常在系统空闲时异步地执行
- 当系统的内存用尽或程序中调用**System.gc()**要求进行垃圾收集时,与系统同步运行。
finalize()方法
在类java.lang.Object中声明,因此 Java中的每一个 类都有该方法
用于释放系统资源,如关闭打开的文件或socket等
声明格式 protected void finalize() throws throwable
如果一个类需要释放除内存以外的资源,则需在类中重写finalize()方法(不可靠)
与C++析构函数不同,finalize()方法在垃圾回收时才会调用。注意:
- 对象不一定会被回收
- 垃圾回收不是析构函数
- 垃圾回收至于内存有关
- 垃圾回收和finalize()都是靠不住的,因为系统执行垃圾回收的时机不确定
同C和C++的区别
C语言中通过free来释放内存,C++中则通过delete来释放内存
在C和C++中,如果程序员忘记释放内存,则容易造成内存泄漏甚至导致内存耗尽
在Java中不会发生内存泄漏情况,但对于其它资源, 则有产生泄漏的可能性
四、枚举类型
Java 5的新特色,可以取代Java 5之前的版本中使用的常量
需要一个有限集合,而且集合中的数据为特定的值时,可以使用枚举类型
格式:
[public] enum 枚举类型名称 [implements 接口名称列表]
{
枚举值;
变量成员声明及初始化;
方法声明及方法体;
}
枚举类型特点
枚举类型是类,而不是简单的整数类型,枚举值是类的对象。
枚举类型是java.lang.Enum类的子类。
枚举类型没有public的构造函数
枚举值是public、static、final的。
1.枚举类型的默认方法
- 取得枚举值的数组
- public static T[] values()
- 取得枚举值对应的字符串
- public String toString()
- 取得对象在枚举类型中的索引
- public final int ordinal()
- 字符串转换为枚举值。
- public static T valueOf(String)
2.枚举类型的自定义属性和方法
枚举类型相当于自定义一个类。因此可以给该类自定义数据和方法。
枚举类型的构造函数默认是private,而且必须是private。
即枚举类型会由JVM在加载的时候,实例化枚举对象,你在枚举类中定义了多少个就会实例化多少个,JVM为了保证每一个枚举类元素的唯一实例,是不会允许外部进行new的,所以会把构造函数设计成private,防止用户生成实例,破坏唯 一性。
因为枚举对象的实例化是由jvm完成的,所以可 以确保该对象只被创建一次。因此,利用枚举可以完美的实现“单例模式”。这是目前最有效的 单例模式实现方法。
补充材料:变量初始化
变量声明时可以设定初值,比如: class Car { int door=4; }
可以在构造函数中设置初值,比如:class Car {Car(){door=5;}}
实例变量的初始化块。可以把构造函数中共同的处理放到初始化块中。编译器会自动地把初始化块的代码插入到构造函数最前面。
初始化块可以写多个。
对于类变量,可以使用静态初始化块。静态初始化块会在类加载时执行。
静态初始化块可以有多个,会按照代码顺序执行。

执行顺序:变量初始化最先执行。
- 在加载类时执行一次静态初始化块(之后不再调用)
- 在每次初始化实例对象时:先执行非静态初始化块,在执行构造方法。
Review
构造方法
- 没有返回值
- 默认构造方法
- 重载: 方法签名(method signature )
内存回收
- 变量的自动释放
- 显式地给引用变量赋值null
- finalize()方法由垃圾回收器执行,执行时机不确定,因此不可靠。
枚举
- 特殊的类,构造方法总是private的
- 枚举值是枚举类型的实例,加载类时由jvm创建(单例模式)
变量初始化
- 实例变量:初值→初始化块→构造方法
- 类变量: 初值→静态初始化块
五、应用举例
七、注解
注解也叫元数据,就是用来描述数据的数据。它其实就是程序代码里的特殊标记,这些标记可以在编 译、类加载、运行时被读取并执行相应的处理。注 解主要用于告知编译器要做什么事情,在程序中可 对任何程序元素进行注解。
根据注解的作用可以将注解分为基本注解、元注解 (或称元数据注解)与自定义注解三种
注解可以理解为对代码贴的标签,这个标签对 代码进行说明和解释。不过这个标签和代码的 注释(comment)不同:
- 注释只存在于代码中,编译器会简单地跳过注释。
- 而编译器会处理注解。而且根据注解自身的特点,可以保留到运行时的内存中。
基本注解:Java提供的注解。主要有三种:
- java.lang.Override: 重载父类的方法
- java.lang.Deprectated: 方法已过时
- java.lang.SuppressWarnings: 抑制编译器警告
1.自定义注解
格式:
[public | ] [abstract | final] @interface 注解名 { 元素定义; }
两类特殊注解
标记注解:没有元素定义
单值注解:只定义一个元素
注解的使用:在注解名前加上”@“符号,并在其后的括号中对个元素赋值
MyMarkerAnnotation { }
MySingleValueAnnotation {
String value ( );
}
MyAnnotation {
String value();
int i();
float f() default 2.0f;
double d();
}
public class AnnotationTester {
public static void main(String[] args) { }
//表示覆盖父类的方法
public String toString() {
return "";
}
public void method1() {
}
public void method2() {
}
public void method3() {
}
public void method4() {
}
}
2.元注解简洁
元注解:注解的注解
在java.lang.annotation
包下,提供了4个元注解:
- Retention:表明注解的保留程度,需要提供一个
java.lang.annotation.RetentionPolicy
枚举类型的枚举值RetentionPolicy.SOURCE
RetentionPolicy.CLASS
RetentionPolicy.RUNTIME
- Target:指定注解所适用的程序元素的类型,需要提供一个
java.lang.annotation.Target
枚举类型的枚举值:- ANNOTATION_TYPE , CONSTRUCTOR , FIELD , LOCAL_VARIABLE , METHOD , PACKAGE , PARAMETER , TYPE , and TYPE_PARAMETER
- 从Java 8开始,注解已经能应用于任何目标。这其中包括 new操作符、类型转换、instanceof检查、泛型类型参数, 以及implements和throws子句。
- Documented:表明可以将注解通过Javadoc或者类似工具进行文档化
- Inherited:表明注解被自动继承
3.注解的处理
从Java 6开始,可以对注解进行处理, javax.annotation.processing
包提供了对注解的处理
八、本章小结
本章内容
- 面向对象程序设计的基本概念和思想
- Java语言类与对象的基本概念和语法,包括类的声明、类成员的访问,以及对象的构造、初始化和回收
本章要求
- 理解类和对象的概念
- 熟练使用类及其成员的访问控制方法
- 熟练掌握各种构造方法
- 了解java的垃圾回收机