1.java中的4种访问制权限:
(1).public:最大访问控制权限,对所有的类都可见。
(2).protect:同一包可见,不在同一个包的所有子类也可见。
(3).default:包访问权限,即同一个包中的类可以可见。默认不显式指定访问控制权限时就是default包访问控制权限。
(4).private:最严格的访问控制权限,仅该类本身可见,对外一切类都不可以访问(反射机制可以访问)。
2.面向对象编程中两种对象组合方式——is-a 和 has-a:
(1).is-a组合:一个类继承具有相似功能的另一个类,根据需要在所继承的类基础上进行扩展。
优点:具有共同属性和方法的类可以将共享信息抽象到父类中,增强代码复用性,同时也是多态的基础。
缺点:子类中扩展的部分对父类不可见,另外如果共性比较少的时候使用继承会增加冗余代码。
(2).has-a组合:has-a组合是在一个类中引用另一个类作为其成员变量。
优点:可扩展性和灵活性高。在对象组合关系中应优先考虑has-a组合关系。
缺点:具有共性的类之间看不到派生关系。
3.多态:
在面向对象编程中,子类中拥有和父类相同方法签名的方法称为子类方法覆盖父类方法,当调用子类方法的某个操作时,不必明确知道子类的具体类型,只需要将子类类型看作是父类的引用调用其操作方法,在运行时,JVM会根据引用对象的具体子类类型而调用应该的方法,这就是多态。
多态的基础是java面向对象编程的晚绑定机制。编程中有如下两种绑定机制:
(1).早绑定:一般在非面向对象编程语言中使用,在程序编译时即计算出具体调用方法体的内存地址。
(2).晚绑定:面向对象编程语言中经常使用,在程序编译时无法计算出具体调用方法体的内存地址,只进行方法参数类型和返回值类型的校验,在运行时才能确定具体要调用方法体的内存地址。
4.java单继承的优点:
相比于C++的多继承,java只支持类的单继承,java中的所有类的共同基类是Object类,Object类java类树的唯一根节点,这种单继承有以下好处:
(1).单继承可以确保所有的对象拥有某种共同的特性,这样对于JVM虚拟机对所有的类进行系统级的操作将提供方便,所有的java对象可以方便地在内存堆栈中创建,传递参数也变的更加方便简单。
(2).java的单继承使得实现垃圾回收器功能更加容易,因为可以确保JVM知道所有对象的类型信息。
5.选择容器对象两个原则:
(1).容器所能提供不同的类型的接口和外部行为是否能够满足需求。
(2).不同容器针对不同的操作效率不同。
6.类型转换:
Java中有两种常见的类型转换:向上类型转换(upcast)和向下类型转换(downcast):
(1).向上类型转换(upcast):
向上类型转换是将子类对象强制类型转换为父类类型,经典用法是面向对象的多态特性。向上类型转换时,子类对象的特性将不可见,只有子类从父类继承的特性仍然保持可见,向上类型转换时编译器会自动检查是否类型兼容,通常是安全的。
(2).向下类型转换:
向下类型转换是将父类类型强制转换为子类类型,转换过后父类中不可见的子类特性又恢复可见性,向下类型转换时,编译器无法自动检测是否类型兼容,往往会产生类型转换错误的运行时异常,通常不安全。
7.java中5个存放数据的地方:
(1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限。在java中不能直接操作寄存器。
(2).栈(Stack):栈位于通用随机访问存储器 (General random-access memory,RAM,内存) 中,通过处理器的栈指针访问,栈指针从栈顶向栈底分配内存,从栈底向栈顶释放内存。栈是仅次于寄存器的速度第二快的存储器,在java程序中,一般的8种 基本类型数据和对象的引用通常存放在栈内存中,不通过new关键字的字符串对象也是存放在栈的字符串池中。栈的优势是,存取速度比堆要快,仅次于寄存器, 栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
(3).堆(Heap):也是位于通用随机访问存储器 (General random-access memory,RAM,内存) 中的共享内存池。Java的堆是一个运行时数据区,类的对象从中分配空间,凡是通过new关键字创建的对象都存放在堆内存中,它们不需要程序代码来显式的 释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器 会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
(4).常量存储器(Constant storage):java中的常量是存放在系统内嵌的只读存储器中(read-only memory,ROM)的。
(5).非随机存储器(Non-RAM storage):对于流对象和持久化对象,通常存放在程序外的存储器,如硬盘。
8.javadoc只处理public和protected访问控制权限的文档注释,private和default权限的稳定注释将被忽略。
9.java中赋值运算:
基本类型赋值是直接复制值,赋值操作后,相互不影响。
引用类型赋值是复制引用值,相当于给对象取一个别名,赋值之后两个引用指向同一个引用对象,相互之间有影响。
在Java中,向方法传递引用类型参数会改变参数的值,不让参数受到影响的解决方法:在方法内首先先将引用克隆一份,然后操作克隆的对象。
10.移位运算:
左移运算符<<:将比特位左移指定位数,右边部分补0,左移一位相当于乘2。
右移运算符>>:将比特位右移指定位数,如果是正数,左边第一位(符号位)补0,其余位补0,如果是负数,左边第一位补1,其余位补0。右移一位相当于除2。
无符号右移运算符>>>:将比特位右移指定位数,不论是正数或者负数,左边移除位统统补0。
11.java中,比int类型小的原始类型(char、byte、short)进行数学运算或者位运算时,数据类型首先转换成int类型,然后进行相应的运算。
12.方法重载(overloading):方法同名,参数列表不同称为方法重载,注意方法的返回值类型不同不能作为方法重载。
13.java中的析构函数:
Java中没有像C/C++的析构函数,用来销毁不用的对象是否内存空间,只有以下三个方法用于通知垃圾回收器回收对象。
(1).finalize( )只是通知JVM的垃圾收集器当前的对象不再使用可以被回收了,但是垃圾回收器根据内存使用状况来决定是否回收。
finalize()最有用的地方是在JNI调用本地方法时(C/C++方法),调用本地方法的析构函数消耗对象释放函数。
(2). System.gc()是强制析构,显式通知垃圾回收器释放内存,但是垃圾回收器也不一定会立即执行,垃圾回收器根据当前内存使用状况和对象的生命周期自行决定是否回收。
(3).RunTime.getRunTime().gc()和System.gc()类似。
注意:这三个函数都不能保证垃圾回收器立即执行,推荐不要频繁使用。
14.垃圾回收器原理:
(1).引用计数(ReferenceCounting)垃圾回收算法:
一种简单但是速度较慢的垃圾回收算法,每个对象拥有一个引用计数器(Reference Counter),当每次引用附加到这个对象时,对象的引用计数器加1。当每次引用超出作用范围或者被设置为null时,对象的引用计数器减1。垃圾回收 器遍历整个对象列表,当发现一个对象的引用计数器为0时,将该对象移出内存释放。
引用计数算法的缺点是,当对象环状相互引用时,对象的引用计数器总不为0,要想回收这些对象需要额外的处理。
引用计数算法只是用来解释垃圾回收器的工作原理,没有JVM使用它实现垃圾回收器。
引用计数的改进算法:
任何存活的对象必须被在静态存储区或者栈(Stack)中的引用所引用,因此当遍历全部静态存储区或栈中的引用时,即可以确定所有存活的对象。每当 遍历一个引用时,检查该引用所指向的对象,同时检查该对象上的所有引用,没有引用指向的对象和相互自引用的对象将被垃圾回收器回收。
(2).暂停复制(stop-and-copy)算法:
垃圾回收器的收集机制基于:任何一个存活的对象必须要被一个存储在栈或者静态存储区的引用所引用。
暂停复制的算法是:程序在运行过程中首先暂停执行,把每个存活的对象从一个堆复制到另一个堆中,已经不再被使用的对象被回收而不再复制。
暂停复制算法有两个问题:
a.必须要同时维护分离的两个堆,需要程序运行所需两倍的内存空间。JVM的解决办法是在内存块中分配堆空间,复制时简单地从一个内存块复制到另一个内存块。
b.第二个问题是复制过程的本身处理,当程序运行稳定以后,只会产生很少的垃圾对象需要回收,如果垃圾回收器还是频繁地复制存活对象是非常低性能的。JVM的解决方法是使用一种新的垃圾回收算法——标记清除(mark-and-sweep)。
一般来说标记清除算法在正常的使用场景中速度比较慢,但是当程序只产生很少的垃圾对象需要回收时,该算法就非常的高效。
(3).标记清除(mark-and-sweep)算法:
和暂停复制的逻辑类似,标记清除算法从栈和静态存储区开始追踪所有引用寻找存活的对象,当每次找到一个存活的对象时,对象被设置一个标记并且不被回收,当标记过程完成后,清除不用的死对象,释放内存空间。
标记清除算法不需要复制对象,所有的标记和清除工作在一个内存堆中完成。
注意:SUN的文档中说JVM的垃圾回收器是一个后台运行的低优先级进程,但是在早期版本的JVM中并不是这样实现的,当内存不够用时,垃圾回收器先暂停程序运行,然后进行垃圾回收。
(4).分代复制(generation-copy)算法:
一种对暂停复制算法的改进,JVM分配内存是按块分配的,当创建一个大对象时,需要占用一块内存空间,严格的暂停复制算法在释放老内存堆之前要求把每个存活的对象从源堆拷贝到新堆,这样做非常的消耗内存。
通过内存堆,垃圾回收器可以将对象拷贝到回收对象的内存堆中,每个内存块拥有一个世代计数(generation count)用于标记对象是否存活。每个内存块通过对象被引用获得世代计数,一般情况下只有当最老的内存块被回收时才会创建新的内存块,这主要用于处理大 量的短存活周期临时对象回收问题。一次完整的清理过程中,内存块中的大对象不会被复制,只是根据引用重新获得世代计数。
JVM监控垃圾回收器的效率,当发现所有的对象都是长时间存活时,JVM将垃圾回收器的收集算法调整为标记清除,当内存堆变得零散碎片时,JVM又重新将垃圾回收器的算法切换会暂停复制,这就是JVM的自适应分代暂停复制标记清除垃圾回收算法的思想。
15.java即时编译技术(JIT):
Java的JIT是just-in-timecomplier技术,JIT技术是java代码部分地或全部转换成本地机器码程序,不再需要JVM解释,执行速度更快。
当一个”.class”的类文件被找到时,类文件的字节码被调入内存中,这时JIT编译器编译字节码代码。
JIT有两个不足:
(1).JIT编译转换需要花费一些时间,这些时间贯穿于程序的整个生命周期。
(2).JIT增加了可执行代码的size,相比于压缩的字节码,JIT代码扩展了代码的size,这有可能引起内存分页,进而降低程序执行速度。
对JIT不足的一种改进技术是延迟评估(lazy evaluation):其基本原理是字节码并不立即进行JIT编译除非必要,在最近的JDK中采用了一种类似延迟JIT的HotSpot方法对每次执行的代码进行优化,代码执行次数越多,速度越快。
16.非内部类的访问控制权限只能是默认的包访问权限或者是public的,不能是protected和private的。内部类的访问控制权限可以是protected和private。
17.Java中的高精度数值类型:
BigInteger和BigDecimal是java中的高精度数值类型,由于它们是用于包装java的基本数据类型,因此这两个高精度数值类型没有对应的原始类型。
(1).BigInteger支持任意精度的整数,即使用BigInteger可以表示任意长度的整数值而在运算中不会因为范围溢出丢失信息。
(2).BigDecimal支持任意精度的固定位数浮点数,可以用来精确计算货币等数值。
普通的float和double型浮点数因为受到小数点位数限制,在运算时不能准确比较,只能以误差范围确定是否相等,而BigDecimal就可以支持固定位数的浮点数并进行精确计算。
18.Java只处理public和protected访问控制权限成员的文档注释,private和默认的包访问控制权限成员的文档注释将被忽略。
19.Java中赋值运算:
Java中赋值运算是把赋值运算符”=”右边的值简称右值拷贝到赋值运算符左边的变量,如a=b,即把b代表的变量或常量值复制给变量a,切记a只能是变量,不能说常量值。
(1).原始类型赋值运算:
Java中8种原始数据类型赋值运算是将赋值运算符右边的值拷贝到赋值运算符左边的变量中。
原始类型赋值运算后,无论改变赋值运算符那一边的值,都不会影响赋值运算符另一边的值。
(2).引用类型的赋值运算:
Java中除了8中原始数据类型外,所有的数据类型都是对象类型,对象类型的赋值运算是操作引用,如a=b,把b引用赋值给a引用,即原本b引用指向的对象现在由a和b引用同时指向。
引用赋值运算符又叫别名运算符,即它相当于给引用对象取了一个别名,其实引用的还是同一个对象。
引用类型的赋值运算,如果赋值运算符任意一边的引用改变了被引用对象的值,赋值运算符另一边的引用也会受影响,因为两个引用指向的是同一个被引用的对象。
1.java类的初始化顺序:
(1).在一个类中,初始化顺序由变量在类中的声明定义顺序决定,成员变量(非set方法和构造方法的初始化)的初始化发生在方法调用之前,包括构造方法。
(2).静态变量在整个存储区只保留一份拷贝,本地变量不能使用静态关键字,基本类型的静态变量不需要初始化,它会根据类型获得初始化值,引用类型的静态变量默认初始化为null。
静态变量的初始化发送在需要使用的时候,一旦被初始化之后,静态变量就不会再初始化。
(3).静态初始化块和静态变量类似的执行也在构造方法之前,并且仅执行一次。
(4).动态初始化块(与静态初始化块类似,只是没有static关键字,即放在一对大括号中的代码块)在静态初始化块初始化结束后执行,动态初始化块每次创建新对象都会初始化一次。
(5).构造方法执行时,先执行父类的构造方法,后执行子类的构造方法。
(6).本地变量初始化最晚,在方法中初始化。
综述,类的初始化顺序依次为:
a.父类的静态变量/静态初始化块;
b.子类类的静态变量/静态初始化块;
c.父类的动态初始化块、非构造方法和set方法的成员变量初始化
d.子类的动态初始化块、非构造方法和set方法的成员变量初始化
e.父类的构造方法。
f.子类的构造方法。
g.父类本地变量。
h.子类的本地变量。
2.数组初始化:
Java中数组初始化有以下3中方式:
(1).数组声明时直接初始化,如:
int[] a = {1,2,3};
(2).动态数组初始化,如:
int[] a = new int[]{1,2,3};
注意:动态数组初始化时,不能在new()操作符中指定数组的大小,即int a = new int[3]{1,2,3}的写法是错误的,数组的大小由初始化数组元素个数决定。
(3).固定长度数组初始化,如:
int[] a = new int[3];
a[1] = 0;
a[2] = 1;
a[3] = 2;
注意:固定长度大小的数组初始化时不能大于所声明的数组长度,没有声明的数组元素使用其默认值,如int默认为0,对象类型的值为引用,默认为null.
3.java代码重用4中方式:
java面向对象编程中提供了如下4中代码重用的方式:
(1).组合:
面向对象编程中最常用的代码复用方式,具体的方式是在一个对象中将另一个对象引用最为成员变量,其最大的优点是既实现松散耦合,有可能提高代码复用率。
(2).继承:
面向对象编程中常用的提高代码复用率的方法之一,适用于子类和父类是同一种抽象类型,具有共同的属性情况。
使用继承,子类可以复用父类除private私有房屋控制权限以为的所有属性和方法,编译器将父类封装为子类对象内部的一个对象。
需要注意的是:调用子类初始化构造方法时,编译器会确保首先调用父类的构造方法初始化父类,然后才初始化子类,如果父类中没有默认的构造方法,即需要显式传入参数的构造方法时,子类必须通过super关键字显式传入参数调用父类的构造方法。
(3).委派:
Java中不支持委派方式的代码复用,但是开发人员可以使用委派机制实现代码的重用。
委派是指,java对象的所有方法其实都是委派调用另一个类的方法实现,但是当前类又不是所委派类的类型,因此使用继承不太合适,解决方式和组合类似,将被委派类作为委派类的成员变量,委派类的方法直接调用被委派类对象应用的方法。
如:
[java] view plaincopy
1. //委派类
2. public Class A{
3. //被委派类
4. private B b = new Class B();
5. public void method1(){
6. b.method1();
7. }
8. public void method2(){
9. b.method2();
10. }
11. ……
12. }
(4).联合使用组合和继承方式:
因为java中不允许多继承,如果某种情况下,一个java类需要使用多个其他类功能,且该类和其中某个类具有很多共同属性,即可以看作同一类,则可以使当前类继承具体共同属性的类,同时将其他类作为成员变量组合引用。
4.组合和继承的区别:
组合和继承都可以复用代码,很多java程序员,甚至是架构师都分不清楚什么情况下该使用组合,什么情况下应该使用继承,具体的区别如下:
(1).组合:
组合通常是在一个类中想使用另一个类已有的特性,但是却不想使用其接口。
使用组合可以可以将一个类作为当前类的内嵌对象,这样在当前类中就可以显式地使用内嵌类已经实现的功能,与此同时又不会影响当前类的调用接口。
(2).继承:
继承是隐式地使用被继承类的功能,相当于提供了一个新版本的父类实现。
使用继承,子类不但可以复用父类的功能,同时还复用了父类的接口,子类和父类的对外调用接口相同的情况下适合使用继承。
使用继承时,很多情况下需要向上类型转换,即将子类看作其父类。在编程时到底选用组合方式还是继承方式,一个简单的判断依据是:是否需要向上类型转换,如果需要就使用继承,如果不需要,则选择组合。
5.final方法:
Java中使用final类型的方法有以下两种原因:
(1).设计原因:
final类型的方法不允许其子类修改方法,即不允许子类覆盖父类的final方法。
(2).效率原因:
在早期的java实现中,如果方法被声明为final,编译器将final方法调用编译为内联调用。
正常的方法调用是:如果方法调用时,将当前的方法上下文保持到栈中,调用被调用的方法,然后在将调用上下文出栈恢复调用现场。
内联调用是:如果方法调用时,将被调用方法体拷贝到当前调用的地方合并成一个方法体,这样就避免因需要保存方法调用线程而进行的进栈和出栈操作,可以提高效率。
新版的使用hotspot技术的java虚拟机可以探测方法调用情况而做效率优化,final方法不再作为提高效率的手段,唯一的作用是确保方法不被子类覆盖。
注意:任何private的方法都是隐式的final类型,同final方法类似,private方法不能被子类所覆盖,但是private比final更严格,基类的private方法对子类不可见,private方法不再是接口的一部分。
6.多态性:
面向对象编程中的多态和继承往往是一起发挥作用的,使用继承,所有的子类和父类使用相同的对外接口,而多态的基础是晚绑定或动态绑定或运行时绑定, 即对象引用使用基类类型,在编译时编译器无法确切知道到底调用哪一个具体类,只有在运行时,java虚拟机才通过类型检查确定调用对象的具体类型。
Java中默认对象引用全部是晚绑定,只有static和final类型的引用时早绑定或编译时绑定。
多态的优势是:程序的可扩张性好,无论添加多少个子类,基类的接口都不用改变,只需要在子类对应方法中提供具体实现即可,也就是所谓的将程序变化的部分和程序保持不变的部分分离。
注意:只有正常的方法可以使用多态,字段和静态方法没有多态机制。
构造方法也不支持多态机制,构造方法是隐式的static声明。
1.java中,可以将一个类的定义放在另一个类的内部,这种叫做内部类。
内部类允许编程人员将逻辑上相关的类组织在一起,并且控制内部类对其他类的可见性。
2.在外部类的非静态方法中创建内部类的对象语法:
外部类类名.内部类类名 对象名 = 外部类对象.new 内部类类名();
如:
[java] view plaincopy
1. public class Outter{
2. class inner{
3. }
4. }
5. Outter out = new Outter();
6. Outter.Inner inner = out.new Inner();
注意:非静态的内部类必须要有外部类对象之后才能创建,因为外部类对象持有内部类的引用,如果内部类是静态的,则不需要外部类对象引用内部类对象。
3.内部类对外部类对象的引用:
外部类中所有的元素对内部类都是可见的,内部类持有对外部类对象引用的语法:外部类名称.this。如:
[java] view plaincopy
1. public class Outter{
2. void f(){
3. System.out.println(“Outter f() method”);
4. }
5. class Inner{
6. public Outter outer(){
7. return Outter.this;
8. }
9. }
10. public Inner inner(){
11. return new Inner();
12. }
13. }
4.方法内部类:
除了最常见的最为成员变量的内部类以外,内部类还可以定义在方法中,如:
[java] view plaincopy
1. public class Outter{
2. public Inner getInner(){
3. class Inner{
4. public void f(){
5. System.out.println(“Method inner class”);
6. }
7. }
8. return new Inner();
9. }
10. public void fout(){
11. Inner inner = getInner();
12. inner.f();
13. }
14. }
5.匿名内部类:
Java中匿名内部类的应用十分广泛,所谓的匿名内部类就是指所创建的内部类没有类名称,也就是不知道内部类的具体类型,如:
[java] view plaincopy
1. public class Outter{
2. public Inner getInner(){
3. return new Inner(){
4. private String name = “inner”;
5. public String getName(){
6. return name;
7. }
8. };
9. }
10. }
上面的return newInner{……};就是一个匿名内部类,这个匿名内部类继承了Inner类,即其基类是Inner,但是其具体类型不清楚,该匿名内部类等效于下面的内部类写法:
[java] view plaincopy
1. public class Outter{
2. class MyInner implement Inner{
3. private String name = “Inner class”;
4. public String getName(){
5. return name;
6. }
7. }
8. public Inner getInner(){
9. return new MyInner();
10. }
11. }
注意:匿名内部类是实现了new关键字之后的接口或继承了类,只是没有具体的类名称。
6.内名内部类传递final参数:
如果在创建匿名内部类,需要外部类传递参数时,参数必须是final类型的,否则,编译时会报错。如:
[java] view plaincopy
1. public class Outter{
2. public Inner getInner(final String name){
3. return new Inner(){
4. public name getName(){
5. return name;
6. }
7. };
8. }
9. }
注意:如果外部类传递的参数在内部类中使用,则必须是final类型,如果没有在内部类中使用(如,仅在基类中使用),则可以不用是final类型的。
7.静态内部类:
静态内部类又称为嵌套类,静态内部类和普通内部类的区别:
(1).对于非静态的内部类来说,内部类和外部类必须保持对象引用,内部类可以访问外部类的任何元素.
(2).静态内部类不需要和外部类保持对象引用,静态内部类只能访问外部类的静态元素。
8.为什么要使用内部类:
使用内部类主要有以下两个原因:
(1).解决java中类不能多继承的问题:
Java中的继承是单继承,在某些情况下接口的多继承可以解决大部分类似C++中多继承的问题,但是如果一个类需要继承两个父类而不是接口,在java中是没法实现这种功能的,内部类可以帮助我们部分解决这种问题,如:
[java] view plaincopy
1. abrstact class A{
2. abstract public void f();
3. }
4. abstract class B{
5. abstract public void g();
6. }
7. public class D extends A{
8. public void f(){}
9. public B makeB(){
10. return new B(){
11. public void g(){};
12. }
13. }
14. }
这样既继承了A类,有在makeB方法中使用匿名内部类继承了B类。
(2).闭包方法问题:
在面向对象中有个术语叫闭包,所谓闭包是指一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。Java中不支持闭包,但是 java的内部类可以看作是对闭包的一种解决方案,因为外部类的所有元素对于普通的内部类来说都是可见的,并且内部类还包含一个指向外部类的对象引用,因 此在作用域内,内部类有权操作外部类的所有成员包括private成员。如:
[java] view plaincopy
1.
interface Incrementable {2. void increment();
3. }
4.
5. class Callee1 implements Incrementable {
6. private int i=0;
7. public void increment() {
8. i++;
9. System.out.println(i);
10. }
11. }
12.
13. class MyIncrement {
14. void increment() {
15. System.out.println("other increment");
16. }
17. static void f(MyIncrement mi) {
18. mi.increment();
19. }
20. }
21.
22. class Callee2 extends MyIncrement {
23. private int i=0;
24. private void incr() {
25. i++;
26. System.out.println(i);
27. }
28. //闭包内部类
29. private class Closure implements Incrementable {
30. public void increment() {
31. incr();
32. }
33. }
34. //回调函数
35. Incrementable getCallbackReference() {
36. //新建内部类
37. return new Closure();
38. }
39. }
40.
41. class Caller {
42. private Incrementable callbackRefference;
43. Caller(Incrementable cbh) {
44. callbackRefference = cbh;
45. }
46. void Go() {
47. //调用increment()方法
48. callbackRefference.increment();
49. }
50. }
51.
52. public class Callbacks {
53. public static void main(String [] args) {
54. Callee1 c1=new Callee1();
55. Callee2 c2=new Callee2();
56. MyIncrement.f(c2);
57. Caller caller1 =new Caller(c1);
58. //将内部类中的Closure赋给Caller
59. Caller caller2=new Caller(c2.getCallbackReference());
60. caller1.go();
61. caller1.go();
62. caller2.go();
63. caller2.go();
64. }
65. }
66.
67.
68. 输出:
69. other increment
70. 1
71. 2
72. 1
73. 2
74. Callee2 通过使用内名内部类提供了一个访问自身increment方法的钩子,通过getCallbackReference()方法获取内部类对象的引用,进而通 过内部类的increment方法调用外部类的回调方法,这就是java中用于实现回调方法的闭包。
75.
4——集合容器
1.集合中添加另一个集合的方法:
(1).Collection.addAll(被添加的Collection对象)方法:
如:list1.addAll(list2);
(2).Collections.addAll(添加到的目标Collection对象,可变参数的集合或者对象)方法:
如:Collections.addAll(list1, new Object1(), new Object2()…);
Collectionns.addAll(list1, list2…);
注意:Collections是java集合容器的工具类,相比于(1),使用Collections的(2)更灵活。
2.Java集合中常用的交集、并集和差集操作:
并集:collection对象1.addAll(collection对象2);
交集:collection对象1. retainAll(collection对象2);
差集:collection对象1. removeAll(collection对象2);
注意:上述的集合操作时,集合元素的equals方法会影响操作结果。
3.将其他类型集合转换为List:
Arrays.asList(非List类型的集合对象/可变参数的对象);方法可以将传递的参数转变为List集合。如:Arrays.asList(new Object1(),new Object2(),…);
Arrays和Collections类似,是Array数组类型集合的工具类。
注意:Arrays.asList()方法转换后的List对象是一个size不能改变的对象,如果对该对象做增加或者删除元素操作时,将会报不支持的操作异常。
4.List集合:
List集合主要有两种具体的集合容器:ArrayList和LinkedList。
(1).ArrayList:底层实现是数组,提供了根据数组下标快速随机访问的能力,但是增加和删除元素时因为需要引动数组的元素,因此比较慢。
(2).LinkedList:底层实现是链表,链表访问元素时必须从链表头至链表尾挨个查找,因此只能顺序访问,速度比随机访问要慢。但是增加和删除元素时,只需要修改链表的指针而不需要移动元素,因此速度比较快。
5.LinkedList:
LinkedList除了实现了基本的List接口以外,还提供了一些特定的方法,使得LinkedList可以方便地实现Stack、Queue以及双端Queue的功能。
LinkedList提供的非List接口方法:
(1).getFirst():获取并且不移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(2).element():获取并且不移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(3).peek():获取并且不移除LinkedList集合中第一个元素。如果集合为空,则返回null。
(4).removeFirst():获取并且移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(5).remove():获取并且移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(6).poll():获取并且移除LinkedList集合中第一个元素。如果集合为空,则返回null。
(7).addFirst():向LinkedList集合的头部插入一个元素。
(8).add():向LinkedList集合的尾部插入一个元素。
(9).offer():向LinkedList集合的尾部插入一个元素。
(10).removeLast():获取并且移除LinkedList集合中最后一个元素。如果集合为空,抛出NoSuchElementException异常。
6.Iterator:
Iterator迭代器在java集合容器中应用比较广泛,对于List类型的集合,可以通过下标索引值获取到指定的元素,而对于Set类型的集合,因为Set是没有索引的,因此只能通过迭代器来遍历。
Iterator迭代器是一个顺序选择和遍历集合元素的对象,使用者不需要关心其底层的数据结构和实现方式。Java中的Iterator迭代器是单向的。
Iterator的常用方法如下:
(1).collection对象.iterator()方法:将集合对象转换为Iterator迭代器。
(2).iterator对象.hasNext()方法:判断迭代器中是否还有元素。
(3).iterator对象.next()方法:获取迭代器中下一个元素。
(4).iterator对象.remove()方法:删除迭代器中当前元素。注意:使用迭代器的好处是,当数据结构从List变为Set之后,迭代集合的相关代码一点都不用改变。
7.ListIterator:
ListIterator是Iterator的子类,它只能有List类型的集合产生,ListIterator是一个双向的迭代器,即它可以向前和向后双向遍历集合。ListIterator的常用方法如下:
(1).list类型对象.listIterator():将List类型的集合转换为ListIterator迭代器。
(2).list类型对象.listIterator(int n):将List类型的集合转换为ListIterator迭代器,同时指定迭代器的起始元素为第n个元素。
(3).listIterator对象.hasNext():判断迭代器中是否还有下一个元素。
(4).listIterator对象.next():获取迭代器中的下一个元素。
(5).listIterator对象.hasPrevious():判断迭代器中是否还有前一个元素。
(6).listIterator对象.previous():获取迭代器中的前一个元素。
(7).listIterator对象.set(元素对象):将当前迭代到的元素设置为另一个值。
8.Map遍历3中方法:
Map
map = new HashMap (); map.put(“test1”, object1);
……
map.put(“testn” , objectn);
(1).Map的values()方法可以获取Map值的集合:
[java] view plaincopy
1. Iterator it = map.values().iterator();
2. while(it.hasNext()){
3. Object obj = it.next();
4. }
(2).Map的keySet方法可以获取Map键的Set集合:
[java] view plaincopy
1. Set
keys = map.keySet(); 2. for(Iterator it = key.iterator(); it.hasNext(); ){
3. String key = it.next();
4. Object obj = map.get(key);
5. }
(3).通过使用Entry来得到Map的key和value:
[java] view plaincopy
1. Set
> entrySet = map.entrySet(); 2. for(Iterator
> it = entrySet.iterator(); it.hasNext(); ){ 3. Map.Entry
entry = it.next(); 4. String key = entry.getKey();
5. Object value = entry.getValue();
6. }
9.Collection和Iterator:
Collection是java中除了Map以外的集合容器的通用接口,如果想自定义一种集合容器类型的类,可以选择实现Collection接口或者继承Collection的子类。
实现Collection接口或者继承Collection子类的时候,必须实现Collection接口的所有方法,而Iterator为定义自 定义集合容器类型提供了另一种方便,Iterator是一种轻量级的接口,只需要实现hasNext(),next()方法即可,remove()方法是 可选方法。
注意:Foreach循环支持所有实现了Iterable接口的集合容器(Collection接口的父接口是Iterable),Map集合没有实现Iterable接口,因此不支持Foreach循环。
10.java集合容器框架图:
5——正则表达式量词匹配
Java正则表达式有3中量词匹配模式:
1.贪婪量词:
先看整个字符串是否匹配,如果没有发现匹配,则去掉最后字符串中的最后一个字符,并再次尝试,如果还是没有发现匹配,那么,再次去掉最后一个字符串的最后一个字符,整个匹配过程会一直重复直到发现一个匹配或者字符串不剩任何字符。简单量词都是贪婪量词。
贪婪量词匹配时,首先将整个字符串作为匹配的对象,然后逐步从后向前移除不匹配的字符,尽可能找到最多的匹配。
2.惰性量词:
先看字符串中的第一个字符是否匹配,如果单独一个字符不够,则读入下一个字符,组成两个字符的字符串,如果还没有发现匹配,惰性量词继续从字符串中添加字符直到发现一个匹配或者整个字符串全部检查完都不匹配。惰性量词和贪婪量词工作方式恰好相反。
惰性量词匹配时,只匹配第一个字符,然后依次添加字符,尽可能找到最少匹配。
3.支配量词:
只尝试匹配整个字符串,如果整个字符串不能产生匹配,则不进行进一步尝试。
支配量词目前只有java中支持,支持量词是贪婪量词第一次匹配不成功时,阻止正则表达式继续匹配,使得正则表达式效率更高。
贪婪量词 惰性量词 支配量词 描述
X? X?? X?+ X出现0次或者1次
X* X*? X*+ X出现0次或者多次
X+ X+? X++ X出现1次或者多次
X{n} X{n}? X{n}+ X只出现n次
X{n,} X{n,}? X{n,}+ X至少出现n次
X{n,m} X{n,m}? X{n,m}+ X至少出现n次,至多不超过m次
6——Java动态代理
代理是一种常用的程序设计模式,如同网络代理一样,代理是介于调用者和真正调用目标对象之间的中间对象,代理在调用真正目标对象时提供一些额外或者不同的操作,真正的对目标对象的操作还是通过代理调用目标对象来完成。
简单的代理例子如下:
[java] view plaincopy
1. //接口
2. interface Interface{
3. void doSomething();
4. void somethingElse(String arg);
5. }
6. //目标对象
7. class RealObject implement Interface{
8. public void doSomething(){
9. System.out.println(“RealObject doSomething”);
10. }
11. public void somethingElse(String arg){
12. System.out.println(“RealObject somethingElse ” + arg);
13. }
14. }
15. //简单代理对象
16. class SimpleProxy implements Interface(
17. private Interface proxied;
18. public SimpleProxy(Interface proxied){
19. this.proxied = proxied;
20. }
21. public void doSomething(){
22. System.out.println(“SimpleProxy doSomething”);
23. proxied.doSomething();
24. }
25. public void somethingElse(String arg){
26. System.out.println(“SimpleProxy somethingElse ” + arg);
27. proxied.somethingElse(arg);
28. }
29. )
30. Class SimpleProxyDemo{
31. public static void consumer(Interface iface){
32. iface.doSomething();
33. iface.somethingElse(“TestProxy”);
34. }
35. public static void main(String[] args){
36. //不是用代理
37. cosumer(new RealObject());
38. //使用代理
39. cosumer(new SimpleProxy(new RealObject()));
40. }
41. }
输出结果为:
RealObject doSomething
RealObjectsomethingElse TestProxy
SimpleProxy doSomething
RealObject doSomething
SimpleProxy somethingElse TestProxy
RealObject somethingElse TestProxy
上面例子可以看出代理SimpleProxy在调用目标对象目标方法之前做了一些额外的操作。
Java中的代理是针对接口的动态代理,当然java也可以使用第三方的CGLIB实现针对类的代理,但是JDK中只支持针对接口的动态代理,我们只分析JDK的动态代理。
JDK动态代理的要素:
(1).实现了InvocationHandler的代理处理类,实现其invoke方法,该方法是代理调用目标对象方法以及提供额外操作的方法。
(2).使用Proxy.newProxyInstance(类加载器, 代理接口列表,InvocationHandler对象);方法创建实现了指定接口的动态代理。
JDK的代理例子如下:
[java] view plaincopy
1. //接口
2. interface Interface{
3. void doSomething();
4. void somethingElse(String arg);
5. }
6. //目标对象
7. class RealObject implement Interface{
8. public void doSomething(){
9. System.out.println(“RealObject doSomething”);
10. }
11. public void somethingElse(String arg){
12. System.out.println(“RealObject somethingElse ” + arg);
13. }
14. }
15. //代理处理类
16. class DynamicProxyHandler implements InvocationHandler{
17. provate Object proxied;
18. public DynamicProxyHandler(Object proxied){
19. this.proxied = proxied;
20. }
21. //动态代理调用目标对象的方法
22. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
23. System.out.println(“Dynamic proxy invoke”);
24. return method.invoke(proxied, args);
25. }
26. }
27. class SimpleDynamicProxy{
28. public static void consumer(Interface iface){
29. iface.doSomething();
30. iface.somethingElse(“DynamicProxy”);
31. }
32. public static void main(String[] args){
33. RealObject real = new RealObject();
34. //不是用代理
35. consumer(real);
36. //创建动态代理
37. Interface proxy = (Interface) Proxy.newProxyInstance(
38. Interface.class.getClassLoader(),
39. new Class[]{Interface.class},
40. new DynamicProxyHandler(real)
41. );
42. cosumer(proxy);
43. }
44. }
输出结果为:
RealObject doSomething
RealObject somethingElse DynamicProxy
Dynamic proxy invoke
RealObject doSomething
Dynamic proxyinvoke
RealObject somethingElse DynamicProxy
7——泛型编程基础
一般的类和方法都是针对特定数据类型的,当写一个对多种数据类型都适用的类和方法时就需要使用泛型编程,java的泛型编程类似于C++中的模板, 即一种参数化类型的编程方法,具体地说就是将和数据类型相关的信息抽象出来,主要提供通用的实现和逻辑,和数据类型相关的信息由使用时参数决定。
1.泛型类/接口:
(1).泛型接口:
如一个提供产生指定类的接口:
[java] view plaincopy
1. public interface Gernerator
{ 2. T next() ;
3. }
4. public class A implement Generator{
5. A next(){
6. return new A();
7. }
8. }
(2).泛型类:
一个使用泛型实现的栈数据结构如下:
[java] view plaincopy
1. public class LinkedListStack
{ 2. //节点内部类
3. private static class Node{
4. U item;
5. Node next;
6. Node(){
7. item = null;
8. next = null;
9. }
10. Node(U item, Node next){
11. this.item = item;
12. this.next = next;
13. }
14. Boolean end(){
15. return item == null && next == null;
16. }
17. }
18. private Node
top = new Node (); 19. public void push
(T item){ 20. top = new Node
(item, top); 21. }
22. public T pop(){
23. T result = top.item;
24. if(!top.end()){
25. top = top.next();
26. }
27. return result;
28. }
29. }
使用这个使用泛型实现的栈,可以操作各种数据类型。
2.泛型方法:
例如:
[java] view plaincopy
1. public class GenericMethods{
2. public
void f(T x){ 3. System.out.println(x.getClass().getName()) ;
4. }
5. public static void main(String[] args){
6. GenericMethods gm = new GenericMethods();
7. gm.f(“”);
8. gm.f(1);
9. gm.f(1.0);
10. ……
11. }
12. }
输出结果为:
java.lang.String
java.lang.Integer
java.lang.Double
3.泛型集合:
Java中泛型集合使用的非常广泛,在Java5以前,java中没有引入泛型机制,使用java集合容器时经常遇到如下两个问题:
a. java容器默认存放Object类型对象,如果一个容器中即存放有A类型对象,又存放有B类型对象,如果用户将A对象和B对象类型弄混淆,则容易产生转换错误,会发生类型转换异常。
b. 如果用户不知道集合容器中元素的数据类型,同样也可能会产生类型转换异常。
鉴于上述的问题,java5中引入了泛型机制,在定义集合容器对象时显式指定其元素的数据类型,在使用集合容器时,编译器会检查数据类型是否和容器指定的数据类型相符合,如果不符合在无法编译通过,从编译器层面强制保证数据类型安全。
(1).java常用集合容器泛型使用方法:
如:
[java] view plaincopy
1. public class New{
2. public static
Map map(){ 3. return new HashMap
(); 4. }
5. public static
List list(){ 6. return new ArrayList
() ; 7. }
8. public static
LinkedList lList(){ 9. return new LinkedList
(); 10. }
11. public static
Set set(){ 12. return new HashSet
(); 13. }
14. public static
Queue queue(){ 15. return new LinkedList
() ; 16. }
17. ;public static void main(String[] args){
18. Map
> sls = New.map(); 19. List
ls = New.list(); 20. LinkedList
lls = New.lList(); 21. Set
ss = New.set(); 22. Queue
qs = New.queue(); 23. }
24. }
(2).Java中的Set集合是数学上逻辑意义的集合,使用泛型可以很方便地对任何类型的Set集合进行数学运算,代码如下:
[java] view plaincopy
1. public class Sets{
2. //并集
3. public static
Set union(Set a, Set b){ 4. Set
result = new HashSet (a); 5. result.addAll(b);
6. return result;
7. }
8. //交集
9. public static
Set intersection(Set a, Set b){ 10. Set
result = new HashSet (a); 11. result.retainAll(b);
12. return result;
13. }
14. //差集
15. public static
Set difference(Set a, Set b){ 16. Set
result = new HashSet (a); 17. result.removeAll(b);
18. return Result;
19. }
20. //补集
21. public static
Set complement(Set a, Set b){ 22. return difference(union(a, b), intersection(a, b));
23. }
24. }
8——泛型编程高级
1.泛型边界:
Java泛型编程时,编译器忽略泛型参数的具体类型,认为使用泛型的类、方法对Object都适用,这在泛型编程中称为类型信息檫除。
例如:
[java] view plaincopy
1. class GenericType{
2. public static void main(String[] args){
3. System.out.println(new ArrayList
().getClass()); 4. System.out.println(new ArrayList
().getClass()); 5. }
6. }
输出结果为:
java.util.ArrayList
java.util.ArrayList
泛型忽略了集合容器中具体的类型,这就是类型檫除。
但是如果某些泛型的类/方法只想针对某种特定类型获取相关子类应用,这时就必须使用泛型边界来为泛型参数指定限制条件。
例如:
[java] view plaincopy
1. interface HasColor{
2. java.awt.Color getColor();
3. }
4. class Colored
{ 5. T item;
6. Colored(T item){
7. this.item = item;
8. }
9. java.awt.Color color(){
10. //调用HasColor接口实现类的getColor()方法
11. return item.getColor();
12. }
13. }
14. class Dimension{
15. public int x, y, z;
16. }
17. Class ColoredDimension
{ 18. T item;
19. ColoredDimension(T item){
20. this.item = item;
21. }
22. T getItem(){
23. return item;
24. }
25. java.awt.Color color(){
26. //调用HasColor实现类中的getColor()方法
27. return item.getColor();
28. }
29. //获取Dimension类中定义的x,y,z成员变量
30. int getX(){
31. return item.x;
32. }
33. int getY(){
34. return item.y;
35. }
36. int getZ(){
37. return item.z;
38. }
39. }
40. interface Weight{
41. int weight();
42. }
43. class Solid
{ 44. T item;
45. Solide(T item){
46. this.item = item;
47. }
48. T getItem(){
49. return item;
50. }
51. java.awt.Color color(){
52. //调用HasColor实现类中的getColor()方法
53. return item.getColor();
54. }
55. //获取Dimension类中定义的x,y,z成员变量
56. int getX(){
57. return item.x;
58. }
59. int getY(){
60. return item.y;
61. }
62. int getZ(){
63. return item.z;
64. }
65. int weight(){
66. //调用Weight接口实现类的weight()方法
67. return item.weight();
68. }
69. }
70. class Bounded extends Dimension implements HasColor, Weight{
71. public java.awt.Color getColor{
72. return null;
73. }
74. public int weight(){
75. return 0;
76. }
77. }
78. public class BasicBounds{
79. public static void main(String[] args){
80. Solid
solid = new Solid (new Bounded()); 81. solid.color();
82. solid.getX();
83. solid.getY();
84. solid.getZ();
85. solid.weight();
86. }
87. }
Java泛型编程中使用extends关键字指定泛型参数类型的上边界(后面还会讲到使用super关键字指定泛型的下边界),即泛型只能适用于extends关键字后面类或接口的子类。
Java泛型编程的边界可以是多个,使用如
语法来声明,其中只能有一个是类,并且只能是extends后面的第一个为类,其他的均只能为接口(和类/接口中的extends意义不同)。 使用了泛型边界之后,泛型对象就可以使用边界对象中公共的成员变量和方法。
2.泛型通配符:
泛型初始化过程中,一旦给定了参数类型之后,参数类型就会被限制,无法随着复制的类型而动态改变,如:
[java] view plaincopy
1. class Fruit{
2. }
3. class Apple extends Fruit{
4. }
5. class Jonathan extends Apple{
6. }
7. class Orange extends Fruit{
8. }
9. 如果使用数组:
10. public class ConvariantArrays{
11. Fruit fruit = new Apple[10];
12. Fruit[0] = new Apple();
13. Fruit[1] = new Jonathan();
14. try{
15. fruit[0] = new Fruit();
16. }catch(Exception e){
17. System.out.println(e);
18. }
19. try{
20. fruit[0] = new Orange();
21. }catch(Exception e){
22. System.out.println(e);
23. }
24. }
编译时没有任何错误,运行时会报如下异常:
java.lang.ArrayStoreException:Fruit
java.lang.ArrayStoreException:Orange
为了使得泛型在编译时就可以进行参数类型检查,我们推荐使用java的集合容器类,如下:
[java] view plaincopy
1. public class NonConvariantGenerics{
2. List
flist = new ArrayList (); 3. }
很不幸的是,这段代码会报编译错误:incompatible types,不兼容的参数类型,集合认为虽然Apple继承自Fruit,但是List的Fruit和List的Apple是不相同的,因为泛型参数在声 明时给定之后就被限制了,无法随着具体的初始化实例而动态改变,为解决这个问题,泛型引入了通配符”?”。
对于这个问题的解决,使用通配符如下:
[java] view plaincopy
1. public class NonConvariantGenerics{
2. List extends Fruit> flist = new ArrayList
(); 3. }
泛型通配符”?”的意思是任何特定继承Fruit的类,java编译器在编译时会根据具体的类型实例化。
另外,一个比较经典泛型通配符的例子如下:
public class SampleClass < T extendsS> {…}
假如A,B,C,…Z这26个class都实现了S接口。我们使用时需要使用到这26个class类型的泛型参数。那实例化的时候怎么办呢?依次写下
SampleClass a = new SampleClass();
SampleClass a = new SampleClass();
…
SampleClass
a = new SampleClass(); 这显然很冗余,还不如使用Object而不使用泛型,使用通配符非常方便:
SampleClass Extends S> sc = newSampleClass();
3.泛型下边界:
在1中大概了解了泛型上边界,使用extends关键字指定泛型实例化参数只能是指定类的子类,在泛型中还可以指定参数的下边界,是一super关键字可以指定泛型实例化时的参数只能是指定类的父类。
例如:
[java] view plaincopy
1. class Fruit{
2. }
3. class Apple extends Fruit{
4. }
5. class Jonathan extends Apple{
6. }
7. class Orange extends Fruit{
8. }
9. public superTypeWildcards{
10. public static void writeTo(List super Apple> apples){
11. apples.add(new Apple());
12. apples.add(new Jonathan());
13. }
14. }
通过? Super限制了List元素只能是Apple的父类。
泛型下边界还可以使用,但是注意不能使用
,即super之前的只能是泛型通配符,如: [java] view plaincopy
1. public class GenericWriting{
2. static List
apples = new ArrayList (); 3. static List
fruits = new ArrayList (); 4. static
void writeExact(List list, T item){ 5. list.add(item);
6. }
7. static
void writeWithWildcards(List super T> list, T item){ 8. list.add(item);
9. }
10. static void f1(){
11. writeExact(apples, new Apple());
12. }
13. static void f2(){
14. writeWithWildcards(apples, new Apple());
15. writeWithWildcards(fruits, new Apple());
16. }
17. public static void main(String[] args){
18. f1();
19. f2();
20. }
21. }
4.无边界的通配符:
泛型的通配符也可以不指定边界,没有边界的通配符意思是不确定参数的类型,编译时泛型檫除类型信息,认为是Object类型。如:
[java] view plaincopy
1. public class UnboundedWildcard{
2. static List list1;
3. static List> list2;
4. static List extends Object> list3;
5. static void assign1(List list){
6. list1 = list;
7. list2 = list;
8. //list3 = list; //有未检查转换警告
9. }
10. static void assign2(List> list){
11. list1 = list;
12. list2 = list;
13. list3 = list;
14. }
15. static void assign3(List extends Object> list){
16. list1 = list;
17. list2 = list;
18. list3 = list;
19. }
20. public static void main(String[] args){
21. assign1(new ArrayList());
22. assign2(new ArrayList());
23. //assign3(new ArrayList()); //有未检查转换警告
24. assign1(new ArrayList
()); 25. assign2(new ArrayList
()); 26. assign3(new ArrayList
()); 27. List> wildList = new ArrayList();
28. assign1(wildList);
29. assign2(wildList);
30. assign3(wildList);
31. }
32. }
List和List>的区别是:List是一个原始类型的List,它可以存放任何Object类型的对象,不需要编译时类型检 查。List>等价于List
5.实现泛型接口注意事项:
由于泛型在编译过程中檫除了参数类型信息,所以一个类不能实现以泛型参数区别的多个接口,如:
[java] view plaincopy
1. interface Payable
{ 2. }
3. class Employee implements Payable
{ 4. }
5. class Hourly extends Employee implements Payable
{ 6. }
类Hourly无法编译,因为由于泛型类型檫除,Payable
和Payable 在编译时是同一个类型Payable,因此无法同时实现一个接口两次。 6.泛型方法重载注意事项:
由于泛型在编译时将参数类型檫除,因此以参数类型来进行方法重载在泛型中要特别注意,如:
[java] view plaincopy
1. public class GenericMethod
{ 2. void f(List
v) { 3. }
4. void f(List
v){ 5. }
6. }
无法通过编译,因为泛型檫除类型信息,上面两个方法的参数都被看作为Object类型,使用参数类型已经无法区别上面两个方法,因此无法重载。
7.泛型中的自绑定:
通常情况下,一个类无法直接继承一个泛型参数,但是你可以通过继承一个声明泛型参数的类,这就是java泛型编程中的自绑定,如:
[java] view plaincopy
1. class SelfBounded
>{ 2. T element;
3. SelfBounded
set(T arg){ 4. Element = arg;
5. return this;
6. }
7. T get(){
8. return element;
9. }
10. }
11. class A extends SelfBounded{
12. }
13. class B extends SelfBounded{
14. }
15. class C extends SelfBounded
{ 16. C setAndGet(C arg){
17. set(arg);
18. return get();
19. }
20. }
21. public class SelfBounding{
22. public static void main(String[] args){
23. A a = new A();
24. a.set(new A());
25. a = a.set(new A()).get();
26. a = a.get();
27. C c = new C();
28. C = c.setAndGet(new C());
29. }
30. }
泛型的自绑定约束目的是用于强制继承关系,即使用泛型参数的类的基类是相同的,强制所有人使用相同的方式使用参数基类。
9——集合容器高级
1.Arrays.asList()方法产生的List是一个固定长度的数组,只支持不改变长度的操作,任何试图改变其底层数据结构长度的操作(如,增加,删除等操作)都会抛出UnsupportedOperationException异常。
为了使Arrays.asList()方法产生的List集合长度可变,可以将其作为集合容器的构造方法参数,如:
Set set = new HashSet(Arrays.asList(newint[]{1,23}));
或者将其作为Collections.addAll()方法的参数,如:
Collections.addAll(Arrays.asList(new int[]{1,23}));
2.SortedSet是一个对其元素进行排序了的Set,SortedSet接口有以下方法:
(1).Comparator comparator():
返回此Set中元素进行排序的比较器,如果该方法返回null,则默认使用自然排序。
(2).Object first():
返回Set中第一个(最低)元素。
(3).Object last():
返回Set中最后一个(最高)元素。
(4).SortedSet subset(fromElement, toElement):
返回此Set中从fromElement(包括)到toElement(不包括)的子Set。
(5).SortedSet headset(toElement):
返回此Set中元素严格小于toElement的子Set。
(6).SortedSet tailSet(fromElement):
返回此Set中元素大于等于fromElement的子Set。
3.SortedMap是一个根据Key排序的Map,SortedMap接口有以下方法:
(1).Comparator comparator():
返回此Map中key进行排序的比较器,如果返回的是null,则该Map的key使用自然排序。
(2).T firstKey():
返回此Map中第一个(最低)key。
(3).T lastKey();
返回此Map中最后一个(最高)key。
(4).SortedMap subMap(fromKey, toKey):
返回此Map中key从fromKey(包括)到toKey(不包括)的子Map。
(5).SortedMap headMap(toKey):
返回此Map中key严格小于toKey的子Map。
(6).SortedMap tailMap(fromKey):
返回此Map中key大于等于fromKey的Map。
4.HashMap/HashSet等Hash算法集合重写equals方法和hashCode方法:
HashSet,HashMap以及它们的子类等这些使用Hash算法的集合存放对象时,如果元素不是java中的8种基本类型(即元素都是对象类型),则必须重写对象的equals和hashCode方法,否则会产生一些错误,例如:
[java] view plaincopy
1. Class A{
2. int i;
3. public A(int i){
4. this.i = i;
5. }
6. Public static void main(String args[]){
7. Map map = new HashMap();
8. map.put(new A(1), “First”);
9. map.put(new A(2), “Second”);
10. A a = new A(1);
11. boolean b = map.containsKey(a);
12. System.out.println(b);
13. }
14. }
输出的结果是:false。
Map中有Key为A(1)对象,但是却没有找到,这是因为HashMap使用Hash算法根据对象的hashCode值来查找给定的对象,如果没 有重写hashCode和equals方法,则对象默认使用Object的hashCode方法,Object默认hashCode方法使用对象的内存地 址作为hashCode值。
为了使Hash算法集合存放对象类型数据符合用户的期望,必须重写对象的hashCode和equals方法,其中hashCode方法用于Hash算法的查找,equals方法用于对象比较,Hash算法中还要用到equals方法如下:
因为Hash算法所使用的hashCode可能会产生碰撞(不相等对象的hashCode值相同),当Hash算法产生碰撞时,就需要再次 Hash(即再次通过其他方法计算hashCode值),所以一个对象使用Hash算法时,其对应的HashCode值可能不止一个,而是一组,当产生碰 撞时就选择另一个hashCode值。当hashCode值产生碰撞时,还必须使用equals方法方法对象是否相等。
注意:由于Hash算法有可能会产生碰撞(不相等的对象hashCode值相同),所以hashCode和equals方法有如下关系:
(1).equals方法相等的对象,hashCode方法值一定相同。
(2).hashCode方法相同的对象,equals不一定相等。
5.创建只读集合容器:
List,Set和Map类型的集合容器都可以通过下面的方法创建为只读,即只可以访问,不能添加,删除和修改。
[java] view plaincopy
1. static Collection
data = new ArrayList (); 2. data.add(“test”);
3. static Map
m = new HashMap (); 4. m.put(“key”, “value”);
(1).只读集合:
[java] view plaincopy
1. Collection
c = Collections.unmodifiableCollection(new ArrayList (data)); 2. System.out.println(c); //可以访问
3. //c.add(“test2”);只读,不可添加
(2).只读List:
[java] view plaincopy
1. List
list = Collections.unmodifiableList(new ArrayList (data)); 2. System.out.println(list.get(0)); //可以访问
3. //list.remove(0);只读,不可删除
(3).只读Set:
[java] view plaincopy
1. Set
set = Collections.unmodifiableSet(new HashSet (data)); 2. System.out.println(set.Iterator().next()) //可以访问
3. //set.add(“test”);只读,不可添加
(4).只读Map:
[java] view plaincopy
1. Map
map = Collections.unmodifiableMap(new HashMap (m)); 2. System.out.println(map.get(“key”)); //可以访问
3. //map.put(“key2”, “value2”);只读,不可添加
只读集合容器会在编译时检查操作,如果对只读集合容器进行增删等操作时,将会抛出UnSupportedOperationException异常。
只读集合容器类似于将集合对象访问控制修饰符设置为private,不同之处在于,其他类可以访问,只是不能修改。
6.线程同步集合容器:
Java集合容器中,Vector,HashTable等比较古老的集合容器是线程安全的,即处理了多线程同步问题。
而Java2之后对Vector和HashTable的替代类ArrayList,HashSet,HashMap等一些常用的集合容器都是非线程安全的,即没有进行多线程同步处理。
Java中可以通过以下方法方便地将非线程安全的集合容器进行多线程同步:
(1).线程同步集合:
Collection
c= Collections.synchronizedCollection(newArrayList ()); (2).线程同步List:
List
c= Collections.synchronizedList(newArrayList ()); (3).线程同步Set:
Set
c= Collections.synchronizedSet(newHashSet ()); (4).线程同步Map:
Map
c= Collections.synchronizedMap(newHashMap ()); 7.对象的强引用、软引用、弱引用和虚引用:
JDK1.2以前版本中,只存在一种引用——正常引用,即对象强引用,如果一个对象被一个引用变量所指向,则该对象是可触及(reached)的状态,JVM垃圾回收器不会回收它,弱一个对象不被任何引用变量指向,则该对象就处于不可触及的状态,垃圾回收器就会回收它。
从JDK1.2版本之后,为了更灵活的控制对象生命周期,引入了四种引用:强引用(java.lang.ref.Reference)、软引用 (java.lang.ref.SoftReference)、弱引用(java.lang.ref.WeakReference)和虚引用 (java.lang.ref.PhantomReference):
(1). 强引用(java.lang.ref.Reference):
即Java程序中普遍使用的正常对象引用,存放在内存中得对象引用栈中,如果一个对象被强引用,则说明程序还在使用它,垃圾回收器不会回收,当内存不足时,JVM抛出内存溢出异常使程序异常终止。
(2). 软引用(java.lang.ref.SoftReference):
如果一个对象只具有软引用,则内存空间足够,垃圾回收器也不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,JVM就会把这个软引用加入到与之关联的引用队列中。
(3). 弱引用(java.lang.ref.WeakReference):
弱引用用来实现内存中对象的标准映射,即为了节约内存,对象的实例可以在一个程序内多处使用。
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程 中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的后台线程,因此不一定会很快发 现那些只具有弱引用的对象。
弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
(4). 虚引用(java.lang.ref.PhantomReference):
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
JVM中,对象的引用往往都是很复杂的,各个对象之间相互引用形成一个内存引用树,java中某个对象是否可触及,有以下两条判断原则:
a.单条引用路径可及性判断:在这条路径中,最弱的一个引用决定对象的可及性。
b.多条引用路径可及性判断:几条路径中,最强的一条的引用决定对象的可及性。
软引用,弱引用,虚引用例子如下:
[java] view plaincopy
1. package com.test.reference;
2. //测试对象
3. class VeryBig {
4. private static final int SIZE = 10000;
5. private long[] la = new long[SIZE];
6. private String ident;
7.
8. public VeryBig(String id) {
9. ident = id;
10. }
11.
12. public String toString() {
13. return ident;
14. }
15.
16. protected void finalize() {
17. System.out.println("Finalizing " + ident);
18. }
19. }
20.
21. package com.test.reference;
22.
23. import java.lang.ref.PhantomReference;
24. import java.lang.ref.Reference;
25. import java.lang.ref.ReferenceQueue;
26. import java.lang.ref.SoftReference;
27. import java.lang.ref.WeakReference;
28. import java.util.LinkedList;
29.
30. public class TestReferences {
31. //引用队列
32. private static ReferenceQueue
rq = new ReferenceQueue (); 33. //检查引用队列是否为空
34. public static void checkQueue() {
35. //强引用,轮询引用队列,看是否有可以的对象引用
36. Reference extends VeryBig> inq = rq.poll();
37. if (inq != null)
38. //如果有可以使用的对象引用,则打印出该引用指向的对象
39. System.out.println("In queue: " + inq.get());
40. }
41.
42. public static void main(String[] args) {
43. int size = 2;
44. //创建存放VeryBig对象的软引用集合
45. LinkedList
> sa = new LinkedList >(); 46. for (int i = 0; i < size; i++) {
47. //将对象和软引用添加到引用队列中
48. sa.add(new SoftReference
(new VeryBig("Soft " + i), rq)); 49. System.out.println("Just created: " + sa.getLast());
50. checkQueue();
51. }
52. //创建存放VeryBig对象的弱引用集合
53. LinkedList
> wa = new LinkedList >(); 54. for (int i = 0; i < size; i++) {
55. //将对象和弱引用添加到引用队列中
56. wa.add(new WeakReference
(new VeryBig("Weak " + i), rq)); 57. System.out.println("Just created: " + wa.getLast());
58. checkQueue();
59. }
60. SoftReference
s = new SoftReference (new VeryBig( 61. "Soft"));
62. WeakReference
w = new WeakReference (new VeryBig( 63. "Weak"));
64. //垃圾回收器回收,在回收之前调用对象的finalize()方法
65. System.gc();
66. //创建存放VeryBig对象的虚引用集合
67. LinkedList
> pa = new LinkedList >(); 68. for (int i = 0; i < size; i++) {
69. //将对象和虚引用添加到引用队列中
70. pa.add(new PhantomReference
(new VeryBig("Phantom " + i), 71. rq));
72. System.out.println("Just created: " + pa.getLast());
73. checkQueue();
74. }
75. }
76. }
输出结果为:
Just created:java.lang.ref.SoftReference@757aef
Just created:java.lang.ref.SoftReference@d9f9c3
Just created:java.lang.ref.WeakReference@9cab16
Just created:java.lang.ref.WeakReference@1a46e30
In queue: null
Finalizing Weak 0
Finalizing Weak
Finalizing Weak 1
Just created:java.lang.ref.PhantomReference@3e25a5
In queue: null
Just created:java.lang.ref.PhantomReference@19821f
注意:由于System.gc()只是通知JVM虚拟机可以进行垃圾回收器可以进行垃圾回收了,但是垃圾回收器具体什么时候允许说不清楚,所以这个输出结果只是个参考,每次运行的结果Finalize方法的执行顺序不太一样。
从程序可以看出,尽管对象被引用,垃圾回收器还是回收了被引用对象,引用队列总是创建一个包含null对象的引用。
8.WeakHashMap:
WeakHashMap专门用于存放弱引用,WeakHashMap很容易实现弱引用对象标准映射功能。
在WeakHashMap中,只存储一份对象的实例及其值,当程序需要对象实例值时,WeakHashMap从现有的映射中找出已存在的对象值映射。
由于弱引用节约内存的技术,WeakHashMap允许垃圾回收器自动清除器存放的key和value。WeakHashMap自动将其中存放的key和value包装为弱引用,当key不再被使用时,垃圾回收器自动回收该key和value。
10——文件和目录常用操作
1.文件目录的List操作:
Java中,File类其实代表文件的路径,它既可以代表一个特定文件的文件,也可以代表一个包含多个文件的目录名称。如果File代表目录,可以使用List列出目录中文件。
[java] view plaincopy
1. import java.util.regex.*;
2. import java.io.*;
3. import java.util.*;
4. public class DirList{
5. public static void main(String[] args){
6. //当前目录
7. File path = new File(“.”);
8. String[] list;
9. //如果没有指定参数,则将目录中文件全部列出
10. if(args.length == 0){
11. list = path.list();
12. }
13. //指定了参数,则根据指定文件名过滤符合条件的文件
14. else{
15. list = path.list(new DirFilter(args[0]));
16. }
17. Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
18. for(String dirItem : list){
19. System.out.println(dirItem);
20. }
21. }
22. }
23. class DirFilter implements FilenameFilter{
24. private Pattern pattern;
25. public DirFilter(String regex){
26. //将输入的命令行参数编译为正则表达式的模式串
27. pattern = Pattern.compile(regex);
28. }
29. //File的List方法回调方法
30. public boolean accept(File dir, String name){
31. //使用正则表达式匹配给定目录下的文件名
32. return pattern.matcher(name).matches();
33. }
34. }
命令行输入参数:“*\.java”
输出结果如下:
DirFilter.java
DirList.java
2.java中除了File类可以表示文件的路径外,还可以表示目录的路径,可以通过File的isDirectory判断File对象是一个文件还是一个目录。
如下的例子通过local()方法列出给定目录中符合条件的文件/目录名称,walk()方法遍历给定的目录:
[java] view plaincopy
1. import java.util.regex.*;
2. import java.io.*;
3. import java.util.*;
4.
5. public final class Directory{
6. //列出目录中符合条件的文件名
7. public static File[] local(File dir, final String regex){
8. return dir.listFiles(new FilenameFilter(){
9. private Pattern pattern = Pattern.compile(regex);
10. public Boolean accept(File dir, String name){
11. return pattern.matcher(new File(name).getName()).matches();
12. }
13. });
14. }
15. //重载列出目录下符合条件的文件名方法
16. public static File[] local(String path, final String regex){
17. return local(new File(path), regex);
18. }
19. //代表文件树信息的静态内部类
20. public static class TreeInfo implements Iterable
{ 21. public List
files = new ArrayList (); 22. public List
dirs = new ArrayList (); 23. //默认的迭代器方法,跌倒文件树元素对象
24. public Iterator
iterator(){ 25. return files.iterator();
26. }
27. void addAll(TreeInfo other){
28. files.addAll(other.files);
29. dirs.addAll(other.dirs);
30. }
31. public String toString(){
32. return “dirs: ” + dirs + “\n\nfiles: ” + files;
33. }
34. }
35. //从指定的文件/目录开始遍历符合条件的文件
36. public static TreeInfo walk(String start, String regex){
37. return recurseDirs(new File(start), regex);
38. }
39. //重载遍历文件/目录方法
40. \public static TreeInfo walk(File start, String regex){
41. return recurseDirs(start, regex);
42. }
43. //默认的指定文件/目录查找任何文件名的文件
44. public static TreeInfo walk(File start){
45. return recurseDirs(start, “.*”);
46. }
47. //重载默认的查找任何文件的方法
48. public static TreeInfo walk(String start){
49. return recurseDirs(new File(start), “.*”);
50. }
51. //从指定的文件/目录开始遍历,查找符合条件的文件名
52. static TreeInfo recurseDirs(File startDir, String regex){
53. TreeInfo result = new TreeInfo();
54. for(File item : startDir.listFiles()){
55. //如果遍历的文件是目录
56. if(item.isDirectory()){
57. result.dirs.add(item);
58. //迭代子目录
59. result.addAll(recurseDirs(item, regex));
60. }
61. //如果遍历的的文件是普通文件
62. else{
63. if(item.getName().matches(regex)){
64. result.files.add(item);
65. }
66. }
67. }
68. return result;
69. }
70. }
3.文件和目录的其他操作:
文件和目录除了常规的查找和遍历操作意外,还有很多其他的操作,例如:创建、删除、判断文件/目录是否已存在,获取文件.目录的绝对路径,已经文件/目录的权限等等,下面的小例子就展示文件/目录的这些操作:
[java] view plaincopy
1. import java.io.*;
2. public class MakeDirectories{
3. //获取文件/目录的基本信息
4. private static void fileData(File f){
5. System.out.println(
6. “Absolute path: ” + f.getAbsolutePath() +
7. “\n Can read: ” + f.canRead() +
8. “\n Can write: ” + f.canWrite() +
9. “\n getName: ” + f.getName() +
10. “\n getParent: ” + f.getParent() +
11. “\n getPath: ” + f.getPath() +
12. “\n length: ” + f.length() +
13. “\n lastModified: ” + f.lastModifed());
14. if(f.isFile()){
15. System.out.println(f.getName() + “ is a file”);
16. }
17. else if(f.isDirectory()){
18. System.out.println(f.getName() + “ is a directory”);
19. }
20. }
21. public static void main(String[] args){
22. File old = new File(“oldFile”);
23. File new = new File(“newFile”);
24. old.renameTo(new);
25. fileData(old);
26. fileData(new);
27. File d = new File(“/test”);
28. if(d.exists()){
29. System.out.println(“Deleting …” + d);
30. d.delete();
31. }
32. else {
33. System.out.prinln(“Creating…” + d);
34. d.mkdirs();
35. }
36. }
37. }
11——Java I/O
Java中使用流来处理程序的输入和输出操作,流是一个抽象的概念,封装了程序数据于输入输出设备交换的底层细节。JavaIO中又将流分为字节流和字符流,字节流主要用于处理诸如图像,音频视频等二进制格式数据,而字符流主要用于处理文本字符等类型的输入输出。
1.字节输入流InputStream
输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。
常用字节输入流的主要类型:
(1).ByteArrayInputStream字节数组输入流:
主要功能:允许内存缓存作为输入流。
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。
注意:关闭ByteArrayInputStream无效,该类中的方法在关闭此流之后仍可被调用,而不会产生任何的IOException。
(2).FileInputStream文件输入流:
主要功能:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。
(3).PipedInputStream管道输入流:
主要功能:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端。
管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,这些数据有某个线程从PipedInputStream对象中读取,并有其他线程将其写入到相应的PipedOutputStream对象中。
注意:不建议PipedInputStream和PipedOutputStream对象使用单线程,因为这样可能思索线程。管道输入流包含一个缓 冲区,可以在缓冲区限定范围内将读操作和写操作分离开,如果先连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
(4).SequenceInputStream顺序输入流:
重要功能:将两个或多个输入流对象转换为一个单个输入流对象。
SequenceInputStream表示其他输入流的逻辑串联关系,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
(5).FilterInputStream过滤输入流:
主要功能:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。
常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。
2.字节输出流OutputStream:
和字节输入流相对应,字节输出流负责字节类型数据想目标文件或设备的输出。常见的字节输出流如下:
(1).ByteArrayOutputStream字节数组输出流:
主要功能:在内存中创建一个缓冲区,将接收到的数据放入该内存缓冲区中。
ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。
注意:和ByteArrayInputStream类似,关闭ByteArrayOutputStream也是无效的,此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。
(2).FileOutputStream文件输出流:
主要功能:将数据写入到指定文件中。
文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。
(3).PipedOutputStream管道输出流:
主要功能:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。
(4).FilterOutputStream过滤输出流:
主要功能:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。
常用的FIlterOutputStream是DataOutputStream数据输出流,它允许程序以适当的方式将java基本数据类型写入输 出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。
3.字符流:
Java中得字节流只能针对字节类型数据,即支持处理8位的数据类型,由于java中的是Unicode码,即两个字节代表一个字符,于是在JDK1.1之后提供了字符流Reader和Writer。
字符流相关常用类如下:
(1).Reader:
用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。
(2).InputStreamReader:
是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。
它使用的字符集可以有名称指定或显式给定,也可以使用平台默认的字符集。
(3).Writer:
用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。
(4).OutputStreamWriter:
是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。
4.综合使用java IO各种流:
Java IO中的各种流,很少单独使用,经常结合起来综合使用,既可以满足特定需求,又搞效。例子如下:
(1).使用缓冲流读取文件:
[java] view plaincopy
1. import java.io.*;
2.
3. public class BufferedInputFile{
4. public static String read(String filename) throws IOException{
5. //缓冲字符输入流
6. BufferedReader in = new BufferedReader(new FileReader(filename));
7. String s;
8. StringBuilder sb = new StringBuilder();
9. //每次读取文件中的一行
10. While((s = in.readLine()) != null){
11. sb.append(s + “\n”);
12. }
13. in.close();
14. return sb.toString();
15. }
16. public static void main(String[] args) throws IOException{
17. System.out.println(read(“BufferedInputFile.java”));
18. }
19. }
(2).读取内存中的字符串:
[java] view plaincopy
1. import java.io.*;
2.
3. public class MemoryInput{
4. public static void main(String[] args) throws IOException{
5. //将字符串包装为字符输入流
6. StringReader in = new StringReader(
7. BufferedInputFile.read(“BufferedInputFile.java”));
8. int c;
9. //读取字符输入流中的字符
10. while((c == in.read()) != -1){
11. System.out.println((char)c);
12. }
13. }
14. }
(3).数据输入/输出流:
[java] view plaincopy
1. import java.io.*;
2.
3. public class DataInputOutput{
4. public static void main(String[] args) thows IOException{
5. DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
6. new FileOutputStream(“Data.txt”)));
7. out.writeDouble(3.14159);
8. out.writeUTF(“That was pi”);
9. out.writeDouble(1.41413);
10. out.writeUTF(“Square root of 2”);
11. out.close();
12. DataInputStream in = new DataInputStream(new BufferedInputStream(
13. new FileOutputStream(“Data.txt”)));
14. System.out.println(in.readDouble());
15. System.out.println(in.readUTF());
16. System.out.println(in.readDouble());
17. System.out.println(in.readUTF());
18. }
19. }
输出结果:
3.14159
That was pi
1.41413
Square root of 2
(4).文本文件输出流:
[java] view plaincopy
1. import java.io.*;
2.
3. public class TextFileOutput{
4. //输出文件名
5. static String file = “BasicFileOutput.out”;
6. public static void main(String[] args) throws IOException{
7. //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
8. BufferedReader in = new BufferedReader(new StringReader
9. (BufferedInputFile.read(“TextFileOutput.java”)));
10. //字符输出流
11. PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
12. int lineCount = 1;
13. String s;
14. While((s = in.readLine()) != null){
15. out.println(lineCount++ + “: ” + s);
16. }
17. out.close();
18. }
19. }
(5).二进制文件读写:
[java] view plaincopy
1. import java.io.*;
2.
3. public class BinaryFileOutput{
4. //输出文件名
5. static String file = “BinaryFileOutput.out”;
6. public static void main(String[] args) throws IOException{
7. //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
8. BufferedInputStream in = new BufferedInputStream(
9. new FileInputStream(“TestFile.png”)));
10. //字符输出流
11. BufferedOutputStream out = new BufferedOutputStream (
12. new FileOutputStream(file));
13. byte[] buf = new byte[1024];
14. int n;
15. While((n = in.read(buf)) > 0){
16. out.write(buf, 0, n);
17. }
18. out.close();
19. }
20. }
12——Java new I/O(一)
为了提高Java I/O的速度和效率,从JDK1.4开始引入了java.nio.*包,即java new I/O(NIO)。
事实上,为了利用java nio的速度和效率优势,原来的java I/O包中相关的类已经使用java nio重新实现,因此在编程中即使没有显式地使用java nio的代码,使用传统java I/O还是利用了nio的速度和效率优势。Java nio的速度提高主要在:文件I/O和网络I/O两个方面。
Java nio的速度提升主要因为使用了类似于操作系统本身I/O的数据结构:I/O通道(Channel)和缓冲区。
1.Channel通道:
通道表示实体,如硬件设备、文件、网络套接字或可以执行的一个或多个不同的I/O操作(如读取或写入)的程序组件的开发的链接,用于I/O操作的链接。
通道可处于打开或关闭状态。创建通道时它处于打开状态,一旦将其关闭,则保持关闭状态。一旦关闭了某个通道,试图对其调用I/O操作都会抛出 ClosedChannelException异常,通过调用通道的isOpen()方法可以探测通道是否处于打开状态。一般情况下通道对于多线程的访问 是安全的。
2.ByteBuffer字节缓冲区:
字节缓冲区是nio中唯一直接和通道channel打交道的缓冲区。字节缓冲区可以存放原始字节,也可以存放java的8中基本类型数据,但是不能存放引 用类型数据,String也是不能存放的。正是由于这种底层的存放类型似的字节缓冲区可以更加高效和绝大部分操作系统的I/O进行映射。
字节缓冲区通过allocation()方法创建,此方法为该缓冲区的内容分配空间,或者通过wrapping方法将现有的字节数组包装到缓冲区中来创建。
字节缓冲区的常用操作:
(1).读写单个字节的绝对和相对get和put方法:
a. 绝对方法:
get(int index):读取指定索引处的字节。
put(int index, byte b):将字节写入指定索引处。
b.相对方法:
get():读取此缓冲区当前位置的字节,然后该位置递增。
put(byte b):将字节写入此缓冲区的当前位置,然后该位置递增。
(2).相对批量get方法:
ByteBuffer get(byte[] dst):将此缓冲区的字节传输到给定的目标数组中。
(3).相对批量put方法:
ByteBuffer put(byte[] src):将给定的源byte数组的所有内容传输到此缓冲区中。
(4).读写其他基本类型值:
getChar(), putChar(char value), getChare(int index), putChar(int index, char value).
getInt(), putInt(int value), getInt(int index), putInt(int index, int value)等等读写基本类型值得相对和绝对方法。
注意:基本类型值得相对和绝对读写方法,根据java基本类型数据底层字节数进行缓冲区移动。
(5).创建视图缓冲区:
为了访问同类二进制数据,允许将字节缓冲区视为包含它们基本类型值的缓冲区,视图缓冲区只是其内容受该字节缓冲区支持的另一种缓冲区。字节缓冲区和视图缓冲区内容更改是相互可见的。这两种缓冲区的位置、限制和标记值都是独立的。创建方法如下:
asCharBuffer(), asDoubleBuffer(), asFloatBuffer(), asIntBuffer(), asLongBuffer(), asReadOnlyBuffer(), asShortBuffer()。
与具体类型的get和put方法系列相比,视图缓冲区优势如下:
a视图缓冲区不是根据字节进行索引,而是根据其特定类型的值得大小进行索引。
b.视图缓冲区提供了相对批量get和put方法,这些方法可以在缓冲区和数组或相同类型的其他缓冲区直接传输连续序列的值。
c.视图缓冲区可能更搞效,因为当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
(6).缓冲区其他操作:
a.ByteBuffer compact():压缩缓冲区,从缓冲区写入数据之后调用,以防写入不完整。
b.ByteBuffer duplicate():创建共享此缓冲区内容的新的字节缓冲区,新缓冲区中的内容为此缓冲区的内容,此缓冲区和新缓冲区内容更改是相互可见的。
c.ByteBuffer slice():创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。
3.直接与非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。
如果字节缓冲区是直接的,则JVM会尽最大努力直接在此缓冲区上执行本机的I/O操作,即在每次调用底层操作系统的一个本机I/O之前(或之后),JVM 都会尽量避免将缓冲区的内容复制到中间缓冲区中(或尽量避免从中间缓冲区复制内容)。直接字节缓冲区可以通过调用allocateDirect()方法来 创建,此方法返回的缓冲区进行分配和取消分配所需的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此它对应用程序的内存 需求量造成的影响可能并不明显,所以建议将直接缓冲区主要分配给那些容易受底层操作系统的本机I/O操作影响的大型的、持久的缓冲区
可以通过调用isDirect()来判断字节缓冲区是直接的还是非直接的。
4.文件通道:Java I/O的FileInputStream,FileOutputStream和RandomAccessFile可以产生文件通道,例子如下:
[java] view plaincopy
1. import java.nio.*;
2. import java.nio.channels.*;
3. import java.io.*;
4.
5. public class FileChannel{
6. //分配字节缓冲区时指定其大小
7. private static final int BSIZE = 1024;
8. public static void main(String[] args) throw Exception{
9. //获取FileOutputStram的文件通道
10. FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
11. //向字节缓冲区中写入字节数组
12. fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
13. fc.close();
14. //以读写方式获取随机访问文件的文件通道
15. fc = new RandomAccessFile(“data.txt”, “rw”).getChannel();
16. //定位到字节缓冲区当前内容之后
17. fc.position(fc.size());
18. //向字节缓冲区中追加内容
19. fc.write(ByteBuffer.wrap(“Some more”.getBytes()));
20. fc.close();
21. //获取FileInputStream的文件通道
22. fc = new FileInputStream(“data.txt”).getChannel();
23. //分配字节缓冲区
24. ByteBuffer buff = ByteBuffer.allocate(BSIZE);
25. //将字节数组从文件通道读入到字节缓冲区中
26. fc.read(buff);
27. //放置缓冲区,为缓冲区写出或相对获取做准备
28. buff.flip();
29. //判断缓冲区是是否还有元素
30. while(buff.hasRemaining()){
31. //获取字节缓冲区字节的相对方法
32. System.out.println((char)buff.get());
33. }
34. }
35. }
输出结果:
Some text Some more
传统java I/O中FileInputStream, FileOutputStream和RandomAccessFile三个类可以产生文件通道。
注意:Java new I/O的目标是提高I/O速度,快速移动大批量的数据,因此,字节缓冲区的大小非常重要,例子中的1K大小不一定是合适的,应用程序需要在生产环境中测试确定合适的缓冲区大小。
5.文件复制:
文件通道和字节缓冲区不但可以实现数据从文件读入和读出,还可以实现文件的复制,例子如下:[java] view plaincopy
1. import java.nio.*;
2. import java.nio.channels/*;
3. import java.io.*
4.
5. public class FileCopy{
6. //字节缓冲区大小
7. private static final int BSIZE = 1024;
8. public static void main(String[] args) throws Exception{
9. //获取文件输入通道
10. FileChannel in = new FileInputStream(“FileCopy.java”).getChannel();
11. //获取文件输出通道
12. FileChannel out = new FileOutputStream(“FileOut.txt”).getChannel();
13. ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
14. While(in.read(buffer) != -1){
15. //重置缓冲区,准备写出
16. buffer.flip();
17. //将字节缓冲区内容写出到文件输出通道
18. out.write(buffer);
19. //清除字节缓冲区
20. buffer.clear();
21. }
22. }
23. }
6.文件通道传输:
文件通道的transferFrom(ReadableByteChannel src, long position, long count)方法将字节从给定的可读字节通道传输到此通道的文件中,transferTo(long position, long count, WritableByteChannel target)方法将此通道的文件传输到给定的可写字节通道。例子如下:[java] view plaincopy
1. import java.nio.channels.*;
2. import java.io.*
3.
4. public class ChannelTransfer{
5. public static void main(String[] args) throws Exception{
6. FileChannel in = new FileInputStream(“ChannelTransfer.java”).getChannel();
7. FileChannel out = new FileOutputStream(“out.txt”).getChannel();
8. //将输入文件通道传输到输出文件通道
9. in.transferTo(0, in.size(); out);
10. //从输入文件通道传输到输出文件通道
11. out.transferFrom(in, 0, in.size());
12. }
13. }
13——Java new I/O(二)
1.数据转换:
使用字节缓冲区时,向字节缓冲区写入或者从字节缓冲区读取内容时,如果内容是字符类型,写入时指定字符编码集对内容进行编码,读取时指定字节编码集对内容解码。每次从字节缓冲区中读取一个字节,程序中还需要对读取的字节进行类型转换才可以使用,如果使用视图缓冲区就可以直接获取所需的数据类型,例子如下:
[java] view plaincopy
1. import java.nio.*;
2. import java.nio.channels.*;
3. import java.nio.charset.*;
4. import java.io.*;
5.
6. public class BufferToText{
7. private static final int BSIZE = 1024;
8. public static void main(String[] args) throws Exception{
9. FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
10. fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
11. fc.close();
12. fc = new FileInputStream(“data.txt”).getChannel();
13. ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
14. fc.read(buffer);
15. buffer.flip();
16. //创建字节缓冲区视图,作为char缓冲区,由于编码问题输出乱码
17. System.out.println(buffer.asCharBuffer());
18. //重绕字节缓冲区,即回到字节缓冲区开始处
19. buffer.rewind();
20. //获取系统默认字节编码
21. String encoding = System.getProperties(“file.encoding”);
22. System.out.println(“Decode using ” + encoding + “: ”
23. + Charset.forName(encoding).decode(buffer));
24. fc = new FileOutputStream(“data2.txt”).getChannel();
25. //创建字节缓冲区时指定字符集
26. fc.write(ByteBuffer.wrap(“Some text”.getBytes(“UTF-16BE”)));
27. fc.close();
28. fc= new FileInputStream(“data2.txt”).getChannel();
29. buffer.clear();
30. fc.read(buffer);
31. buffer.flip();
32. //创建字节缓冲区的视图,将其作为char缓冲
33. System.out.println(buffer.asCharBuffer());
34. }
35. }
输出结果:
????
Decoded using Cp1252: Some text
Some text
从上面例子可以看出,使用java.nio.charset.Charset类对字符进行编码/解码之后,写入/读取字节缓冲区时,字节缓冲区中的字符内容才可以正常显示。
2.创建字节缓冲区视图:
字节缓冲区中只能存放字节内容,java 8种基本类型数据向字节缓冲区中写入,或者从字节缓冲区中读取,就需要使用字节缓冲区视图和读取8中java基本类型数据的方法,例子如下:[java] view plaincopy
1. import java.nio.*;
2.
3. public class ViewBuffer{
4. private static final int BSIZE = 1024;
5. public static void main(String[] args){
6. ByteBuffer bb = ByteBuffer.allocate(BSIZE);
7. int i = 0;
8. //检查字节缓冲区的内容是否为0
9. while(i++ < bb.limit()){
10. if(bb.get() != 0){
11. System.out.println(“nonzero”);
12. }
13. System.out.println(“i = ” + i);
14. //回到字节缓冲区开始处
15. bb.rewind();
16. }
17. //创建字节缓冲区视图,作为char缓冲
18. bb.asCharBuffer().put(“Hello!”);
19. char c;
20. while((c = bb.getChar)) != 0){
21. System.out.println(c + “ ”);
22. }
23. bb.rewind();
24. //创建字节缓冲区视图,作为short缓冲
25. bb.asShortBuffer().put((Short)12390);
26. System.out.println(bb.getShort());
27. bb.rewind();
28. //创建字节缓冲区视图,作为int缓冲
29. bb.asIntBuffer().put(99471142);
30. System.out.println(bb.getInt());
31. bb.rewind();
32. //创建字节缓冲区视图,作为long缓冲
33. bb.asLongBuffer().put(99471142);
34. System.out.println(bb.getLong());
35. bb.rewind();
36. //创建字节缓冲区视图,作为float缓冲
37. bb.asFloatBuffer().put(99471142);
38. System.out.println(bb.getFloat());
39. bb.rewind();
40. //创建字节缓冲区视图,作为double缓冲
41. bb.asDoubleBuffer().put(99471142);
42. System.out.println(bb.getDouble());
43. }
44. }
输出结果:
i = 1025
H e l l o !
12390
99471142
99471142
9.9471142E7
9.9471142E7
对于刚分配的字节缓冲区来说,其所有内容都是0,所有第一次i的输出值为1025.
向字节缓冲区中写入,或者从字节缓冲区中读取8中基本类型java数据最简便的方法是使用字节缓冲区的视图缓冲区。
3.java nio相关类图:
下图是从《java编程思想第4版》中截的java nio相关类图:
从类图中可以看出,java nio中,只有字节缓冲区ByteBuffer可以向文件通道写数据或者从文件通道中读取数据。
4.缓冲区详解:
缓冲区由:内容数据和4个索引组成。
缓冲区索引:
a. 缓冲区标记(mark):使缓冲区能够记住一个位置并在之后将其返回。并非总需要定义标记,但在定义标记时,不能将其定义为负数,且不能大于其位置。
b. 缓冲区位置(position):是缓冲区下一个要读取或写入元素的索引,位置不能为负,且不能大于其限制。
c. 缓冲区限制(limit):是第一个不应该读取或写入的元素的索引,限制不能为负,且不能大于其容量。
d. 缓冲区容量(capacity):是缓冲区所包含的元素数量,不能为负,且不能更改。
缓冲区索引遵循以下不变公式:
0 <= 标记 <= 位置 <= 限制 <= 容量
通过这些索引,缓冲区可以高效的访问和操作缓冲区中的内容数据,新创建的缓冲区总有一个0位置和一个未定义的标记,初始限制可以为0,也可以为其他值,取决于缓冲区类型和构建方式。
缓冲区的以下方法可以查询,设置和充值缓冲区的索引:
(1).capacity()方法:
返回此缓冲区的容量。
(2).clear()方法:
清除此缓冲区,将缓冲区位置设置为0,将缓冲区限制设置为容量,并丢弃标记。
(3).flip()方法:
反转此缓冲区,首先将限制设置为当前位置,然后将位置设置为0,如果已定义了标记,则丢弃该标记。
(4).limit()方法:
返回此缓冲区的限制。
(5).limit(int lim)方法:
设置此缓冲区的限制,如果位置大于新的限制,则将位置设置为新的限制,如果标记已定义且大于新限制,则丢弃该标记。
(6).mark()方法:
在此缓冲区的位置设置标记。
(7).position()方法:
返回此缓冲区的位置。
(8).position(int pos)方法:
设置此缓冲区的位置,如果标记已定义且大于新的位置,则丢弃该标记。
(9).remaining()方法:
返回当前位置与限制之间的元素个数,即limit-position。
(10).hasRemaining()方法:
判断在当前位置和限制之间是否有元素。
14——I/O高级
1.内存映射文件:
内存映射文件允许把比内存大的文件读入内存中创建和修改,使用内存映射文件,可以像使用内存中数组一样在内存中访问整个文件,例子如下:
[java] view plaincopy
1. import java.nio.*;
2. import java.nio.channels.*;
3. import java.io.*;
4.
5. pulbic class MemoryMappedFile{
6. //十六进制,128MB大小
7. static int length = 0x8FFFFFF;
8. public static void main(String[] args)throws Exception{
9. //通过文件通道将文件映射为内存中的自己缓冲区
10. MappedByteBuffer out = new RandomAccessFile(“test.dat”, “rw”).getChannel()
11. .map(FileChannel.MapMode.READ_WRITE, 0, length);
12. for(int i = 0; i < length; i++){
13. out.put((byte)’x’);
14. }
15. System.out.println(“Finished writing”);
16. for(int I = length/2; i < length/2 + 6; i++){
17. System.out.println((char)out.get(i));
18. }
19. }
20. }
MappedByteBuffer是直接字节缓冲区,其内容是文件的内存映射区域,映射的字节缓冲区和它所表示的文件映射关系在该缓冲区本身成为垃圾回收之前一直保持有效。
FileChannel的MappedByteBuffermap(FileChannel.MapMode mode, long position, long size) thorws IOException方法可以将此通道的文件区域直接映射到内存中。
可以通过以下三种模式将文件区域映射到内存中:
(1).只读:视图修改得到的缓冲区将导致抛出ReadOnlyBufferException。
(2).读/写:对得到的缓冲区的更改将最终传播到文件,该更改对映射到同一文件的其他程序不一定是可见的。
(3).专用:对的到的缓冲区更改将不会被传播到文件,并且该更改对映射到同一文件的其他程序也是不可见的,相反,会创建缓冲区已修改部分的专用副本。
创建映射时指定文件区域映射的起始位置和大小,因此,只有指定的部分才会被影响到内存的字节缓冲区中,而不再指定范围内的文件区域不会被映射到内存中,因此,对于大型文件来说,可以大大提高程序性能。
注意:映射关系一经创建,就不再依赖于创建它时所用的文件通道,特别是关闭该通道对映射关系的有效性没有任何影响。另外,从性能观点来讲,通常相对较大的文件映射到内存中才是值得的。
2.文件锁定:
在同一个JVM中,共享资源的文件可以通过线程同步来确保访问的安全性,但是在不同的JVM或者Java线程和操作系统本地线程共同竞争一个共享的文件资源时,就必须通过对文件的锁定机制来确保,例子如下:
[java] view plaincopy
1. import java.nio.channels.*;
2. import java.util.concurrent.*;
3. import java.io.*;
4.
5. public class FileLocking{
6. public static void main(String[] args)throws Exception{
7. FileOutputStream fos = new FileOutputStream(“file.txt”);
8. //试图对文件通道的文件锁定
9. FileLock fl = fos.getChannel().tryLock();
10. //文件锁定成功
11. if(fl != null){
12. System.out.println(“Locked File”);
13. TimeUnit.MILLISECONDS.sleep(100);
14. //释放文件锁
15. fl.release();
16. System.out.println(“Released Lock”);
17. }
18. foc.close();
19. }
20. }
输出结果:
Locked File
Released Lock
文件通道的tryLock()方法试图获取对此通道的文件给定区域的锁定,是个非阻塞方法,无论是否已成功获得请求区域的锁定,调用总是立即返回。如果由于另一个程序保持这一个重叠锁而无法锁定,则此方法返回null.
文件锁定方法:
(1).FileLock tryLock():
试图获取对此通道文件的独占锁定。
(2).FileLock tryLock(long position, long size, Boolean shared):
视图获取对此通道文件给定区域的锁定。
(3).FileLock lock():
获取此通道的文件的独占锁定。
(4).FileLock lock(long position, long size, Boolean shared):
获取此通道的文件的给定区域锁定。
文件锁定方法是共享锁还是排斥锁取决于底层操作系统,可以使用FileLock.isShared()方法判断使用的是何种类型的文件锁。
3.压缩/解压缩:
Java I/O类库中提供了一些关于压缩和加压的类,由于压缩和解压缩算法是针对字节数据进行操作的,因此javaI/O中关于压缩和加压素的类是继承自InputStream和OutputStream字节流体系。
Java压缩和解压的相关类在java.util.zip包下,具体的类如下:
(1).CheckedInputStream:
需要维护所读取数据校验和的输入流,校验和可用于验证输入数据的完整性。
(2).CheckedOutputStream:
需要维护所写入数据校验和的输出流。
(3).Deflater:
使用流行的”ZLIB”压缩程序库为通用压缩提供支持。
(4).Inflater:
使用流行的”ZLIB”压缩程序库为通用解压缩提供支持。
(5).DeflaterInputStream:
为压缩“deflate“格式压缩数据实现输入流过滤器。
(6).DeflaterOutputStream:
为压缩 “deflate“格式压缩数据实现输出流过滤器,它还用作其他类型的压缩过滤器(如GZIPOutputStream)的基础。
(7).InflaterInputStream:
为解压缩”deflate”压缩格式的数据实现输入流过滤器,它还用作其他解压缩过滤器(如GZIPInputStream)的基础。
(8).InfaterOutputStream:
为解压缩“deflate”压缩格式存储数据实现输出流过滤器。
(9).ZipOutputStream:
为以”zip”文件格式写入文件实现输出流过滤器,包括对已压缩和未压缩条目的支持。
(10).ZipInputStream:
为读取以”zip”文件格式的文件实现输入流过滤器,包括对已压缩和未压缩条目的支持。
(11).GZIPOutputStram:
为使用“GZIP“文件格式写入压缩数据实现输出流过滤器。
(12).GZIPInputStram:
为读取“GZIP“文件格式的压缩数据实现输入流过滤器。
Java I/O压缩和解压缩操作小例子如下:
[java] view plaincopy
1. import java.util.zip.*;
2. import java.io.*;
3.
4. public class GZIPCompress{
5. public static void main(String[] args)throws IOException{
6. BufferedReader in = new BufferedReader(new FileReader(“test.dat”));
7. BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(
8. new FileOutputStream(“test.gz”)));
9. int c;
10. //写GZIP格式压缩文件
11. while((c = in.read()) != -1){
12. out.write(c);
13. }
14. in.close();
15. out.close();
16. BufferedReader in2 = new BufferedReader(new InputStreamReader(
17. new GZIPInputStream(new FileInputStream(“test.gz”))));
18. String s;
19. //读取GZIP格式压缩文件
20. while((s = in2.readLine()) != null){
21. System.out.println(s);
22. }
23. in2.close();
24. }
25. }
使用java中压缩相关类时,只需对输入流使用GZIPInputStream或ZipInputStream包装,对输出流使用GZIPOutputStream或ZipOutputStream包装即可,其他的操作和普通的java I.O操作相同。
4.使用zip多文件压缩:
zip格式的压缩文件是最常用的压缩方式,使用zip多文件压缩时,可以将多个文件压缩在一个压缩包中,同时还可以从一个包含多个文件的压缩包中读取所有的压缩文件。使用zip进行多文件压缩时,一般要使用CheckSum类计算校验和,校验和的计算有两种算法:
(1).Adler32:速度比较快。
(2).CRC32:速度比较慢,但是更精确。
使用zip多文件压缩的例子如下:
[java] view plaincopy
1. import java.util.zip.*;
2. import java.io.*;
3. import java.util.*;
4.
5. public class ZipCompress{
6. public static void main(String[] args)throws Exception{
7. FileOutputStream f = new FileOutputStream(“test.zip”);
8. //使用Adler32算法为文件输入流产生输出校验和文件
9. CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
10. ZipOutputStream zos = new ZipOutputStream(csum);
11. BufferedOutputStream out = new BufferedOutputStream(zos);
12. //设置zip文件注释
13. zos.setComment(“A test of java zipping”);
14. //向zip压缩文件写入多个文件
15. for(String arg : args){
16. System.out.println(“Writing file:” + arg);
17. BufferedReader in = new BufferedReader(new FileReader(arg));
18. //写入一个zip文件条目,并将流定位到条目数据的开始处
19. zos.putNextEntry(new ZipEntry(arg));
20. int c;
21. //写入zip文件内容
22. while((c = in.read()) != -1){
23. out.write©;
24. }
25. in.close();
26. out.flush();
27. }
28. out.close();
29. //文件关闭后获取校验和
30. System.out.println(“Checksum:” + csum.getChecksum().getValue());
31. FileInputStream fi = new FileInputStream(“test.zip”);
32. //使用Adler32算法为输入文件流产生输入校验和文件流
33. CheckedInputStream csumi = new CheckedInputStream(fi, new Adler32());
34. ZipInputStream in2 = new ZipInputStream(csumi);
35. BufferedInputStream bis = new BufferedInputStream(in2);
36. ZipEntry ze;
37. //读取zip文件条目
38. While((ze = in2.getNextEntry()) != null){
39. System.out.println(“Reading file:” + ze);
40. int x;
41. //读取zip文件条目内容
42. while((x = bis.read()) != -1){
43. System.out.println(x);
44. }
45. }
46. if(args.length == 1){
47. System.out.println(“Checksum:” + csumi.getChecksum().getValue());
48. }
49. bis.close();
50. //另一种读取zip文件的方法
51. ZipFile zf = new ZipFile(“test.zip”);
52. //获取zip文件的条目
53. Enumeration e = zf.entries();
54. while(e.hasMoreElements()){
55. ZipEntry ze2 = (ZipEntry)e.nextElement();
56. System.out.println(“Reading File:” + ze2);
57. }
58. }
59. }
15——对象序列化
Java的对象序列化是一个轻量级的持久化过程,序列化时,可以将java对象以字节序列形式写入硬盘、数据库或者通过网络传输到另一个JVM等等,反序列化是读取序列化的文件,将其在JVM中还原为java对象的过程。
1.简单的序列化和反序列化:
最简单的序列化是对象实现Serializable序列化接口,这个接口是一个标记接口,不需要实现任何方法。
下面的小例子简单展示java对象序列化和反序列化的过程:
[java] view plaincopy
1. import java.io.*;
2.
3. //简单的测试对象
4. public class TestObject implements Serializable{}
5.
6. import java.io.*;
7.
8. //序列化
9. public class SerializeObject{
10. public static void main(String[] args)throws Exception{
11. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“x.file”));
12. TestObject obj = new TestObject();
13. //将TestObject对象序列化到x.file文件中
14. out.write(obj);
15. }
16. }
17.
18. import java.io.*;
19.
20. //反序列化
21. public class DeserializeObject{
22. public static void main(String[] args)throw Exception{
23. ObjectInputStream in = new ObjectInputStream(new FileInputStream(“x.file”));
24. Object myObj = in.readObject();
25. System.out.println(myObj.getClass());
26. }
27. }
输出结果:
class TestObject
在序列化时,使用ObjectOutputStream将对象序列化写出,反序列化时,使用ObjectInputStream将对象反序列化读入。
注意:反序列化对象使用时,对象的class文件必须在classpath中,否则,JVM找不到对象的class文件会抛出ClassNotFoundException。
2.序列化控制:
默认的Serializable序列化是将对象整体序列化,但是对于一些特殊的需求例如:序列化部分对象或者反序列化部分对象的情况,就必须使用 Externalizable接口来代替Serializable接口,Externalizable的writeExternal()和 readExternal()方法可以实现对序列化的控制,这两个方法在对象序列化和反序列化时自动调用,例子如下:
[java] view plaincopy
1. import java.io.*;
2.
3. class Blip1 implements Externalizable{
4. public Blip1(){
5. System.out.println(“Blip1 Constructor”);
6. }
7. public void writeExternal(ObjectOutput out)throws IOException{
8. System.out.println(“Blip1.writeExternal”);
9. }
10. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
11. System.out.println(Blip1.readExternal);
12. }
13. }
14.
15. class Blip2 implements Externalizable{
16. Blip1(){
17. System.out.println(“Blip2 Constructor”);
18. }
19. public void writeExternal(ObjectOutput out)throws IOException{
20. System.out.println(“Blip2.writeExternal”);
21. }
22. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
23. System.out.println(Blip2.readExternal);
24. }
25. }
26.
27. public class Blips{
28. public static void main(String[] args)throws IOException, ClassNotFoundException{
29. System.out.println(“Constructing objects:”);
30. Blip1 b1 = new Blip1();
31. Blip2 b2 = new Blip2();
32. //序列化
33. ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“Blips.out”));
34. System.out.println(“Saving objects:”);
35. o.writeObject(b1);
36. o.writeObject(b2);
37. //反序列化
38. ObjectInputStream in = new ObjectInputStream(new FileInputStream(“Blips.out”));
39. System.out.println(“Recovering b1:”);
40. b1 = (Blip1)in.readObject();
41. //由于Blip2的默认无参数构造方法不是public的,所以会抛异常
42. //System.out.println(“Recovering b2:”);
43. //b2 = (Blip2)in.readObject();
44. }
45. }
输出结果:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal
注意:由于对象在反序列化时需要调用默认的public的无参数构造方法创建对象,所以Blip2非public类型无参数构造方法无法反序列化。 所以使用Externalizable序列化和反序列化时,对象必须要有public类型的无参数构造方法,这一点是Externalizable和 Serializable的区别。
Serializable和Externlizable反序列化的区别:
(1).Serializable的反序列化是通过序列化的字节数组序列进行反序列化的。对象的序列化的反序列化都是自动进行的。
(2).Externalizable的反序列化是通过调用默认的构造方法进行的。对象的序列化需要使用writeExternal()显式控制,对象的反序列化需要使用readExternal()显式控制,不是自动进行的。
3. Externlizable序列化高级:
使用Externlizable序列化和反序列化对象时,readExternal()和writeExternal()方法可以对象中指定字段进行初始化,例子如下:
[java] view plaincopy
1. import java.io.*;
2.
3. public class Test implements Externalizable{
4. private int i;
5. private String s;
6. public Test(){
7. System.out.println(“Test Constructor”);
8. }
9. public Test(String x, int a){
10. System.out.println(“Test(String x, int a)”);
11. i = a;
12. s = x;
13. }
14. public void toString(){
15. return s + i;
16. }
17. public void writeExternal(ObjectOutput out)throws IOException{
18. System.out.println(“Test.writeExternal”);
19. out.writeObject(s);
20. out.writeInt(i);
21. }
22. public void readExternal(ObjectInput in)throws IOException, ClassNotFoundException{
23. System.out.println(“Test.readExternal”);
24. s = (String)in.readObject();
25. i = in.readInt();
26. }
27. public static void main()throws IOException, ClassNotFoundException{
28. System.out.println(“Constructing objects:”);
29. Test t1 = new Test(“A String”, 47);
30. Test t2 = new Test();
31. System.out.println(t1);
32. System.out.println(t2);
33. //序列化
34. ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“Test.out”));
35. System.out.println(“Saving objects:”);
36. o.writeObject(t1);\
37. o.writeObject(t2);
38. o.close();
39. //反序列化
40. ObjectInputStream in = new ObjectInputStream(new FileInputStream(“Test.out”));
41. System.out.println(“Recovering objects:”);
42. t1 = (Test)in.readObject();
43. System.out.println(t1);
44. t2 = (Test)in.readObject();
45. System.out.println(t2);
46. }
47. }
输出结果:
Constructing objects:
Test(String x, int a)
A String 47
Test Constructor
null0
Saving objects:
Test.writeExternal
Test.writeExternal
Recovering objects:
Test Constructor
Test.readExternal
A String 47
Test Constructor
Test.readExternal
null0
该例子中,当创建对象使用非无参数构造方法时,对象字段被初始化,因此序列化时可以将初始化的值保存。当创建对象使用无参数的构造方法时,对象的字段没有被赋值而使用默认的初始化值。反序列化时只会调用默认的无参数构造方法。
因此,为了使序列化和反序列化更高效使用,在序列化时,调用writeExternal()只写有用的重要数据,而在反序列化时,readObject()只读取有用的重要数据。
4.transient关键字:
当对象实现Serializable接口进行自动序列化时,类中某些字段不想被序列化,需要使用transient关键字,虽然 Externalizable通过writeExternal()方法也可以实现此功能,但是序列化不是自动进行的,使用Serializable和 transient关键字更加方便。
例子如下:
[java] view plaincopy
1. import java.io.*;
2.
3. public class User implements Serializable{
4. private String username;
5. private transient String password;
6. public User(String name, String pwd){
7. username = name;
8. password = pwd;
9. }
10. public String toString(){
11. return “username:” + username + “, password:” + password;
12. }
13. public static void main(String[] args)throws Exception{
14. User user = new User(“Test”, “passwd”);
15. System.out.println(“User=” + user);
16. ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“User.out”));
17. o.writeObject(user);
18. o.close();
19. ObjectInputStream in = new ObjectInputStream(new FileInputStream(“User.out”));
20. System.out.println(“Recovering object:”);
21. user = (User)in.readObject();
22. System.out.println(“User=” + user);
23. }
24. }
输出结果:
User=username:Test,password:passwd
Recovering object:
User=username:Test,password:null
注意:由于Externalizable默认序列化不存储任何字段,所以transient关键字只在Serializable中使用。
16——枚举
1.在枚举中添加方法:
Java中的枚举是一种特殊的类,不但可以添加字段,构造方法,普通方法,甚至可以添加main()方法,例子如下:
[java] view plaincopy
1. public enum Test{
2. //调用枚举构造方法创建枚举对象
3. WEST(“Go west”), NORTH(“Go north”),
4. EAST(“Go east”), SOUTH(“Go south”);
5. private String description;
6. //私有构造方法
7. private Test(String description){
8. this.description = description;
9. }
10. public String getDescription(){
11. return description;
12. }
13. public static void main(String[] args){
14. for(Test witch : Test.values()){
15. System.out.println(witch + “: ” + witch.getDescription());
16. }
17. }
18. }
输出结果:
WEST:Go west
NORTH:Go north
EAST:Go east
SOUTH:Go south
注意:若要在枚举中定义字段和方法,必须在枚举对象的分号之后,java强制规定枚举对象必须放在枚举的最开始处。
另外,枚举的构造方法必须是private的,因为只允许在枚举内部定义枚举对象时使用,不允许在枚举类之后使用枚举的构造方法。
2.switch分支语句中使用枚举:
Java的switch分支语句中,switch的条件只能是整数数值类型,在JDK5以后枚举之后,由于枚举对象的顺序也是整数数值,因此switch也支持枚举,例子如下:
[java] view plaincopy
1. enum Signal{GREEN, YELLOW, RED}
2. public class TrafficLight{
3. Signal color = Signal.RED;
4. public void change(){
5. switch(color){
6. case RED:
7. color = Signal.GREEN;
8. break;
9. case GREEN:
10. color = Signal.YELLOW;
11. break;
12. case YELLOW:
13. color = Signal.RED;
14. break;
15. }
16. }
17. public String toString(){
18. return “The traffic light is ” + color;
19. }
20. public static void main(String[] args){
21. TrafficLight t = new TrafficLight();
22. for(int i=0; i<5; i++){
23. System.out.println(t);
24. t.change();
25. }
26. }
27. }
输出结果:
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
The traffic light is GREEN
3.枚举Set:
EnumSet枚举Set是JDK5中引入的与枚举类型一起使用的专用Set实现,EnumSet中所有键都必须来自单个枚举类型,枚举Set在内 部表示为位向量。位向量表示形式的EnumSet非常紧凑高效,空间性和时间性非常好,同时由于枚举对象含义清楚,因此EnumSet可以替换传统技艺 int的位标志(在程序开发中,很多时候需要使用一串数字序列的位标志,每位代表不同的含义,例如网络协议等)。例子如下:
[java] view plaincopy
1. //星期枚举
2. public enum Week{
3. MON, TUE, WEN, THU, FRI, SAT, SUN
4. }
5.
6. import java.util.*;
7. //EnumSet例子
8. public class EnumSets{
9. public static void main(String[] args){
10. //EnumSet的noneOf方法创建指定枚举类型的空EnumSet
11. EnumSet
weeks = EnumSet.noneOf(Week.class); 12. weeks.add(MON);
13. System.out.println(weeks);
14. //EnumSet的of方法创建包含指定枚举类型元素的EnumSet
15. weeks.addAll(EnumSet.of(TUE, WEN, THU));
16. System.out.println(weeks);
17. // EnumSet的allOf方法创建包含指定枚举类型所有元素的EnumSet
18. weeks = EnumSet.allOf(Week.class);
19. System.out.println(weeks);
20. weeks.removeAll(EnumSet.of(FRI, SAT, SUN));
21. System.out.println(weeks);
22. // EnumSet的rang方法创建包含指定枚举类型两个元素之间的EnumSet
23. weeks.removeAll(EnumSet.rang(MON, WEN));
24. System.out.println(weeks);
25. // EnumSet的complementOf方法创建指定EnumSet所不包含元素的EnumSet
26. weeks.removeAll(EnumSet.complementOf(weeks));
27. System.out.println(weeks);
28. }
29. }
输出结果:
[MON]
[MON, TUE, WEN, THU]
[MON, TUE, WEN, THU, FRI, SAT, SUN]
[MON, TUE, WEN, THU]
[THU]
[MON, TUE, WEN, FRI, SAT, SUN]
注意:EnumSet的元素排列是依据Enum的定义顺序排列的,所有的基本操作都在固定时间内执行,速度比HashSet可能更快。
4.枚举Map:
EnumMap是与枚举类型一起使用的专用Map实现,枚举Map中所有的键都必须来自单个枚举类型,枚举Map在内部表示为数组。和 EnumSet类似,EnumMap键的顺序也是和Enum的定义顺序一致,EnumMap的所有操作也都在固定时间内执行,它的速度可能比 HashMap更快。
下面的例子使用EnumMap演示command interface的设计模式:
[java] view plaincopy
1. public enum Directions{
2. EAST, NORTH, WEST, SOUTH
3. }
4. interface Command{
5. public void action();
6. }
7. public class EnumMaps{
8. Public static void main(String[] args){
9. EnumMap
em = 10. new EnumMap
(Directions.class); 11. em.put(EAST, new Command(){
12. public void action(){
13. System.out.println(“Go east”);
14. }
15. });
16. em.put(NORTH, new Command(){
17. public void action(){
18. System.out.println(“Go north”);
19. }
20. });
21. em.put(WEST, new Command(){
22. public void action(){
23. System.out.println(“Go west”);
24. }
25. });
26. for(Map.Entry
e : em.entrySet()){ 27. System.out.println(e.getKey() + “: ” + e.getValue().action());
28. }
29. try{
30. em.get(SOUTH).action();
31. }catch(Exception e){
32. System.out.println(e);
33. }
34. }
35. }
输出结果:
EAST: Go east
NORTH: Go north
WEST: Go west
java.lang.NullPointerException
从最后的空指针我们可以看出,EnumMap在创建时将Enum所有元素全部放进了key中,但是如果没有使用put显式将key和value关联的话,对应的value是null。
5.枚举特定方法:
在枚举中可以定义一个抽象的公共方法,然后各个枚举实例提供具体实现,实现一种类似匿名内部类的多态方式,例子如下:
[java] view plaincopy
1. import java.util.*;
2. import java.text.*;
3.
4. public enum ConstantSpecificMethod{
5. DATE_TIME{
6. String getInfo(){
7. return DateFormat.getDateInstance().format(new Date());
8. }
9. },
10. CLASS_PATH{
11. String getInfo(){
12. return System.getenv(“CLASSPATH”);
13. }
14. },
15. VERSION{
16. String getInfo(){
17. return System.getProperty(“java.version”);
18. }
19. };
20. //抽象方法
21. abstract String getInfo();
22. public static void main(String[] args){
23. for(ConstantSpecificMethod csm : values()){
24. System.out.println(csm.getInfo());
25. }
26. }
27. }
通过这种多态方式,同样方便高效实现Commandinterface设计模式。
17——注解Annotation
注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式。使用注解Annotation可以使元数据写在 程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Annotation的类型检查,使得在编译期间就可以排除语法错误。
1JDK内置的3中Annotation:
在JDK5中,内置了3个通用目的的注解Annotation,这三个内置的注解在java.lang包下:
(1)@Override:
这个注解常用在继承类或实现接口的子类方法上,表面该方法是子类覆盖父类的方法,该方法的方法签名要遵循覆盖方法的原则:即访问控制权限必能比父类更严格,不能比父类抛出更多的异常。
(2)@Deprecated:
这个注解告诉编译器该元素是过时的,即在目前的JDK版本中已经有新的元素代替该元素。
(3)@SuppressWarnings:
该注解关闭编译器中不合适的警告,即强行压制编译器的警告提示。
2.注解Annotation的定义:
注解Annotation的定义和接口类似,实际上,编译器也把注解Annotationn像接口一样编译成class字节码文件。例如:
[java] view plaincopy
1. import java.lang.annotation.*;
2.
3. @Target(ElementType.METHOD)
4. @Retention(RetentionPolicy.RUNTIME)
5. public @interface Test{}
像这种没有任何元素的空注解Annotation叫做标记Annotation.
在声明注解的时候往往需要使用@Target,@Retention等注解,这种注解被称为注解的注解(元数据注解),即是专门用于处理注解Annotation本身的。
(1).@Target注解:
用于指示注解所应用的目标程序元素种类,该注解常和ElementType枚举类型一起联合使用,ElementType枚举提供了java程序中声明的元素类型如下:
a. ANNOTATION_TYPE:注释类型声明。
b. CONSTRUCTOR:构造方法声明。
c. FIELD:字段声明(包括枚举常量)。
d. LOCAL_VARIABLE:局部变量声明。
e. METHOD:方法声明。
f. PACKAGE:包声明。
g. PARAMETER:参数声明。
h. TYPE::类,接口或枚举声明。
(2)@Retention注解:
该注解用于指示所定义的注解类型的注释在程序声明周期中得保留范围,该注解常和RetentionPolicy枚举联合使用。RetentionPolicy枚举常量定义了注解在代码中的保留策略:
a. CLASS:编译器把注解记录在类文件中,但在运行时JVM不需要保留注解。
b. RUNTIME:编译器把注解记录在类文件中,在运行时JVM将保留注解,因此可以通过反射机制读取注解。
c. SOURCE:仅保留在源码中,编译器在编译时就要丢弃掉该注解。
3.创建和处理自定义注解Annotation:
正常使用注解时,需要在注解中定义元素,用于接收程序设置的值,正常定义注解的例子如下:
[java] view plaincopy
1. import java.lang.annotation.*;
2.
3. @Target(ElementType.METHOD)
4. @Retention(RetentionPolicy.RUNTIME)
5. public @interface UseCase{
6. public int id();
7. public String description() default “no description”;
8. }
正常定义注解Annotation的方式类似定义接口,id和description是注解UseCase的属性,而不是方法,注解中不能定义方法 只能定义属性。其中description属性有默认的值“no description“,即在使用时如果没有指定description的值,则程序使用其默认值。
上面UseCasee注解用于跟踪方法的测试用例说明,使用上面注解的例子如下:
[java] view plaincopy
1. import java.util.*;
2.
3. public class PasswordUtils{
4. @UseCase(id = 47, description = “Passwords must contain at least one numeric”)
5. public Boolean validatePassword(String password){
6. return (password.mathes(“\\w*\\d\\w*”));
7. }
8. @UseCase(id = 48)
9. public String encryptPassword(Srring password){
10. return new StringBuilder(password).reverse()/toString();
11. }
12. @UseCase(id = 49, description = “New passwords can’t equal previously used ones”)
13. public Boolean checkForNewPassword(List
prevPasswords, String password){ 14. return !prevPasswords.contains(password);
15. }
16. }
JDK5中提供了Annotation相关的API,结合使用java的反射机制可以实现自定义的Annotation注解处理器(JDK中也提供 了使用APT,Annotationprocess tool方式处理注解,在后面会讲解),处理上述Annotation的例子如下:
[java] view plaincopy
1. import java.lang.reflect.*;
2. import java.util.*;
3.
4. public class UseCaseTracker{
5. public static void traceUseCases(List
useCases, Class> clazz){ 6. //获取指定类中所有声明的方法
7. for(Method m : clazz.getDeclaredMethods()){
8. //获取方法上指定类型的注解
9. UseCase uc = m.getAnnotation(UseCase.class);
10. if(uc != null){
11. System.out.println(“Found Use Case:” + uc.id() + “ ” + uc.description());
12. useCases.remove(new Integer(uc.id()));
13. }
14. }
15. for(int i : useCases){
16. System.out.println(“Warning: Missing use case-” + i);
17. }
18. }
19. public static void main(String[] args){
20. List
useCases = new ArrayLis (); 21. Collections.addAll(useCases, 47, 48, 49, 50);
22. trackUseCases(useCases, PasswordUtils.class);
23. }
24. }
输出结果:
Found Use Case:47 Passwords must contain at least onenumeric
Found Use Case:48 no description
Found Use Case:49 New Passwords can’t equal previously usedones
Warning: Missing use case-50
注意:使用反射获取到注解对象之后,类似使用调用方法的方式获取注解的值,如uc.id()等。另外,注解不支持继承,因此声明注解时不能使用extends和implements关键字。
4.Annotation注解元素:
Annotation注解中的元素只能是下面的数据类型:
(1).java的8中基本类型,如int, boolean等等,如果可以自动装箱和拆箱,则可以使用对应的对象包装类型。
(2).String类型。
(3).Class类型。
(4).Enums类型。
(5).Annotation类型。
(6).上面类型的数组。
除了上面这些类型以外,如果在注解中定义其他类型的数据,编译器将会报错。
注意:注解中的元素要么指定默认值,要么由使用的类赋值,如果即没有默认值,使用类也没有赋值的话,注解元素是不会像普通类成员变量一样给定默认值,即必须赋值或者显示指定默认值。默认值例子如下:
[java] view plaincopy
1. import java.lang.annotation.*;
2.
3. @Target(ElementType.METHOD)
4. @Retention(RetentionPolicy.RUNTIME)
5. public @interface DefaultValue{
6. public int id() default -1;
7. public String description() default “”;
8. }
5.一个使用Annotation实现ORM的例子:
从EJB3之后,EJB的实体Bean持久化技术被单独抽取出来形成了JPA技术,JPA和Hibernate3之后都支持使用 Annotation注解方式进行对象和关系型数据库映射(ObjectRelationship Mapping, ORM),下面使用Annotation注解方式实现一个简单的ORM功能:
(1).相关注解:
[java] view plaincopy
1. import java.lang.annotation.*;
2.
3. @Target(ElementType.TYPE)//该注解只能应用在类上
4. @Retention(RetentionPolicy.RUNTIME)
5. public @interface DBTable{//指定数据库名称
6. public String name() default “”;
7. }
8.
9. @Target(ElementType.FIELD)
10. @Retention(RetentionPolicy.RUNTIME)
11. public @interface Constraints{//数据库约束
12. boolean primaryKey() default false;
13. boolean allowNull() default true;
14. boolean unique() default false;
15. }
16.
17. @Target(ElementType.FIELD)
18. @Retention(RetentionPolicy.RUNTIME)
19. public @interface SQLString{//String类型数据
20. int value() default 0;
21. String name() default “”;
22. Constraints constraints() default @Constraints;//注解的属性元素也是注解
23. }
24.
25. @Target(ElementType.FIELD)
26. @Retention(RetentionPolicy.RUNTIME)
27. public @interface SQLInteger{//int类型数据
28. String name() default “”;
29. Constraints constraints() default @Constraints;
30. }
(2).使用Annotation注解的实体类:
[java] view plaincopy
1. @DBTable(name=”MEMBER”)
2. public class Member{
3. @SQLString(30)//当只指定一个属性的值,并且该属性名为value时,不用写属性名称
4. String firstName;
5. @SQLString(50)
6. String lastName;
7. @SQLInteger
8. Integer age;
9. @String(value=30, constraints=@Constraints(primaryKey = true))
10. String handle;
11. static int memberCount;
12. }
注意:当为注解多于1个以上的属性指定值时,即使有value属性也要写value的属性名称。
(3).实现自定义注解处理器:
[java] view plaincopy
1. import java.lang.annotation.*;
2. import java.lang.reflect.*;
3. import java.util.*;
4.
5. public class TableCreator{
6. public static void mian(String[] args)throws Exception{
7. Member m = new Member();
8. Class clazz = m.getClass();
9. String tableName = null;
10. DBTable dbTable = class.getAnnotation(DBTable.class);
11. if(db != null){
12. tableName = db.name();
13. }
14. If(tableName == null){//如果没有指定Table名称,使用类名
15. tableName = clazz.getName().toUpperCase;
16. }
17. List
columnDefs = new ArrayList (); 18. //构造SQL创建列语句
19. for(Field f : clazz.getDeclaredFields()){//遍历类中声明的所有字段
20. String columnName = null;
21. //获取当前字段上声明的所有注解
22. Annotationn[] anns = f.getDeclaredAnnotationns();
23. if(anns.length() < 1){//当前字段上没有声明注解
24. continue;
25. }
26. if(anns[0] instanceof SQLInteger){//注解数组第一个定义的是数据类型
27. SQLInteget sInt = (SQLInteger) anns[0];
28. if(sInt.name().length() < 1){//如果没有指定列名称,则使用字段名称
29. columnName = f.getName().toUpperCase();
30. }else{
31. columnName = sInt.name();
32. }
33. columnDefs.add(columnName + “ INT” + getConstraints(sInt.constraints()));
34. }
35. if(anns[0] instanceof SQLString){
36. SQLString sString = (SQLString) anns[0];
37. if(sString.name().length() < 1){//如果没有指定列名称,则使用字段名称
38. columnName = f.getName().toUpperCase();
39. }else{
40. columnName = sInt.name();
41. }
42. columnDefs.add(columnName + “ VARCHAR(” + sString.value() +”)”
43. + getConstraints(sString.constraints()));
44. }
45. }
46. StringBuilder createCommand = new StringBuilder(“CREATE TABLE”
47. + tableName + “(”);
48. for(String columnDef : columnDefs){
49. createCommand.append(“\n ” + columnDef + “,”);
50. }
51. //删除最后的”,”
52. String tableCreate = createCommand.subString(0, createCommand.length() - 1)
53. + “);”;
54. System.out.println(“Table creation SQL for ” + className + “ is :\n”
55. + tableCreate);
56. }
57. //获取约束
58. Private static String getConstraints(Constraints con){
59. String constraints = “”;
60. if(!con.allowNnull()){
61. constraints += “ NOT NULL”;
62. }
63. if(con.primaryKey()){
64. constraints += “ PRIMARY KEY”;
65. }
66. if(con.unique()){
67. constraints += “ UNIQUE”;
68. }
69. return constraints;
70. }
71. }
输出结果:
Table creation SQL for Member is:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30) PRIMARY KEY);
6.使用apt处理Annotation注解:
Annotation processing tool, apt是sun提供的第一个版本的Annotation注解处理器,apt的使用和javac类似,是真的未编译的源码,而非已经编译的class字节码 文件,使用apt的时候不能使用java的反射机制,因为源码尚未编译,需要使用mirrorAPI,mirror API可以使得apt看到未编译源代码中的方法,字段和类信息。使用apt的例子如下:
(1).注解:
[java] view plaincopy
1. package annotations;
2.
3. import java.lang.annotation.*;
4.
5. @Target(ElementType.TYPE)
6. @Retention(RetentionPolicy.SOURCE)
7. public @interface ExtractInterface{
8. public String value();
9. }
这个注解用于从使用该注解的类中抽象公共的方法,并为该类生成一个接口。
(2).使用注解的类:
[java] view plaincopy
1. package annotations;
2.
3. @ExtractInterface(“IMultiplier”)
4. public class Multiplier{
5. public int multiply(int x, int y){
6. int total = 0;
7. for(int i = 0; I < x; i++){
8. total = add(total, y);
9. }
10. return total;
11. }
12. private int add(int x, int y){
13. return x + y;
14. }
15. }
(3).注解处理器:
[java] view plaincopy
1. package annotations;
2.
3. import com.sun.mirror.apt.*;
4. import com.sun.mirror.declaration.*;
5. import java.io.*;
6. import java.util.*;
7.
8. public class InterfaceExtractorProcessor implements AnnotationProcessor{
9. //注解处理器的工作环境
10. private final AnnotationProcessorEnvironment env;
11. private List
interfaceMethods = 12. new ArrayList< MethodDeclaration>();
13. public InterfaceExtractorProcessor(AnnotationProcessEnvironment env){
14. this.env = env;
15. }
16. public void process(){
17. //查询注解处理器环境中的类型声明
18. for(TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations()){
19. //获取注解
20. ExtractInterface annot = typeDecl.getAnnotation(ExtractInterface.class);
21. if(annot == null){
22. break;
23. }
24. //遍历所有添加注解的方法
25. for(MethodDeclaration m : typeDecl.getMethods()){
26. //方法签名中的访问控制符是public的,且不是静态方法
27. if(m.getModifiers().contains(Modifier.PUBLIC) &&
28. !(m.getModifiers().contains(Modifier.STATIC))){
29. interfaceMethods.add(m);
30. }
31. }
32. if(interfaceMethods.size() > 0){
33. try{
34. PrintWriter writer = env.getFiler().createSourceFile(annot.value());
35. writer.println(“package ” + typeDecl.getPackage().getQualifiedName()
36. + “;”);
37. writer.println(“public interface ” + annot.value() + “{“);
38. //写方法声明
39. for(MethodDeclaration m : interfaceMethods){
40. writer.print(“ public’);
41. writer.print(m.getReturnType() + “ ”);
42. writer.print(m.getSimpleName() + “ ”);
43. int i = 0;
44. //写方法参数列表
45. for(ParametherDeclaration parm : m.getParameters()){
46. writer.print(parm.getType() + “ ” + parm.getSimpleName());
47. if( ++i < m.getParameters().size()){
48. writer.print(“, ”);
49. }
50. }
51. writer.println(“);”)
52. }
53. writer.println(“}”);
54. writer.close();
55. }catch(Exception e){
56. Throw new RuntimeException(e);
57. }
58. }
59. }
60. }
61. }
使用sun的mirror API可以获取源码中的Field, type, method等信息。
(4).为apt工厂提供注解处理器:
[java] view plaincopy
1. package annotations;
2.
3. import com.sun.mirror.apt.*;
4. import com.sun.mirror.declaration.*;
5. import java.util.*;
6.
7. public class InterfaceExtractorProcessorFactory implements AnnotationProcessorFactory{
8. //获取注解处理器
9. public AnnotationProcessor getProcessorFor(Set
atds, 10. AnnotationProcessorEnvironment env){
11. return new InterfaceExtractorProcess(env);
12. }
13. //定义注解处理器所支持的注解类型
14. public Collection
supportedAnnotationTypes(){ 15. return Collections.singleton(“ExtractInterface”);
16. }
17. //定义注解处理器支持的选项
18. public Collection
supportedOptions(){ 19. return Collections.emptySet();
20. }
21. }
(5).运行apt:
使用下面的命令允许apt:
apt-factory annotations.InterfaceExtractorProcessorFactory Multiplier.java –s ../annotations
输出结果:
package annotations;
public interface IMultiplier{
public int multiply(intx, int y);
}
18——并发编程(一)
线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础。
多核心CPU可以真正实现多个任务并行执行,单核心CPU4程序其实不是真正的并行运行,而是通过时间片切换来执行,由于时间片切换频繁,使用者感 觉程序是在并行运行。单核心CPU中通过时间片切换执行多线程任务时,虽然需要保存线程上下文,但是由于不会被阻塞的线程所阻塞,因此相比单任务还是大大 提高了程序运行效率。
1.线程的状态和切换:
线程的7种状态及其切换图如下:
2.多线程简单线程例子:
Java中实现多线程常用两种方法是:实现Runnable接口和继承Thread类。
(1).继承Thread类的多线程例子如下:
[java] view plaincopy
1. class PrimeThread extends Thread {
2. long minPrime;
3. PrimeThread(long minPrime) {
4. this.minPrime = minPrime;
5. }
6. //重写Thread类的run方法
7. public void run() {
8. . . .
9. }
10. }
启动继承Thread类线程的方法:
[java] view plaincopy
1. PrimeThread p = new PrimeThread(143);
2. p.start();
(2).实现Runnable接口的多线程例子如下:
[java] view plaincopy
1. class PrimeRun implements Runnable {
2. long minPrime;
3. PrimeRun(long minPrime) {
4. this.minPrime = minPrime;
5. }
6. public void run() {
7. . . .
8. }
9. }
启动实现Runnable接口线程的方法:
[java] view plaincopy
1. PrimeThread p = new Thread(new PrimeThread(143));
2. p.start();
由于java的单继承特性和面向接口编程的原则,建议使用实现Runnable接口的方式实现java的多线程。
3.使用Executors线程池:
在JDK5中,在java.util.concurrent包中引入了Executors线程池,使得创建多线程更加方便高效,例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2.
3. public class CachedThreadPool{
4. public static void main(String[] args){
5. //创建一个缓冲线程池服务
6. ExecutorService exec = Executors.newCachedThreadPool();
7. for(int i=0; i<5; i++){
8. //线程池服务启动线程
9. exec.execute(
10. new Runnable(){
11. //使用匿名内部类实现的java线程
12. public void run(){
13. System.out.println(“Thread ” + i + “ is running”);
14. }
15. }
16. );
17. //线程池服务停止
18. exec.shoutdown();
19. }
20. }
21. }
Executors.newCachedThreadPool()方法创建缓冲线程池,即在程序运行时创建尽可能多需要的线程,之后停止创建新的线 程,转而通过循环利用已经创建的线程。Executors.newFixedThreadPool(intsize)方法创建固定数目的线程池,即程序会 创建指定数量的线程。缓冲线程池效率和性能高,推荐优先考虑使用。
Executors.newSingleThreadPool()创建单线程池,即固定数目为1的线程池,一般用于长时间存活的单任务,例如网络socket连接等,如果有多一个任务需要执行,则会放进队列中顺序执行。
4.获取线程的返回值:
实现Runnable接口的线程没有返回值,如果想获取线程的返回值,需要实现Callable接口,Callable是JDK5中引入的现实线程的接口,其call()方法代替Runnable接口的run方法,可以获取线程的返回值,例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2. import java.util.*;
3.
4. class TaskWithResult implements Callable
{ 5. private int id;
6. public TaskWithResult(int id){
7. this.id = id;
8. }
9. public String call(){
10. return “result of TaskWithResult ” + id;
11. }
12. public static void main(String[] args){
13. ExecutorService exec = Executors.newCachedThreadPool();
14. List
> results = new ArrayList >(); 15. for(int i=0; i<5; i++){
16. //将线程返回值添加到List中
17. results.add(exec.submit(new TaskWithResult(i)));
18. }
19. //遍历获取线程返回值
20. for(Future
fs : results){ 21. try{
22. System.out.println(fs.get());
23. }catch(Exception e){
24. System.out.println(e);
25. }finally{
26. exec.shutdown();
27. }
28. }
29. }
30. }
输出结果(可能的结果,由于多线程执行顺序不确定,结果不固定):
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
注解:使用线程池服务的submit()方法执行线程池时,会产生Future
对象,其参数类型是线程Callable的call()方法返回值的类型,使用Future对象的get()方法可以获取线程返回值。 5.线程休眠:
在jdk5之前,使用Thread.sleep(1000)方法可以使线程休眠1秒钟,在jdk之后,使用下面的方法使线程休眠:
TimeUnit.SECONDS.sleep(1);
线程休眠的方法是TimeUnit枚举类型中的方法。
注意:不论是Thread.sleep还是TimeUnit的sleep线程休眠方法,都要捕获InterruptedExecutors。
6.线程优先级:
线程的优先级是指线程被线程调度器调度执行的优先级顺序,优先级越高表示获取CPU允许时间的概率越大,但是并不是绝对的,因为线程调度器调度线程 是不可控制的,只是一个可能性的问题。可以通过Thread线程对象的getPriority()方法获取线程的优先级,可以通过线程对象的 setPriority()方法设置线程的优先级。
Java的线程优先级总共有10级,最低优先级为1,最高为10,Windows的线程优先级总共有7级并且不固定,而Sun的Soloaris操 作系统有23级,因此java的线程优先级无法很好地和操作系统线程优先级映射,所有一般只使用 MAX_PRIORITY(10),NORM_PRIORITY(5)和MIN_PRIORITY(1)这三个线程优先级。
7.守护线程:
守护线程(DaemonThread)是某些提供通用服务的在后台运行的程序,是优先级最低的线程。当所有的非守护线程执行结束后,程序会结束所有的守护线程而终止运行。如果当前还有非守护线程的线程在运行,则程序不会终止,而是等待其执行完成。守护进程的例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2.
3. public class SimpleDaemons implements Runnable{
4. public void run{
5. try{
6. System.out.println(“Start daemons”);
7. TimeUtil.SECONDS.sleep(1);
8. }catch(Exception e){
9. System.out.println(“sleep() interrupted”);
10. }finally{
11. System.out.println(“Finally is running”);
12.
13. }
14. }
15. public static void main(String[] args) throws Exception{
16. Thread daemon = new Thread(new SimpleDaemons());
17. daemon.setDaemon(true);
18. daemon.start();
19. }
20. }
输出结果:
Start daemons
Finally没有执行,如果注释掉daemon.setDaemon(true)设置守护进程这一句代码。
输出结果:
Start daemons
Finally is running
之所以产生这样的结果原因是,main()是这个程序中唯一的非守护线程,当没有非守护线程在运行时,JVM强制推出终止守护线程的运行。
通过Thread对象的setDaemon方法可以设置线程是否为守护线程,通过isDaemon方法可以判断线程对象是否为守护线程。
由守护线程创建的线程对象不论有没有通过setDaemon方法显式设置,都是守护线程。
8.synchronized线程同步:
编程中的共享资源问题会引起多线程的竞争,为了确保同一时刻只有一个线程独占共享资源,需要使用线程同步机制,即使用前对共享资源加锁,使用完毕之后释放锁。
Java中通过synchronized关键字实现多线程的同步,线程同步可以分为以下几种:
(1).对象方法同步:
[java] view plaincopy
1. public synchronized void methodA(){
2. System.out.println(this);
3. }
每个对象有一个线程同步锁与之关联,同一个对象的不同线程在同一时刻只能有一个线程调用methodA方法。
(2).类所有对象方法同步:
[java] view plaincopy
1. public synchronized static void methodB(){
2. System.out.println(this);
3. }
静态方法的线程同步锁对类的所有对象都起作用,即所有对象的线程在同一时刻只能有一个类的一个线程调用该方法。
(3).对象同步代码块:
[java] view plaincopy
1. public void methodC(){
2. synchronized(this){
3. System.out.println(this);
4. }
5. }
使用当前对象作为线程同步锁,同一个对象的不同线程在同一时刻只能有一个线程调用methodC方法中的代码块。
(4).类同步代码块:
[java] view plaincopy
1. public void methodD(){
2. synchronized(className.class){
3. System.out.println(this);
4. }
5. }
使用类字节码对象作为线程同步锁,类所有对象的所有线程在同一时刻只能有一个类的一个线程调用methodD的同步代码块。
注意:线程的同步是针对对象的,不论是同步方法还是同步代码块,都锁定的是对象,而非方法或代码块本身。每个对象只能有一个线程同步锁与之关联。
如果一个对象有多个线程同步方法,只要一个线程访问了其中的一个线程同步方法,其它线程就不能同时访问这个对象中任何一个线程同步方法。
9.线程锁:
JDK5之后,在java.util.concurrent.locks包中引入了线程锁机制,编程中可以显式锁定确保线程间同步,例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2. import java.util.concurrent.locks.*;
3.
4. public class Locking{
5. //创建锁
6. private ReentrantLock lock = new ReentrantLock();
7. public void untimed(){
8. boolean captured = lock.tryLock();
9. try{
10. System.out.println(“tryLock(): ” + captured);
11. }finally{
12. if(captured){
13. lock.unlock();
14. }
15. }
16. }
17. public void timed(){
18. boolean captured = false;
19. try{
20. //对象锁定两秒钟
21. captured = lock.tryLock(2, TimeUnit.SECONDS);
22. }catch(InterruptedException e){
23. Throw new RuntimeException(e);
24. }try{
25. System.out.println(“tryLock(2, TimeUnit.SECONDS): ” + captured);
26. }finally{
27. if(captured){
28. lock.unlock();
29. }
30. }
31. }
32. public static void main(String[] args){
33. //主线程
34. final Locking al = new Locking();
35. al.untimed();
36. al.timed();
37. //创建新线程
38. new Thread(){
39. {//动态代码块,对象创建时执行
40. setDaemon(true);//当前线程设置为守护线程
41. }
42. public void run(){
43. //获取al对象的线程锁并锁定al对象
44. al.lock.lock();
45. System.out.println(“acquired”);
46. }
47. }.start();
48. //主线程让出CPU
49. Thread.yield();
50. al.untimed();
51. al.timed();
52. }
53. }
输出结果:
tryLock(): true
tryLock(2, TimeUnit.SECONDS): true
acquired
tryLock(): false
tryLock(2, TimeUnit.SECONDS): false
由于创建的守护线程锁定对象之后没有释放锁,所有主线程再也无法获取对象锁。
ReentrantLock可以通过lock()锁定对象,也可通过tryLock()方法来锁定对象,对于显式使用线程锁的方法体或代码块必须放在try-catch-finally块中,必须在finally中释放对象锁。
ReentrantLock和synchronized的功能是类似的,区别在于:
(1). Synchronized代码简单,只需要一行代码即可,不用try-catch-finally捕获异常,同时不用显式释放对象锁。
(2).ReentrantLock可以控制锁的锁定和释放状态,也可以指定锁定时间等。
10.volatile传播性:
volatile关键字确保变量的垮程序可见性,使用volatile声明的字段,只要发生了写改动,所有读取该字段的值都会跟着改变,即使使用了本地缓存仍然会被改变。
volatile字段会立即写入主内存中,所有的读取值都是从主内存中读取。
volatile关键字告诉编译器移除线程中的读写缓存,直接从内存中读写。volatile适用的情况:
字段被多个任务同时访问,至少有一个任务是写操作。
volatile字段的读写操作实现了线程同步。
19——并发编程(二)
1.java中的原子操作类:
原子操作是指程序编译后对应于一条CPU操作指令,即原子操作是最小的不可再分指令集,编程中的原子操作是线程安全的,不需要使用进行线程同步和加锁机制来确保原子操作的线程同步。
JDK5中引入了java.util.concurrent.atomic原子操作类包,提供了常用的原子类型操作数据类型,如:AtomicInteger,AtomicLong, AtomicReference等等,原子操作类例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2. import java.util.concurrent.atomic.*;
3. import java.util.*;
4.
5. public class AtomicIntegerTest implement Runnable{
6. //创建一个值为0的Integer类型原子类
7. private AtomicInteger i = new AtomicInteger(0);
8. public int getValue(){
9. //获取Integer类型原子类的值
10. return i.get();
11. }
12. private void evenIncrement(){
13. //值增加2
14. i.addAndGet(2);
15. }
16. public void run(){
17. while(true){
18. evenIncrement();
19. }
20. }
21. public static void main(String[] args){
22. //创建一个定时任务,5秒钟终止运行
23. new Timer().schedule(new TimerTask(){
24. public void run(){
25. System.err.println(“Aborting”);
26. System.exit(0);
27. }
28. }, 5000);
29. ExecutorService exec = Executors.newCachedThreadPool();
30. AtomicIntegerTest at = new AtomicIntegerTest();
31. Exec.execute(at);
32. while(true){
33. int val = at.getValue();
34. //奇数
35. if(val % 2 != 0){
36. System.out.println(val);
37. System.exit(0);
38. }
39. }
40. }
41. }
Java中加减运算等不是原子操作,为了确保线程安全必须确保线程同步安全,使用整数类型的原子类之后,整数原子类的加减运算是原子操作,因此evenIncrement()方法不再需要synchronized关键字或者线程锁机制来确保线程安全。
注意:原子类不是Integer等通用类的通用替换方法,原子类不顶用诸如hashCode和compareTo之类的方法,因为源自变量是可变的,所以不是hash表键的好的选择。
2.线程局部变量:
防止多个线程对同一个共享的资源操作碰撞的另一种方法是使用线程局部变量,线程局部变量的机制是为共享的变量在每个线程中创建一个存储区存储变量副 本。线程局部变量ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程相关联。例子如下:
[java] view plaincopy
1. import java.util.concurrent.atomic.AtomicInteger;
2.
3. public class UniqueThreadGenerator{
4. private static final AtomicInteger uniqueId = new AtomicInteger(0);
5. //创建一个线程局部变量
6. private static final ThreadLocal
uniqueNum = new ThreadLocal (){ 7. //覆盖线程局部变量的initialValue方法,为线程局部变量初始化赋值
8. protected Integer initialValue(){
9. return uniqueId.getAndIncrement();
10. }
11. }
12. public static int getCurrentThreadId(){
13. //获取线程局部变量的当前线程副本中的值
14. return uniqueId.get();
15. }
16. }
线程局部变量java.lang.ThreadLocal类有下面四个方法:
(1).T get():返回此线程局部变量的当前线程副本中的值。
(2).protected T initialValue():返回此线程局部变量的当前线程的初始值。
(3).void remove():移除此线程局部变量当前线程的值。
(4).void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
只要线程是活动的并且线程局部变量实例是可访问的,每个线程都保持对其线程局部变量副本的隐式引用。在线程消失之后,其线程局部变量实例的所有副本都会被垃圾回收。
3.线程的yield,sleep和wait的区别:
(1).yield:
正在运行的线程让出CPU时间片,让线程调度器运行其他优先级相同的线程。使用yield()处于可运行状态的线程有可能立刻又进入执行状态。
yield不释放对象锁,即yield线程对象中其他synchronized的数据不能被别的线程使用。
(2).sleep:
当前正在运行的线程进入阻塞状态,sleep是线程Thread类的方法,在sleep的时间内线程肯定不会再运行,sleep可使优先级低得线程得到执行的机会,也可以让同优先级和高优先级的线程有执行机会。
sleep的线程如果持有对象的线程锁,则sleep期间也不会释放线程锁,即sleep线程对象中其他synchronized的数据不能被别的线程使用。
sleep方法可以在非synchronized线程同步的方法中调用,因为sleep()不释放锁。
(3).wait:
正在运行的线程进入等待队列中,wait是Object的方法,线程调用wait方法后会释放掉所持有的对象锁,即wait线程对象中其他synchronized的数据可以被别的线程使用。
wait(), notify()和notifyAll()方法必须只能在synchronized线程同步方法中调用,因为在调用这些方法之前必须首先获得线程锁,如果 在非线程同步的方法中调用时,编译时没有问题,但在运行时会抛IllegalMonitorStateException异常,异常信息是当前线程不是方 法对象的监视器对象所有者。
4.线程间的通信:
Java中通过wait(),notify()和notifyAll()方法进行线程间的通信,在JDK5之后,又引入了signal()和signalAll()进行线程通信。
(1).wait()和notify(),notifyAll():
wait使得一个正在执行的线程进入阻塞状态,wait也可以象sleep一样指定时间,但是常用的是无参数的wait()方法。notify和 notifyAll唤醒处于阻塞态的线程进入可运行状态,通知他们当前的CPU可用。这三个方法都是Object中的方法,不是线程Thread的特有方 法。例子如下:
[java] view plaincopy
1. import java.util.concurent.*;
2.
3. class Car{
4. private Boolean waxOn = false;
5. public synchronized void waxed(){
6. waxOn = true;
7. notifyAll();
8. }
9. public synchronized void buffed(){
10. waxOn = false;
11. notifyAll();
12. }
13. public synchronized void waitForWaxing()throws InterruptedException{
14. while(waxOn == false){
15. wait();
16. }
17. }
18. public synchronized void waitForBuffing()throws InterruptedException{
19. while(waxOn == true){
20. wait();
21. }
22. }
23. }
24. class WaxOn implements Runnable{
25. private Car car;
26. public WaxOn(Car c){
27. car = c;
28. }
29. public void run(){
30. try{
31. while(!Thread.interrupted()){
32. System.out.println(“Wax On!”);
33. TimeUnit.SECONDS.sleep(1);
34. car.waxed();
35. car.waitForBuffing();
36. }
37. }catch(InterruptedException e){
38. System.out.println(“Exiting via interrupt”);
39. }
40. System.out.println(“Ending Wax On task”);
41. }
42. }
43. class WaxOff implements Runnable{
44. private Car car;
45. public WaxOff(Car c){
46. car = c;
47. }
48. public void run(){
49. try{
50. while(!Thread.interrupted()){
51. car.waitForWaxing();
52. System.out.println(“Wax Off!”);
53. TimeUnit.SECONDS.sleep(1);
54. car.buffed();
55. }
56. }catch(InterruptedException e){
57. System.out.println(“Exiting via interrupt”);
58. }
59. System.out.println(“Ending Wax Off task”);
60. }
61. }
62. public class WaxOMatic{
63. public static void main(String[] args)throws Exception{
64. Car car = new Car();
65. ExecutorService exec = Executors.newCachedThreadPool();
66. exec.execute(new WaxOff(car));
67. exec.execute(new WaxOn(car));
68. TimeUnit.SECONDS.sleep(5);
69. exec.shutdownNow();
70. }
71. }
输出结果:
Wax On! Wax Off! Wax On! Wax Off! Wax On!
Exiting via interrupt
Ending Wax On task
Exiting via interrupt
Ending Wax Off task
注意:waite (),notify()和notifyAll()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或 synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调 用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
(2).await(), signal()和signalAll()进行线程通信:
JDK5中引入了线程锁Lock来实现线程同步,使用Condition将对象的监视器分解成截然不同的对象,以便通过这些对象与任意线程锁Lock组合使用,使用signal和signalAll唤醒处于阻塞状态的线程,例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2. import java.util.concurrent.lock.*;
3.
4. class Car{
5. private Lock lock = new ReentrantLock();
6. //获取线程锁的条件实例
7. private Condition condition = lock.newCondition();
8. private Boolean waxOn = false;
9. public void waxed(){
10. lock.lock();
11. try{
12. waxOn = true;
13. //唤醒等待的线程
14. condition.signalAll();
15. }finally{
16. lock.unlock();
17. }
18. }
19. public void buffed(){
20. lock.lock();
21. try{
22. waxOn = false;
23. //唤醒等待的线程
24. condition.signalAll();
25. }finally{
26. lock.unlock();
27. }
28. }
29. public void waitForWaxing()throws InterruptedException{
30. lock.lock();
31. try{
32. while(waxOn == false){
33. //使当前线程处于等待状态
34. condition.await();
35. }finally{
36. lock.unlock();
37. }
38. }
39. }
40. public void waitForBuffing()throws InterruptedException{
41. lock.lock();
42. try{
43. while(waxOn == true){
44. //使当前线程处于等待状态
45. condition.await();
46. }finally{
47. lock.unlock();
48. }
49. }
50. }
51. }
52. class WaxOn implements Runnable{
53. private Car car;
54. public WaxOn(Car c){
55. car = c;
56. }
57. public void run(){
58. try{
59. while(!Thread.interrupted()){
60. System.out.println(“Wax On!”);
61. TimeUnit.SECONDS.sleep(1);
62. car.waxed();
63. car.waitForBuffing();
64. }
65. }catch(InterruptedException e){
66. System.out.println(“Exiting via interrupt”);
67. }
68. System.out.println(“Ending Wax On task”);
69. }
70. }
71. class WaxOff implements Runnable{
72. private Car car;
73. public WaxOff(Car c){
74. car = c;
75. }
76. public void run(){
77. try{
78. while(!Thread.interrupted()){
79. car.waitForWaxing();
80. System.out.println(“Wax Off!”);
81. TimeUnit.SECONDS.sleep(1);
82. car.buffed();
83. }
84. }catch(InterruptedException e){
85. System.out.println(“Exiting via interrupt”);
86. }
87. System.out.println(“Ending Wax Off task”);
88. }
89. }
90. public class WaxOMatic{
91. public static void main(String[] args)throws Exception{
92. Car car = new Car();
93. ExecutorService exec = Executors.newCachedThreadPool();
94. exec.execute(new WaxOff(car));
95. exec.execute(new WaxOn(car));
96. TimeUnit.SECONDS.sleep(5);
97. exec.shutdownNow();
98. }
99. }
输出结果:
Wax On! Wax Off! Wax On! Wax Off! Wax On!
Exiting via interrupt
Ending Wax On task
Exiting via interrupt
Ending Wax Off task
5.使用ScheduledThreadPoolExecutor实现定时任务:
JDK1.5之前,使用Timer实现定时任务,JDK1.5之后引入了ScheduleThreadPoolExecutor实现多线程的定时任务。例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2. import java.util.*;
3.
4. public class DemoSchedule{
5. private final ScheduledExecutorService scheduler =
6. Executors.newScheduledThreadPool(1);
7. //创建并在给定延迟后时间启动的一次性操作
8. public void schedule(Runnable event, long delay){
9. scheduler.schedule(event, delay, TimeUnit.SECOND);
10. }
11. //创建并在给定延迟时间之后,每个给定时间周期性执行的操作
12. public void repeat(Runnable event, long initialDelay, long period){
13. scheduler.scheduleAtFixedRate(event, initialDelay, period, TimeUnit.SECOND);
14. }
15. }
16. class OnetimeSchedule implement Runnable{
17. public void run(){
18. System.out.println(“One time schedule task”);
19. }
20. }
21. class RepeatSchedule implement Runnable{
22. public void run(){
23. System.out.println(“Repeat schedule task”);
24. }
25. }
26. class TestSchedule {
27. public static void main(String[] args){
28. DemoSchedule scheduler = new DemoSchedule();
29. scheduler.schedule(new OnetimeSchedule(), 5);
30. scheduler.schedule(new RepeatSchedule (), 10, 10);
31. }
32. }
输出结果:
5秒钟之后打印输出:One timeschedule task
10秒钟之后打印输出:Repeatschedule task
之后每隔10秒钟打印输出:Repeat schedule task
6.信号量Semaphore:
一个正常的线程同步锁concurrent.locks或者内置的synchronized只允许同一时间一个任务访问一个资源,而计数信号量 Semaphore运行多个任务在同一时间访问一个资源。Semaphore是一个计数信号量,维护了一个许可集,通常用于限制可以访问某些资源的线程数 目。例子如下:
[java] view plaincopy
1. import java.util.concurrent.*;
2. import java.util.*;
3.
4. public class Pool
{ 5. //限制的线程数目
6. private int size;
7. //存放资源集合
8. private List
items = new ArrayList (); 9. //标记资源是否被使用
10. private volatile boolean[] checkedOut;
11. private Semaphore available;
12. public Pool(Class
classObject, int size){ 13. this.size = size;
14. checkedOut = new boolean[size];
15. //创建信号量对象
16. available = new Semaphore(size, true);
17. for(int i = 0; i < size; ++i){
18. try{
19. //加载资源对象并存放到集合中
20. items.add(classObject.newInstance());
21. }catch(Exception e){
22. throw new RuntimeException(e);
23. }
24. }
25. }
26. //访问资源
27. public T checkout() throws InterruptedException{
28. //从信号量中获取一个资源许可
29. available.acquired();
30. return getItem();
31. }
32. //释放资源
33. public void checkIn(T x){
34. if(releaseItem(x))
35. //释放一个资源许可,将其返回给信号量
36. available.release();
37. }
38. //获取资源
39. private synchronized T getItem(){
40. for(int i = 0; i < size; ++i){
41. //资源没有被使用
42. if(!checkedOut[i]){
43. //标记资源被使用
44. checkedOut[i] = true;
45. return items.get(i);
46. }
47. }
48. return null;
49. }
50. private synchronized boolean releaseItem(T item){
51. int index = items.indexOf(item);
52. //资源不在资源集合中
53. if(index == -1) return false;
54. //资源正在被使用
55. if(checkedOut[index]){
56. //将资源标记为不再使用
57. checkedOut[index] = false;
58. return true;
59. }
60. return false;
61. }
62. }
在访问资源前,每个线程必须首先从信号量获取许可,从而保证可以使用该资源。该线程结束后,将资源释放回资源池中并将许可返回给信号量,从而允许其他线程获取和使用该资源。
如果当前信号量中没有资源访问许可,则信号量会阻塞调用的线程直到获取一个资源许可,否则,线程将被中断。
注意:调用信号量的acquire时无法保持同步锁,因为这会阻止将该资源返回到资源池中,信号量封装所需要的同步,以限制对资源池的访问。
7.Exchanger线程同步交换器:
Exchanger类,可以用来完成线程间的数据交换。 类java.util.concurrent.Exchanger提供了一个同步点,在这 个同步点,一对线程可以交换数据。每个线程通过exchange()方法的入口提供数据给他的伙伴线程,并接收他的伙伴线程提供的数据,并返回。当两个线 程通过Exchanger交换了对象,这个交换对于两个线程来说都是安全的。例子如下:
[java] view plaincopy
1. //杯子类
2. class Cup{
3. private int capacity = 0;
4. public Cup(int capacity){
5. this.capacity = capacity;
6. }
7. public int getCapacity(){
8. return capacity;
9. }
10. public void addWaterToCup(int i){
11. capacity += I;
12. capacity = capacity > 100 ? 100 : capacity;
13. }
14. public void drinkWaterFromCup(int i){
15. capacity += I;
16. capacity = capacity < 0 ? 0 : capacity;
17. }
18. public void isFull(){
19. return capacity == 100 ? true : false;
20. }
21. public void isEmpty(){
22. return capacity == 0 ? true : false;
23. }
24. }
25. import java.util.concurrent.Exchanger;
26. import java.util.*;
27.
28. public class TestExchanger{
29. Cup emptyCup = new Cup(0);
30. Cup fullCup = new Cup(100);
31. Exchanger
exchanger = new Exchanger (); 32.
33. //服务员类
34. class Waiter implements Runnable{
35. private int addSpeed = 1;
36. public Waiter(int addSpeed){
37. this.addSpeed = addSpeed;
38. }
39. public void run(){
40. while(emptyCup != null){
41. try{
42. //如果杯子已满,则与顾客交换,服务员有获得空杯子
43. if(emptyCup.isFull()){
44. emptyCup = exchanger.exchange(emptyCup);
45. System.out.println(“Waiter: ” + emptyCup. getCapacity());
46. }else{
47. emptyCup.addWaterToCup(addSpeed);
48. System.out.println(“Waiter add ” + addSpeed +
49. “ and current capacity is:” + emptyCup. getCapacity());
50. TimeUnit.SECONDS.sleep(new Random().nextInt(10));
51. }
52. }catch(InterruptedException e){
53. e.printStackTrack();
54. }
55. }
56. }
57. }
58. //顾客类
59. class Customer implements Runnable{
60. int drinkSpeed = 1;
61. public Customer(int drinkSpeed){
62. this.drinkSpeed = drinkSpeed;
63. }
64. public void run(){
65. while(fullCup != null){
66. try{
67. //如果杯子已空,则与服务员进行交换,顾客获得装满水的杯子
68. if(fullCup.isEmpty()){
69. fullCup = exchanger.exchanger(fullCup);
70. System.out.println(“Customer: ” + fullCup. getCapacity());
71. }else{
72. fullCup.drinkWaterFromCup(drinkSpeed);
73. System.out.println(“Customer drink ” + drinkSpeed +
74. “ and current capacity is:” + fullCup.getCapacity());
75. TimeUnit.SECONDS.sleep(new Random().nextInt(10));
76. }
77. }catch(InterruptedException e){
78. e.printStackTrack();
79. }
80. }
81. }
82. }
83. public static void main(String[] args){
84. new Thread(new Waiter(3)).start();
85. new Thread(new Customer(6)).start();
86. }
87. }
《Effective java》读书笔记1——避免创建不必要的对象
Java中Sting很特别,有如下两种初始化方式:
(1).String s1 = “This isstring1”;
(2).String s2 = new String(“Thisis string2”);
第一种字符串初始化方式,当有多于一个字符串的内容相同情况,字符串内容会放在字符串缓冲池中,即字符串内容在内存中只有一份。
第二种字符串初始化方式,不论有没有字符串值相同,每次都会在内存堆中存储字符串的值。
如果一个方法中字符串的值都相同,调用100万次情况下第一种字符串初始化方式的内存占用率很低,性能非常高,而第二种方式的字符串初始化则会占用大量的内存.
看下面一个例子,直观感受创建不必要对象的性能危害:
[java] view plaincopy
1. public class Person{
2. private Date birthDate;
3. //判断是否是婴儿潮出生的人
4. public boolean isBabyBoomer(){
5. Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(“GMT”));
6. //婴儿潮开始时间
7. cal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
8. Date boomStart = cal.getTime();
9. //婴儿潮结束时间
10. cal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
11. Date boomEnd = cal.getTime();
12. return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
13. }
14. }
每次调用isBabyBoomer()方式时,都需要创建Calendar,TimeZone,boomStart和boomEnd四个对象,测试调用该方法1000万次,大约耗时32秒。
Calendar,TimeZone,boomStart和boomEnd四个对象是所有调用者共用的对象,只需创建一份即可,改进之后代码如下:
[java] view plaincopy
1. public class Person{
2. private Date birthDate;
3. private static final Date BOOM_START;
4. private static final Date BOOM_END;
5. static{
6. Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(“GMT”));
7. //婴儿潮开始时间
8. cal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
9. BOOM_START = cal.getTime();
10. //婴儿潮结束时间
11. cal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
12. BOOM_END = cal.getTime();
13. }
14. //判断是否是婴儿潮出生的人
15. public boolean isBabyBoomer(){
16. return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
17. }
18. }