Files
OnJava8/book/03-Objects-Everywhere.md
2019-01-24 01:43:32 +08:00

777 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第三章 万物皆对象
>如果我们说不同的语言,我们会感觉到一个不同的世界!— Ludwig Wittgenstein (1889-1951)
尽管 Java 基于 C++ ,但 Java 是一种更纯粹的面向对象程序设计语言。Java 和 C++ 都是混合语言。在 Java 中,语言设计者认为混合并不像 C++ 那样重要。混合语言允许多种编程风格;这也是 C++ 支持与 C 语言的向后兼容性原因。因为 C++ 是 C 语言的超集,所以它也包含了许多 C 语言的不良特性,这可能使得 C++ 在某些方面过于复杂。
Java 语言预设你已经编写过面对对象的程序。在此之前,你必须将自己的思维置于面对对象的世界。在本章中你将了解 Java 语言的基本组成,学习 Java (几乎)万物皆对象的思想。
<!-- You Manipulate Objects with References -->
## 对象操纵
名字的意义在于,当我们听到“玫瑰”这个词时,就会想到一种闻起来很甜蜜的的花。(引用自 莎士比亚,《罗密欧与朱丽叶》)。
所有的编程语言都会操纵内存中的元素。有时程序员必须要有意识地直接或间接地操纵它们。示例:在 C/C++ 语言中是通过指针来完成操作的。
Java 使用万物皆对象的思想和特有的语法方式来简化问题。虽然万物皆可为对象,但你操纵的标识符实际上是只对象的“引用” [^1]。 示例:我们可以将这种“引用”想象成电视(对象)和遥控器(引用)之间的关系。只要拥有对象的“引用”,就可以操纵该“对象”。我们无需直接接触电视,只要掌握遥控器就可以在房中自由地控制电视(对象)的频道和音量。此外,没有电视机,遥控器也可以单独存在。引申来说,仅仅因为你有一个“引用”并不意味着你必然有一个关联的“对象”。
下面来创建一个 String 的引用,用于保存单词语句。代码示例:
```java
String s;
```
这里我们仅仅只是创建了一个 String 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 s 赋值--附加任何引用的对象。通常更安全的做法是:在声明变量引用的同时初始化对象信息。代码示例:
```java
String s = "asdf";
```
Java 语法允许我们使用带双引号的文本内容来初始化字符串。同样,其他类型的对象也有相应的初始化方式。
<!-- You Must Create All the Objects -->
## 对象创建
“引用”用来连接“对象”。在 Java 中,通常我们使用`new`这个操作符来来创建一个新的对象。`new`关键字代表:创建一个新的对象实例。所以,前面的代码实例我们也可以这样来表示:
```java
String s = new String("asdf");
```
以上的代码示例展示了字符串对象的创建过程以及如何初始化生成字符串。Java 本身自带了许多现成的数据类型,在此基础之上我们还可以创建自己的数据类型。类型的创建是 Java 的基本操作。在本书后面的学习中将会接触到。
<!-- Where Storage Lives -->
### 数据存储
那么, 程序在运行时是如何存储的呢?尤其是内存。下面我们就来形象地描述下, Java 中数据存储的5个不同的地方
1. **寄存器** *Registers* 最快的保存区域位于CPU内部 [^2]。然而寄存器的数量十分有限所以寄存器是根据需要由编译器分配。我们对其没有直接的控制权也无法在自己的程序里找到寄存器存在的踪迹另一方面C/C++ 允许开发者向编译器建议寄存器的分配)。
2. **栈内存***Stack* 存在于常规内存RAM区域中可通过栈指针获得处理器的直接支持。栈指针下移创建新内存上移释放该内存顺序后进先出速度仅次于寄存器。创建程序时Java 编译器必须准确地知道栈内保存的所有数据的“长度”以及生命周期。栈内存的这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象本身却是保存在堆内存的。
3. **堆内存***Heap* 这是一种常规用途的内存池(也在 RAM区域所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 new 命令实例化代码即可。执行这些代码时,数据会在堆内存里自动进行保存。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果你甚至可以用 Java 在栈内存上创建对象就像在C++ 中那样。随着时间的推移Java 的堆内存分配机制现已非常快,因此这不是一个值得关心的问题了。
4. **常量存储** *Constant storage* 常量值通常直接放在程序代码中因为它们永远不会改变。如需严格保护可考虑将它们置于只读存储器ROM中 [^3]。
5. **非 RAM 存储** *Non-RAM storage* 数据完全存在于程序之外在程序未运行以及脱离程序控制后依然存在。两个主要的例子1序列化对象对象被转换为字节流通常被发送到另一台机器2持久化对象对象被放置在磁盘上即使程序终止数据依然存在。这些存储的方式都是将对象转存于另一个介质中并在需要时恢复到常规内存中。Java 为轻量级持久性提供支持。诸如 JDBC 和 Hibernate 之类的库为使用数据库存储和检索对象信息提供了更复杂的支持。
<!-- Special Case: Primitive Types -->
### 基本类型的存储
有一组类型在 Java 中使用频率很高,这就是 Java 的基本类型。对于此类数据的存储我们需要特别对待。之所以说这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在 **Heap** 内存中的, 用它来创建小、简单的基本类型的数据是不划算的。所以对于这些基本类型的创建方法, Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量容纳了具体的值,并置于栈内存中,能够更高效地存取。
Java 预设了每种基本类型的初始内存占用大小。 这些大小标准不会随着机器环境的变化而变化。这种不变性也是Java 的跨平台的一个原因。
| 基本类型 | 大小 | 最小值 | 最大值 | 包装类型 |
| :------: | :------: | :------: | :------: | :------: |
| boolean | — | — | — | Boolean |
| char | 16 bits | Unicode 0 | Unicode $2^{16}-1$ | Character |
| byte | 8 bits | $-128$ | $+127$ | Byte |
| short | 16 bits | $-2^{15}$ | $+2^{15}-1$ | Short |
| int | 32 bits | $-2^{31}$ | $-2^{31}-1$ | Integer |
| long | 64 bits | $-2^{63}$ | $-2^{63}-1$ | Long |
| float | 32 bits | IEEE754 | IEEE754 | Float |
| double | 64 bits |IEEE754 | IEEE754 | Double |
| void | — | — | — | Void |
所有的数值类型都是有正/负符号的。布尔boolean类型的大小没有明确的规定通常定义为采用文字 “true” 和 “false”。基本类型有自己对应的包装类型如果你希望在堆内存里表示基本类型的数据就需要用到它们的包装类。代码示例
```java
char c = 'x';
Character ch = new Character(c);
```
或者你也可以使用下面的形式 基本类型自动转换成包装类型(自动装箱):
```java
Character ch = new Character('x');
```
相对的,包装类型转化为基本类型(自动拆箱):
```java
char c = ch;
```
个中原因将在以后的章节里解释。
<!-- High-Precision Numbers -->
### 高精数值的存储
在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger``BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有相应的基本类型形式。
这两个类都有自己特殊的“方法”,对应于我们针对基本类型数值执行的操作。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以做一样可以,只不过必须要通过调用它们的方法来实现而非运算符。此外,由于涉及到的计算量更多,所以运算速度会慢一些。诚然,我们牺牲了速度,但换来了精度。
BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。
BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的币值计算。至于具体使用什么方法,跟多详情,请参考 JDK 官方文档。
<!-- Arrays in Java -->
### 数组的存储
许多编程语言都支持数组类型。在 C 和 C++ 中使用数组是危险的,因为那些数组只是内存块。如果程序访问了其内存块之外的数组或在初始化之前使用该段内存(常见编程错误),则结果是不可预测的。
Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程序员的问题不会在 Java 中再现。在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外数据。这种长度检查的代价是每个阵列都有少量的内存开销以及在运行时验证索引的额外时间,但是这种安全性的前提对于提高的生产率是值得的。(并且 Java 经常可以优化这些操作)。
当我们创建对象数组时,实际上是创建了一个数据的引用,并且每个引用的初始值都为 **null** 。在使用该数组之前,我们必须为每个引用分配一个对象 。如果我们尝试使用为**null**的引用,则会在运行时报告该问题。因此,在 Java 中就防止了数组操作的典型错误。
我们还可创建基本类型的数组。编译器通过将该数组的内存归零来保证初始化。本书稍后将详细介绍数组,特别是在数组章节中。
[^1]: 脚注预留
[^2]: 脚注预留
[^3]: 脚注预留
<!-- Comments -->
## 代码注释
Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以 `/*` 开头,可以跨越多行,到 `*/ ` 结束。**注意**,许多程序员在多行注释的每一行开头添加 `*`,所以你经常会看到:
```java
/* This is a comment
* that continues
* across lines
*/
```
但请记住, `/*``*/` 之间的内容都是被忽略的。所以你将其改为下面的风格也是没有区别的。
```JAVA
/* This is a comment that
continues across lines */
```
第二种注释形式来自 C++ 。它是单行注释,以 `//` 开头并一直持续到行结束。这种注释方便且常用,因为它很直观和简单。所以你经常看到:
```JAVA
// 这是单行注释
```
<!-- You Never Need to Destroy an Object -->
## 对象清理
In some programming languages, managing storage lifetime requires
significant effort. How long does a variable last? If you are supposed to
destroy it, when should you? Confusion over storage lifetime can lead
to many bugs, and this section shows how Java simplifies the issue by
releasing storage for you.
Scoping
Most procedural languages have the concept of scope. This determines
both the visibility and lifetime of the names defined within that scope.
In C, C++, and Java, scope is determined by the placement of curly
braces {}. Here is a fragment of Java code demonstrating scope:
{
int x = 12;
// Only x available
{
int q = 96;
// Both x & q available
}
// Only x available
// q is "out of scope"
}
A variable defined within a scope is available only until the end of that
scope.
Indentation makes Java code easier to read. Since Java is a free-form
language, the extra spaces, tabs, and carriage returns do not affect the
resulting program.
You cannot do the following, even though it is legal in C and C++:
{
int x = 12;
{
int x = 96; // Illegal
}
}
The Java compiler will announce that the variable x has already been
defined. Thus the C and C++ ability to “hide” a variable in a larger
scope is not allowed, because the Java designers thought it led to
confusing programs.
Scope of Objects
Java objects do not have the same lifetimes as primitives. When you
create a Java object using new, it persists past the end of the scope.
Thus, if you say:
{
String s = new String("a string");
} // End of scope
the reference s vanishes at the end of the scope. However, the
String object that s points to is still occupying memory. In this bit
of code, there is no way to access the object after the end of the scope,
because the only reference to it is out of scope. In later chapters youll
see how the reference to the object can be passed around and
duplicated during the course of a program.
Because objects created with new exist as long as you need them, a
whole slew of C++ programming problems vanish in Java. In C++ you
must not only make sure that the objects stay around as long as
necessary, you must also destroy the objects when youre done with
them.
This brings up a question. If Java leaves the objects lying around, what
keeps them from filling up memory and halting your program, which
is exactly the kind of problem that occurs in C++? In Java, a bit of
magic happens: the garbage collector looks at all the objects created
with new and finds those that are no longer referenced. It then
releases the memory for those objects, so the memory can be used for
new objects. This means you dont worry about reclaiming memory
yourself. You simply create objects, and when you no longer need
them, they go away by themselves. This prevents an important class of
programming problem: the so-called “memory leak,” when a
programmer forgets to release memory.
Creating New Data
Types: class
If everything is an object, what determines how a particular class of
object looks and behaves? Put another way, what establishes the type
of an object? You might expect a keyword called “type,” and that would
certainly make sense. Historically, however, most object-oriented
languages use the keyword class to describe a new kind of object.
The class keyword (so common it will often not be bold-faced
throughout this book) is followed by the name of the new type. For
example:
class ATypeName {
// Class body goes here
}
This introduces a new type, although here the class body consists only
of a comment, so there is not too much you can do with it. However,
you can create an object of ATypeName using new:
ATypeName a = new ATypeName();
You cant tell it to do much of anything—that is, you cannot send it any
interesting messages—until you define some methods for it.
Fields
When you define a class, you can put two types of elements in your
class: fields (sometimes called data members), and methods
(sometimes called member functions). A field is an object of any type you
can talk to via its reference. A field can also be a primitive type. If
it is a reference to an object, you must initialize that reference to
connect it to an actual object (using new, as seen earlier).
Each object keeps its own storage for its fields. Ordinarily, fields are
not shared among objects. Here is an example of a class with some
fields:
class DataOnly {
int i;
double d;
boolean b;
}
This class doesnt do anything except hold data. As before, you create
an object like this:
DataOnly data = new DataOnly();
You can assign values to the fields by referring to object members. To
do this, you state the name of the object reference, followed by a
period (dot), followed by the name of the member inside the object:
objectReference.member
For example:
data.i = 47;
data.d = 1.1;
data.b = false;
What if your object contains other objects that contain data you want
to modify? You just keep “connecting the dots.” For example:
myPlane.leftTank.capacity = 100;
You can nest many objects this way (although such a design might
become confusing).
Default Values for Primitive Members
When a primitive data type is a field in a class, it is guaranteed to get a
default value if you do not initialize it:
Primitive
Default
boolean
false
\u0000
char
(null)
byte
(byte)0
short
(short)0
int
0
long
0L
float
0.0f
double
0.0d
The default values are only what Java guarantees when the variable is
used as a member of a class. This ensures that primitive fields will
always be initialized (something C++ doesnt do), reducing a source of
bugs. However, this initial value might not be correct or even legal for
the program you are writing. Its best to always explicitly initialize
your variables.
This guarantee doesnt apply to local variables—those that are not
fields of a class. Thus, if within a method definition you have:
int x;
Then x will get some arbitrary value (as it does in C and C++); it will
not automatically be initialized to zero. You are responsible for
assigning an appropriate value before you use x. If you forget, Java
definitely improves on C++: You get a compile-time error telling you
the variable might not be initialized. (C++ compilers often warn you
about uninitialized variables, but in Java these are errors.)
<!-- Methods, Arguments,and Return Values -->
### 方法使用
Methods, Arguments,
and Return Values
In many languages (like C and C++), the term function is used to
describe a named subroutine. In Java, we use the term method, as in
“a way to do something.”
Methods in Java determine the messages an object can receive. The
fundamental parts of a method are the name, the arguments, the
return type, and the body. Here is the basic form:
ReturnType methodName( /* Argument list */ ) {
// Method body
}
ReturnType indicates the type of value produced by the method
when you call it. The argument list gives the types and names for the
information you pass into the method. The method name and
argument list are collectively called the signature of the method. The
signature uniquely identifies that method.
Methods in Java can only be created as part of a class. A method can
be called only for an object4, and that object must be able to perform that
method call. If you try to call the wrong method for an object,
youll get an error message at compile time.
You call a method for an object by giving the object reference followed
by a period (dot), followed by the name of the method and its
argument list, like this:
objectReference.methodName(arg1, arg2, arg3);
Consider a method f() that takes no arguments and returns a value
of type int. For a reference a that accepts calls to f(), you can say this:
int x = a.f();
The type of the return value must be compatible with the type of x.
This act of calling a method is sometimes termed sending a message
to an object. In the preceding example, the message is f() and the
object is a. Object-oriented programming can be summarized as
“sending messages to objects.”
The Argument List
The method argument list specifies the information you pass into the
method. As you might guess, this information—like everything else in
Java—takes the form of objects. The argument list must specify the
object types and the name of each object. As always, where you seem
to be handing objects around, you are actually passing references.5
The type of the reference must be correct, however. If a String
argument is expected, you must pass in a String or the compiler will
give an error.
Here is the definition for a method that takes a String as its
argument. It must be placed within a class for it to compile:
int storage(String s) {
return s.length() * 2;
}
This method calculates and delivers the number of bytes required to
hold the information in a particular String. The argument s is of
type String. Once s is passed into storage(), you can treat it like
any other object—you can send it messages. Here, we call length(),
which is a String method that returns the number of characters in a
String. The size of each char in a String is 16 bits, or two bytes.
You can also see the return keyword, which does two things. First, it
means “Leave the method, Im done.” Second, if the method produces
a value, that value is placed right after the return statement. Here,
the return value is produced by evaluating the expression
s.length() * 2.
You can return any type you want, but if you dont return anything at
all, you do so by indicating that the method produces void (nothing).
Here are some examples:
boolean flag() { return true; }
double naturalLogBase() { return 2.718; }
void nothing() { return; }
void nothing2() {}
When the return type is void, the return keyword is used only to
exit the method, and is therefore unnecessary if called at the end of the
method. You can return from a method at any point, but if youve
given a non-void return type, the compiler will force you to return
the appropriate type of value regardless of where you return.
It might look like a program is just a bunch of objects with methods
that take other objects as arguments and send messages to those other
objects. That is indeed much of what goes on, but in the following
Operators chapter youll learn how to do the detailed low-level work by
making decisions within a method. For this chapter, sending messages
will suffice.
<!-- Writing a Java Program -->
## 程序编写
There are several other issues you must understand before seeing your
first Java program.
Name Visibility
A problem in any programming language is the control of names. If
you use a name in one module of the program, and another
programmer uses the same name in another module, how do you
distinguish one name from another and prevent the two names from
“clashing?” In C this is especially challenging because a program is
often an unmanageable sea of names. C++ classes (on which Java
classes are modeled) nest functions within classes so they cannot clash
with function names nested within other classes. However, C++
continues to allow global data and global functions, so clashing is still
possible. To solve this problem, C++ introduced namespaces using
additional keywords.
Java avoided all these problems by taking a fresh approach. To
produce an unambiguous name for a library, the Java creators want
you to use your Internet domain name in reverse, because domain
names are guaranteed to be unique. Since my domain name is
MindviewInc.com, my foibles utility library is named
com.mindviewinc.utility.foibles. Following your
reversed domain name, the dots are intended to represent
subdirectories.
In Java 1.0 and Java 1.1 the domain extensions com, edu, org, net, etc., were
capitalized by convention, so the library would appear:
Com.mindviewinc.utility.foibles. Partway through the
development of Java 2, however, they discovered this caused
problems, so now the entire package name is lowercase.
This mechanism means all your files automatically live in their own
namespaces, and each class within a file has a unique identifier. This
way, the language prevents name clashes.
Using reversed URLs was a new approach to namespaces, never before
tried in another language. Java has a number of these “inventive”
approaches to problems. As you might imagine, adding a feature
without experimenting with it first risks discovering problems with
that feature in the future, after the feature is used in production code,
typically when its too late to do anything about it (some mistakes were
bad enough to actually remove things from the language).
The problem with associating namespaces with file paths using
reversed URLs is by no means one that causes bugs, but it does make
it challenging to manage source code. By using
com.mindviewinc.utility.foibles, I create a directory
hierarchy with “empty” directories com and mindviewinc whose
only job is to reflect the reversed URL. This approach seemed to open
the door to what you will encounter in production Java programs:
deep directory hierarchies filled with empty directories, not just for the
reversed URLs but also to capture other information. These long paths
are basically being used to store data about what is in the directory. If
you expect to use directories in the way they were originally designed,
this approach lands anywhere from “frustrating” to “maddening,” and
for production Java code you are essentially forced to use one of the
IDEs specifically designed to manage code that is laid out in this
fashion, such as NetBeans, Eclipse, or IntelliJ IDEA. Indeed, those
IDEs both manage and create the deep empty directory hierarchies for
you.
For this books examples, I didnt want to burden you with the extra
annoyance of the deep hierarchies, which would have effectively
required you to learn one of the big IDEs before getting started. The
examples for each chapter are in a shallow subdirectory with a name
reflecting the chapter title. This caused me occasional struggles with
tools that follow the deep-hierarchy approach.
Using Other Components
Whenever you use a predefined class in your program, the compiler
must locate that class. In the simplest case, the class already exists in
the source-code file its being called from. In that case, you simply use
the class—even if the class doesnt get defined until later in the file
(Java eliminates the so-called “forward referencing” problem).
What about a class that exists in some other file? You might think the
compiler should be smart enough to go and find it, but there is a
problem. Imagine you use a class with a particular name, but more
than one definition for that class exists (presumably these are different
definitions). Or worse, imagine that youre writing a program, and as
youre building it you add a new class to your library that conflicts with
the name of an existing class.
To solve this problem, you must eliminate all potential ambiguities by
telling the Java compiler exactly what classes you want using the
import keyword. import tells the compiler to bring in a package,
which is a library of classes. (In other languages, a library could
consist of functions and data as well as classes, but remember that all
activities in Java take place within classes.)
Much of the time youll use components from the standard Java
libraries that come with your compiler. With these, you dont worry
about long, reversed domain names; you just say, for example:
import java.util.ArrayList;
This tells the compiler to use Javas ArrayList class, located in its
util library.
However, util contains a number of classes, and you might want to
use several of them without declaring them all explicitly. This is easily
accomplished by using * to indicate a wild card:
import java.util.*;
The examples in this book are small and for simplicitys sake well
usually use the * form. However, many style guides specify that each
class should be individually imported.
The static Keyword
Creating a class describes the look of its objects and the way they
behave. You dont actually get an object until you create one using
new, at which point storage is allocated and methods become
available.
This approach is insufficient in two cases. Sometimes you want only a
single, shared piece of storage for a particular field, regardless of how
many objects of that class are created, or even if no objects are created.
The second case is if you need a method that isnt associated with any
particular object of this class. That is, you need a method you can call
even if no objects are created.
The static keyword (adopted from C++) produces both these
effects. When you say something is static, it means the field or
method is not tied to any particular object instance. Even if youve
never created an object of that class, you can call a static method or
access a static field. With ordinary, non-static fields and
methods, you must create an object and use that object to access the
field or method, because non-static fields and methods must target
a particular object.6
Some object-oriented languages use the terms class data and class
methods, meaning that the data and methods exist only for the class as
a whole, and not for any particular objects of the class. Sometimes
Java literature uses these terms too.
To make a field or method static, you place the keyword before the
definition. The following produces and initializes a static field:
class StaticTest {
static int i = 47;
}
Now even if you make two StaticTest objects, there is still only
one piece of storage for StaticTest.i. Both objects share the same
i. For example:
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
Both st1.i and st2.i have the same value of 47 since they are the
same piece of memory.
There are two ways to refer to a static variable. As in the preceding
example, you can name it via an object; for example, st2.i. You can
also refer to it directly through its class name, something you cannot
do with a non-static member:
StaticTest.i++;
The ++ operator adds one to the variable. Now both st1.i and
st2.i have the value 48.
Using the class name is the preferred way to refer to a static
variable because it emphasizes the variables static nature7.
Similar logic applies to static methods. You can refer to a static
method either through an object as you can with any method, or with
the special additional syntax ClassName.method(). You define a
static method like this:
class Incrementable {
static void increment() { StaticTest.i++; }
}
The Incrementable method increment() increments the
static int i using the ++ operator. You can call increment()
in the typical way, through an object:
Incrementable sf = new Incrementable();
sf.increment();
However, the preferred approach is to call it directly through its class:
Incrementable.increment();
static applied to a field definitely changes the way the data is
created—one for each class versus the non-static one for each
object. When applied to a method, static allows you to call that
method without creating an object. This is essential, as you will see, in
defining the main() method that is the entry point for running an
application.
<!-- Your First Java Program -->
## 小试牛刀
Finally, heres the first complete program. It starts by displaying a
String, followed by the date, using the Date class from the Java
standard library.
// objects/HelloDate.java
import java.util.*;
public class HelloDate {
public static void main(String[] args) {
System.out.println("Hello, it's: ");
System.out.println(new Date());
}
}
In this book I treat the first line specially; its always a comment line
containing the the path information to the file (using the directory
name objects for this chapter) followed by the file name. I have
tools to automatically extract and test the books code based on this
information, and you will easily find the code example in the
repository by referring to the first line.
At the beginning of each program file, you must place import
statements to bring in any extra classes you need for the code in that
file. I say “extra” because theres a certain library of classes
automatically included in every Java file: java.lang. Start up your
Web browser and look at the documentation from Oracle. If you
havent downloaded the JDK documentation from the Oracle Java site, do so
now8, or find it on the Internet. If you look at the list of packages, youll see
all the different class libraries that come with Java.
Select java.lang. This will bring up a list of all the classes that are
part of that library. Since java.lang is implicitly included in every
Java code file, these classes are automatically available. Theres no
Date class listed in java.lang, which means you must import
another library to use that. If you dont know the library where a
particular class is, or if you want to see all classes, select “Tree” in the
Java documentation. Now you can find every single class that comes
with Java. Use the browsers “find” function to find Date. Youll see it
listed as java.util.Date, which tells you its in the util library
and you must import java.util.* in order to use Date.
If inside the documentation you select java.lang, then System,
youll see that the System class has several fields, and if you select
out, youll discover its a static PrintStream object. Since its
static, you dont need to use new—the out object is always there,
and you can just use it. What you can do with this out object is
determined by its type: PrintStream. Conveniently,
PrintStream is shown in the description as a hyperlink, so if you
click on that, youll see a list of all the methods you can call for
PrintStream. There are quite a few, and these are covered later in
the book. For now all were interested in is println(), which in
effect means “Print what Im giving you out to the console and end
with a newline.” Thus, in any Java program you can write something
like this:
System.out.println("A String of things");
whenever you want to display information to the console.
One of the classes in the file must have the same name as the file. (The
compiler complains if you dont do this.) When youre creating a
standalone program such as this one, the class with the name of the
file must contain an entry point from which the program starts. This
special method is called main(), with the following signature and
return type:
public static void main(String[] args) {
The public keyword means the method is available to the outside
world (described in detail in the Implementation Hiding chapter). The
argument to main() is an array of String objects. The args wont
be used in the current program, but the Java compiler insists they be
there because they hold the arguments from the command line.
The line that prints the date is interesting:
System.out.println(new Date());
The argument is a Date object that is only created to send its value
(automatically converted to a String) to println(). As soon as
this statement is finished, that Date is unnecessary, and the garbage
collector can come along and get it anytime. We dont worry about
cleaning it up.
When you look at the JDK documentation, you see that System has
many other useful methods (one of Javas assets is its large set of
standard libraries). For example:
// objects/ShowProperties.java
public class ShowProperties {
public static void main(String[] args) {
System.getProperties().list(System.out);
System.out.println(System.getProperty("user.name"));
System.out.println(
System.getProperty("java.library.path"));
}
}
/* Output: (First 20 Lines)
-- listing properties --
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=C:\Program
Files\Java\jdk1.8.0_112\jr...
java.vm.version=25.112-b15
java.vm.vendor=Oracle Corporation
java.vendor.url=http://java.oracle.com/
path.separator=;
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
user.script=
user.country=US
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=
java.vm.specification.name=Java Virtual Machine
Specification
user.dir=C:\Users\Bruce\Documents\GitHub\on-ja...
java.runtime.version=1.8.0_112-b15
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:\Program
Files\Java\jdk1.8.0_112\jr...
os.arch=amd64
java.io.tmpdir=C:\Users\Bruce\AppData\Local\Temp\
...
*/
The first line in main() displays all “properties” from the system
where you are running the program, so it gives you environment
information. The list() method sends the results to its argument,
System.out. You will see later in the book that you can send the
results elsewhere, to a file, for example. You can also ask for a specific
property—here, user.name and java.library.path.
The /* Output: tag at the end of the file indicates the beginning of
the output generated by this file. Most examples in this book that
produce output will contain the output in this commented form, so
you see the output and know it is correct. The tag allows the output to
be automatically updated into the text of this book after being checked
with a compiler and executed.
Compiling and Running
To compile and run this program, and all the other programs in this
book, you must first have a Java programming environment. The
installation process was described in Installing Java and the Book
Examples. If you followed these instructions, you are using the Java
Developers Kit (JDK), free from Oracle. If you use another
development system, look in the documentation for that system to
determine how to compile and run programs.
Installing Java and the Book Examples also describes how to install the
examples for this book. Move to the subdirectory named
objects and type:
javac HelloDate.java
This command should produce no response. If you get any kind of an
error message, it means you havent installed the JDK properly and
you must investigate those problems.
On the other hand, if you just get your command prompt back, you can
type:
java HelloDate
and youll get the message and the date as output.
This is the process to compile and run each program (containing a
main()) in this book9. However, the source code for this book also has a file
called build.gradle in the root directory, and this
contains the Gradle configuration for automatically building, testing,
and running the files for the book. When you run the gradlew
command for the first time, Gradle will automatically install itself
(assuming you have Java installed).
<!-- Coding Style -->
## 编码风格
The style described in the document Code Conventions for the Java
Programming Language 10 is to capitalize the first letter of a class name. If
the class name consists of several words, they are run
together (that is, you dont use underscores to separate the names),
and the first letter of each embedded word is capitalized, such as:
class AllTheColorsOfTheRainbow { // ...
This is sometimes called “camel-casing.” For almost everything else—
methods, fields (member variables), and object reference names—the
accepted style is just as it is for classes except that the first letter of the
identifier is lowercase. For example:
class AllTheColorsOfTheRainbow {
int anIntegerRepresentingColors;
void changeTheHueOfTheColor(int newHue) {
// ...
}
// ...
}
The user must also type these long names, so be merciful.
The Java code you find in the Oracle libraries also follows the
placement of open-and-close curly braces in this book.
## 本章小结
This chapter shows you just enough Java so you understand how to
write a simple program. Youve also seen an overview of the language
and some of its basic ideas. However, the examples so far have all been
of the form “Do this, then do that, then do something else.” The next
two chapters will introduce the basic operators used in Java
programming, and show you how to control the flow of your program.
1. This can be a flashpoint. There are those who say, “Clearly, its a
pointer,” but this presumes an underlying implementation. Also,
the syntax of Java references is much more akin to C++ references
than to pointers. In the 1st edition of Thinking in Java, I chose to
invent a new term, “handle,” because C++ references and Java
references have some important differences. I was coming out of
C++ and did not want to confuse the C++ programmers whom I
assumed would be the largest audience for Java. In the 2nd
edition of Thinking in Java, I decided that “reference” was the
more commonly used term, and that anyone changing from C++
would have a lot more to cope with than the terminology of
references, so they might as well jump in with both feet. However,
there are people who disagree even with the term “reference.” In
one book I read that it was “completely wrong to say that Java
supports pass by reference,” because Java object identifiers
(according to that author) are actually “object references.” And
(he goes on) everything is actually pass by value. So youre not
passing by reference, youre “passing an object reference by
value.” One could argue for the precision of such convoluted
explanations, but I think my approach simplifies the
understanding of the concept without hurting anything (well,
language lawyers may claim Im lying to you, but Ill say that Im
providing an appropriate abstraction).↩
2. Most microprocessor chips do have additional cache memory but
this is organized as traditional memory and not as registers↩
3. An example of this is the String pool. All literal Strings and
String-valued constant expressions are interned automatically
and put into special static storage. ↩
4. static methods, which youll learn about soon, can be called for
the class, without an object.↩
5. With the usual exception of the aforementioned “special” data
types boolean, char, byte, short, int, long, float, and double. In general,
though, you pass objects, which really means
you pass references to objects. ↩
6. static methods dont require objects to be created before they
are used, so they cannot directly access non-static members or
methods by calling those other members without referring to a
named object (since non-static members and methods must be
tied to a particular object).↩
7. In some cases it also gives the compiler better opportunities for
optimization↩
8. Note this documentation doesnt come packed with the JDK; you
must do a separate download to get it. ↩
9. For every program in this book to compile and run through the
command line, you might also need to set your CLASSPATH. ↩
10. (Search the Internet; also look for “Google Java Style”). To keep
code listings narrow for this book, not all these guidelines could
be followed, but youll see that the style I use here matches the
Java standard as much as possible.↩
<!-- 分页 -->
<div style="page-break-after: always;"></div>