第二章 一切都是对象
Java基于C++,但是相比之下,Java是一种更“纯粹”的面向对象程序设计语言。
2.1 用引用操纵对象
每种编程语言都有自己的操纵内存中元素的方式,在Java中,一切都被视为对象,但操纵的标识符实际上是对象的一个引用(reference),可以将这一情形想象成遥控器(引用)来操纵电视机(对象)。此外,即使没有电视机,遥控器也可以独立存在,也就是说,你拥有一个引用,但并不一定需要有一个对象与它关联。
String s;
如果想操纵一个词或句子,我们可以创建一个String引用,但是这里所创建的只是引用,并不是对象。如果此时向s发送一个消息,就会返回一个错误,这是因为s没有与任何事物相关联。一种安全的做法是,创建一个引用的同时进行初始化。
String s = “asdf”;
2.2 必须由你创建所有对象
一旦创建一个引用,就希望它能与一个新对象关联,通常用new操作符来实现这一目的。前面的例子就可以写成 :
String s = new String(“asdf”);
除了String类型,Java还提供了大量的现成类型,同时你也可以自行创建类型。
2.2.1 存储到什么地方
程序运行时,有五个地方可以存储数据:
1.存储器。它位于处理器内部,是最快的存储区,但是寄存器的数量有限,所以要按需分配,你不能直接控制,也不能在程序中感觉到寄存器存在的迹象。
2.堆栈。它位于RAM中,但是可以通过堆栈指针从处理器获得直接支持。这是一种快速有效的分配存储方式,仅次于存储区,但是创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针,这一约束限制了程序的灵活性,所以虽然某些Java数据存储在堆栈中,但Java对象并不存储在这里。
3.堆。它是一种通用的内存池,也位于RAM中,用于存放所有的Java对象。堆对于堆栈的好处是,编译器不需要知道存储的数据在堆里存活多长时间,因此在堆里分配存储有很大的灵活性,new一个对象时,会自动在堆里进行存储分配。当然这种灵活性也要付出相应的代价:用堆进行分配和清理可能比用堆栈进行存储分配需要更多的时间。
4.常量存储。常量值通常直接存放在程序代码内部,这样做是安全的,他们永远不会改变。在嵌入式系统中,常量本身会和其他部分分隔离开,在这种情况下,可以选择将其存放在ROM中。
5.非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在,例如流对象和持久化对象。在流对象中,对象转化成字节流,被发送给另一台机器。在持久化对象中,对象被存放在磁盘上。及时程序终止,他们也可以保持自己的状态。这种方式的技巧在于把对象转化成可以存放在其他媒介上的事物,在需要时恢复成常规的、基于RAM的对象。
2.2.2 特例:基本类型
之所以说是特例。是因为new经常把对象存储在堆中,用new来创建一个对象,特别是小的、简单的变量,往往不是很有效。因此,我们创建一个并非是引用的“自动”变量,这个变量直接存储值,并置于堆栈中,更加高效。
所有的数值类型都有正负号,所以无需寻找无符号的数值类型。
Boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true或false。
基本类型具有的包装器类,是的可以在堆中创建一个非基本对象,用来表示对应的基本类型。如:
Char c = ‘x’; Character ch = new Character(c);
也可以这样用:
Character ch = new Character(‘x’);
Java SE5的自动包装能自动将基本类型转换为包装器类型:
Character ch = ‘x’;
并可以反向转换:
Char c = ch;
高精度数字
Java提供了两个用于高精度计算的类:BIgInteger 和 BigDecimal。虽然它们大体上属于包装器类的范畴,但是它们都没有对应的基本类型。
但是这两个类包含的方法,提供的操作与基本类型所能执行的操作相似,也就是说能用于int或float的操作,同样能作用于BIgInteger 和 BigDecimal。
BIgInteger 支持任意精度的整数,也就是说可以准确表示任何大小的整数值。
BigDecimal支持任何精度的定点数,如精确的货币计算。
2.2.3 Java中的数组
Java确保数组会被初始化,而且不能在它的范围之外被访问。以每个数组上少量的内存开销及运行下边检查为代价,换来了Java数组的安全性和效率的提高。
当创建一个数组对象时,实际上创建了一个引用数组,每个引用都会自动地被初始化一个特定值,该值拥有自己的关键字null,一旦Java看见null,就知道这个引用还没有指向某个对象,如果试图使用一个null的引用,在运行时将会报错。因此常规的数组错误在Java中可以避免。
2.3 永远不需要销毁对象
2.3.1 作用域
大多数过程型语言都有作用域(scope)的概念。作用域决定了在其内定义的变量名的可见性和生命周期。在C、C++、Java中,作用域由花括号的位置决定,如:
{ Int x = 12; // 只有x有效 { Int q = 96; // x和q都有效 } // 只有x有效,超出了q的作用域 }
在作用域里定义的变量只可用于作用域结束前。任何位于“//”之后到行末的文字都是注释。缩排格式使Java代码更容易阅读,Java是一种自由格式语言,空格、制表符、换行都不会影响程序的执行结果。
2.3.2 对象的作用域
Java对象不具备和基本类型一样的生命周期,当new一个Java对象时,它可以存活于作用域之外,如:
{ String s = new String(“a string”); } // 作用域结束
s在作用域终点就消失了,然而s指向的String对象仍继续占据内存空间,由new创建的对象,只要你需要就会一直保留下去。那么如果Java对象继续存在,就会产生这些对象填满内存空间,进而阻塞你的程序。Java有一个垃圾回收器,用来监视所有由new创建的对象,并辨别那些不会再被引用的对象,随后释放这些空间,以便其他新的对象使用,也就是说你根本不必担心内存回收的问题,你只需创建对象,一旦不再需要他们就会自行消失。
2.4 创建新的数据类型:类
每个对象都有用来存储其字段的空间,普通字段不能在对象间共享。下面是一个具有某些字段的类:
class DataOnly { int i; double d; boolean b; }
尽管这个类除了保存数据之外啥也不能做,但仍旧可以创建它的一个对象:
DataOnly data = new DataOnly();
可以给对象的字段赋值,格式: objectReference.menber,如:
data.i = 47; data.d = 1.1; data.b = false;
基本成员默认值
若类的基本成员是基本数据类型,即使没有初始化,Java也会确保它获得一个默认值,如下表所示:
当变量作为类的成员使用时,Java才确保给定默认值,来确保这些基本类型的成员变量得到初始化。但是这些初始值对你的程序来说可能是不正确的或者不合法的,所以最好明确对变量进行初始化。
然而上述确保初始化的方法不适用于局部变量(即并非某个类的字段),如:
int x;
那么变量x得到的可能是任意值而不是自动初始化为0。
2.5 方法、参数和返回值
Java方法的基本组成包括:名称、参数、返回值、方法体。基本形式如下:
returnType methodName( /* Argument list */ ) { /* Method body */ }
2.5.1 参数列表
方法的参数列表指定要传递给方法什么样的消息。假设某方法接收String的参数:
int storage(String s) { return s.length() * 2; }
这个方法告诉你,传入一个String类型的参数,参数名是s,调用String的基本方法length()获取字符串的字符数,返回该值乘以2。
通过上面的例子还可以了解到return关键字的用法,它表示“已经做完,离开此方法”,同时返回一个值。你可以定义方法返回的任意想要的类型,如果不想返回任何值,可以指示方法返回void,如:
boolean flag() { return true; } double naturalLogBase() { return 2.718; } void nothing() { return; } void nothing2() {}
若返回类型是void,return关键字的作用只是用来退出方法。
2.6 构建一个Java程序
2.6.1 名字可见性
为避免与其他类中的函数名相冲突,C++通过几个关键字引入命名空间(namespace)的概念,Java通过不同的包(package)来解决这个问题。如java.com.example.demo。
这种机制意味着所有的文件都能够自动存活于他们自己的命名空间内,而且同一个文件内的每个类都有唯一的标识符。
2.6.2 运用其他构件
如果想在自己程序中使用预先定义好的类,那么编译器就必须知道怎么定位它们。我们使用import关键字来准确地告诉编译器你想要的类是什么。大多时候我们使用与编译器附在一起的Java标准类库里的构件,如:
import java.util.ArrayList;
这行代码告诉编译器,你想使用Java的ArrayList类,有时你想使用其中的几个,又不想逐一声明,可以使用通配符*来达到这个目的:
import java.util.*;
2.6.3 static关键字
当声明一个事物是static时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以即使从未创建某个类的任何对象,也可以调用static方法或访问static域。如:
class StaticTest { static int i = 48; }
现在,即使创建了两个StaticTest对象,StaticTest.i 也只有一份存储空间,这两个对象共享一个i,如:
StaticTest st1 = new StaticTest(); StaticTest st2 = new StaticTest();
在这里,st1.i和st2.i指向同一存储空间,因此他们具有相同的值。
引用static变量的方法有两种,一种是例子中所示,通过一个对象去调用它,如st1.i,也可以通过其类名直接引用,如StaticTest.i,但这对于非静态成员不行。
类似逻辑也应用于静态方法,即可以通过一个对象来引用某个静态方法,也可以通过特殊语法形式ClassName.method()加以引用,如:
class Incrementable { static void increment() { StaticTest.i++; } }
我们就可以通过对象来调用increment():
Incrementable sf = new Incrementable(); sf.increment();
或者直接通过他的类直接调用:
Incrementable.increment();
2.7 你的第一个Java程序
我们在最后编写一个完整的程序,目的是打印当前的日期,这里用到了Java标准库中的Date类。
import java.util.*; public class HelloDate { public static void main(String[] args) { System.out.println(“Hello. it’s: ”); System.out.println(new Date()); } }
在每个程序文件的开头,必须声明import语句,以便引入文件代码中需要用到的额外类。
2.7.1 编译和运行
首先必须要有一个Java开发环境,我们使用oracle的JDK(Java Developer’s Kit,Java开发人员工具包)开发环境(https://www.oracle.com/technetwork/java/javase/downloads/index.html)。若使用其他的开发系统,请查阅该系统的相应文档。
有关JDK的安装使用,请参考我的博客https://www.cnblogs.com/parable/p/11340889.html
2.8 注释和嵌入式文档
java有两种注释风格,一种是传统的C语言风格注释,以“/*”开始,随后是注释内容,可跨越多行,最后以“*/”结束。如:
/* this is a comment * that continues * across lines */
进行编译时,位于/*和*/之间的所有东西都会被忽略。
第二种风格源于C++,这种注释是单行注释,以“//”起头,直到句末,如:
// this is a one-line comment
2.8.1 注释文档
代码文档撰写的最大问题大概就是对文档的维护了。javadoc便是用于提取注释的工具,是JDK安装的一部分,它采用Java编译器的某些技术,查找程序内的特殊注释标签。它不仅解析由这些标签标记的信息,也将毗邻注释的类名或方法名抽取出来。
2.8.2 语法
所有javadoc命令都只能在“/**”注释中出现,结束于“*/”。使用javadoc的方式主要有两种:嵌入HTML,或使用文档标签。独立文档标签是一些一“@”字符开头的命令,且要置于注释行的最前面。而行内文档标签则可以出现在javadoc注释中的任何地方,也是以“@”开头,但要在花括号内。
共有三类注释文档,分别对应注释位置后面的三种元素:类,域,方法。如:
//: objectDocumentation1.java /** A class comment */ public class Documentation1 { /** A method comment */ public void f() {} }///:~
注意:priva和包内可访问成员的注释会被忽略掉,输出结果中看不到它们。
2.8.3 嵌入式HTML
javadoc通过生成的HTML文档传送HTML命令,这使你能充分利用HTML。当然主要目的是为了对代码进行格式化,如:
//: object/Documentation2.java /** * <pre> * System.out.println(new Date()); * </pre> */ ///:~
也可以像其他Web文档中那样运用HTML对普通文本按照你所描述的尽心格式化:
//:object/Documentation3.java /** * You can <em>even</em> insert a list: * <ol> * <li> Item one * <li> Item two * <li> Item three * </ol> */ ///:~
所有类型的注释文档——类、域和方法都支持嵌入式HTML
2.8.4 一些标签示例
1.@see:引用其他类
@see classname
@see fully-qualified-classname
@see fully-qualified-classname#method-name
2.{@link package.class#member label}
只用于行内
3.{@docRoot}
用于文档树页面的显式超链接
4.{@inheritDoc}
从当前这个类的最直接的基类中继承相关文档到当前文档注释中
5.@version
@version version-infomation
version-infomation可以是任何你认为适合包含在版本说明中的重要信息。
6.@author
@author authoe-infomation
7.@since
该标签允许你指定程序代码最早使用的版本
8.@param
该标签用于方法文档中,@param parameter-name description
9.@return
该标签用于方法文档中,@return description
10.@throws
@throw fully-qualified-class-name description
给出一个异常类无歧义的名字
11.@deprecates
该标签用于指出一些旧特性已由改进的新特性所取代
2.8.5 示例文档
回到第一个Java程序,但是加上了注释
//: object/HelloDate.java import java.util.*; /** The first Thinking in Java ecample program. * Displays a string and today’s date * @author Zhang San * @author www.123.com * @version 4.0 */ public class HelloDate { /** Entrv point to class & appication. * @param args array of string arguments * throws exceptions No exceptions thrown */ public static void main(String[] args) { System.out.println(“Hello. it’s: ”); System.out.println(new Date()); } }/* Output: (55% match) Hello. it’s: Mon Oct 12 17:15:36 MDT 2019 *///:~