|
|
## 1. 初探类与对象
|
|
|
|
|
|
### 1.1. 现实生活中类和对象的关系
|
|
|
|
|
|
我们首先看看日常生活中的一些例子,假如我们要做一辆模型汽车,我们需要有哪些步骤?
|
|
|
首先我们需要设计,可能你需要画图纸,进行一些标注和说明。当设计完成后,就可以准备材料,按照图纸进行制作了。如果你愿意,你可以按照图纸再做几辆模型车,可能会改变颜色,不同的涂装,大一点的只需要按照比例放大就可以了。
|
|
|
|
|
|

|
|
|
|
|
|
我们制作出来的所有模型车都是按照图纸的,因此具备设计中所包含的所有特性。假如我们把这个设计叫做A100,那么所有成品模型车都是一类车(Class)每一辆模型车都是A100的一个实例(Object 可能有些属性不一样,例如颜色)。
|
|
|
|
|
|
好了,这就是类与对象的基本内涵。类是一个模板(抽象的描述),对象是模板的一个实例(具体的)。这一点有点类似C中结构体的定义与结构体变量的关系,定义是抽象描述,变量是实体。
|
|
|
|
|
|
我们再深入讨论一下对象。对象(Object)这个概念广泛存在于我们的生活当中,一般只一个独立的个体,具备一定的特性(properties),并有一定的能力(capability)。例如,一辆A100:
|
|
|
|
|
|

|
|
|
|
|
|
### 1.2. 程序空间中类与对象的关系
|
|
|
|
|
|
请思考在我们学习的C语言中,有没有抽象描述和具体实例化的应用?例如,在C语言中,如果描述一个平面中的圆?
|
|
|
|
|
|

|
|
|
|
|
|
考虑到已经学习过C语言,使用C语言中的结构体来帮助理解抽象描述与实例的关系。
|
|
|
|
|
|
这个例子是描述平面中的一个圆。这里我们需要两组参数来描述圆,第一个参数是坐标位置(X、Y);另外一个是圆本生的描述,例如大小(半径r),可能还有颜色等等,简单来说,我们目前只有半径。如果从C语言出发,需要建立一个结构体来描述圆,请同学们写出该结构体的代码。
|
|
|
|
|
|
如果从C语言出发,需要建立一个结构体来描述圆,代码如下:
|
|
|
|
|
|
```c
|
|
|
struct Circle
|
|
|
{
|
|
|
float x;
|
|
|
float y;
|
|
|
float r;
|
|
|
};
|
|
|
```
|
|
|
|
|
|
上面的代码是抽象描述(Class)还是一个具体的实例(Object)?
|
|
|
|
|
|
上面只是定义了一个结构体(抽象,代表所有圆),但是还不存在一个真正的圆的实例(具体的对象),因此需要定义一个变量,其类型是Circle这个结构体。
|
|
|
|
|
|
```c
|
|
|
struct Circle c1;
|
|
|
```
|
|
|
|
|
|
定义变量后,变量c1在内存中就分配了存储空间,这样好比是把一个抽象的描述(Class)实例化了,因此c1在这里可以看成是一个对象(Object),这好像是从图纸到成品的过程。
|
|
|
|
|
|

