二、万物皆对象
本章你将了解到 Java 程序的基本组成,学习在 Java 中万物(几乎)皆对象的思想。
1、对象操纵
Java 利用万物皆对象的思想和单一一致的语法方式来简化问题。虽万物皆可为对象,但我们所操纵的标识符实际上只是对对象的“引用” 。仅仅有一个“引用”并不意味着你必然有一个与之关联的“对象”。
创建一个 String 引用
String s;
这里我们只是创建了一个 String 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 s 赋值–指向任何对象。通常更安全的做法是:创建一个引用的同时进行初始化。
String s = "asdf";
2、对象创建
“引用”用来关联“对象”。
在 Java 中,通常我们使用new操作符来创建一个新对象。new 关键字代表:创建一个新的对象实例。
String s = new String("asdf");
3、数据存储
程序在运行时是如何存储?内存是怎么分配?
有5个不同的地方可以存储数据:
- 寄存器(Registers) 最快的存储区域,位于 CPU 内部。寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹。(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。
- 栈内存(Stack) 存在于常规内存 RAM(随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。
- 堆内存(Heap) 这是一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 new 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。
- 常量存储(Constant storage) 常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中。
- 非 RAM 存储(Non-RAM storage) 数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。
4、基本类型存储
它们的创建并不是通过 new 关键字来产生。
通常 new 出来的对象都是保存在堆内存中的,以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法,Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 new 创建变量,而是使用一个“自动”变量。 这个变量直接存储"值",并置于栈内存中,因此更加高效。
Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。这种不变性也是 Java 更具可移植性的一个原因。
- 都是有正/负符号的。
- 布尔(boolean)类型的大小没有明确的规定,通常定义为取字面值 “true” 或 “false” 。
如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。
char c = 'x';
Character ch = new Character(c);
自动装箱
Character ch = new Character('x');
自动拆箱
char c = ch;
5、高精度数值
BigInteger 和 BigDecimal,用于高精度的计算。它们并没有对应的基本类型。
个类包含的方法提供的操作,必须要通过调用它们的方法来实现而非运算符。
BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。 BigDecimal 支持任意精度的定点数字。
6、数组存储
在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外的数据。
这种范围检查,是以每个数组上少量的内存开销及运行时检查下标的额外时间为代价的,但由此换来的安全性和效率的提高是值得的。
7、对象清理
一个变量需要存活多久?
如果我们想销毁它,应该什么时候去做呢?
7.1、作用域
在 C、 C++ 和 Java 中,作用域是由大括号 {} 的位置决定的。
Java 的变量只有在其作用域内才可用。Java 是一种*格式的语言,额外的空格、制表符和回车并不会影响程序的执行结果。
在 Java 中,你不能执行以下操作:
{
int x = 12;
{
int x = 96; // Illegal
}
}
Java 编译器会提示变量 x 已经被定义过了。(C/C++中可以)。
7.2、对象作用域
Java 对象与基本类型具有不同的生命周期。
使用 new 关键字来创建 Java 对象时,它的生命周期将会超出作用域。
{
String s = new String("a string");
}
// 作用域终点
引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。在这段代码中,我们无法在这个作用域之后访问这个对象,因为唯一对它的引用 s 已超出了作用域的范围。
我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?
答案是:Java 的垃圾收集器会检查所有 new 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。
8、类的创建
8.1、类型
大多数面向对象的语言都使用 class 关键字类来描述一种新的对象。 通常在 class 关键字的后面的紧跟类的的名称。
8.2、字段
可以往类里存放两种类型的元素:方法(method) 和字段(field)。
类的字段可以是基本类型,也可以是引用类型。
如果类的字段是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。
通常,字段不在对象间共享。
必须通过这个对象的引用来指定字段值。
8.3、基本类型默认值
这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的字段始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。为了安全,我们最好始终显式地初始化变量。
局部变量 —— 那些不属于类的字段的变量,不会自动初始化。
8.4、方法使用
在 Java 中,我们使用术语 方法(method)来表示“做某事的方式”
[返回类型] [方法名](/*参数列表*/){
// 方法体
}
8.4.1、返回类型
返回类型表明了当你调用它时会返回的结果类型。
参数列表则显示了可被传递到方法内部的参数类型及名称。
作为方法的唯一标识。方法名和参数列表统称为方法签名(signature of the method)。
Java 中的方法只能作为类的一部分创建。它只能被对象所调用 ,并且该对象必须有权限来执行调用。
8.4.2、参数列表
int storage(String s) {
return s.length() * 2;
}
参数列表必须指定每个对象的类型和名称。同样,我们并没有直接处理对象,而是在传递对象引用 。
return 关键字,它执行两项操作。首先,它意味着“方法执行结束”。其次,如果方法有返回值,那么该值就紧跟 return 语句之后。
如果我们不想方法返回数据,则可以通过给方法标识 void 来表明这是一个无需返回值的方法。当返回类型为 void 时, return 关键字仅用于退出方法,因此在方法结束处的 return 可被省略。
9、程序编写
9.1、命名可见性
如果你在两个模块中使用相同的命名,那么如何区分这两个名称,并防止两个名称发生“冲突”呢?
- C++ 将函数嵌套在类中,所以它们不会和嵌套在其他类中的函数名冲突。然而,C++ 还是允许全局数据和全局函数,因此仍有可能发生冲突。为了解决这个问题,C++ 使用附加的关键字引入了命名空间。
- Java 为一个类库生成一个明确的名称。整个包名都是小写。
9.2、使用其他组件
如果一个类位于其他文件中,又会怎样呢?
要解决此问题,你必须通过使用 import 关键字来告诉 Java 编译器具体要使用的类。import 指示编译器导入一个包,也就是一个类库。
在其他语言中,一个库不仅包含类,还可能包括函数和数据,但请记住 Java 中的所有代码都必须写在类里)。大多数时候,我们都在使用 Java 标准库中的组件。有了这些构件,你就不必写一长串的反转域名。
9.3 static
当面临下面两种情况时:
- 有时你只想为特定字段(注:也称为属性、域)分配一个共享存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建对象。
- 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。
使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。
相比非静态的对象,static 属性改变了数据创建的方式。同样,当 static 关键字修饰方法时,它允许我们无需创建对象就可以直接通过类的引用来调用该方法。
【参考】
On Java 8中文翻译
上一篇: 关于判断字符的一些合集