Java程序设计(2021春)——第二章笔记与思考
Java程序设计(2021春)——第二章笔记与思考
本章概览:
面向对象方法的特征
抽象:从同类型对象中抽象出共同属性
封装:把数据和处理数据的方法封到一个类中
继承:在已有的类的基础上开发新的类
多态:在由继承的环境下,超类(父类)和子类都能响应共同的消息,但是响应消息的具体实现办法可以不同
类与对象基础
类的声明
对象的创建
数据成员
方法成员
包
类的访问权限控制
类成员的访问权限控制
对象初始化和回收
构造方法(初始化)
内存回收
枚举类型
简单枚举类型
枚举类(功能更为强大)
应用举例
银行账号示例
2.1 面向对象方法的特性
抽象
抽象的思想:忽略问题中与当前目标无关的方面,只关注与当前目标有关的内容。
封装
封装是一种信息隐蔽技术。利用抽象数据类型将数据和基于数据的操作封装在一起;用户只能看到对象的封装界面信息,对象的内部细节对用户是隐蔽的;封装的目的在于将对象的使用者和设计者分开,使用者不必知道行为实现的细节。
继承
继承是一种基于已有类产生新类的机制。是指新的类可以获得已有类(成为超类、基类或父类)的属性和行为,称新类为已有类的子类(也成为派生类),在Java中一般用超类、子类的术语;在继承过程中子类继承了超类的特性,包括方法和实例变量;子类也可以修改继承的方法或增加新的方法;有助于解决软件的可重用性问题,使程序结构清晰,降低了编码和维护的工作量。
单继承
一个子类只有单一的直接超类。
多继承
一个子类可以有一个以上的直接超类。
在Java中仅支持单继承。
多态
在有继承的情况下,超类和他的子类都可以响应同名的消息,但是这些对象对这些同名的消息的实现方式可以是不一样的。主要通过子类覆盖从超类继承过来的方法来实现多态。
2.2-1 类声明与对象创建
类与对象的关系
类是对一类对象共同的属性和行为的一种抽象,是一种抽象出来的数据类型;
对象是类的具体的实例
类声明
/*完整语法结构*/
/*方括号里的关键字是可选项,可有可无*/
[public][abstract|final]class类名称//class关键字是必须的,表示后面定义的是一个类
[extends父类名称]
[implements接口名称列表]//大括号中为类体
{
数据成员声明及初始化;
方法声明及方法体;
}
class
表明其后声明的是一个类。extends
如果所声明的类是从某一父类派生而来,那么,父类的名字应该写在extends
之后。即,当我们要继承已有的类,形成新类时,要用extends
关键字implements
(用来实现接口)如果所声明的类要实现某些接口,那么,接口的名字应写在implements
之后。public
表明此类为公有类(后续章节介绍类的访问控制属性时会介绍public
)。abstract
是抽象的意思,有abstract
修饰的类是抽象类(后续章节会介绍)。final
表明这个类是终结类,表明这个类不可以被继承。
对象引用声明
语法
类名 引用变量名;
例:
Clock
是已经声明的类名,声明引用变量aclock
,勇于存储该对象的引用。
Clock aclock;
此时,对象还没有生成,我们只是创建了一个引用,且为空引用。
对象的创建
语法
意思是分配新的内存空间(在运行时分配),勇于存放一个Clock
类型的对象。此时没有进行初始化,如果希望初始化,则需要在圆括号中给出初始化参数(后续会介绍)
new <类名>()
例:
aclock = new Clock();
new
的作用是在内存中为Clock
类型的对象分配内存空间,同时返回对象的引用。
医用变量可以被赋以空值,如
aclock = null;
2.2-2 数据成员
数据成员用来表示对象的状态,也可以存放在整个类所有对象之间要共享的数据;数据成员可以是任意的数据类型,如基本类型,另外一个类的对象,数组等。
语法形式
方括号中为可选项,在需要的时候写,不需要的时候可以不写。
[public|protected|private]
[static][final][transient][volatile]
数据类型 变量名1[=变量初值],变量名2[=变量初值],...;
说明
- 数据类型必须说明,可以是基本类型,也可以是类类型,还可以是数组(数组也是对象)。
public
protected
private
称为访问控制符,是用来控制对类成员的访问权限的。static
指明这是一个静态成员变量(类变量)(后面会介绍)。final
指明变量的值不可以被修改。transient
指明变量不需要序列化(后面介绍文件IO时会涉及到)。volatile
指明变量是共享变量。
实例变量
-
没有
static
修饰的变量(数据成员)成为实例变量。 -
实例变量,也叫属于对象的属性(实例属性),是用来描述每个对象的属性的,不同对象的属性即实例变量的值往往是不同的,这些值用来区分此对象与彼对象。
-
访问实例变量要通过变量名访问,语法形式为
<实例名>.<实例变量名>
。不是所有实例变量都可以这样访问,要注意属性或变量的访问控制权限。
例1:圆类
/*圆类保存在文件Circle.java中,测试类保存在文件ShapeTester.java中,两文件放在相同的目录下*/
public class Circle{
int radius;
}
public class ShapeTester{
public static void main(String args[]){//定义了一个主方法
Circle x;//定义了一个圆类的引用
x = new Circle();//用new获得一个新的圆对象,并把引用赋给x
System.out.println(x);
System.out.println("radius = " + x.radius);
}
}
输出结果(在本机测试,与网课中不同)
Circle@379619aa
radius = 0
对输出结果的说明:
所有的类中都有默认的toString()
方法,默认的toString
的返回:getClass().getName()+"@"+Integer.toHexString(hashCode())
,即,先取得类的类名并转成字符串,输出@
符号,然后调用hashCode()
方法,将对象的哈希码转成十六进制形式的字符串。
该默认的toString
不是很有意义,后续章节将会介绍如何自己写一个toString
覆盖已有的toString
。
例2:矩形类
/*矩形类保存在Recrangle.java中,测试类保存在ShapeTester.java中,两文件保存在相同目录下*/
public class Rectangle {
double width = 10.128;//在类里面已经定义好初始值了
double height = 5.734;//在类里面已经定义好初始值了
}
public class ShapeTester{
public static void main(String args[]){//定义了一个主方法
Circle x;
Rectangle y;
x = new Circle();//圆对象依然没初始化(和上方代码中相同)
y = new Rectangle();
System.out.println(x + " " + y);
}
}
输出结果
Circle@cac736f hello.Rectangle@5e265ba4
类变量
- 整个类 所有对象 共享的数据称作类变量(静态变量)。
- 用
static
修饰。 - 在整个类中只有一个值,存储一份即够。
- 类初始化的同时就被赋值。
- 使用情况:类中所有对象都相同的属性;需要经常共享的数据;系统中用到的一些常量值。
- 引用形式:
<类名|实例名>.<类变量名>
,无需用对象名使用,但是用对象名使用也可。
例3:具有类变量的圆类
public class Circle{
static double PI = 3.14159265;//类变量(静态变量)圆里面所有对象都共享常量pi
int radius;
}
//当我们生成Circle类的实例时,在每一个实例中并没有存储PI的值,PI的值储存在类中(只存一份)
对类变量进行测试
public class ClassVariableTester {
public static void main(String[] args) {
Circle x = new Circle();//构造圆对象,并把引用赋给x
System.out.println(x.PI);//通过<实例名>.<类变量名>输出PI的值
System.out.println(Circle.PI);//通过<类名>.<类变量名>输出PI的值
Circle.PI = 3.14;
System.out.println(x.PI);
System.out.println(Circle.PI);
}
}
输出结果如下
3.14159265
3.14159265
3.14
3.14
由以上测试可以看出,对象名访问和类名访问静态成员的时候是一样的
小结
此部分涉及的新名词较多,需要着重辨析不同名词所指代内容是否相同,及时予以总结。
2.2-3 方法成员
类定义的方法分为两类,类的方法和实例的方法。
类的方法是用来表示类的一些共同的行为或功能的。
实例的方法是用来表示每一个实例的功能或者行为的。
语法形式
在类中定义方法和C语言中定义函数很相像,只不过方法不是独立的,即不是全局的,是必须出现在类体里面的。
/*方括号中为可选内容*/
[public|protected|private]
[static][final][abstract][native][synchronized]
返回类型 方法名([参数列表])[throws exceptionList]//返回类型类似C中返回值类型,方法名类似C中函数名,参数列表类似C中函数形参表
{
方法体;//类似C中函数体
}
public
protected
private
控制访问权限。static
指明这是一个类方法(静态方法)。final
指明这是一个终结方法。abstract
指明这是一个抽象方法(只有方法原型,没有方法体体现)。native
用来集成java
代码和其他语言的代码(本课程不涉及)。synchronized
用来控制多个并发线程对共享数据的访问(在Java语言程序设计进阶中涉及)。- 返回类型:方法返回值的类型们可以是任意的Java数据类型;当不需要返回值时,返回类型为void。
- 参数类型:简单数据类型、引用类型(数组、类、接口);可以有多个参数,也可以没有参数,方法声明时的参数称为形式参数。
- 方法体:方法体的实现;包括局部变量的声明以及所有合法的Java语句;局部变量的作用域只限制在该方法体内部。
throw exceptionList
列出这个方法有可能抛出的异常,即异常抛出列表(在后续章节会介绍异常处理)
实例方法
实例方法属于每个对象,用来表示每个对象的功能或者行为。定义实例方法时不用static
关键字。
实例方法调用
给对象发消息,使用对象的某个行为/功能时调用方法(因为方法即代表对象的行为或者功能)。
语法
实例方法调用格式
<对象名>.<方法名>([参数列表])
<对象名>
为消息的接收者。
从内外来调用方法时通过对象名来调用;如果在类体里面方法之间互相调用,前面则不需要挂一个对象名,即在类体内部方法与方法之间可以直接互相调用,直接用方法名即可。
参数传递
值传递:参数类型为基本数据类型时,用实参初始化形参,实际上是一次性的单向传递,然后实参和形参之间没有关系了。
引用传递:参数类型为对象类型或数组时,传对象作为参数,实际上传的是对象的引用,实参名和形参名两个不同的名字指向了同一个对象。
例:具有实例方法的圆类
public class Circle{
static double PI = 3.14159265;
int radius;
public double circumference() {//求圆周长的方法
return 2 * PI * radius;
}
public void enlarge(int factor) {//将圆扩大若干倍的方法,参数是倍数
radius = radius * factor;
}
public boolean fitsInside(Rectangle r) {//参数是另一个类的对象,方法要计算是否可以将圆装入矩形并返回布尔值
return (2 * radius < r.width) && (2 * radius < r.height);
}
}
测试如下:
public class InsideTester {
public static void main(String[] args) {
Circle c1 = new Circle();
c1.radius = 8;
Circle c2 = new Circle();
c2.radius = 15;
Rectangle r = new Rectangle();
r.width = 20;
r.height = 30;
System.out.println("Circle 1 fits inside Rectangle:" + c1.fitsInside(r));
System.out.println("Circle 2 fits inside Rectangle:" + c2.fitsInside(r));
}
}
运行结果如下:
Circle 1 fits inside Rectangle:true
Circle 2 fits inside Rectangle:false
类方法(静态方法)
- 类方法用来表示类里所有对象的共同行为。
- 类方法也成为静态方法,声明前需加
static
修饰。 - 不能被声明为抽象方法。
- 可以通过类名直接调用,也可以通过类实例调用。
例 :温度转换
只需要方法,不需要对象。
public class Converter {
public static int centigradeToFahrenheit(int cent) {
return (cent * 9 / 5 + 32);
}
}
方法调用
Converter.contigradeToFahrenheit(10)
可变长参数
方法参数列表中可以定义可变长参数列表。
- 可变长参数使用省略号表示,其实质是数组,例如
String...s
表示String[] s
。 - 对于具体可变长参数的方法,传递给可变长参数的实际参数可以是0到多个对象。
例:可变长参数
static double maxArea(Circle c,Rectangle...varRec){
Rectangle[] rec = varRec;
for(Rectangle r:rec){//基于范围的for循环,定义一个Rectangle 的引用,冒号后面是 数组名,这个循环的作用是在循环每一次依次从数组中取出一个元素,赋给r,来访问这个元素。此方法也是访问可变长参数的常用手段
//...(没有具体实现方法体,只是做一个示意)
}
}
参数表中有一个圆对象的引用做参数,有若干个矩形对象的引用做参数,其中...
表示varRec
本质上是一个Rectangle
对象的引用数组,只是数组元素数不确定,具体调用形式如下。
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,r11,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));
}
2.2-4 包
包是一组类的集合;一个包可以包含若干个类文件,还可以包含若干个包。
包的作用
- 将相关的源代码文件组织在一起
- 类名的空间管理,利用包来划分空间可以避免类名冲突(程序规模比较大的时候,还要用到很多域地域库的时候,可能会发生重名,此时利用包来划名字空间,将一组一组功能相关的类放在一个包里)
- 提供包一级的封装及存取权限。
包的命名
- 每个包的名称必须是独一无二的(包名不重)。
- Java中包名使用小写字母表示。
- Java建议的命名方式为将机构的Internet域名反序作为包名的前导;若包名中有任何不可用于标识符的字符,用下划线替代;若包名中的任何部分与关键字冲突,后缀下划线;若包名中的任何部分以数字或其他不能用作标识符起始的字符开头,前缀下划线。
编译单元
- 一个Java源代码文件称为一个编译单元,由三部分组成:①所属包的声明(省略则属于默认包)②
Import
(引入)包的声明,用于导入外部的类(使用其他包里面的类)③(自己定义的)类和接口的声明。 - 一个编译单元中只能有一个
public
类,该类名与文件名相同,编译单元中的其他类往往是public
类的辅助类,经过编译,每个类都会产生一个class
文件,文件名必须是相同的;辅助的类不叫public
类,叫缺省的default
类,在内部起辅助作用。
包的声明
命名的包(Named Packages)
例如:package Mypackage;
默认包(未命名的包)
不含有包声明的编译单元是默认包的一部分。
包与目录
- 包名就是文件夹名,即目录名(每个包对应一个目录即文件夹)。
- 目录名不一定是包名(每个目录不一定对应一个包)。
引入包
-
引用包是为了使用包所提供的类,此时需要使用
import
语句引入所需要的类。 -
Java编译器回味所有程序自动引入包
java.lang
。 -
引入更多其它包时:
import
语句的格式:import package1[.package2...].(classname|*);
包名可以是多级,由上文介绍Java推荐包命名规则知域名反序形如
.packagei
;如果要引入包的某个类名,就将类名写在这classname
,如果要引入包里面所有的类,则可以使用*
代替类名。
静态引入
在内外使用一个类的静态成员,我们的引用方法是类名.静态方法名
来使用,如果大量使用某些静态方法,可以用静态引入简化之。
-
单一引入是指引入某一个指定的静态成员,例如
import static java.lang.Math.PI;
引入了PI
常量。 -
全体引入是指引入类中所有的静态成员,例如
import static java.lang.Math.*;
-
例如
import static java.lang.Math.PI; public class Circle{ int radius; public double circumference(){ return 2 * PI * radius;//此时可以直接使用PI而不用带着类名,方便。 } }
2.2-5 类的访问权限控制
类在不同范围是否可以被访问
类型 | 无修饰(默认) | public |
---|---|---|
同一包中的类 | 是 | 是 |
不同包中的类 | 否 | 是 |
类的成员访问权限控制
公有(public
)
可以被其他任何方法访问(前提是对类成员所属的类有访问权限)。
保护(protected
)
只可被同一类及其子类的方法访问。
私有(private
)
只可被同一类的方法访问。
默认(default
)
仅允许同一个包内的访问;又被称为包(package
)访问权限。
类成员在不同范围是否可以被访问
以下前提为该类可以被访问
类型 | private | 无修饰 | protected | public |
---|---|---|---|---|
同一类 | 是 | 是 | 是 | 是 |
同一包中的子类 | 否 | 是 | 是 | 是 |
同一包中的非子类 | 否 | 是 | 是 | 是 |
不同包中的子类 | 否 | 否 | 是 | 是 |
不同包中的非子类 | 否 | 否 | 否 | 是 |
即,在该类可以被访问的情况下,public
公有成员都可以被访问;protected
保护乘员主要在有继承关系的时候,可以被子类的其他方法访问,无论子类和超类是否在同一个包中;无修饰(默认)访问控制权限是包内的,即同一个类中的其他方法可以访问这种无修饰的数据成员和方法成员;private
最为严格,只可以被同一个类中的其他方法访问,其他类中不可以看到别的类中的private
成员,无论是否在同一个包中。
例:改进的圆类
public class Circle{
static double PI = 3.14159265;
private int radius;//将半径设为私有
public double circumference() {
return 2 * PI * radius;
}
}
再编译CircumferenceTester.java
public class CircumferenceTester{
public static void main(String[] args){
Circle c1 = new Circle();
c1.radius = 50;
Circle c2 = new Circle();
c2.radius = 10;
double circum1 = c1.circumference();
double circum2 = c2.circumference();
System.out.println("Circle 1 has circumference " + circum1);
System.out.println("Circle 2 has circumference " + circum2);
}
}
上述测试类会出现语法错误提示The field Circle.radius is not visible
,原因是类的private
私有成员在其它类中不能直接访问,后续会介绍如何通过公有接口访问私有成员(在类中提供公有接口,一般叫get
方法和set
方法,get
方法用于获取数据成员的值(属性),set
方法用于升格至数据成员的值(属性))。
get
方法(public
)
-
功能是取得属性变量的值。
-
get方法名以
get
开头,后面跟实例变量的名字。例如:
public int getRadius(){ return radius; }
以上实例中
getRadius
中的R
大写,为一般惯例(老师语)。笔者感觉像是驼峰命名法的应用(上学期一直这么用来着(逃
set
方法(public
)
-
功能是修改属性变量的值。
-
set
方法名以set
开头,后面是实例变量的名字。例如:
public void setRadius(int r){ radius = r; }
this
关键字
如果方法内的局部变量(包括形参)名与实例变量名相同,则方法体内访问实例变量时需要this
关键字。
例如:
public void setRadius(int radius){
this.radius = radius;
}
Tip:在set
方法中将形参名和要设置的变量名相同是一种常用的处理方式,可以使得set
方式的可读性很好。
2.3-1 对象初始化
当我们定义基本类型变量的时候往往会希望在定义变量的同时指定初始值,叫做变量的初始化;当我们构造对象的时候,也希望给对象指定一个初始状态。
- 对象初始化:系统在生成对象时,会为对象分配内存空间,并自动调用构造方法对实例变量进行初始化。其中构造方法是我们自己写的描述如何对对象进行初始化的方法,即描述初始化的算法。
- 对象回收:对象不再使用时,系统会调用垃圾回收程序将其占用的内存回收(下一节介绍)。
对象的初始化不会由编译器自动完成,这有别于基本类型的变量初始化。
构造方法
- 方法名与类名相同。
- 不定义返回类型,也不能写
void
。 - (大多数情况下)会被声明为公有的(
public
),也存在一些特殊场合我们不希望对象被随意构造时,也可能不声明为公有的(初学者无需考虑)。 - 是类的方法成员,可以有多个参数且可以有任意多个参数。
- 主要作用是完成对象的初始化,即,不要在构造方法中写过多的其他功能。
- 不能在程序中显示地调用。
- 在生成一个对象时,会自动调用该类的构造方法为新对象初始化。
- 每个类都有且必须有构造方法,如果我们没有显示地声明构造方法,编译器也不会报错,会隐含生成默认的构造方法。
默认构造方法
- 没有参数(内部类除外),方法体为空(内部类会在后续介绍)。
- 使用默认的构造方法初始化对象时,如果在类声明没有给实例变量赋初值,则对象的属性值为空或零。
例:一个银行账户类及测试代码
银行账户类
public class BankAccount {
String ownerName;
int accountNumber;
float balance;//余额
}
测试
public class BankTester {
public static void main(String[] args) {
BankAccount myAccount = new BankAccount();
System.out.println("ownerName=" + myAccount.ownerName);
System.out.println("accountNumber=" + myAccount.accountNumber);
System.out.println("balabne=" + myAccount.balance);
}
}
输出结果
ownerName=null
accountNumber=0
balabne=0.0
从输出结果可以看到,引用类型初始值为空引用,账户(数值)初始值为0,余额(数值)初始值为0.0。
自定义构造方法与重载
- 在生成对象时给构造方法传送初始值,为对象进行初始化。
- 构造方法可以被重载:一个类中可以有两个及以上同名的方法,但参数表不同,这种情况被称为重载;在方法调用时,可以通过参数列表的不同来辨别应调用哪一个方法。
- 只要显示声明了构造方法,编译器就不再生成默认的构造方法。
- 也可以显示声明无参数的构造方法,方法体中可以定义默认初始化方式(相当于构造出了默认构造方法,此时允许我们对对象不给参数初始化,而不是不进行初始化)。
例:为银行账户类声明构造方法
public class BankAccount {
String ownerName;
int accountNumber;
float balance;
/* 为BankAccount声明一个有三个参数的构造方法 */
public BankAccount(String initName, int initAccountNumber, float initBalance) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
/* 假设一个新账号的初始余额可以为0,则可以增加一个带有两个参数的构造方法 */
public BankAccount(String initName, int initAccountNumber) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = 0.0f;
}
/* 无参数的构造方法——自定义默认的初始化方法 */
public BankAccount() {
ownerName = "";
accountNumber = 999999;
balance = 0.0f;
}
}
以上构造方法中的逻辑本质相同,只是参数的个数不同,此时,可以采用如下方法减少冗余。
声明构造方法时使用this
关键字
- 可以使用
this
关键字在一个构造方法中调用另外的构造方法。 - 代码更简洁,维护起来更容易。
- 通常用参数个数较少的构造方法调用参数个数最多的构造方法。
例:使用this
的重载构造方法
public BankAccount() {
this("", 999999, 0.0f);//this代表本类的参数方法参数名,把参数作为实参
}
public BankAccount(String initName, int initAccountNumber) {
this(initName, initAccountNumber, 0.0f);
}
public BankAccount(String initName, int initAccountNumber, float initBalance) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
final
变量的初始化
如果我们希望某个属性一经初始化就不能再被改变,即为常量,则可以使用final
。
- 实例变量和类变量都可以被声明为
final
。 final
实例变量可以在类中定义时给出初始值,或者在每个构造方法结束之前完成初始化(最晚的时刻)。- 一旦构造方法执行结束,
final
变量的值就不能再被改变。 final
类变量必须在声明的同时完成初始化:因为类变量属于整个类,在整个类中只有一份,不属于任何对象。同样地,声明完成后即不能再被改变。
2.3-2 内存回收
对象的自动回收
- 无用对象:离开了作用域的对象;无引用指向的对象(无需担心内存泄漏的问题)。
- Java运行时系统通过垃圾收集器周期性地释放无用对象所使用的内存。
- Java运行时系统会在对对象进行自动垃圾回收之前(的最后一刻),自动调用对象的
finalize()
方法(每个类中默认都有finalize()
方法,这个方法也可以被覆盖)。
垃圾收集器
- 自动扫描对象的动态内存区,对不再使用的对象做上标记以进行垃圾回收。
- 作为一个后台线程运行,通常在系统空闲时异步地执行。
finalize()
方法
- 在类
java.lang.Object
中声明,因此Java中的每一个类都有该方法:protected void finalize() throws throwable
。java.lang.Object
是所有Java类的直接的或间接的超类,因此Java中每一个类都有该方法(从Object
继承而来)。 - 用于释放资源。
- 类可以覆盖(重写)
finalize()
方法。 finalize()
方法可能在任何时机以任何次序执行,所以如果要覆盖finalize()
方法,释放的操作不能有严格次序关系。
2.4 枚举类
声明枚举类
[public]enum 枚举类型名称
[implements 接口名称列表]{
枚举值;
变量成员声明及初始化;
方法声明及方法体;
}
枚举类是一种类,也可以声明为public
,不声明则默认为包内的(见[2.2-5类的访问权限控制](##2.2-5 类的访问权限控制 ))
例:简单的枚举类型
enum Score {
EXCELLENT, QUALIFIED, FAILED;
};
public class ScoreTester {
public static void main(String[] args) {
giveScore(Score.EXCELLENT);
}
public static void giveScore(Score s) {
switch (s) {
case EXCELLENT:
System.out.println("Excellent");
break;
case QUALIFIED:
System.out.println("Quqlified");
break;
case FAILED:
System.out.println("Failed");
break;
}
}
}
枚举类的特点
- 枚举定义实际上是定义了一个类。
- 所有枚举类型都隐含集成(扩展)自
java.lang.Enum
类,因此枚举类型不能再继承其他任何类(继承会在后续章节介绍)。 - 枚举类型的类体中可以包括方法和变量。
- 枚举类型的构造方法必须是包内私有或者私有的。定义在枚举开头的常量会被自动创建,不能显式地调用枚举类的构造方法。
枚举类型的默认方法
- 静态的
values()
方法用于获得枚举类型的枚举值的数组,即values()
会返回一个数组包含所有枚举值。 toString
方法返回枚举值的字符串描述,即,将枚举值转换成字符串类型。valueOf
方法将以字符串形式表示的枚举值转化为枚举类型的对象。Ordinal
方法获得对象在枚举类型中的位置索引。
例:
public enum Planet {//Planet类代表太阳系中行星的枚举类型
MERCURY(3.303e+23, 2.4397e6), VENUS(4.869e+24, 6.0518e6), EARTH(5.976e+24, 6.37814e6), MARS(6.421e+23, 3.3972e6),
JUPITER(1.9e+27, 7.1492e7), SATURN(5.688e+26, 6.0268e7), URANUS(8.686e+25, 2.5559e7), NEPTUNE(1.024e+26, 2.4746e7);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {//定义了构造方法,用参数方法初始化mass和radius
this.mass = mass;
this.radius = radius;
}
private double mass() {//返回mass值,私有方法
return mass;
}
private double radius() {//返回radius值,私有方法
return radius;
}
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;//静态final常量
double surfaceGravity() {
return G * mass / (radius * radius);//处理枚举对象,说明枚举对象也可以有功能
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();//处理枚举对象
}
public static void main(String[] args) {//获得命令行参数
if (args.length != 1) {
System.err.println("Usage: java Planet <earth_weight>");
System.exit(-1);
}
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / EARTH.surfaceGravity();
for (Planet p : Planet.values())//专门处理数组/集合类型的增强型循环,p是对象,是在定义枚举类的时候自动生成的
System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass));
}
}
2.5 应用举例
本章将会通过一个银行账户类的实例复习学过的语法。
初步的BankAccount类——BankAccount.java
public class BankAccount {
private String ownerName;
private int accountNumber;
private float balance;
public BankAccount() {
this("", 0, 0);
}
public BankAccount(String initName, int initAccNum, float initBal) {
ownerName = initName;
accountNumber = initAccNum;
balance = initBal;
}
}
public String getOwnerName() {
return ownerName;
}
public int getAccountNumber() {
return accountNumber;
}
public float getBalance() {
return balance;
}
public void setOwnerName(String newName) {
ownerName = newName;
}
public void setAccountNumber(int newNum) {
accountNumber = newNum;
}
public void setBalance(float newBalance) {
balance = newBalance;
}
测试类——AccountTester.java
public class AccountTester {
public static void main(String[] args) {
BankAccount anAccount;
anAccount = new BankAccount("ZhangLi", 100023, 0);
anAccount.setBalance(anAccount.getBalance() + 100);
System.out.println("Here in the account: " + anAccount);
System.out.println("Account name: " + anAccount.getOwnerName());
System.out.println("Account number:" + anAccount.getAccountNumber());
System.out.println("Balance:$" + anAccount.getBalance());
}
}
输出结果
Here in the account: BankAccount@379619aa
Account name: ZhangLi
Account number:100023
Balance:$100.0
以下将对银行账户类进行修改并测试:
- 覆盖
toString()
方法。 - 声明存取款方法。
- 使用
Decimal Format
类。 - 声明类方法生成特殊的实例。
- 声明类变量。
1. 覆盖toString()
方法
在每个类中默认都有一个toString()
方法,当我们在上下文需要一个字符串String
类型的时候,如果我们给了一个类的对象,会自动调用类的toString()
方法。即,System.out.println(anAccount);
和System.out.println(anAccount.toString());
等价。
由于自带的toString()
方法用处不大,我们如果需要特殊的转换功能,则需要自己覆盖toString()
方法,并需要遵循以下原则。
- 必须被声明为
public
类型。 - 返回类型必须是
String
。 - 方法的名称必须为
toString()
,且没有参数。 - 在方法体中不要使用输出方法
System.out.println()
。
为BankAccount
覆盖toString()
方法
public String toString() {
return ("Account#" + accountNumber + " with balance $" + balance);
}
重新编译BankAccount
类,并运行测试类BankAccountTester
,结果如下:
Here in the account: Account#100023 with balance $100.0
Account name: ZhangLi
Account number:100023
Balance:$100.0
2. 声明存取款操作
银行账户中的余额不应当是随意变动的,而是通过存取款操作来发生改变的。
给BankAccount
类增加存款及取款方法
//存钱
public float deposit(float anAmount) {
balance += anAmount;
return (balance);
}
//取钱
public float withdraw(float anAmount) {
balance -= anAmount;
return (balance);
}
测试存取款——修改AccountTester.java
public class AccountTester {
public static void main(String[] args) {
BankAccount anAccount;
anAccount = new BankAccount("ZhangLi", 100023, 0);
anAccount.setBalance(anAccount.getBalance() + 100);
System.out.println(anAccount);
System.out.println();
anAccount = new BankAccount("WangFang", 100024, 0);
System.out.println(anAccount);
anAccount.deposit(225.67f);
anAccount.deposit(300.00f);
System.out.println(anAccount);
anAccount.withdraw(400.17f);
System.out.println(anAccount);
}
}
测试结果
Account#100023 with balance $100.0
Account#100024 with balance $0.0
Account#100024 with balance $525.67
Account#100024 with balance $125.49997
3. DecimalFormat
类(格式化)
DecimalFormat
类在java.text
包中。- 在
toString()
方法中使用DecimalFormat
类的实例方法format
对数据进行格式化。
进一步修改toString
方法,给输出金额设置格式
public String toString() {
return ("Account#" + accountNumber + " with balance" + new java.text.DecimalFormat("$0.00").format(balance));
}
更多内容关于DecimalFormat
的内容可以查看Java的API文档,学会查看文档是一个程序员的必备技能。
4. 使用类方法生成特殊的实例
例:使用静态方法(类方法)生成三个样例账户
public static BankAccount example1() {
BankAccount ba = new BankAccount();
ba.setOwnerName(("LiHong"));
ba.setAccountNumber(554000);
ba.deposit(1000);
return ba;
}
public static BankAccount example2() {
BankAccount ba = new BankAccount();
ba.setOwnerName("ZhaoWei");
ba.setAccountNumber(554001);
ba.deposit(1000);
ba.deposit(2000);
return ba;
}
public static BankAccount emptyAccountExample() {
BankAccount ba = new BankAccount();
ba.setOwnerName("HeLi");
ba.setAccountNumber(554002);
return ba;
}
5. 设置类变量
例:修改账号生成、余额变动方式
- 修改构造方法,取消账户参数。(账号不应当是人为指定)
- 不允许直接修改账号,取消
setAccountNumber
方法。 - 增加类变量
LAST_ACCOUNT_NUMBER
初始值为0,当生成一个新的BankAccount
对象时,其账号(accountNumber
)自动设置为LAST_ACCOUNT_NUMBER
的值累加1。 - 取消
setBalance
方法,仅通过存取款操作改变余额。
修改后完整的BankAccount2.java
package hello;
package hello;
public class BankAccount2 {
private static int LAST_ACCOUNT_NUMBER = 0;
private int accountNumber;
private String ownerName;
private float balance;
public BankAccount2() {
this("", 0);
}
public BankAccount2(String initName) {
this(initName, 0);
}
public BankAccount2(String initName, float initBal) {
ownerName = initName;
accountNumber = ++LAST_ACCOUNT_NUMBER;
balance = initBal;
}
public String getOwnerName() {
return ownerName;
}
public int getAccountNumber() {
return accountNumber;
}
public float getBalance() {
return balance;
}
public void setOwnerName(String newName) {
ownerName = newName;
}
public String toString() {
return ("Account#" + new java.text.DecimalFormat("000000").format(accountNumber) + " with balance"
+ new java.text.DecimalFormat("$0.00").format(balance));
}
public float deposit(float anAmount) {
balance += anAmount;
return (balance);
}
public float withdraw(float anAmount) {
balance -= anAmount;
return (balance);
}
public static BankAccount2 example1() {
BankAccount2 ba = new BankAccount2();
ba.setOwnerName(("LiHong"));
ba.deposit(1000);
return ba;
}
public static BankAccount2 example2() {
BankAccount2 ba = new BankAccount2();
ba.setOwnerName("ZhaoWei");
ba.deposit(1000);
ba.deposit(2000);
return ba;
}
public static BankAccount2 emptyAccountExample() {
BankAccount2 ba = new BankAccount2();
ba.setOwnerName("HeLi");
return ba;
}
}
测试程序AccountTester2.java
public class AccountTester2 {
public static void main(String[] args) {
BankAccount2 bobsAccount, marysAccount, biffsAccount;
bobsAccount = BankAccount2.example1();
marysAccount = BankAccount2.example1();
biffsAccount = BankAccount2.example2();
marysAccount.setOwnerName("Mary");
marysAccount.deposit(250);
System.out.println(bobsAccount);
System.out.println(marysAccount);
System.out.println(biffsAccount);
}
}
样例输出:
Account#000001 with balance$1000.00
Account#000002 with balance$1250.00
Account#000003 with balance$3000.00
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步