#### 初始化和清理涉及安全的两个问题。
####Java 采用构造器,并额外提供了”垃圾回收器“
构造器:在创建对象时被自动调用的特殊方法
垃圾回收器:对于不再使用的内存资源,垃圾回收器能自动将其释放
》》用构造器确保初始化
#### 在创建对象时,如果其类具有构造器,Java 就会在用户有能力操作
对象之前自动调用相应的构造器,从而保证了初始化的进行。
#### 构造器采用与类名相同的名称。
#### 调用构造器是编译器的责任。
#### 在用 new 关键字创建对象时,将会为对象分配存储空间,并调用相
应的构造器。这就确保了在你能操作对象之前,它已经被恰当地初始化
了。
#### 请注意:由于构造器的名称与类名完全相同,所以”每个方法首字母小
写“的编码风格并不适用于构造器。
#### 不接受任何参数的构造器,叫做默认构造器。--->无参构造器
#### 如果类中已经有了带有参数的构造器,那么编译器就不会允许你以
其他任何方式创建对象( 只能以构造器的形式创建对象)
#### 在 Java 中,”初始化“和”创建“捆绑在一起,两者不能分离。
#### 构造器是一种特殊的方法,它没有返回值。
》》方法重载
#### 所谓方法是给某个动作取的名字。
####在程序设计语言中,每个方法(有些称为函数)都提供一个独一无二
的标识符
####为了让方法名相同而形式参数不同的构造器同时存在,必须用到
方法重载。同时,尽管方法重载是构造器所必需的,但它亦可应用于
其他方法,且用法同样方便。
####区分方法重载方法:
---------每个重载的方法都必须有一个独一无二的参数类型列表。
####涉及基本类型的重载:
--------基本数据类型能从一个”较小“的类型自动提升为一个”较大“的
类型,此过程一旦牵涉到重载,可能会造成一个些混淆。
------- 如果传入的数据类型(实际参数类型)小于方法中声明的形式
参数类型,实际数据类型就会被提升。
char 型略有不同,如果无法找到恰好接受 char 参数的方法,
就会把 char 直接提升至 int 型。
------如果传入的实际参数较大,就得通过类型转换来执行窄化转换,
如果不这样做,编译器就会报错。
#### 以返回值区分重载方法-------->行不通的
-------只能以方法名和参数列表来区分各个方法
》》默认构造器
#### 默认构造器就是没有形式参数的构造器---它的作用就是创建一个
”默认对象“
#### 如果你写的类中没有构造器,则编译器会自动帮你创建一个默认
构造器。
####如果已经定义了构造器(无论是否有参数),编译器就不会帮你
自动创建默认构造器
》》 this关键字
#### 如果你希望在方法的内部获取当前对象的引用。由于这个引用是
由编译器”偷偷“传入的,所以没有标识符可用。但是,为此有个
专门的关键字:this
#### this 关键字只能在方法内部使用,表示对”调用方法的那个对象“
的引用。
#### this 的用法和其他对象引用并无不同。但要注意,如果在方法
内部调用同一个类的另一个方法,就不必使用 this ,直接调用
即可。当前方法中的 this 引用会自动应用于同一类中的其他
方法。
#### 只有当需要明确指出对当前对象的引用时,才需要使用 this
关键字。例如,需要返回对当前对象的引用,就常常在 return
语句里这样写:
return this;
#### this 关键字对于将当前对象传递给其他方法也很有用
#### 在构造器中调用构造器:
--------可能为一个类写了多个构造器,有时可能想在一个构
造器中调用另一个构造器,以避免重复代码。可用 this
关键字做到这一点。
--------在构造器中,为 this 添加参数列表,将会调用
符合此参数列表的某个构造器。
--------尽管可以使用 this 调用一个构造器,但却不能调用两个。
-------- 通过 this 调用其他构造器,必须将 this 放在最起始处,
否则编译会报错。
---------当数据成员 s 和参数 s 的名称相同时,可以使用 this.s
来代表数据成员(避免两者产生分歧)
--------除了构造器之外,编译器禁止在其他方法中使用 this 来
调用其他构造器
#### static 的含义:
-------static 方法就是没有 this 的方法。
-------在static 方法的内部不能调用非静态的方法
-------static 方法可以在没有创建任何对象的前提下,仅仅通过
类本身来调用 static 方法
-------Java 中禁止使用全局方法,但你在类中置入 static 方法
就可以访问其他 static 方法和 static 域。
------使用 static 方法时,由于不存在 this , 所以不是通过
”向对象发送消息“的方式来完成的
》》清理:终结处理和垃圾回收
-------Java 垃圾回收器负责回收无用对象占据的内存资源。
-------Java 里的对象并非总是被垃圾回收,或者换句话说:
@@ 对象可能不被垃圾回收
@@ 垃圾回收并不等于”析构“
@@垃圾回收只与内存有关
--------使用垃圾回收器的唯一原因是:为了回收程序不再使用
的内存
-------无论对象是如何创建的,垃圾回收器都会负责释放对象
占据的所有内存
补充:finalize() 不是进行普通的清理工作的合适场所。
--------你必须实施清理:
@@Java 不允许创建局部对象,必须使用 new 创建对象
@@如果希望进行除释放存储空间之外的清理工作,还是得
明确调用某个恰当的 Java 方法
@@记住,无论是”垃圾回收“还是”终结“,都不保证一定会
发生。如果Java 虚拟机(JVM)并为面临内存耗尽的情形,
它是不会浪费时间去执行垃圾回收以恢复内存的。
--------终结条件
@@注意,System.gc(); 用于强制进行终结动作。
--------垃圾回收器如何工作:
@@通过垃圾回收器对对象重新排列,实现了高速的、有无限
空间可供分配的堆模型。
@@其他系统中的垃圾回收机制:
%% ”引用记数“
**这种技术是一种简单但速度很慢的垃圾回收技术
**每个对象都有一个引用计数器,当有引用连接至对象
时,引用计数加1 ; 当引用离开作用域或被置为 null
时,引用计数减1
**垃圾回收器会在含有全部对象的列表上遍历,当发现
某个对象的引用计数为 0 时, 就释放其占用的空间。
** 这种方法有个缺陷,如果对象之间存在循环引用,
可能会出现”对象应该被回收,但引用计数却不为零“
的情况
** 引用计数常用来说明垃圾收集的工作方式,但是似乎
从未被应用于任何一种 Java 虚拟机实现中
%%”自适应的垃圾回收技术“--》”标记--清扫“模式与”停止--复制“
模式根据情况自动切换
**先寻找”活“ 的对象
对任何”活“的对象,一定能最终追溯到存活在堆栈或静态
存储区之中的引用。这个引用链条可能会穿过数个对象层次。
对于发现的每个引用,必须追踪它所引用的对象,然后是此
对象包含的所有引用,如此反复进行,直到”根源于堆栈和
静态存储区的引用“所形成的网络全部被访问为止。
**如何处理找到的”活“ 的对象
不同的Java 虚拟机的实现都不同
有一种做法为:停止---复制
<
停止--复制:先暂停程序的运行(所以它不属于后台回收
模式),然后将所有存活的对象从当前堆复制到另一个堆,
没有被复制的全部都是垃圾。当对象被复制到新推后时,
它们是一个挨着一个的,所以新堆保持紧凑排列,然后
就可以按一定的方法简单、直接地分配新空间了。
当把对象从一处搬到另一处时,所有指向它的那些引用
都必须修正
>
**补充:上面的技术,需要有两个堆,这两个分离的堆之间来回
倒腾
<
某些 Java 虚拟机 对此问题的处理方式是:按需从堆中分配
几块较大的内存,复制动作发生在这些大块内存之间
>
**程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。
尽管如此,复制式回收器仍然会将所有内存自一处复制到另一处,
这很浪费。为了避免这种情形,一些 java 虚拟机会进行检查:
要是没有新垃圾的产生,就会转换到另一种工作模式(即”自适应“
),这种模式称为:”标记----清扫“
**”标记---清扫“所依据的思路是从堆栈和静态存储区出发,遍历所有的
引用 , 进而找出所有存活的对象。每当它找到一个存活对象,就会
给对象设一个标记,这个过程不会回收任何对象。只有全部标记工作
完成的时候,清理工作才会开始。在清理过程中,没有标记的对象
将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,
垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
**Java 虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的
效率降低的话,就切换到”标记---清扫“方式;同样,Java 虚拟机会
跟踪”标记--清扫“的效果,要是堆空间出现很多碎片,就会切换到
”停止----复制“方式。这就是”自适应“技术。
** Java 虚拟机中有许多附加技术用以提升速度。尤其是与加载器
操作有关的,被称为”即时“编译器(JIT)的技术。
<
这种技术可以把程序全部或部分翻译成本地机器码(这本来是
Java 虚拟机的工作),程序运行速度因此得以提升。当需要装载
某个类(通常是在为该类创建第一个对象)时,编译器会先找到其
.class 文件,然后将该类的字节码装入内存。此时,有两种方案
可供选择:
一种是:让即时编译器编译所有代码
另一种是:惰性评估,意思是即时编译器只在必要的时候才编译
代码。这样,从不会被执行的代码也许就压根不会被JIT
编译。
>
》》成员初始化
--------Java 尽力保证:所有变量在使用前都能得到恰当的初始化。
--------对于方法中的局部变量:一定要提供初始值
--------类的每个基本类型数据成员保证都会有一个初始值。
即使没有显式赋值,系统都会给定默认的值
---------在类里定义一个对象引用时,如果不将其初始化,此引用就会获得一个
特殊值 null
--------指定初始化:
@@在定义类成员变量的地方为其赋值。
(不论是基本类型的还是非基本类型的变量)
@@如果在没有给定初始值就尝试使用它,就会出现运行时错误,
会产生一个异常
》》构造器初始化
---------可以使用构造器来进行初始化。在运行时刻,可以调用方法或执行
某些动作来确定初值,这为编程带来了更大的灵活性。
但要牢记:无法阻止自动初始化的进行,它将在构造器被调用之前
发生。
----------初始化顺序:
@@ 在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量
定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)
被调用之前得到初始化。
---------静态数据的初始化:
@@无论创建多少个对象,静态数据都只占用一份存储区域。static
关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态
的基本类型域,且没有对它进行初始化,那么它就会获得基本类型的
标准初值;如果它是一个对象引用,那么它的默认初始值就是null 。
@@ 初始化的顺序是先静态对象(如果尚未因为前面的对象创建过程而
被初始化),而后是“非静态”对象。
@@即使没有显式地使用 static 关键字,构造器实际上也是静态方法。
当首次创建某个类的对象时(构造器可以看成是静态方法),或者某个
类的静态方法或静态域首次被访问时,Java 解释器必须查找类路径,
以定位 .class 文件
然后载入 .class 文件(这将创建一个 Class 对象),有关静态初
始化 的所有动作都会执行。因此,静态初始化只有在 Class 对象首次
加载的时候进行一次。
当使用 new 关键字创建对象的时候,首先将在堆上为 该对象分配
足够的存储空间。而且这块存储空间在不用的时候,会被清零,这就
自动地将对象上的所有基本数据类型都设置成了默认值,而引用则被
设置成了 null
----------显式的静态初始化:
@@ Java 允许将多个静态初始化动作组织成一个特殊的“静态子句”
(有时也叫“静态块”)
格式如下:
public class Spoon{
static int i ;
static {
i = 47;
}
}
@@ 上面的静态块中的代码,仅仅只会执行一次。
当首次生成这个类的一个对象时,或者首次访问属于那个类的静态
数据成员时(即便从未生成那个类的对象),静态初始化动作只进行
一次。
-----------非静态实例初始化
@@ Java 中也有被称为实例初始化的类似语法,用来初始化每一个对象
的非静态变量。
@@非静态初始化的格式:
例如: class Mug{
Mug(int marker){
}
public void f(int marker){
}
}
pulic class Mugs{
Mug mug1;
Mug mug2;
{
mug1 = new Mug(1);
mug2 = new Mug(2);
}
}
@@ 非静态初始化的初始化块,和静态初始化的块是一样的,
只是少了一个 static 关键字。这种语法对于支持"匿名内
部类"的初始化是必须的,但是它也使得你可以保证无论
调用哪个显式的构造器,某些操作都会发生。实例化子
句是在构造器之前进行的,是在静态子句之后进行的。
@@每次创建一个对象实例的时候,非静态初始化块都会被
执行一次。但是,静态初始化块仅仅只执行一次
》》数组初始化
----------数组是相同类型的、用一个标识符名称封装到一起的一个
对象序列或基本类型数据序列。
-----------数组是通过方括号下标操作符 [ ] 来定义使用的。要定义
一个数组,只需在类型名后面加上一对 空括号即可:
int [] a1 ; 或者是 int a1[] ; (前一种格式在java 中更适用)
---------编译器不允许指定数组的大小。
-------- 对于数组,初始化动作可以发生在代码的任何地方,但也
可以使用一种特殊初始化表达式,它必须在创建数组的地方
出现。这种特殊的初始化是由一对花括号括起来组成的。
在这种情况下,存储空间的分配(等价于使用 new 关键字)
将由编译器负责。如:
int[] a1 = {1,2,3,4} ;
----------- 在 Java 中可以将一个数组赋值给另一个数组。其实真正
做的只是复制了一个引用。(两个引用,指向内存的同一块
内存空间)
-----------所有数组(无论它们的元素是对象还是基本类型)都有一个
固有成员,可以通过它获知数组内包含了多少个元素,但不能
对其进行修改。这个成员就是 length
------------Java 数组计算是从第 0 个元素开始的,所以能使用的最大
下标为 length - 1 。要是超出了这个界,Java 会出现运行时
错误(即异常)。
----------- 如果在编写程序时,并不能确定在数组里面需要多少个元素,
可以直接用 new 在数组里面创建元素。尽管创建的是基本类型
数组,new 仍然可以工作。(不能用 new 创建单个的基本数据
类型)
例如:
<初始化基本数据类型>
Random rand = new Random(47);
int[] a1;
a1 = new int[rand.nextInt(20)]
<初始化包装类>
Integer [] a = new Interger[rand.nextInt(20)];
-----------可以用花括号括起来的列表来初始化对象数组:
有两种形式:
形式一:
Integer [] a = {
new Integer(1),
new Integer(2)
}
形式二:
Integer[] a = new Integer[]{
new Integer(1),
new Integer(2)
}
补充:Arrays.toString() 方法属于 java.util 标准类库,
它将产生一维数组的可打印版本。
----------可变参数列表:
例如:
static void printArray(Obejct... args){
for(Object obj : args){
System.out.println(obj+"");
}
}
说明:上面会打印数组的内容,如果数组中的内容都是
“引用”的话,在默认行为(没有定义 toString () 方法的话)
就是打印类的名字和对象的地址
@@ foreach 遍历 使用了 可变参数列表的内容
@@ getClass( ) 方法属于 Object 的一部分,它将产生对象
的类,并且在打印该类时,可以看到表示该类类型的
编码字符串。
@@ 可变参数列表不依赖自动包装机制,而实际上使用的是
基本类型。
@@ 可变参数列表使得重载变得复杂了。
》》枚举类型
----------enum 关键字,它使得我们在需要群组并使用枚举类型
集时,可以很方便地使用。
--------- Java 中枚举类型:
例如:
public enum Spiciness{
NOT, MILD , MEDIUM , HOT , FLAMING
}
说明:枚举类型的实例都是常量,因此按照命名惯例它们都
用大写字母表示(如果在一个名字中有多个单词,用下划线将
它们隔开)
----------- 为了使用 enum ,需要创建一个该类型的引用,并将其赋值
某个实例:
public class SimpleEnumUse{
public static void main(String[] args){
Spiciness howHot = Spiciness.HOT;
System.out.println(howHot);
}
}
---------------在你创建 enum 时,编译器会自动添加一些有用的特性。
例如: toString() 方法: 可以很方便的显示某个 enum
实例的名字
ordinal() 方法: 用来表示某个特定 enum 常量的
声明顺序
static values() 方法:用来按照 enum 常量的声明
顺序,产生由这些常量值构成的
数组
------------enum 确实是类,并且具有自己的方法。
------------enum 有一个特别实用的特性,即它可以在 switch 语句
内使用。
-----------由于 switch 是要在有限的可能值集合中进行选择,因此它
与 enum 正是绝佳的组合。请注意, enum 的名字是如何
能够清楚地表明程序意欲何为的。
-----------可以将 enum 用作另外一种创建数据类型的方式,然后直接
将所得到的类型拿来使用。
》》总结:
-----------构造器,这种精巧的初始化机制,暗示读者:初始化在 Java
中占有至关重要的地位。
-----------构造器能保证正确的初始化和清理(没有正确的构造器调用,
编译器就不允许创建对象),所以有了完全的控制,也很安全。
-----------在Java 中,垃圾回收器会自动为对象释放内存,可以极大地
简化编程工作,而且在处理内存的时候也更安全。
---------- 垃圾回收器增加了运行时的开销
----------随着时间的推移,Java 在性能方面已经取得了长足的进步,但
速度问题仍然是涉足某些特定编程领域的障碍。
---------由于要保证所有对象都被创建,构造器实际上要比上面讨论的
更加复杂。特别当通过组合或继承生成新类的时候,这种保证
仍然成立,并且需要一些附加的语法来提供支持。