如何理解对象
在Java中, 所操纵的标识符实际上只是对对象的引用。注意:仅仅有一个引用并不意味着你必然有一个与之关联的对象。
比如:下面来创建一个 String 引用,用于保存单词或语句。代码示例:
String s;
这里我们只是创建了一个 String 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 s
赋值--指向任何对象。通常更安全的做法是:创建一个引用的同时进行初始化。代码示例:
String s = "asdf";
对象创建
引用用来关联“对象”。在 Java 中,通常我们使用new
操作符来创建一个新对象。new
关键字代表:创建一个新的对象实例。所以,我们也可以这样来表示前面的代码示例:
String s = new String("asdf");
数据存储
程序在运行时是如何存储的呢?尤其是内存是怎么分配的。有5个不同的地方可以存储数据:
寄存器
栈内存
可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存。这是一种仅次于寄存器的非常快速有效的分配存储方式。创建程序时,Java 系统必须知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据(如对象引用),但 Java 对象本身的数据却是保存在堆内存中。
堆内存
这是一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 new
命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。
常量存储
常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中 ^3。
对象作用域
Java 对象与基本类型具有不同的生命周期。当我们使用 new
关键字来创建 Java 对象时,它的生命周期将会超出作用域。因此,下面这段代码示例:
{
String s = new String("a string");
}
// 作用域终点
上例中,引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。在这段代码中,我们无法在这个作用域之后访问这个对象,因为唯一对它的引用 s 已超出了作用域的范围。在后面的章节中,我们还会学习怎么在编程中传递和复制对象的引用。
只要你需要,new
出来的对象就会一直存活下去。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围之内,还必须在使用完它们之后,将其销毁。
那么问题来了:我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?
答案是:Java 的垃圾收集器会检查所有 new
出来的对象并判断哪些不再可用,继而释放那些被占用的内存,供其他新的对象使用。也就是说,我们不必担心内存回收的问题了。你只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。
static 关键字
类是对象的外观及行为方式的描述。通常只有在使用 new
创建那个类的对象后,数据存储空间才被分配,对象的方法才能供外界调用。这种方式在两种情况下是不足的。
- 有时你只想为特定字段(注:也称为属性、域)分配一个共享存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建对象。
- 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。
static 关键字(从 C++ 采用)就符合上述两点要求。当我们说某个事物是静态时,就意味着该字段或方法不依赖于任何特定的对象实例 。 即使我们从未创建过该类的对象,也可以调用其静态方法或访问其静态字段。相反,对于普通的非静态字段和方法,我们必须要先创建一个对象并使用该对象来访问字段或方法,因为非静态字段和方法必须与特定对象关联 ^6 。
我们可以在类的字段或方法前添加 static
关键字来表示这是一个静态字段或静态方法。 代码示例:
class StaticTest {
static int i = 47;
}
现在,即使你创建了两个 StaticTest
对象,但是静态变量 i
仍只占一份存储空间。两个对象都会共享相同的变量 i
。 代码示例:
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
st1.i
和 st2.i
指向同一块存储空间,因此它们的值都是 47。引用静态变量有两种方法。在前面的示例中,我们通过一个对象来定位它,例如 st2.i
。我们也可以通过类名直接引用它,这种方式对于非静态成员则不可行:
StaticTest.i++;
++
运算符将会使变量结果 + 1。此时 st1.i
和 st2.i
的值都变成了 48。
网友评论