整理总结
随着计算机革命的发展,“不安全”的编程方式已逐渐称为编程代价高昂的主因之一。
其中,初始化与清理正是涉及安全的两个问题。
初始化:许多程序错误都源于程序员忘记初始化变量。
清理:当使用完元素之后,如果元素一直占用资源而不释放,结果将是资源(尤其是内存)用尽,所以,清理也显的十分重要。
1.1用构造器确保初始化
在java中,创建对象时,如果其类具有构造器,那么java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。(在java中,如果没有定义构造器方法,每个类就会有一个默认的无参构造方法)
在创建对象时,如:new Rock();
则会为对象分配存储空间,并调用相应的构造器,这就保证了在你操作对象之前,它就已经被初始化了。
构造器特点:
1:名字与类名相同
2:有无参构造器和带参构造器
3:没有返回值
2.2方法重载
方法重载:方法名相同而形式参数不同的方法。
2.2.1区分重载方法
1:方法名相同,参数类型不同
2:方法名相同,参数顺序不同(不建议,会使得代码难以维护)
2.2.2涉及基本类型的重载
基本类型能从一个“较小”的类型自动提升至一个“较大”的类型,一旦涉及到重载,可能会造成一些混乱
例子:(sob是我省略的输出语法而已,以下括号为了好看一点采用中文的)
public class Test{
void f1(int x) {
sob("f1(int)") ;
}
void f2(long x) {
sob("f2(long)");
}
void f3(float x) {
sob("f3(float )") ;
}
void test(){
f1(5);
f2(5);
f3(5);
}
public static void main(String[] args){
Test t=new Test();
t.test();
}
}
结果:
f1(int)
f2(long)
f3(float)
可以看出,5在f1()方法中会被当作int处理,但在另外两个方法中,实际参数类型小于方法中声明的形式参数类型,会被自动提升数据类型,char型略有不同,如果找不到恰好接受char型参数的方法,就会把char直接提升至int型。
当实际参数类型大于方法中声明的形式参数类型时,则需要我们进行强制转换,否则编译器会报错。
5.2.3以返回值区分重载方法
其实是行不通的。
原因:在某种情况下编译器可以根据语境判断出语义,如
void f(){}
Int f(){return 1;}
但是有时我们不关心方法的返回值,只想要方法调用的其他效果,如直接调用f();
此时java无法判断该调用哪个方法,因此,根据方法返回值来区分重载方法是行不通的。
5.3默认构造器
默认构造器是没有形式参数的,它的作用是创建一个默认对象。
如果你的类中没有构造器,则编译器会自动帮你创建一个默认构造器。
如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。
5.4 this关键字
假设你希望在方法内部获得当前对象的引用,就可以使用this关键字。
this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。
注意:如果在方法内部调用同一类的另一个方法,就不必使用this。
5.4.1在构造器中调用构造器
注意事项:
1.在一个构造器中,尽管可以用this调用一个构造器,但不能调用两个。
2.必须将构造器调用置于最起始处,否则编译器会报错。
this还有另一种用法。当参数与数据成员名字相同时,使用(this.名称)表示数据成员。
5.4.2 static方法
在static方法内部不能调用非静态方法,反过来倒是可以。
在没有创建任何对象的情况下,可以直接通过类调用static方法。
5.5清理:终结处理和垃圾回收
java虽然有垃圾回收器负责回收无用对象占据的内存资源,但是也有特殊情况:假定你的对象(并非用new)获得了一块特殊的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块特殊内存。为了应付这种情况,java允许在类中定义一个名为finalize()的特殊方法。它的工作原理假定是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作
在java中
1:对象可能不被垃圾回收
2:垃圾回收不等于“析构”(C++中销毁对象必须用到这个函数)
3:垃圾回收只与内存有关
无论对象如何创建,垃圾回收器都会负责释放对象占据的内存。这就将finalize()的需求限制到一种特殊情况,即通过某种创建对象方式意外的方式为对象分配了存储空间
之所有要有finalize()方法,是由于在分配内存时可能采用了类似C语言中的做法,而非java中的通常做法。这种情况主要发生在“本地方法”的情况下,本地方法是一种在java中调用非java代码的方法。在非java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用free()函数,否则存储空间将得不到释放,从而造成内存泄漏。当然,free()是C和C++中的函数,所以需要在finalize()中用本地方法调用它。
finalize()不是进行普通的清理工作的合适场所
无论是“垃圾回收”还是“终结”,都不保证一定会发生。入股java虚拟机并未面临内存耗尽的情形,他是不会浪费时间区执行垃圾回收以恢复内存的。
终结条件
通常,不能指望finalize(),必须创建其他的清理方法,并且明确地调用他们。不过,finalize()有一个有趣地用法,他并不依赖每次都要对finalize()进行调用,这就是对象终结条件地验证。
例如,要是对象代表了一个打开的文件,在对象被回收前应该关闭这个文件。只要是对象中存在没有被适当清理地部分,程序就存在很隐晦地缺陷。finalize()可以用来最终发现这种情况--尽管它并不总是会被调用。如果某次fialize()地动作使得缺陷被发现,那么就可以找出问题所在,这才是人们真正关心的。
以下例子示范了finalize()可能地使用方式:
class Book{
boolean checkedOut=false;
Book(boolean checkOut){
checkedOut =checkOut;
}
void checkIn(){
checkedOut=false;
}
protected void finalize(){
if(checkedOut){
sob("Error:check out");
}
}
public static void main(String[] args){
Book novel =new Book(true);
novel.checkIn();
new Book(true);
System.gc();
}
}
结果:Error:check out
本例地终结条件是:所有地Book对象在被当作垃圾回收前都应被签入(check in),但在main方法中,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。
垃圾回收器如何工作
在某些java虚拟机中,堆的实现更像一个传送带,每分配一个新对象,它就往前移动一格。这意味这对象存储空间分配速度非常快。java“堆指针”只是简单地移动到尚未分配地区域,齐效率比得上C++在堆栈上分配空间地效率。
当然,java地堆未必完全像传送带那样工作,因为那会导致频繁地内存页面调度--将其移仅移除移进移出硬盘,最终,在创建足够多地对象之后,内存资源将耗尽。所以还要有垃圾回收器地介入,当它工作时,将一面回收空间,一面使堆中地对象紧凑排列,这样“堆指针”就可以很容易移动到更靠近传送到地开始处,也就尽量避免了页面错误。通过垃圾回收器对对象重新排列,实现了一种高速的,有无限空间可供分配地堆模型。
java虚拟机的两种工作模式
1:停止-复制。
先停止程序地运行(所以它不属于后台回收模式),然后将所有存活地对象从当前堆复制到另一个堆,没有被复制地全部都是垃圾。当对象被复制到新堆时,它们时一个挨一个的,所以新堆保持紧凑排列,然后可以按前述方法简单直接分配新空间了。
当把对象从一处搬到另一处时,所有指向它的那些引用都必须修正。位于堆或静态存储区的引用可以直接修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到。
这种“复制式回收期”效率低的原因。首先,得有两个堆,然后在两个堆之间来回倒腾,从而使得维护比实际需要多一倍的空间。某些java虚拟机的处理方式是,按需分配几块较大的内存,复制动作发生在这些较大内存之间。第二个问题在于复制。程序处于稳定状态之后,可能只产生少量垃圾甚至没有,尽管如此,复制式回收器仍将所有内存自一处复制到另一处,这很浪费,所有java虚拟机会进行检查,要是没有新垃圾产生,就会转换到另一种工作模式(即“自适应”)。“标记-清扫”。
2:标记-清扫
虽然标记-清扫速度相当慢,但只有少量垃圾甚至不会产生垃圾时,它的速度就很快了。
原理:从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象设一个标记,这个过程不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。
java虚拟机会进行监视,如果所有对象都很稳定,就会切换到“标记-清扫”方式,同样,java虚拟机会跟踪“标记-清扫”的效果,要是堆空间出现很多碎片,就会切换回“停止-复制”方式。这就是自适应技术。
5.6构造器初始化
初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散步于方法定义之间,它们仍旧会在任何方法(包括构造器)内调用之前得到初始化。
例子
class Window{
Window(int i){
sob(i)
}
}
class House{
Window w1=new Window(1);
House(){
sob("House");
w3=new Window(33);
}
Window w2=new Window(2);
void f(){
sob("f()");
}
w3=new Window(3);
public static void main(String[] args){
House h=ew House();
h.f();
}
}
结果:
1
2
3
House
33
f()
静态数据初始化
无论创建多少个对象,静态数据只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域时静态的基本类型域且也没有对他进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那它的默认初始值就是null。
静态初始化只有在必要时刻才会进行
例子
class Bowl{
Bowl(int marker){
sob("Bowl("+marker+")");
}
void f1(int marker){
sob("f1("+marker+")");
}
}
class Table{
static Bowl bowl1=new Bowl(1);
Table(){
sob("Table()“);
bowl2.f1(1);
}
void f2(int marker){
sob("f2("+marker+")");
}
static Bowl bowl2=new Bowl(2);
}
class Cupboard{
Bowl bowl3=new Bowl(3);
static Bowl bowl4=new Bowl(4);
Cupboard(){
sob("Cupboard");
bowl4.f1(2);
}
void f3(int marker){
sob("f3("+marker+")");
}
static Bowl bowl5=new Bowl(4);
}
public class StaticInitialization{
public static void main(String[] args){
sob("Createing");
new Cupboard();
sob("Createing");
new Cupboard();
table.f2(1);
cupboard.f3(1)
}
static Table table=new Table();
static Cupboard cupboard=new Cupboard();
}
结果:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard
f1(2)
Createing
Bowl(3)
Cupboard
f1(2)
Createing
Bowl(3)
Cupboard
f1(2)
f2(1)
f3(1)
显示的静态初始化
例子
public class Spoon{
static int i
static{
i=47;
}
}
这段带啊吗只执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时(即便从未生成过那个类的对象)。
非静态实例初始化
public class Spoon{
{
sob(47);
}
}
保证当你调用类构造器时就会被调用,且会在构造器之前执行。
另外还有(可变参数列表)和(枚举类型)都要了解一下,这里就不整理了。