您的位置:首页 > 编程语言 > Java开发

JAVA编程思想学习第五篇の初始化与清理

2014-05-21 15:06 405 查看
随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。

1.用构造器确保初始化

在JAVA中,通过提供构造器,类的设计者可确保每个对象都会得到初始化。创建对象时,如果其类具有构造器,java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。

命名方式和类的名称相同。访问权限与定义类的访问权限一致(即并非一定是public)。构造器是一种特殊类型方法,因为它没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会自动返回什么,但是仍可选择让它返回别的东西。

2.方法重载

为了让方法名相同而形式参数不同的构造器同时存在,必须用到方法重载。同样也适用于其他方法。

class Tree{
int height;
Tree(){
height=0;
}
Tree(int i){
height=i;
}
void info(){
}
void info(String s){
}
}


区分重载方法:方法名相同返回值相同,参数不同。

*设计基本类型的重载:

基本类型能从一个“较小”的类型自动提升至一个“较大”的类型,此过程一旦牵涉重载,可能会造成一些混淆。见下例:

public class test{
void f1(int i){print("f1,int");}
void f1(long i){print("f1,long");}
void f1(double i){print("f1,double");}
void f2(long i){print("f2,long");}
void f2(double i){print("f2,double");}
void f3(double i){print("f3,double");}
public static void main(String[] args){
test t1=new test();
t1.f1(5);
t1.f2(5);
t1.f3(5);
}
}/*Output:
f1,int
f2,long
f3,double
*///:~


如果传入的实际参数大于重载方法声明的形式参数,会出现什么情况呢?如果传入的实际参数较大,就得通过类型转换来执行窄化转换。如果不这样,编译器就会报错。但是这会丢失精度。

3.默认构造器

如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。

4.this关键字

class Banana{ void peel(int i){}}

public class BananaPeel{
public static void main(String[] args){
Banana a=new Banana();
Banana b=new Banana();
a.peel(1);
b.peel(2);
}
}


如果只有一个peel()方法,它如何知道是被a还是b调用的呢?

编译器做了一些事情。它暗自把“所操作对象的引用”作为第一个参数传给了peel()。所以上面调用变成了Banana.peel(a,1);Banana.peel(b,2);

这是内部表示形式,我们并不能这样写。

this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。

class Person{
public void eat(Apple apple){
Apple peeled=apple.getPeeled();
}
}

class Peeler{
static Apple peel(Apple apple){
return apple;
}
}

class Apple{
Apple getPeeled(){ return Peeler.peel(this);}
}

public class PassingThis{
public static void main(String[] args){
new Person().eat(new Apple());
}
}


Apple需要调用Peeler.peel()方法,它是一个外部的工具方法,将执行由于某种原因而必须放在Apple外部的操作,为了将其自身传给外部方法,Apple必须使用this关键字。

构造器中调用构造器也可以用this关键字。需要注意:1.不能调用两个。2.必须将构造器调用置于最起始处,否则编译器会报错。

static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来可以。

5.清理:终结处理和垃圾回收

Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。为什么会在下一次垃圾回收动作发生时,才会真正回收对象占用的内存?

也许你会发现,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也是有开销的,要是不使用它,那就不用支付这部分开销了。

那finalize()的用途是什么呢?由于在分配内存时可能采用了类似C语言的做法,而非Java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,在非Java代码中也许会调用C的malloc函数,所以需要在finalize()中用本地方法调用free()。

垃圾回收器如何工作呢?Java的垃圾回收器依据的思想是:对于任何“活”的对象,一定能追溯到其存活的堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须是活的。

在这种方式下,Java虚拟机将采用一种自适应的垃圾回收技术。至于如何找到的存活对象,取决于不同的java虚拟机实现。有一种做法为停止-复制。显然这意味着,先暂停程序的运行(它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的都是垃圾。当对象被复制到新堆时,他们是一个挨着一个的,所以以新堆保持紧凑排列,然后就可以按前述方法简单、直接分配新空间了。

把对象从一处搬到另一处时,所有指向他们的引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但可能还有其他指向他们的对象引用,他们在遍历的过程中才能被找到。

这种“复制式回收器”效率比较低,有2个原因。1.得有2个堆,然后得在这两个堆来回倒腾。从而得维护比实际需要多一倍的时间。某些Java虚拟机对次处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。

第2个问题在于复制。程序进入稳定状态之后,可能只产生很少垃圾,甚至没有垃圾。如果还用内存复制方式,这很浪费。为了避免这种情况,一些java虚拟机进行检查;要是没有新垃圾产生,就会转换成另一种工作模式。这种模式叫“标记-清扫”。这种模式也必须在程序暂停情况下才能进行。

“标记-清扫”所依据的思想同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理工作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆就on关键是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。

在java虚拟机中,内存分配以较大的“块”为单位。如果对象较大,它会占用单独的块,严格来说,“停止-复制”要求在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块以后,垃圾回收器在回收的时候就可以往飞起的块里拷贝对象了。每个块都用相应的代数来记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器会定期进行完整的清理动作----大型对象仍然不会被复制(只是代数增加),内含的小型对象的那些快则被复制并整理。Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就会切换到“标记-清扫”方式;同样,java虚拟机会跟踪“标记-清扫”的效果,要是堆空间出现很多碎片,就会切换到“停止-复制”方式。这就是“自适应”技术。

6.成员初始化

java方法内的局部变量必须初始化,否则会报错。类中的数据成员是基本类型的话,Java就会保证他们初始化,要是类中定义一个对象引用时,如果不将其初始化,此引用就会获得一个特殊的null值。

7.构造器初始化



初始化顺序

在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
class window{
window(int i){
print("window("+i+")");
}
}

class House{
window w1=new window(1);
House(){
print("House()");
w3=new window(33);
}
window w2=new window(2);
window w3=new window(3);
void f(){print("f()")};
}

public class test{
public static void main(String[] args){
House h=new House();
h.f();
}
}/*Output:
window(1)
window(2)
window(3)
House()
window(33)
f()
*///:~


2.静态数据的初始化

静态数据都只占用一份存数区域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化就是null。static数据会在类中其它成员初始化以前初始化,且只会被初始化一次。

<span style="font-size:14px;">class Bowl{
Bowl(int marker){
print("Bowl("+marker+")");
}
void f1(int marker){
print("f1("+makrker+")");
}
}

class Table{
static Bowl bowl1=new Bowl(1);
Table(){
print("Table()");
bowl2.f1(1);
}
void f2(int marker){
print("f2("+marker+")");
}
static Bowl bowl2=new Bowl(2);
}

class Cupboard{
Bowl bowl3=new Bowl(3);
static Bowl bowl4=new Bowl(4);
Cupboard(){
print("Cupboard");
bowl4.f1(2);
}
void f3(int marker){
print("f3("+marker+")");
}
static Bowl bowl5=new Bowl(5);
}

public class staticTest{
public static void main(String[] args){
print("Creating new Cupboard() in main");
new Cupboard();
print("Creating new Cupboard() in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table=new Table();
static Cupboard cupboard=new Cupboard();
}/*Output:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*///:~</span>


8.数组初始化

定义方式:int[] a1或者int a1[],编译器不允许指定数组的大小。

9.枚举类型

public enum Spiciness{
NOT,MILD,MEDIUM,HOT,FLAMING
}

public class SimpleEnum{
public static void main(String[] args){
Spiciness howHot=Spiciness.MEDIUM;
System.out.println(howHot);
}
}/*Output:
MEDIUM
*///:~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: