Java编程指南-Chapter4 对象和类
Chapter4 对象和类
1.内存中的对象
在类、类级或者是在方法级中声明变量时,要为赋给变量的数据分配内存空间。对于基本类型,可以很容易计算出占用的内存空间。例如,声明一个int
需要4个字节,声明一个long
需要8个字节。但是计算引用变量占用的内存空间就不一样了。
程序在运行时,一些内存空间分配给了数据。这个数据在空间逻辑上分为两个部分:堆和栈。基本类型分配在栈中,Java对象则放在堆中。
声明基本类型时,栈中就会分配一些字节。声明引用变量时,栈中也会分配一些字节,但是内存不包含对象的数据,它包含的是对象在堆中的地址,换句话说就是当声明:
Book book;
就把一些字节分配给了引用变量book。book的初始值为null,因为还没有对象赋给它。当编写一下代码时:
Book book = new Book();
这就创建了一个Book的实例,它存储在堆中,并将这个实例的地址赋给引用变量book。
Java的引用变量就像是C++中的指针,只不过不能操作引用变量而已。在Java中,引用变量用来存取它所引用对象的成员。因此,如果Book类有公有方法review,那么就可以用下面的语法来调用这个方法:
book.review();
一个对象可以被多个引用变量引用,例如:
Book myBook = new Book();
Book yourBook = myBook;
第二行代码把myBook的值复制给yourBook,结果是yourBook现在和myBook引用同一个Book对象。
另一方面,下面的代码则创建了两个不同的Book对象:
Book myBook = new Book();
Book yourBook = new Book();
2.Java包
如果你正在开发一个由不同部分组成的应用程序,可能会想组织好各类以便于维护。有了Java,就可以将相关的类或者功能相似的类打包到一起。例如,标准Java类组成一个包,Java核心类放到java.lang包中,所有执行输入和输出操作的类放到java.io包中,等等。
如果需要更细致地组织某个包,可以创建和前者部分名称相同的包。例如,Java类库就有java.lang.annotation和java.lang.reflect包。但是要注意共用部分名称并不会让两个包有关联。例如java.lang和java.lang.reflect包是两个不同的包。
以java开头的包名预留给核心库使用,因此不能创建以单词java开头的包。你可以对属于此类包的类进行编译,但是不能运行它们。此外,以javax开头的包是准备给核心库配套的扩展库使用的,因此也不要创建以javax打头的包。
除了对类进行组织外,打包还可以避免命名冲突。例如,两个同名的类属于不同的包,那么应用程序就可以在使用来自A公司的MathUtil类的同时,又使用来自另一家公司同名的MathUtil类。为此,根据规范,包名称应该基于逆序的域名。
因此,Sun公司的包名以com.sun开头,如果我的域名为example.com,那么我的包名称相应以com.example开头。例如,我可以把所有的applet都放到com.example.applet包中,把servlet放到com.example.sesrvlet包中。
包不是一个物理对象,因此不需要创建包。要把类放到包中,就要使用关键字package
,在它后面跟着的是包名称。
package lynn.test;
public class Main {
// example code
}
没有包声明的类我们说它属于缺省包。应该始终使用包,因为缺省包中的类型不能为缺省包之外的其他类型所用(除非使用称之为映射的技术)。类要是没有包,这不是什么好事。
尽管包不是物理对象,但是包名和它的类源文件的物理位置还是有关系的。包名表示一个目录结构,包名中的句点表示子文件夹。
3.传值还是传引用?
可以将基本类型变量或者引用变量传递给方法。基本类型变量是传值传递,引用变量则是传引用传递。即传递基本类型时,JVM将会把传递进来的变量复制给一个新的局部变量。如果修改了局部变量的值,则这个变化不会影响传入的基本类型变量。
如果传递的是引用变量,那局部变量就指向和传入引用变量一样的对象。如果改变方法中引用的对象,这个改变也会反映到调用代码中。如下:
package test.test1;
class Point {
public int x;
public int y;
}
public class Test {
public static void increment(int x) {
x++;
}
public static void reset(Point point) {
point.x = 0;
point.y = 0;
}
public static void main(String[] args) {
int a = 9;
increment(a);
System.out.println(a); // prints 9
Point p = new Point();
p.x = 400;
p.y = 600;
increment(p.x);
System.out.println(p.x);// prints 400
reset(p);
System.out.println(p.x); // prints 0
}
}
代码分析:
-
在Test类中有两个方法:increment和reset,分别是递增方法和重置为0方法。在main方法中,将a(值是9)传递给increment方法,方法调用之后输出a的值还是9,即a的值没有改变。
然后创建一个Point对象,将引用赋给p。接着初始化它的域,并将它传递给reset方法。reset方法的改变会影响到Point对象,因为对象是传引用传递的。因此输出p.x的值的结果为0。
但是如果只是单独将引用对象的某个域值传递给某个方法,并且该域为基本类型变量(如代码中的x变量),那么传递时还是按照传值的方式传递(如同上述代码中的a)
4.小结
-
OOP是根据现实中的对象来构建应用程序的模型。由于Java是一种OOP语言,因此对象在Java中扮演着核心的角色。对象是根据称为类的模板来创建的。类的成员有很多种类型:域、方法和构造器。还有其他的Java成员类型,比如枚举和内部类。
OOP拥有两种强大的特性:抽象和封装。OOP中的抽象是指用编程对象来表示真实对象的这种行为。封装是一种机制,它保护对象中需要保证安全的部分,只暴露对象中可以安全暴露的部分。另一个特性是方法重载,只要签名完全不同,方法重载允许类有同名方法。
Java还配备有一个垃圾回收器,有了它就无需手动销毁不同的对象。当对象超出作用域或者不再被引用时,它们就会被回收。