2.2.3 Java中的数组
几乎所有的程序设计语言都支持数组。在C和C++中使用数组是很危险的,因为C和C++中的数组就是内存块。如果一个程序要访问其自身内存块之外的数组,或在数组初始化前使用内存(程序中常见的错误),都是产生那一预料的后果。
Java 的主要目标之一是安全性,所以许多在C和C++里困扰程序员的问题在Java里不会再出现。Java确保数组会被初始化,而且不能在它的范围之外被访问。这种范围检查,是以每个数组上少量的内存开销及运行时的下标检查为代价的。但由此换来的是安全性和效率的提高,因此付出的代价是值得的(并且Java有时可以优化这些操作)。
当创建一个数组对象时,实际上就是创建了一个引用数组,并且每个引用都会自动被初始化为一个特定值,该值拥有自己的关键字null。一旦Java看到null,就知道这个引用还没有指向某个对象。在使用任何引用前,必须为其指定一个对象;如果试图使用一个还是null的引用,在运行时将会报错。因此,常犯的数组错误在Java中就可以避免。
还可以创建用来存放基本数据类型的数组。同样,编译器也能确保这种数组的初始化,因为它会将这种数组所占的内存全部置零。
数组将在以后的章节中详细讨论(期待吧,哈哈)
2.3 永远不需要销毁对象
在大多数程序设计语言中,变量生命周期的概念,占据了程序设计工作中非常重要的部分,变量需要存活多长时间?如果想要销毁对象,那什么时刻进行呢?变量生命周期的混乱往往会导致大量的程序bug,本节将介绍Java是怎样替代我们完成所有的清理工作,从而大大地简化这个问题的。
2.3.1 作用域
大多数过程型语言都有作用域(scope)的概念。作用域决定了在其内定义的变量名的可见性和生命周期。在C、C++和Java中,作用域由花括号的位置决定。
例如:
{
int x= 12;
// Only x available
{
int q =96;
//Both x & q available
}
// Only x avalilable
// q is “ out of scope ”
}
在作用域里定义的变量只有可用于作用域结束之前。
由于 Java 是一种自由格式(free-form)的语言,所以,空格、制表符、换行符都不会影响程序的执行结果。
尽管以下代码在C和C++中是合法的,但是在Java 中却不能这样书写:
{
int x = 12;
{
int x = 96; //Illegal (非法的)
}
}
编译器将会报告变量 x 已经定义过。所以,在C和C++里将一个较大作用域的变量 “ 隐藏 ”起来的做法,在Java里是不允许的。因为Java的设计者认为这样会导致程序混乱。
2.3.2 对象的作用域
Java对象不具备和基本类型一致的生命周期。
当用 new 创建一个Java对象时,它可以存活于作用域之外。所以加入你采用代码
{
String s = new String(“ a string ”);
}// End of scope
引用s 在作用域终点就消失了。然而,s指向String 对象仍继续占据内存空间。在这一小段代码中,我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已经超出了作用域的范围。
在后续的章节中,读者将会看到:在程序执行过程中,怎样传递和复制对象引用。
事实证明,由 new 创建的对象,只要你需要,就会一直保留下去。这样许多C++编程问题在Java中就完全消失了。在C++中,你不仅必须要确保对象的保留时间与你需要这些对象的时间一样长,而且还必须在你使用完它们之后,将其销毁。
这样便带来一个有趣的问题。如果Java对象继续存在,那么靠什么才能防止这些对象填满内存空间,进而阻塞你的程序呢?
这正是C++里可能会发生的问题。也是Java神奇之所在。
Java有个垃圾回收器,用来监视用 new 创建的所有对象,并辨别那些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。也就是说,你根本不必担心内存回收的问题。你需要创建对象,一旦不再需要,它们就会执行小时。这样做就消除了这类编程问题(即“ 内存泄露 ”),这是由于程序员忘记释放内存而产生的问题。
2.4 创建型的数据类型:类
如果一切都是对象,那么是什么决定了某一类对象的外观与行为呢?换句话说,是什么确定了对象的类型?你可能期望有一个名为“ type ”的关键字,当然它必须还要有响应的含义。然而,从历史发展角度来看,大多数面向对象的程序设计语言习惯用关键字 class来表示 “ 我准备告诉你一种新类型的对象看起来像什么样子 ”。 class 这个关键字之后紧跟着的是新类型的名称。
class ATypeName { /* Class body goes here */}
这就引入了一种新的类型,尽管类主体仅包含一条注释语句。因此,你还不能用它做太多的事情。然而,你已经可以用new来创建这种类型的对象:
ATypeName a = new ATypeName();
但是,在定义它的所有方法之前,还没有办法能让他去做更多的事情(也就是说,不能向它发送任何有意义的消息)。
2.4.1 字段和方法
一旦定义了一个类(在Java中你所作的全部工作就是定义类,产生那些类的对象,以及发送消息给这些对象),就可以在类中设置两种类型的元素:
字段 (有时被称作 数据成员)和 方法 (有时被称作 成员函数 )
字段可以是任何类型的对象,可以通过其引用与其进行通信;也可以是基本类型中的一种。如果字段是对某个对象的引用,那么必须初始化该引用,以便使其与一个实际的对象(如前所述,使用new来实现)相关联。
每个对象都有用来存储字段的空间;普通字段不能在对象间共享。下面是一个具有某些字段的类:
Class DateOnly {
int i;
double d;
boolean b;
}
尽管这个类除了存储数据之外什么也不能做,但是仍旧可以像下面这样创建它的一个对象:
DataOnly data = new DataOnly();
可以给字段赋值,但首先必须知道如何引用一个对象的成员。具体的实现为:在对象引用的名称之后紧接着一个句号,然后再接着是对象内部的成员名称:
objectReference.member
例如:
data.i = 55;
data.d = 1.1;
data.b = false;
想修改的数据也有可能位于对象所包含的其他对象中。在这种情况下,只需要再使用连接句点即可。
例如:
myPlan.leftTank.capacity = 100;
DataOnly类除了保存数据外没有别的用处,因为它没有任何成员方法。如果想了解成员方法的运行机制,就得先了解参数和返回值的概念,稍后将对此作简略描述。
基本成员默认值
若类的某个成员是基本数据类型,即使没有进行初始化,Java也会确保它获得一个默认值,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/4a5bf8c8abbdb731fa89b340ca658e1b.png)
当变量作为累的成员使用时,Java 才确保给定默认值,以确保那些是基本类型的成员变量初始化(C++没有此功能),防止产生程序错误。但是,这些初始值对你的程序来说,可能是不正确的,甚至是不合法的。所以最好明确地对变量进行初始化。
然而上述确保初始化的方法并不适用于“ 局部 ”变量(即并非某个类的字段)。因此,如果某个方法定义中有
int x;
那么变量 x 得到的可能是任意值(与C) ,而不会被自动初始化为零。所以在使用 x 前,应先对其赋一个适当的值。
如果忘记了这么做,Java会在编译时返回一个错误,告诉你此变量没有初始化,这正是Java优于C++的地方。(许多C++编译器会对未初始化变量给予警告,而Java则视为错误)。
2.5 方法、参数和返回值
许多程序设计语言(像C和C++)用函数这个术语来描述命名子程序;而在Java里却常用方法这个数据来表示“ 做某些事情的方式 ”。实际上,继续把它看作是函数也无妨。尽管这只是用词上的差别。
Java的方法决定了一个对象能够接收什么样的消息。方法的基本组成部分包括:名称、参数、返回值和方法体。下面是它最基本的形式:
ReturnType methodName(/* Arruement list */){
/* Method body */
}
返回类型描述的是在调用方法之后从方法返回的值。参数列表给出了要传给方法的信息类型和名称。方法名和参数列表(它们合起来被称为“ 方法签名 ”)唯一地标识出某个方法。
Java中的方法只能作为类的一部分来创建。方法只有通过对象才能被调用,且这个对象必须能执行这个方法调用。
通过对象调用方法:
objectName.methodName(arg1,arg2,arg3);
例如,假设由一个方法f(),不带任何参数,返回类型是int。如果有个名为 a 的对象,可以通过它调用f(),那么久可以这样写:
int x = a.f();
返回值的类型必须要与 x 的类型兼容。
这种调用方法的行为通常被称为 发送消息给对象。 在上面的例子中,消息是f(),对象是 a。面向对象的程序设计通常简单地归纳为“ 向对象发送消息 ”。
2.5.1 参数列表
方法的参数列表指定要传递给方法什么样的消息。正如你可能料想的那样,这些信息向Java中的其他信息一样,采用的都是对象形式。因此,在参数列表中必须指定每个所传递对象类的类型及名字。像Java中任何传递对象的场合一样,这里传递的实际上也是引用,并且引用的类型必须正确。如果参数被设为String类型,则必须传递一个String对象;否则,编译器将抛出错误。
假设某个方法接受String为参数,下面是其具体定义,它必须置于某个类的定义内才能被正确编译。
int storage(String s){
retur s.length()*2;
}
此方法告诉你,需要多少个字节才能容纳一个特定的String对象中的信息(字符串中的每个字符的尺寸都是16位或2个字节,以此来提供对Unitcode字符集的支持)。此方法的参数类型是String,参数是 s。一旦将 s 传递给此方法,就可以把他当做对象一样进行处理。
到此为止,读者或许觉得:程序似乎只是一系列带有方法的对象组合,这些方法以其他对象为参数,并发送消息给其他对象。大体上确实是这样,但在以后章节中,读者将会学到怎样在一个方法内进行判断,做一些细致的底层工作。