目录
2.1 用引用操纵对象
2.1.1 所有编程语言都有它们自己操纵对象的方式
- C/C++中通过指针(pointer)操作对象;
- Java通过引用(reference)操作对象。只要能拿到纸箱对象的那个引用,就可以操纵这个对象。
- …
2.1.2 程序示例
public class Test01 {
public static void main(String[] args) {
/*
// 这只是创建了一个引用,它还没有和对象关联。
String s;
// 编译时会报以下错误
// 错误: 可能尚未初始化变量s System.out.println(s);
// ^
System.out.println(s);
*/
/*
正确的做法是:创建引用时,同时对它初始化。
通常必须对对象采用一种更通用的初始化方法。
带引号字符串文本对String对象初始化,是最通用的初始化方法。
*/
String s = "asdf"; // 创建一个String类型的引用S并初始化为"asdf"
System.out.println(s);
}
}
2.2 必须由你创建所有对象
- new关键字:“给我一个新对象”,即创建一个新对象。
String s = new String("asdf");
2.2.1 存储到什么地方
在java中有5个不同的地方可以传输数据
- 寄存器。分配最快的存储区域,因为寄存器位于处理器内部。在java中无法操作寄存器。在C/C++中可以向编译器建议寄存器的分配方。
- 堆栈。分配速度次于寄存器。位于通用RAM(随机访问存储器)中。用于存放局部变量。可以通过堆栈指针操作堆栈。堆栈指针向上移动,分配新的内存;堆栈向下移动,释放那些内存。在java中,创建程序时,系统必须知道存储在堆栈内的所有项的确切生命周期,以便上下移动堆栈指针。这限制了程序的灵活性。所以虽然某些数据(对象引用)存储在堆栈中,但是java对象存储堆在中,没存储在堆栈中。
- 堆。一种通用的内存池(位于RAM区)。用于存放所有java对象。在java中通过new关键字在堆中创建对象,在C++中在栈中创建对象。堆栈和堆的区别:在堆中存储对象,编译器不需要知道存储的数据在堆里面存储多长时间,这样在堆中分配存储具有很大的灵活性。代价是用堆进行存储分配和清理对象时比队堆栈进行存储分配需要更多的时间。
- 常量存储。常量值通常存放在程序代码内部,这样做是安全的,因为他们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分隔开,所以在这种情况下,可以选择将其存放在ROM(只读存储器)中。
例子:字符串池。所有的字面常量字符串和具有字符串值的常量表达式都自动是内存限定的,并且胡置于特殊的静态存储区中。 - 非RAM存储。数据完全在程序之外,它不受程序的任何控制,在程序没有运行时也可以存在。
例子:流对象和持久化对象。流对象中,对象转化成字节流,被发送到另一台机器上。持久化对象中,对象存放在磁盘上。在需要时,可以恢复成常规的基于RAM的对象。
2.2.2 特例:基本类型
对于基本类型,Java和C/C++相同的方法,不使用new来创建变量,而是创建一个并非是引用得的“自动“变量,这个变量直接存储”值“,位于堆栈中,因此更加高效。
2.2.2.1 九种基本数据类型
java中,每种基本类型所占存储空间大小是固定的,不随机器硬件结构变化,这是可移植性的原因之一。
基本类型 | 大小(Byte) | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
byte | 1 | -128 | 127 | Byte |
char | 2 | ‘\u0000’ | ‘\uFFFF’ | Character |
short | 2 | -32768 | 32767 | Short |
int | 4 | 0x80000000 | 0x7fffffff | Integer |
long | 8 | 0x8000000000000000L | 0x7fffffffffffffffL | Long |
float | 4 | 0x0.000002P-126f (1.4e-45f) | 0x1.fffffeP+127f (3.4028235e+38f) | Float |
double | 8 | 0x0.0000000000001P-1022 (4.9e-324) | 0x1.fffffffffffffP+1023 (1.7976931348623157e+308) | Double |
void | - | - | - | Void |
说明:
- 所有基本数值类型都有正负号,没有无符号的数值类型(C/C++中有无符号数值类型
unsign
)。 boolean
类型所占空间大小没有明确规定,只定义为字面值true
或false
。- 所有基本类型都有相应的包装类。可以在堆中创建一个非基本对象,表示对应的基本类型。
代码示例
public class Test03 {
public static void main(String[] args) {
char c = 'x';
Character ch1 = new Character(c);
// JAVA SE5 支持自动装箱和自动拆箱。
Character ch2 = 'x';
char ch3 = ch2;
}
}
2.2.2.2 高精度数字
BigInteger
和BigDecimal
两个类用于高精度计算。
BigInteger
支持任意精度的整数计算、BigDevimal
支持任意精度的定点数计算。
import java.math.BigDecimal;
import java.math.BigInteger;
public class Test04 {
public static void main(String[] args) {
BigInteger n1 = new BigInteger("-10");
BigInteger n2 = new BigInteger("20");
BigInteger ret1 = n1.add(n2);
BigInteger ret2 = n2.divide(n1);
System.out.println(ret1);
System.out.println(ret2);
BigDecimal d1 = new BigDecimal("20.0");
BigDecimal d2 = new BigDecimal("3.0");
// 将20.0 除以 3.0 的结果转换成整数
BigDecimal ret11 = d1.divideToIntegralValue(d2);
System.out.println(ret11); // 6
// 计算 20.0 除以 3.0 的 余数
BigDecimal ret12 = d1.remainder(d2);
System.out.println(ret12); // 2.0
}
}
2.2.3 java中数组
java中创建的数组也是在堆中的对象。java确保数组会被初始化,而且不能再它的范围之外被访问。在C/C++中可以在自身内存块之外访问。在java中创建一个数组对象时,实际上是创建了一个引用数组,并且每个引用都会自动被初始化为特定值(null
)。一旦在java中看到null
,就说明这个引用还没有指向某个对象。在使用任何引用之前吗,必须为其指定一个对象;如果使用一个null
的引用,运行时就会报空指针异常。
基本类型数组,所占的内存都活全部置零。
2.3 永远不需要销毁对象
在java中没有变量生命周期概念。java替我们完成了所有的清理工作,从而大大简化这个问题。
2.3.1 作用域
作用域(scope)概念,决定了内部定义的变量名的课件性和生命周期。基本数据类型只能在作用域开始后和结束前吗,使用作用域内定义的变量名。
代码示例
public class Test05 {
public static void main(String[] args) {
int x = 12;
{
int q = 95;
System.out.println(x);
}
/*
Error:(12, 29) java: 找不到符号
符号: 变量 q
位置: 类 com.qww.Test05
*/
// System.out.println(q);
{
// Error:(15, 17) java: 已在方法 main(java.lang.String[])中定义了变量 x
// int x = 10;
}
}
}
2.3.2 对象的作用域
java对象的生命周期和基本类型不一样,new创建一个java对象时,它可以存活在作用域之外。在作用域之外是可以读取到作用域内修改过的结果的。
代码示例
public class Test06 {
public static void main(String[] args) {
String s = "aa";
System.out.println("before: " + s);
{
s = "bb";
}
System.out.println("after: " + s);
// 在作用域结束后,引用str1就销毁了,但是通过new创建的对象依然还在堆中存在,它依然还占据这内存空间。
{
String str1 = new String("asdf");
}
}
}
在java中有一个垃圾回收器(garbage collector),用来监视用new创建出来的所有对象。当没有任何引用指向该对象一段时间后,garbage collector就会调用该对象的protected void finalize()方法,销毁对象,通知操作系统释放该对象所占的内存空间。也就不会再单线内存泄漏问题了。
2.4 创建新的数据类型:类
用class
关键字来定义新的数据类型。然后可以通过new
关键字创建新的数据类型的对象。
public class Test07 {
public static void main(String[] args) {
ATypeName aTypeName = new ATypeName();
}
}
class ATypeName {
// Class body goes here
}
2.4.1 字段和方法
一旦定义了类,就可以在类中定义字段(数据成语)和方法(成员方法),字段可以是任意数据类型。包括基本数据类和引用类型。如果字段是某个对象的引用,那就必须对这个引用进行初始化,使它与一个实际的对象相关联(使用new
关键字实现)。
每个对象都有用来存储它的字段的空间,普通字段不能再对象间共享。
代码示例
public class Test08 {
public static void main(String[] args) {
DataOnly dataOnly = new DataOnly();
System.out.println(dataOnly.i); // 0
System.out.println(dataOnly.d); // 0.0
System.out.println(dataOnly.b); // 0.0
dataOnly.i = 1;
dataOnly.d = 10.0;
dataOnly.b = 2.5;
SomeClass some = new SomeClass();
some.id = 20;
dataOnly.some = some;
System.out.println(dataOnly.i); // 1
System.out.println(dataOnly.d); // 10.0
System.out.println(dataOnly.b); // 2.5
}
}
class DataOnly {
int i;
double d;
double b;
SomeClass some;
}
class SomeClass {
int id;
}
2.4.1.1 基本成员的默认值
类的成语是基本类型,即使没有初始化,也有默认值。C/C++没有这个功能。但是这种初始化可能不正确,甚至是不合法。所以最好明确地对变量进行初始化。
基本类型 | 默认值 |
---|---|
boolean | false |
char | '\u000'(null) |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
对于局部变量,不会赋默认值。如果一个局部变量没有初始化,java会在编译时返回一个错误。
代码示例
public class Test10 {
public static void main(String[] args) {
new Test10().method1();
}
public void method1() {
int x;
// Error:(11, 28) java: 可能尚未初始化变量x
// System.out.println(x);
}
}
2.5 方法、参数和返回值
java的方法决定了一个对象能够接收什么样的消息。方法的接班组成部分包括:名称、参数、返回值和方法体。
ReturnType methodName(/* Argument list */) {
/* Method body */
}
方法名和参数列表合起来称为“方法签名”,可以唯一地标识出=一个方法。
Java中方法只能在类中创建。实例方法只能通过对象来调用。statric
修饰的静态方法不依赖与对象存在,通过类名来调用。
objectName.methodName(arg1, arg2, arg3);
代码示例
public class Test12 {
public static void main(String[] args) {
Test12 t = new Test12();
int x = t.f(20);
}
public int f(int i) {
System.out.println(i);
return 0;
}
}
调用方法的行为通常被称为发送消息给对象。消息是f()
,消息的类型是int
,消息的名字是i
,发送消息的值是20
,将消息发送到的对象是t
。面向对象的程序设计通常简单地归纳为“向对象发送消息”。
2.5.1 参数列表
方法的参数列表指的是要传递给方法什么样的消息,也就是指定消息的类型和名字。
在Java中参数列表传递实际上传递的是引用,并且传递引用的类型必须一致(正确)。尽管传递的是对象,而实际上传递的是对象的引用。基本数据类型是例外,是传值。
public class Test13 {
public static void main(String[] args) {
// Error:(8, 25) java: 不兼容的类型: char无法转换为java.lang.String
// Test13.method1('a');
int x = Test13.storage("asdf");
System.out.println(x);
}
/**
* 需要多少分字节才能容纳一个特定的String对象中的信息
* 字符串中的每个字符的存储空间大小为2个字节或16位,来支持unicode字符集
* @param s 字符串对象
* @return 字符串所占空间大小 单位字节
*/
public static int storage(String s) {
return s.length() * 2;
}
}
return
关键字的用法,包括两个方面:
return
代表“已经做完,离开此方法”。- 如果这个方法产生了一个运算结果值,这个值要放在
return
语句后面。
如果不想返回任何值,可将这个方法的返回值类型修改为void
。如果方法的返回值类型是void
,return
关键字的作用则是推出方法。所以可以在任意位置使用return
关键字结束方法。如果返回值类型不是void
,无论在哪里返回,编译器都会强制要求返回一个正确类型的值。
代码示例
public class Test14 {
public static void main(String[] args) {
}
public boolean flag() {
return true;
}
public double naturalLogBase() {
return 3.14;
}
public void nothing() {
return;
}
public void nothing2() {}
/*
// Error:(24, 5) java: 缺少返回语句
public String method1() {}
*/
/*
// Error:(28, 16) java: 不兼容的类型: int无法转换为java.lang.String
public String method2() {
return 0;
}
*/
}
2.6 构建一个Java程序
2.6.1 名字可见性
名字管理十分重要。如果在一个程序的某个模块里,使用了a这个名字,那么其他人在这个程序的另一个模块中使用了也使用了a这个名字,那么该怎么区分这呢?这个问题在C语言中尤其严重。
C++的解决方法:将函数包于其内,避免了其他类中函数名的冲突。然而C++允许有全局数据和全局函数的存在,这也有可能会出现名字冲突。为了解决这个问题,C++通过几个关键字引入了命名空间的概念。
java的解决方法:通过使用反转的域名来区分,这样可以保证它们是独一无二的。如:net.mindview.utility.foibles
,句点用来代表子目录的划分。在java2可发到一半时,设计者们发现会出现一些问题,所以就整个包名都改成小写了。
2.6.2 运用其他构件
如何才能使用自己预先定义好的类呢,即使不在同一个源文件中?在写一个程序时,在程序构建过程中,你想要将某个新类添加的哦类库中,但却与已有德类名冲突。
解决方法:使用关键字import
。告诉编译器你想要的类是什么。
// 一次导入一个类
import java.util.ArrayList;
// 一次导入多个类
import java.util.*;
2.6.3 static关键字
创建类时,就时在描述那个类的对象的外观和行为。只有通过new
关键字才能创建对象,数据空间才可以分配出来,类中的相应方法才能被调用。
static
关键字可以解决一下情形:
- 只想为某个特定域分配单一的分配空间,不去考虑究竟要创建多少个对象。
- 希望某个方法不与包含它的类的任何对象相关联在一起。
当声明一个字段或方法时static
时,那么这个字段或者方法不会和包含它的类创建的任何对象关联在一起。所以即使不船舰对象,也可以通过类来调用方法或访问变量。static
修饰的字段为静态变量,修饰的成员方法为静态变量。而非static
域或方法,必须通过创建对象来调用或访问。
代码示例
public class Test15 {
public static void main(String[] args) {
System.out.println(StaticTest.i); // 0
StaticTest.i = 20;
System.out.println(StaticTest.i); // 20
StaticTest t1 = new StaticTest();
StaticTest t2 = new StaticTest();
System.out.println(t1.i); // 20
System.out.println(t2.i); // 20
new Incrementable().increment();
System.out.println(StaticTest.i); // 21
}
}
class StaticTest {
static int i;
}
class Incrementable {
public void increment() {
StaticTest.i++;
}
}
即使创建了两个对象,StaticTest.i
也只有一份空间,这里容易出现线程安全问题。
出现线程安全问题的条件
- 多线程环境下,同时访问同一份变量
- 有共享资源
- 对共享资源执行非原子性操作
java中的原子性操作有
- 基本类型的读取和赋值操作,并且赋值操作必须时数字赋值给变量,因为变量之间的相互赋值不是原子操作
- 所i有引用(reference)的赋值操作
java.convurrent.Atmoic
包下的所有类的操作
建议通过类名来引用static
变量(首选方式)。因为它强调了变量的static
结构,会为编译器优化提供更好的机会。
statoc
方法用法就是在不创建任何对象时,通过类名引用来调用它。这对于定义main
方法很重要,因为main
方法是运行一个程序的入口。
2.7 你的第一个Java程序
代码示例
import java.util.Date;
public class Test16 {
public static void main(String[] args) {
System.out.println("Hello it's: ");
System.out.println(new Date());
}
}
java.lang
包下的所有类是默认导入每个Java文件中的。所有在java.lang
包下的所有类,如:String
、System
、StingBuilder
、StringBuffer
等,可以不用导入,直接使用。
System.out.println();
// 在System类中有in、out、err三个可供外界访问的static的成员变量
public final class System {
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
}
// 在PrintStream中有很多重载的print()和println()方法
public class PrintStream extends FilterOutputStream
implements Appendable, Closeable
{
public void print(int i) { /* ... */ }
// ...
public void println(int i) { /* ... */ }
// ...
}
在System
中out
是static
的,所以可以通过System
(类名)拿到PrintStream out
的实例对象。然后通过out
对象调用PrintStream
中的println()
实例方法。
System中的其他静态方法
public class Test16 {
public static void main(String[] args) {
// 显示所有从运行程序的系统中获得的所有“属性”,可以查看环境信息
System.out.println(System.getProperties());
System.out.println("===========================================");
// list()方法将结果发送给它的参数(System.out),当然也可以将结果发送给任何地方,例如发送到文件中。
System.getProperties().list(System.out);
System.out.println("===========================================");
// 通过System.getProperty(key)可以获得具体的属性值。
System.out.println(System.getProperty("user.name"));
System.out.println(System.getProperty("os.name"));
System.out.println(System.getProperty("java.library.path"));
}
}
2.11 练习
练习1
public class Exec01 {
int sno; // 默认初始化为0
char ch; // 默认初始化为
public static void main(String[] args) {
Exec01 exec01 = new Exec01();
System.out.println(exec01.sno); // 0
System.out.println("-->" + exec01.ch + "<--"); // --> <--
}
}
// 运行结果
0
--> <--
练习2
public class Exec02 {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
// 运行结果
Hello World
练习3
第四版的书在地69页的下面。
public class Exec03 {
public static void main(String[] args) {
ATypeName a = new ATypeName();
a.score = 99.0;
a.showScore();
}
}
class ATypeName {
int sno;
double score;
public void showScore() {
System.out.println(score);
}
}
// 运行结果
99.0
练习4和练习5
public class Exec04AndExec05 {
public static void main(String[] args) {
DataOnly dataOnly = new DataOnly();
dataOnly.i = 10;
dataOnly.d = 20.1;
dataOnly.b = false;
System.out.println("i=" + dataOnly.i + ", d=" + dataOnly.d + ", b=" + dataOnly.b);
}
}
class DataOnly {
int i;
double d;
boolean b;
}
// 运行结果
i=10, d=20.1, b=false
练习6
public class Exec06 {
public static void main(String[] args) {
int x = new Exec06().storage("asdfghjkl;");
System.out.println(x); // 20
}
public int storage(String s) {
return s.length() * 2;
}
}
// 运行结果
20
练习7
public class Exec07 {
public static void main(String[] args) {
System.out.println(StaticTest.i); // 0
Incrementable.increment();
System.out.println(StaticTest.i); // 1
}
}
class StaticTest {
static int i;
}
class Incrementable {
static void increment() {
StaticTest.i++;
}
}
// 运行结果
0
1
练习8
public class Exec08 {
static A a;
public static void main(String[] args) {
Exec08.a = new A();
System.out.println(Exec08.a);
for (int i = 0; i < 10; i++) {
Exec08 e = new Exec08();
System.out.println(Exec08.a);
}
}
}
class A {
}
// 运行结果
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
com.qww.exec.A@1b6d3586
创建了10个Exec08对象,但静态变量a的地址的hash值没有变化。因此,无论创建多少个特定类的对象,这个类的static A a
静态变量都只有一个实例。
练习9
public class Exec09 {
public static void main(String[] args) {
// 自动装箱
Integer i = 10;
// 自动拆箱
int x = new Integer(100);
System.out.println(i);
System.out.println(x);
}
}
// 运行结果
10
100
练习10
public class Exec10 {
public static void main(String[] args) {
int x = Integer.parseInt(args[0]);
boolean y = Boolean.parseBoolean(args[1]);
String s = args[2];
System.out.println("x=" + x + ", y=" + y + ", s=" + s);
}
}
// 运行结果
x=99, y=true, s=asdf
练习11
public class Exec11 {
public static void main(String[] args) {
AllTheColorsOfTheRainbow a = new AllTheColorsOfTheRainbow();
a.anIntegerRepresentColors = 0x111;
System.out.println(a.anIntegerRepresentColors);
int newHue = 0x222;
a.changeTheHueOfTheColor(newHue);
System.out.println(a.anIntegerRepresentColors);
}
}
class AllTheColorsOfTheRainbow {
int anIntegerRepresentColors;
void changeTheHueOfTheColor(int newHue) {
this.anIntegerRepresentColors = newHue;
}
}
// 运行结果
273
546
练习12
import java.util.Date;
public class Exec12 {}
/**
* <code>HelloDate</code>的第二个版本<br>
* @author qww
* @version 1.0
* @see com.qww.exec.Exec12
* @since 1.0
*/
class HelloDate {
/**
* main method<br>
* <p>the entrance of program</p>
* @param args String array of the terminal arguments
* @throws exeception no execption thrown
*/
public static void main(String[] args) {
System.out.println("Hello, it's: ");
System.out.println(new Date());
}
}
// 运行结果
Hello, it's:
Sat Mar 27 17:58:59 CST 2021
在cmd中运行javadoc -d doc -encoding UTF-8 -charset UTF-8 Exec12.java
命令,生成的网页代码在doc目录下。用浏览器打开index.html文件,就可以查看文档了。
练习13
Document1.java的浏览器结果:
Document2.java的浏览器结果:
Document3.java的浏览器结果:
练习14
练习15
练习16
/**
* TODO
*/
public class Overloading {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// ...
}
}
}