|
|
|
|
|
|
上面只是生成了一个圆的数据存储空间,描述了圆的属性。但是一个完整的圆是不是应该还有行为能力?例如我们希望打印这个圆的基本信息,应该如何做?
|
|
|
|
|
|
C语言的实现需要利用一个函数(行为能力),其输入参数是结构体类型的变量,然后在函数中对传输参数进行解析与打印。
|
|
|
|
|
|
|
|
|
问题:既然这个打印函数与圆的属性紧密相关,为什么不把圆的数据描述与行为能力(打印函数)放在一起?这样做有什么好处?
|
|
|
|
|
|
|
|
|
|
|
|
其实我们最希望的是结构体的定义和结构体的操作函数可以打包在一个更优化的代码当中。例如上述的打印函数应该和结构体进行强关联,最好成为结构体的一部分。这样做如同我的化学实现工具和化学实验用的材料应该放在一个地方一样。
|
|
|
|
|
|
|
|
|
|
|
|
显然,C语言并没有提供相应的语法结构用于描述一个类/对象(包含数据和函数)。如果把相关数据和函数封装在一起,具备更好的程序结构,也便于我们阅读和理解。当然C++可以扩展一个结构体,使其可以包含函数,这不是我们要讨论的内容。
|
|
|
|
|
|
### 1.3. 用Java来实现
|
|
|
|
|
|
我们来看看Java是如何做的。首先,我们定义一个圆,不过这圆的属性(变量)与行为能力(函数)在一起。
|
|
|
|
|
|
```java
|
|
|
class Circle {
|
|
|
float x;
|
|
|
float y;
|
|
|
float r;
|
|
|
|
|
|
public void printCircle() {
|
|
|
System.out.printf("The position x=%f, y=%f, radius=%f!\n", x, y, r);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
大家会发现,这个圆的定义和C中结构体的定义类似,只不过包含了打印函数。因为打印函数在Circle这个类中,可以直接调用这个类的变量(x、y、r),因此不用再通过参数来传递一个圆的数据。
|
|
|
|
|
|
接下来我们看看如何使用这个类:
|
|
|
|
|
|
```java
|
|
|
public class Circle_Test {
|
|
|
public static void main(String[] args) {
|
|
|
Circle circle = new Circle();
|
|
|
circle.x = 1.2F;
|
|
|
circle.y = 2.4F;
|
|
|
circle.r = 3.3F;
|
|
|
circle.printCircle();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**注意前面字面量的1.2F,2.4F等,因为如果不写,字面量的类型是double,所以不能赋值给 float 的变量。**
|
|
|
|
|
|
问题:Java的语法与C的语法有什么不一样?
|
|
|
1、看起来和C的语法很像;
|
|
|
2、所有的代码都必须定义在类当中;
|
|
|
3、有更多的关键字,如static、public,这些我们以后再分析;
|
|
|
4、主函数不一样,且一定要这样写;
|
|
|
5、没有结构体的定义,而是类(class)的定义;
|
|
|
6、从类生成一个对象使用的是 new 操作;
|
|
|
|
|
|
### 1.4. 优化代码
|
|
|
|
|
|
对圆的参数赋值需要三条语句,太麻烦了,可以如何改进?对,我们可以使用一个函数来对圆的属性进行赋值,代码如下:
|
|
|
|
|
|
```java
|
|
|
class Circle {
|
|
|
float x;
|
|
|
float y;
|
|
|
float r;
|
|
|
|
|
|
public void printCircle() {
|
|
|
System.out.printf("The position x=%f, y=%f, radius=%f!\n", x, y, r);
|
|
|
}
|
|
|
|
|
|
public void setCircle(float x, float y, float r) {
|
|
|
this.x = x;
|
|
|
this.y = y;
|
|
|
this.r = r;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class Circle_Test {
|
|
|
public static void main(String[] args) {
|
|
|
Circle circle = new Circle();
|
|
|
circle.setCircle(1.1F, 2.2F, 3.3F);
|
|
|
circle.printCircle();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**注意`setCircle`函数中的`this.x = x`这样的写法。因为这个对象本生有变量x、y、z;这些变量叫做成员变量,其作用域是在整个类的定义范围内;但是`setCircle`的形式参数中的x、y、z与成员变量重名了,但是语义不同。为了区分这两个变量,使用this关键字,`this.x`代表对象的成员变量,而`x`代表传递进来的参数。**
|
|
|
|
|
|
### 1.5. 构造函数
|
|
|
|
|
|
构造函数是一个特殊的函数,在调用的时候产生一个新对象。构造函数名永远和类的名字相同。不带参数的构造函数对一个类来说非常重要。
|
|
|
|
|
|
在定义一个类的时候,可以不提供构造函数,这时,编译器将自动产生一个不带参数的构造函数。如果你定义了一个带参数的构造函数,编译器将不再自动产生一个不带参数的构造函数。这时,建议你写一个不带参数的构造函数,这样会避免出错,特别是这个类有子类的时候。
|
|
|
|
|
|
在上面一个例子中,我们并没有定义Circle的构造函数,系统自动为这个类分配了一个不带参数的构造函数,因此我们可以使用`new Circle()`来构造一个Circle对象。出现在new 操作符后面的函数就是构造函数。
|
|
|
|
|
|
下面我们给Circle增加一个带参数的构造函数,这样可以在new 操作的时候就对圆的参数进行初始化;同时,我们也编写了一个不带参数的构造函数,在这个构造函数中,调用了带参数的构造函数来对圆的数据进行初始化。
|
|
|
|
|
|
```java
|
|
|
class Circle {
|
|
|
float x;
|
|
|
float y;
|
|
|
float r;
|
|
|
|
|
|
public Circle() {
|
|
|
this(0, 0, 1);
|
|
|
}
|
|
|
|
|
|
public Circle(float x, float y, float r) {
|
|
|
setCircle(x, y, r);
|
|
|
}
|
|
|
|
|
|
public void printCircle() {
|
|
|
System.out.printf("The position x=%f, y=%f, radius=%f!\n", x, y, r);
|
|
|
}
|
|
|
|
|
|
public void setCircle(float x, float y, float r) {
|
|
|
this.x = x;
|
|
|
this.y = y;
|
|
|
this.r = r;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class Circle_Test {
|
|
|
public static void main(String[] args) {
|
|
|
Circle circle = new Circle(1.1F, 2.2F, 3.3F); // 或者 Circle circle = new Circle();
|
|
|
circle.printCircle();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
> A class may be defined without constructors. In this case, a no-arg constructor with an empty body is implicitly defined in the class. This constructor, called a default constructor, is provided automatically only if no constructors are explicitly defined in the class.
|
|
|
|
|
|
如果你写了一个带参数的构造函数,建议写一个不带参数的构造函数,这样会省掉很多麻烦,后面会讲述。关于构造函数,会在后面的学习中详细讲述。
|
|
|
|
|
|
**注意,构造函数一般不声明为私有,不能声明为静态的,不能写返回值的类型!**
|
|
|
|
|
|
其他的一些例子:
|
|
|
|
|
|
1. TestSimpleCircle
|
|
|
2. TestTv
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 变量的缺省值
|
|
|
|
|
|
The default value of a data field is null for a reference type, 0 for a numeric type, false for a boolean type, and '\\u0000' for a char type. However, Java assigns no default value to a local variable inside a method.
|
|
|
|
|
|
成员变量(类中定义的变量)的缺省值:
|
|
|
|
|
|
1. 引用变量:缺省是 null;
|
|
|
2. 基本数值类型(小写的 int、float、double):缺省是0;
|
|
|
3. char:缺省是 '\\u000';
|
|
|
4. boolean:缺省是false;
|
|
|
|
|
|
数组中的变量缺省值遵循同样的标准
|
|
|
|
|
|
局部变量(函数中定义的变量)没有缺省值!因此如果不赋值进行操作可能会出现问题(编译错误)。
|
|
|
|
|
|
```java
|
|
|
public class Test {
|
|
|
public static void main(String[] args) {
|
|
|
int x; // x has no default value
|
|
|
String y; // y has no default value
|
|
|
System.out.println("x is " + x);
|
|
|
System.out.println("y is " + y);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
上述代码会产生编译错误。
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 引用变量的赋值
|
|
|
|
|
|
前面学习的很多基本类型(Primitive),都是以小写字母开始的(int,float,char,boolean等),这是基本类型。而预定的很多类(Scanner,String)这些叫做引用类型(使用 new 关键字建立对象/实体)。基本类型在赋值和参数传递的时候使用值传递;引用类型使用引用(Reference)传递。引用传递**类似**C中的指针地址传递。
|
|
|
|
|
|

|
|
|
|
|
|
引用赋值(对象)和C的指针非常类似,只不过在Java中,不用关心指针悬空(未回收的内存空间)的问题,因为Java有一种叫做垃圾回收机制,可以自动清理没有引用的内存空间(如上图中赋值后o1的存储空间)。
|
|
|
|
|
|
引用类型有一个特殊值是null,对应C语言中的空指针。
|
|
|
|
|
|
```java
|
|
|
Circle c1; // 缺省值是 null
|
|
|
c1 = new Circle(); // 引用变量指向一个对象
|
|
|
c1 = null; // 引用变量可以赋值成 null
|
|
|
if (c1 == null) { // 可以判断一个引用变量是否没有被赋值
|
|
|
...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
SDK中的一些类:
|
|
|
|
|
|
1. The Random Class
|
|
|
2. The Point2D Class
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 类的成员
|
|
|
|
|
|
在本章节以前,我们也看到过类,也有相应的变量和函数,但是大多是静态的。本章开始,如同Circle类一样,定义的变量、函数都没有static的修饰,叫做实体变量和实体函数(区别于动态)。实体类型的变量和函数只能在引用对象的变量上面进行调用,而不能在类上进行调用。
|
|
|
|
|
|
定义在类中的变量、常量、函数都叫做类的成员,根据这些成员的不同性质,总结一下:
|
|
|
|
|
|
1. 常量成员:一定是静态的,且使用final进行修饰。对于常量来说,整个程序空间内只存在一份。
|
|
|
2. 静态变量成员:前面的章节大多使用静态变量,一个静态变量在整个程序空间内只存在一份拷贝。静态变量可以理解成C的全局变量,只不过其包含在一个特定的类(盒子)当中。
|
|
|
3. 静态函数成员:前面章节大多使用静态函数。静态函数不用和对象进行绑定,可以在类上面进行调用,例如前面的Math中的静态函数。当然,在对象的引用变量上调用该对象对应的类的静态函数也是合法的。
|
|
|
4. 实例(instance)变量成员:实例变量成员和实例(对象)绑定,如同C语言结构体中的变量,每个实例对象的实例成员变量都可能有不同的值。
|
|
|
5. 实例函数成员:只能在实例(对象)上面进行调用。
|
|
|
|
|
|
|
|
|
|
|
|
静态变量是全局共享的。实例变量是每个对象所独享的,有多个对象就存在多个不同成员变量。例如前面圆的半径,有多个不同的圆的对象,其半径都是不同的(因此叫做实例/对象变量)。
|
|
|
|
|
|
下面的例子中在`CircleWithStaticMembers`这个类中定义了一个静态变量`static int numberOfObjects = 0;`用来统计`CircleWithStaticMembers`这个类一共被创建出了几个对象。
|
|
|
|
|
|

|
|
|
|
|
|
```java
|
|
|
class CircleWithStaticMembers {
|
|
|
double radius;
|
|
|
|
|
|
static int numberOfObjects = 0;
|
|
|
|
|
|
static int getNumberOfObjects() {
|
|
|
return numberOfObjects;
|
|
|
}
|
|
|
|
|
|
CircleWithStaticMembers() {
|
|
|
radius = 1.0;
|
|
|
numberOfObjects++;
|
|
|
}
|
|
|
|
|
|
CircleWithStaticMembers(double newRadius) {
|
|
|
radius = newRadius;
|
|
|
numberOfObjects++;
|
|
|
}
|
|
|
|
|
|
double getArea() {
|
|
|
return radius * radius * Math.PI;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public class Test {
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
System.out.println("Before creating objects");
|
|
|
System.out.println("The number of Circle objects is " + CircleWithStaticMembers.getNumberOfObjects());
|
|
|
|
|
|
// Create c1
|
|
|
CircleWithStaticMembers c1 = new CircleWithStaticMembers();
|
|
|
|
|
|
// Display c1 BEFORE c2 is created
|
|
|
System.out.println("\nAfter creating c1");
|
|
|
System.out.println("c1: radius (" + c1.radius + ") and number of Circle objects ("
|
|
|
+ CircleWithStaticMembers.numberOfObjects + ")");
|
|
|
|
|
|
// Create c2
|
|
|
CircleWithStaticMembers c2 = new CircleWithStaticMembers(5);
|
|
|
|
|
|
// Modify c1
|
|
|
c1.radius = 9;
|
|
|
|
|
|
// Display c1 and c2 AFTER c2 was created
|
|
|
System.out.println("\nAfter creating c2 and modifying c1");
|
|
|
System.out.println("c1: radius (" + c1.radius + ") and number of Circle objects (" + c1.numberOfObjects + ")");
|
|
|
System.out.println(
|
|
|
"c2: radius (" + c2.radius + ") and number of Circle objects (" + c2.getNumberOfObjects() + ")");
|
|
|
}
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. 静态变量`numberOfObjects`的初始值是0,因此在主函数的第二行,使用`CircleWithStaticMembers.getNumberOfObjects()`取得这个静态变量的值的时候是0。这里证明了静态变量不依附对象存在,可以在类上面直接调用。
|
|
|
2. `CircleWithStaticMembers`的两个构造函数(重载的构造函数)都对`numberOfObjects`进行了加1操作,语义是当创建一个`CircleWithStaticMembers’`对象的时候,让静态变量`numberOfObjects`加1,这样就得到了创建`CircleWithStaticMembers`对象的总数量。
|
|
|
3. 注意后面的代码,得到`CircleWithStaticMembers`类实例化对象的个数可以使用静态变量`numberOfObjects`,也可以使用静态函数`getNumberOfObjects()。`且这两个静态成员即可以在类上面调用,也可以在对象的引用变量上调用。
|
|
|
|
|
|
**静态成员可以在类上访问,也可以在该类的引用变量上访问;这两种方式都是合法的,且没有任何的区别。**
|
|
|
|
|
|
## 5. 访问修饰
|
|
|
|
|
|
对任何一个类成员(常量、变量、函数),有一个访问修饰,来说明该成员对与外部调用的可见性。在一个类的内部,好像是一个大家庭,所有成员相互都是可以调用的;但是对于外部来说,就需要进行一定的限制。限制外部对类成员的访问有很多好处,第一位的就是安全性。你肯定不希望一个人闯入你的家里指手画脚,对于类也是一样的。有些函数和变量是在类的内部使用的,而不需要外部来进行读写,这时就可以使用访问修饰符来限定该成员的访问级别。
|
|
|
|
|
|
成员访问修饰包括:private(私有);缺省;protected(保护);public(公开)。这里我们讨论除protected外的三种访问修饰,因为protected需要到类的继承才有用。
|
|
|
|
|
|
对于类成员来说,访问限制如下图:
|
|
|
|
|
|

|
|
|
|
|
|
上述的三个类都有包的定义,请注意第一行的`package`定义。包的概念可以参考第一章,可以简单理解为目录,上述的C1和C2在一个包(p1)中;C3在包p2中。根据上述的可以总结为:
|
|
|
|
|
|
The private modifier restricts access to within a class, the default modifier restricts access to within a package, and the public modifier enables unrestricted access.
|
|
|
|
|
|
1. private:该类的内部可以访问;
|
|
|
2. 缺省(没有修饰):同一个包可以访问;
|
|
|
3. public:任何外部均可访问。
|
|
|
|
|
|
对于类来说,一般是public,但是也可以使用缺省,但是一般不会是private。
|
|
|
|
|
|

|
|
|
|
|
|
类的访问限制和类成员是一致的。
|
|
|
|
|
|
|
|
|
|
|
|
**一个Java源代码文件中,只能存在一个 public 类,且这个类与文件名相同;如果要定义其他的类,请使用缺省。这个在拼题平台中经常使用到。**
|
|
|
|
|
|
### 5.1. 为什么要私有?
|
|
|
|
|
|
一个类当中可能存在一些私有成员,例如私有的函数或者是变量。对于私有的函数,我只想让类内部的其他函数调用,而不会让外部调用,这个比较好理解,可能是为了安全与保密。但是有些成员变量设置成私有就有点感觉不应该了。例如在Circle的这个例子中,最好把圆的半径设置成私有。这不是有点多此一举吗?我们不是希望外部的代码来读取和设置圆的半径吗?对我们希望外部代码读写圆的半径,但是不希望乱读写。圆的半径是不能为负数的,否则可能出现逻辑错误。如果我们把圆的半径公开,那么你就把保证圆半径这个责任交给了外部代码,这样做是非常危险的。
|
|
|
|
|
|
|
|
|
|
|
|
如果我们把圆的半径设置成私有,那么外部代码如何读写这个变量?想一想,在类的内部,所有成员都是公开的,那么我们设置两个函数,一个负责设置半径,一个负责读取半径,并把这两个函数设置成public不就行了?Java把这两个函数叫做getter和setter函数。看看下面的例子:
|
|
|
|
|
|
```java
|
|
|
public class CircleWithPrivateDataFields {
|
|
|
/** The radius of the circle */
|
|
|
private double radius = 1;
|
|
|
|
|
|
/** The number of the objects created */
|
|
|
private static int numberOfObjects = 0;
|
|
|
|
|
|
/** Construct a circle with radius 1 */
|
|
|
CircleWithPrivateDataFields() {
|
|
|
numberOfObjects++;
|
|
|
}
|
|
|
|
|
|
/** Construct a circle with a specified radius */
|
|
|
public CircleWithPrivateDataFields(double newRadius) {
|
|
|
radius = newRadius;
|
|
|
numberOfObjects++;
|
|
|
}
|
|
|
|
|
|
/** Return radius */
|
|
|
public double getRadius() {
|
|
|
return radius;
|
|
|
}
|
|
|
|
|
|
/** Set a new radius */
|
|
|
public void setRadius(double newRadius) {
|
|
|
radius = (newRadius >= 0) ? newRadius : 0;
|
|
|
}
|
|
|
|
|
|
/** Return numberOfObjects */
|
|
|
public static int getNumberOfObjects() {
|
|
|
return numberOfObjects;
|
|
|
}
|
|
|
|
|
|
/** Return the area of this circle */
|
|
|
public double getArea() {
|
|
|
return radius * radius * Math.PI;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
这样清楚了,`radius` 这个变量被设置成私有,`getRadius()` 是共有函数,用于读取半径;`setRadius()` 是共有函数,用于设置半径。这两个函数就是 getter 和 setter 函数。特别注意,在 `setRadius` 函数中对传入参数进行了判断。这个函数好像是看门人,保证半径永远不为复数。
|
|
|
|
|
|
因为getter和setter函数用得太多了,eclipse 和大多Java的IDE为这个函数提供了快速构建方式。你只需要选择菜单`Source`-`Generate Getters and Setters ...`就会出现下面的窗口。
|
|
|
|
|
|

|
|
|
|
|
|
选择你需要生成getter和setter的私有变量,然后点击Generate按钮,就会自动生成getter及或是setter函数。
|
|
|
|
|
|
## 6. 对象作为函数参数
|
|
|
|
|
|
如果是基本类型的变量,那么使用的是值传递,上一章已经说明了。如果是引用类型(大写的类型,对象类型),那么使用的是引用传递。引用传递如同C语言的指针一样,我们可以在函数的内部改变引用对象内部成员的值。
|
|
|
|
|
|
```java
|
|
|
class CircleWithPrivateDataFields {
|
|
|
/** The radius of the circle */
|
|
|
private double radius = 1;
|
|
|
|
|
|
/** The number of the objects created */
|
|
|
private static int numberOfObjects = 0;
|
|
|
|
|
|
/** Construct a circle with radius 1 */
|
|
|
CircleWithPrivateDataFields() {
|
|
|
numberOfObjects++;
|
|
|
}
|
|
|
|
|
|
/** Construct a circle with a specified radius */
|
|
|
public CircleWithPrivateDataFields(double newRadius) {
|
|
|
radius = newRadius;
|
|
|
numberOfObjects++;
|
|
|
}
|
|
|
|
|
|
// get radius
|
|
|
public double getRadius() {
|
|
|
return radius;
|
|
|
}
|
|
|
|
|
|
// set radius
|
|
|
public void setRadius(double radius) {
|
|
|
this.radius = radius;
|
|
|
}
|
|
|
|
|
|
/** Return numberOfObjects */
|
|
|
public static int getNumberOfObjects() {
|
|
|
return numberOfObjects;
|
|
|
}
|
|
|
|
|
|
/** Return the area of this circle */
|
|
|
public double getArea() {
|
|
|
return radius * radius * Math.PI;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class TestPassObject {
|
|
|
/** Main method */
|
|
|
public static void main(String[] args) {
|
|
|
// Create a Circle object with radius 1
|
|
|
CircleWithPrivateDataFields myCircle = new CircleWithPrivateDataFields(1);
|
|
|
|
|
|
// Print areas for radius 1, 2, 3, 4, and 5.
|
|
|
int n = 5;
|
|
|
printAreas(myCircle, n);
|
|
|
|
|
|
// See myCircle.radius and times
|
|
|
System.out.println("\n" + "Radius is " + myCircle.getRadius());
|
|
|
System.out.println("n is " + n);
|
|
|
}
|
|
|
|
|
|
/** Print a table of areas for radius */
|
|
|
public static void printAreas(CircleWithPrivateDataFields c, int times) {
|
|
|
System.out.println("Radius \t\tArea");
|
|
|
while (times >= 1) {
|
|
|
System.out.println(c.getRadius() + "\t\t" + c.getArea());
|
|
|
c.setRadius(c.getRadius() + 1);
|
|
|
times--;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
`printAreas`函数接受两个参数,第一个参数是`CircleWithPrivateDataFields`类型的引用变量,第二个参数是循环的次数;主函数先生成一个`CircleWithPrivateDataFields`对象,然后使用`printAreas`函数;在`printAreas`函数内部,进行了5次循环,每次通过`CircleWithPrivateDataFields`的`setRadius`函数使得`CircleWithPrivateDataFields`对象的半径+1;然后再打印`CircleWithPrivateDataFields`对象的面积;当`printAreas`函数执行完成后,我们在主函数中打印`CircleWithPrivateDataFields`对象`c`的半径,发现半径已经变成了6(初始值是1,5次累加后变成6)。
|
|
|
|
|
|
|
|
|
|
|
|
这个例子证明,如果是引用变量,在函数内部可以改变传入对象的成员变量值;其实这里使用的是setter函数改变半径的,因为半径是私有的;如果半径是public的,使用直接赋值也可以改变。
|
|
|
|
|
|
如果我么在`printAreas`函数执行完成后,打印n这个变量(作为参数times传入`printAreas`函数),会发现这个值还是5;因为基本类型的变量使用的是值传递。
|
|
|
|
|
|
## 7. 对象数组
|
|
|
|
|
|
上一章我们学了数组,不过数组中元素的类型是基本类型。其实数组也是一个特殊的类(其变量也是引用变量),那么我们也可以在函数内部改变数组元素的值,无论数组内部是基本类型还是引用类型。
|
|
|
|
|
|
数组元素是基本类型:
|
|
|
|
|
|
```java
|
|
|
public class Test {
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
int a[] = { 1, 2, 3, 4, 5 };
|
|
|
System.out.printf("Before run changeArray, a[0] is %d\n", a[0]);
|
|
|
changeArray(a);
|
|
|
System.out.printf("After run changeArray, a[0] is %d\n", a[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
static void changeArray(int a[]) {
|
|
|
a[0] = 1000;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
运行后,我们发现a[0]的值已经被修改成了1000,这种例子前面也遇到过。
|
|
|
|
|
|
下面看看数组中的元素是引用类型的情况,借用上个例子的`CircleWithPrivateDataFields`类型。
|
|
|
|
|
|

|
|
|
|
|
|
```java
|
|
|
public class TotalArea {
|
|
|
/** Main method */
|
|
|
public static void main(String[] args) {
|
|
|
// Declare circleArray
|
|
|
CircleWithPrivateDataFields[] circleArray;
|
|
|
|
|
|
// Create circleArray
|
|
|
circleArray = createCircleArray();
|
|
|
|
|
|
// Print circleArray and total areas of the circles
|
|
|
printCircleArray(circleArray);
|
|
|
}
|
|
|
|
|
|
/** Create an array of Circle objects */
|
|
|
public static CircleWithPrivateDataFields[] createCircleArray() {
|
|
|
CircleWithPrivateDataFields[] circleArray = new CircleWithPrivateDataFields[5];
|
|
|
|
|
|
for (int i = 0; i < circleArray.length; i++) {
|
|
|
circleArray[i] = new CircleWithPrivateDataFields(Math.random() * 100);
|
|
|
}
|
|
|
|
|
|
// Return Circle array
|
|
|
return circleArray;
|
|
|
}
|
|
|
|
|
|
/** Print an array of circles and their total area */
|
|
|
public static void printCircleArray(CircleWithPrivateDataFields[] circleArray) {
|
|
|
System.out.printf("%-30s%-15s\n", "Radius", "Area");
|
|
|
|
|
|
for (int i = 0; i < circleArray.length; i++) {
|
|
|
System.out.printf("%-30f%-15f\n", circleArray[i].getRadius(), circleArray[i].getArea());
|
|
|
}
|
|
|
|
|
|
System.out.println("-----------------------------------------");
|
|
|
|
|
|
// Compute and display the result
|
|
|
System.out.printf("%-30s%-15f\n", "The total areas of circles is", sum(circleArray));
|
|
|
}
|
|
|
|
|
|
/** Add circle areas */
|
|
|
public static double sum(CircleWithPrivateDataFields[] circleArray) {
|
|
|
// Initialize sum
|
|
|
double sum = 0;
|
|
|
|
|
|
// Add areas to sum
|
|
|
for (int i = 0; i < circleArray.length; i++)
|
|
|
sum += circleArray[i].getArea();
|
|
|
|
|
|
return sum;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. 数组可以作为函数的返回值,例如`createCircleArray`函数,生产了5个元素的数组,其半径都随机的。
|
|
|
2. `CircleWithPrivateDataFields`和`sum`函数都是数组作为参数。
|
|
|
|
|
|
你可以在函数中改变数组中引用对象内部成员的值,如同**对象作为函数参数**中一样。
|
|
|
|
|
|
|
|
|
|
|
|
## 8. Immutable 类与对象(了解)
|
|
|
|
|
|
If the contents of an object cannot be changed once the object is created, the object is called an immutable(不变的) object and its class is called an immutable class. If you delete the set method in the Circle class in Listing 8.10, the class would be immutable because radius is private and cannot be changed without a set method.
|
|
|
|
|
|
这个概念需要了解,我们目前使用到的Immutable类是Spring,下一章中所有的 warp 类型也是 Immutable。
|
|
|
|
|
|
## 9. 变量作用域
|
|
|
|
|
|
The scope of instance and static variables is the entire class. They can be declared anywhere inside a class.
|
|
|
|
|
|
The scope of a local variable starts from its declaration and continues to the end of the block that contains the variable. A local variable must be initialized explicitly before it can be used.
|
|
|
|
|
|
## 10. This关键字
|
|
|
|
|
|
The this keyword is the name of a reference that refers to an object itself. One common use of the this keyword is reference a class’s hidden data fields. Another common use of the this keyword to enable a constructor to invoke another constructor of the same class.
|
|
|
|
|
|
```java
|
|
|
public class Circle {
|
|
|
public double radius;
|
|
|
|
|
|
public Circle() {
|
|
|
this(1.0);
|
|
|
}
|
|
|
|
|
|
public Circle(double radius) {
|
|
|
this.radius = radius;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
上个例子有两个构造函数,这是构造函数的overload。在`public Circle(double radius)` 这个构造函数中,因为参数列表中有一个`radius`,这时在构造函数内部如果使用`radius`这个变量,则是指该函数参数列表中的`radius`变量。那么类`Circle`中的`radius`变量就无法直接访问了。因此使用this关键字代表**本对象**,那么`this.radius`就代表`Circle`的成员变量`radius`。最后`this.radius = radius;`就没有歧义了。
|
|
|
|
|
|
|
|
|
|
|
|
不带参数的构造函数`public Circle()`可以调用其他构造函数,这里用this(1.0),表示调用该类中带参数的构造函数。
|
|
|
|
|
|
|
|
|
|
|
|
## 11. 本章重点
|
|
|
|
|
|
除了特别标注,都是重点 |