第二章 一切都是对象
2.1 用引用操作对象
在Java中,一切都被视为对象。
尽管一切都看作对象,但是操纵对象的是它的一个引用(refrence)。
可以把对象想象成电视机,引用想象成遥控器。遥控器(引用)可以独立于电视机(对象)存在,比如创建一个String引用:
String s;
此时只是创建了引用,s实际上还没有和任何事物关联。可以采取一种比较安全的做法即创建引用时便初始化:
String s = "asdf";
2.2 创建对象
一旦创建了一个引用,就希望能和一个新的对象关联。使用操作符new来实现,意思是“给我一个新对象”。使用之前的例子:
String s = new String("asdf");
它不仅表示“给我一个新字符串”,还通过提供一个初始字符串,给出了怎样产生这个String的信息。
2.2.1 存储位置
1.寄存器
2.堆栈:位于通用RAM中,通过堆栈指针操作。对于堆栈中的数据,Java需要知道确切的生命周期。对象引用存储在堆栈中。
3.堆:也位于通用RAM中,用于存放所有Java对象。Java不需要知道数据在堆里存活多久,因此分配存储比较灵活。
4.常量存储:常量值直接存放在程序代码内部。
5.非RAM存储:数据不受程序控制,比如流对象(转化成字节流被发送)和持久化对象(在磁盘中)。
2.2.2 特例:基本类型
程序中常用的类型,被定义为“基本”类型。它们不用new来创建,而是创建一个不是引用的“自动”变量。这个变量直接存储“值”,存储于堆栈中,因而更加高效。
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
boolean | — | — | — | Boolean |
char | 16-bit | Unicode 0 | Unicode 216-1 | Character |
byte | 8-bit | -128 | 127 | Byte |
short | 16-bit | -215 | 215-1 | Short |
int | 32-bit | -231 | 231-1 | Integer |
long | 64-bit | -263 | 263-1 | Long |
float | 32-bit | IEEE754 | IEEE754 | Float |
double | 64-bit | IEEE754 | IEEE754 | Double |
void | — | — | — | Void |
所有数值类型都有正负号,没有无符号的数值类型。
基本类型具有包装器类,可以用它创建非基本对象表示对应的基本类型。例如:
char c = 'x'; Character ch = new Character(c);
也可以这样:
Character ch = new Character('x');
*高精度数字--用于高精度计算的类
-
-
- BigInteger:支持任意精度的整数
- BigDecimal:支持任意精度的定点数
-
2.2.3 Java中的数组
在C、C++中使用数组是十分危险的,因为C、C++中的数组就是内存块。Java确保数组会被初始化,并且提供越界检查。
- 当创建一个数组对象时,实际上创建了一个引用数组,并且每个引用会自动初始化为一个特定值null。试图使用一个还是null的引用,会在运行时报错。
- 还可以创建基本数据类型的数组,同样编译器也能确保初始化,将数组所占内存全部置零。
2.3 永远不需要销毁对象
2.3.1 作用域
作用域(scope)决定了在它内部定义的变量名的可见性和生命周期。和C、C++一样,Java中的作用域由花括号的位置决定。例如:
{ int x = 12; //only x available { int q = 96; //Both x and q available } //only x available
//q is out of scope }
注意:与C、C++不同,Java不允许把较大作用域的变量“隐藏”,例如:
{ int x = 12; { int x = 96; //非法 } }
2.3.2 对象的作用域
Java对象不具备和基本类型一样的生命周期,它可以存活于作用域之外。例如:
{ String s = new String("a string"); } // 作用域结束
引用s在作用域结束就消失了,但是s指向的String对象却仍然在内存中。在这段代码中,不能在作用域之后访问该对象,因为它的唯一引用s超出其作用域范围。
C++中需要手动销毁对象,而Java不需要,Java有一个垃圾回收器,用来监视所有创建的对象,找出不会再用的对象来销毁释放内存。在Java中,只管创建对象就行了,不需要考虑内存泄漏的问题。
2.4 创建新的数据类型:类
使用关键字class来表示“我准备告诉你一种新类型的对象是什么样的”,例如:
class ATypeName { /* class body goes here */ }
此时类主体只有一句注释,但已经可以用new来创建这种类型的对象:
ATypeName a = new ATypeName();
2.4.1 字段和方法
一旦定义了一个类,就可以在类中设置两种类型的元素:字段(也叫数据成员)和方法(也叫成员函数)。
字段可以是任何类型的对象,也可以是基本类型中的一种。
class DataOnly { int i; double d; boolean b; } DataOnly data = new DataOnly(); //创建一个对象data
通过英文句点来引用对象的成员:
//objectReference.member //例如 data.i = 47; data.d = 1.1; data.b = false;
基本成员的默认值:若类的某个成员是基本数据类型,即使没有初始化,Java也会确保它有一个默认值:
基本类型 | 默认值 |
boolean | false |
char | '\u0000'(null) |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
注意: 如果变量不是类的字段,而是“局部”变量,那么Java不会对其默认初始化。
2.5 方法、参数和返回值
Java的方法决定了一个对象能够接收什么样的消息。方法由名称、参数、返回值和方法体组成:
ReturnType methodName( /* Argument list */ ) { /* Method body */ }
Java中的方法只能作为类的一部分来创建。通过对象调用方法时,用英文句点连接对象名和方法名以及参数列表,如:
objectName.methodName(arg1, arg2, arg3);
看下面这个例子:
int x = a.f();
通过对象a调用方法f(),称为发送消息给对象,其中对象是a,消息是f(),因此OOP可以简单概括为“向对象发送消息”。
2.6 构建一个Java程序
2.6.1 名字可见性
为了解决命名冲突,C++中引入了命名空间的概念,Java采用全新的方法来避免。Java希望程序员反过来使用自己的Inetnet域名,因为这样可以保证它们肯定是独一无二的。例如域名MindView.net,那么各种类库就被命名为net.mindview.utility.foibles。域名反转后,句点就表示目录划分。
2.6.2 使用其他构件
使用关键字import来准确告诉编译器你想要的类是什么。import指示编译器导入一个包,也就是一个类库。
使用Java标准类库里的构件,就不用写一长串的反转域名了:
import java.util.Arraylist;
也可已使用通配符*一次导入一群类:
import java.util.*;
2.6.3 static关键字
通常来说,只有执行new创建对象,数据存储空间才会被分配。但是有如下困难:
-
- 只想为摸个特定域分配单一存储空间,而不考虑是否要创建对象;
- 希望即使没有创建对象也能调用方法。
使用static关键字,意味着这个域或方法和类的任何一个对象无关。所以即使没有创建对象也可以调用static域或static方法。
class StaticTest { static int i = 47; } //创建两个StaticTest对象 StaticTest st1 = new StaticTest(); StaticTest st2 = new StaticTest();
引用static变量的两种方法:①通过对象名引用 ②通过类名引用(首选)
static方法也类似。
2.7 第一个Java程序
// HelloDate.java import java.util.*; public class HelloDate { public static void main() { System.out.println("Hello, it's: "); System.out.println(new Date()); } }
使用JDK文档(Java SE8)查看Java配套的各种类库。由于java.lang是默认导入到每一个Java文件中的,所以它的所有类都可以被直接使用。
看上面的程序,Date类并不包含于java.lang中,而是在另外一个类库java.util中,使用import导入。在文档中查看java.lang中的System类,可以看到它的域中有三个字段err、in、out,都是静态对象。程序中使用的out类型是PrintStream,out能做什么就由它决定。查看PrintStream的方法,程序中用到了println(),它的实际作用是将数据打印到控制台并换行。
类名必须和文件名相同。如果创建一个可以独立运行的程序,文件中必须有一个类的名字和文件名一样。
*每个文件最多只有1个public类:若有,则该类名必须同文件名;若无,文件名随意。
2.8 注释和嵌入式文档
/* This is a comment * that continues * across lines */ /* This is a comment that continues across lines */ // This is a one-line comment
2.8.1 注释文档
将代码和文档“链接”起来,会使文档维护变得简单。用特殊的注释语法来标记文档,然后使用某种工具提取出来。
javadoc就是提取注释的工具,它不仅查找并解析特殊注释标签,也将毗邻的类名或方法名抽出来,以HTML文件输出。
2.8.2 语法
所有javadoc命令只能在“/**”注释中出现,并结束于“*/”。使用javadoc的方式有两种:①嵌入HTML;②使用“文档标签”。
文档标签有:
①独立文档标签:以“@”开头,在注释行最前面;
②行内文档标签:同样以“@”开头,可以在注释中任意位置,但要包在花括号内。
注释文档共有三种:①类注释 ②域注释 ③方法注释,它们都位于对应元素之前:
//: object/Documentation1.java /** A class comment */ public class Ducumentation1 { /** A field comment */ public int i; /** A method commend */ public void f() {} } ///:~
注意:javadoc只能为public和protected成员进行文档注释。
2.8.4 标签示例
-
- @see :引用其他类,生成“See Also”(参见)超链接
- {@link package.class#member label} :同@see,行内,超链接为“label”
- {@docRoot} :文档根目录相对路径
- {@inheritDoc} :直接基类继承文档
- @version :版本说明信息
- @author :姓名
- @since :程序最早使用版本
- @param :参数
- @return :返回值含义
- @throws :异常说明
- @deprecated :旧特性警告
2.9 编码风格
类名:首字母大写,每个单词的首字母也大写,不要用下划线,例如: class AllTheColorsOfTheRainbow { // ...
其他:首字母小写,其他和类名一样,例如:
class AllTheColorsOfTheRainbow { int anIntegerRepresentingColors; void changeTheHueOfTheColor(int newHue) { // ... } // ... }