遗留问题?
11.13.1 适配器 ?
15.2.2 Java SE5的自动打包和自动拆包功能?
第三章、Java的操作符
3.1、对类的字节复赋值操作,实际是将两个对象指向共同的引用,
例如:
class tank(){
int level
}
tank1 = tank2 ; //引用操作(相当于两个指针之间的赋值)
//当修改tank1的值时,tank2的值也会改变
tank1.level = tank2.level // 赋值操作
3.2、程序里使用直接常量时,为了编译器可以准确的识别,需要添加额外的 后缀或前缀
例如:
后缀: l/L,f/F,d/D | 1.0f
前缀:0x/0X,o/O | 0x12354,o1234
一般情况下,常量不需要加 后缀或前缀,但是
例如:
float f4 = 1e-43f //在编程语言中通常使用 e 来表示10的幂次
//但是编译器通常会将e 作为double处理,如果不加f就会显示出错
3.3、移位操作符
有符号数:<<,>> //正数在高位插入0,负数在高位插入1
无符号数:<<<,>>> //无论正负都在高位插入0(Java的扩展)
注意:
-java对char,byte,short的移位操作,都会将其转换为int类型,并且得出的结果也是int,并且为了移位不超过int类型的位数,只会取移位数的最低5位有效(2^5=32)。
-对long进行移位操作,得到的结果是long,即最低6位有效
例如:
1 << 123456 | 只会选取最低5位有效,即 移位数 = 123456mod32
3.4、字符串操作
-Java中可以字节使用 + ,+= 来拼接字符串(内定义的操作符重载)
注意:
-Java中如果一个表达式字符串开头,那么其后的所有字符串都将转换为string类型处理
例如:
int x,y,z;
printf(x + " " + y); //x int ,y string
printf(" " + x) == Integer.toString() //简便的字符串转换方式
3.5、使用操作符常犯的错误
例如:
while(x = y){} //(先计算x=y,然后对结果进行逻辑判断)
//在c/c++中不会出现错误,但在Java中会报错,因为Java中不会自动将int转换为bool
-同样的&&与& 和 ||与|的误用Java都会报错,但是c/c++不会
3.6、类型转换
扩展转换:不会引起数据丢失,Java编译器会隐式转换
窄化转换:会引起数据丢失,必须显示转换 如:(int)long
注意:
-Java不允许对bool和class进行任何类型转换,因此需要借助特殊的方法
-java在进行窄化转换时,采取的是截尾法,| 舍入法: Math.round()
3.7、c/c++中额是sizeof是为了方便移植,因为同一类型在不同的机器中的长度不一样,但是java的类型在不同的机器中的长度都时一样的
第四章、控制执行流程
4.1、foreach语法
for(float x : f){}
for(int i : range(10)) //0~9
for(int i : range(5,9)) //5~9
FOR(int i : range(5,20,3)) //0~20 ,step 3
注意:
4.2、带标签的break和continue (c/c++中没有这种用法)
label1:
for(int i=1;i<10;i++){
label2: //由于标签后接的不是循环语句,此标签会被忽略,后面如果使用会报错
for(int j=1;j<10;j++){
break; //break会跳过for内的递增语句
continue //跳回循环顶部继续循环,不会跳过for内的递增语句
continue level //跳回label处继续执行循环,跳过for内的递增语句
//保留循环内部的局部变量的值,并不会再次执行for内的初始化语句
break level; // 跳出标签所指的循环,即标签后紧接的循环语句
}
i+=j;
}
第五章、初始化与清理
5.1、构造器/构造函数
c++中如果没有自定义构造函数,编译器会自动调用默认构造函数,默认构造函数与类同名且不带参数,将内部成员初始化为0或NULL。特备要注意默认构造函数和无参构造函数的区别(是否有显示定义)。
6.1、this指针
this.fun == classA.fun(classA,1); //编译器的内部转换
6.1、java的资源回收
java没有c++类似的析构函数,java的资源回收是在系统资源即将用完时,系统自动进行垃圾回收,但是java的资源回收只会释放由new分配的内存,如果对象使用了特殊内存,则需要自己手动释放,即自定义资源释放函数(finalize()),但它不同于c++中的析构函数,他只是一个普通的成员函数。
在系统调用资源回收程序之前,会先调用用户finalize(),但是调用了finalize()之后内存并没有被立即释放,而是等到系统进行垃圾回收时才会释放,但c++中的被析构函数调用之后,对象所占用的内存会立即被释放。
为什么要有finalize()?
java中无论对象是通过何种方式创建的,系统的垃圾回收都可以将它释放,但是如果程序通过非创建对象方式为对象分配了内存,如使用本地方式(在java中调用非java代码,一般为c/c++)为对象分配了内存时(malloc),我们就需要在finalize中调用free()函数来释放符分配的内存。
使用finalize()进行终结条件验证?
当某个对象可以被清理时应该处于某种状态,例如:打开的文件要被关闭。因此调用finalize()时就可以发现这些隐晦的缺陷,当然这个缺陷在JVM进行垃圾回收时也会被发现,但垃圾回收的时间是不确定的。
例如:
super.finalize()
System.gc() //强制进行终结动作(垃圾回收)
java的垃圾回收期如何工作?
垃圾回收机制:
引用计数
如果对象之间初存在循环引用,可能会出现“对象应该被回收,但引用计数不为0的情况
非引用计数
任何“活”对象,都能罪追溯到其存活在堆栈或静态存储区之中的引用,因此从堆栈和静态存储区开始,便遍历所有的引用找到所有“活”的对象
java虚拟机的垃圾回收技术:
停止-复制
暂停程序,将“活”对象从一个堆复制到另一个堆,新堆保持紧密排列
标记-清理
当程序运行稳定后,只会产生少量的垃圾,堆空间会留下碎片
JVM的即时编译器(JIT,just-in-time):
将程序部分或全部编译成机器码,提升程序的运行速度,但会增大程序体积,可能引起页面调度
惰性评估:
只在必要的时候才编译代码
5.7、java对象的初始化
注意:
无论是在定义时赋值,还是使用构造器初始化,都无法阻止自动初始化的进行,它发生在所有初始化之前。
初始化顺序:
静态域初始化 > 系统自动初始化 > 基类默认构造器初始化 > 赋值初始化 > 构造器初始化
注意:
class house{
window w1 = new window(1)
house(){
window w2 = new window(2)
}
window w3 = new window(3)
}
初始化顺序:w1 > w3 > w2
静态成员变量的初始化:
--静态成员变量只会在被首次创建对象时被创建并初始化一次。
--在执行静态方法时,系统会先对静态域进行初始化
--构造器实际上也是静态方法,因此在首次创建对象时,Java会查找类路径,定位class文件,然后执行所有的静态初始化,当对象被 new 时,编译器为对象分配存储空间,并将内存清零,这就导致了普通成员变量的自动初始化。然后再开始执行赋值初始化。
--每次新建对象,都会重新执行赋值初始化和构造器初始化过程,但对静态量只会被初始化一次,而成员变量则会随着对象的建立进行多次初始化。
显示的静态初始化:
class a {
static int a,b;
{ a = 1; //匿名内部类,
b = 2; //同自动初始化一样,先于构造函数执行
}
}
5.8数组的初始化
Java中的对象和对象的引用
Person person; //对象的引用,指向一个对象的变量叫作引用,引用内存分配在栈中,相当于c++中的指针
person = new Person(); //对象,真正存储数据的实体,内存分配在堆中
注意:
c++中的引用与Java不同,c++中的引用是同一内存的不同别名,即c++中的应用不在栈中占据额外的内存,而是只存在于符号列表中。因此Java相当于采取c++指针的方式统一了c++的指针和引用两种用法。
名字空间:
c++为了解决不同代码体系中的重名问题而引入的一种新的作用域级别,一般分为四级:局部作用域(使用{}包含的代码),函数域,类域,全局作用域。
声明:
namespace "name"{
int a=5;
extern int otherVar; //另一个文件中同名名字空间中定义
using std::cin;
class b{};
void func();
namespace cin{
申明列表
}}
namespace {申明序列} //匿名名字空间
使用:
using namespace "name" //整体引用
using std::cin; //单个引用
--名字空间可以在不同文件中分段定义和使用声明
--名字空间内部可以定义类型、函数、变量等内容,但名字空间不能定义在类和函数的内部。
--名字空间中的变量可以在内部定义也可以在外部定义
--未命名的名字空间中定义的变量(或函数)只在包含该名字空间的文件中可见,但其中的变量的生存期却从程序开始到程序结束。
--匿名名字空间在提供了类似静态全局变量限制作用域的功能,但匿名名字空间却具有静态全局变量没有的外部链接性,这在使用类模板时会有体现,类模板的非类型参数要求是编译时常量表达式,或者是指针类型的参数且要求指针指向的对象具有外部连接性。
C++名字空间详解: https://dablelv.blog.csdn.net/article/details/48089725
符号列表:
c++中的符号列表:
c++对于同名函数,在编译时会将同名函数转换为携带有参数和返回值类型信息的函数签名,这也就是c++的函数重载原理。而不同编译器采用的函数签名格式不同,这就导致不同编辑器产生的目标文件不能相互链接。
注意:
函数的重载仅通过返回值是无法区分的。
查看符号列表:
nm –C test.o
nm –C test.obj //windows
数组的初始化:
--Java编译器不允许指定数组的大小,Java中数组的赋值,实际上是引用的赋值(这和对象之间的赋值类似)。即Java中所有的数组申明相当于c++中的数组指针(int[] a)。
例如:
int[] a1 = {1,2,3,4,5};
int[] a2 = a1; //a1和a2指向相同的地址空间,修改一个会影响另一个
数组的初始化:
int[] a = new int[length]; a[i] = i;
interger[] a = {new interger(1),new interger(1)}; //只能用在定义处
interger[] a = new interger[]{new interger(1),new interger(1)}; //在任何地方都可以用
注意:
不能用new创建单个的基本类型数据
Java为String类提供了缓冲池机制,
例如:
string a = "tom",b = "tom"; //a与b指向相同的缓冲区,当使用""d定义对象时,java会先去查看缓冲池是否有相同内容的字符串,没有就创建新的对象放入缓冲池
string a = new string("tom"),b = new string("tom"); //a,b具有不同的内存空间
可变参数列表:
void printArray(Object... args){
for(Object obj : args){
system.out.print(obj + " ");
}}
printArray(1,1.0f,1.11) //Object可以自动匹配类型,并将参数填充到数组
printArray((Object[])new integer[]{1,2,3,4}); //如果参数是数组就直接传递
printArray();
而当可变参数与函数重载结合使用时,编译器会根据参数的类型去调用不同的重载函数。
例如:
void printArray(int... args);
void printArray(char... args);
printArray(1,2,3);
printArray('a','b','c');
printArray() //如果没有指定参数,编译器会因不知道调用哪个函数而报错,可以通过添加一个非可变参数来来解决该问题 如:void printArray(int a,int... args);
第六章、访问权限控制
6.1 包:库单元
为了防止类之间的名字冲突,Java的每个编译单元(源文件)必须有一个public类,并且该类的名称必须与包名相同,包括大小写,否则编译器会报错。如果编译单元之中还有其他的类,那么这些类在包之外是无法看见的。这些类主要用来为public提供支持。
如果编译单元中没有public类,编译也不会报错,只是这个源文件中的所有对象都会对外不可见。
6.1.1代码组织
当编译一个.Java文件(源文件)时,文件中的每一个类都会有一个后缀名为 .class 的输出文件。Java的可运行程序是一组可以打包并压缩为一个Java文档文件(.jar文件)的.class文件。解释器负责这些文件的查找、装载和解释。
类库实际上是一组类文件,其中每一个文件都有一个public类和多个非public类,如果要这些构件同属于一个组群,就需要使用 package 关键字。
注意:
package语句必须是文件除注释以外的第一条语句。如: package access ,任何想使用该类的人,都必须使用import关键字或给出全名。
如果源文件没有使用package语句,那么编译器会将没有使用package语句的源文件统一放入该目录下的default包中
6.1.2 创建一个独一无二的包名
将同一个包的.class文件都位于统一目录下,然后将.class文件的路径编码成package的全名,一般情况下package名称的第一部分为类创建者的反序排列的internet域名,这也是包名独一无二的保证。因此 package 语句就决定了源文件的存放路径。
Java解释器的运行过程:
环境变量classpath,查找.class文件的根目录,然后再将包名转换为目录结构。
根目录的查找顺序是以classpath(系统环境变量)中指定的顺序,因此classpath的第一个一般为 "."表示优先从文件所在的本目录查找。
6.2 Java访问权限修饰词
6.2.1 包访问权限
如果类成员没有使用任何访问权限修饰词(public,protected,private),那么该成员就具有包访问权限,有时也表示为friendly。即包内的其他类都对改成员有访问权限,但在包外无法访问。
proctcted具有包访问权限和派生类访问权限
注意:
C++类的默认访问权限是private,而且变量的访问权限是跟随前面离他最近的修饰词。
c++友元函数的概念
6.2.2 应用
class soup{
private soup(){} //private的构造函数,导致不可在类外通过构造函数创建对象
public static soup makeSoup(){
return new soup(); //可以在这之前进行其他操作,例如:限制soup的实例对象个数
} }
class soup{
private soup(){}
private static soup s1 = new soup();
public static soup access(){
return s1; //只允许创建单个soup实例
} }
第七章、复用类
7.1继承语法
创建类时如果没有明确指明从其它类继承,那么该类将从Java的标准根类Object进行继承。
例如:
class a extends b { } //a继承b
在每个类中都创建一个 main()可以很方便的进行单元测试
使用super关键字,可以指定调用的是基类的函数(直接基类);
7.2初始化基类
如果基类没有定义构造函数,在创建派生类实例时,系统会自动调用基类的默认构造函数。但是当基类自定义类构造函数,那么系统就不会为基类创建默认构造函数,在创建派生类实例时,系统也就无法使用默认的构造函数对基类进行初始化,这时就需要我们在派生类的构造函数中手动对基类进行初始化(使用super关键字)。
例如:
class a { a()}
class b extends a{
b(){ super.a(); }
}
7.3代理
组合:
将一个对象的引用置于另一个类中,通过类中的引用对象调用引用对象的方法
继承:
继承会将基类的所有方法都暴露在派生类中
代理:
将一个对象的引用置于另一个类中,而应用对象中的方法都通过类中定义的同名方法(并不要求一定同名)进行间接调用。
7.4 名称屏蔽(覆盖)
@Override
如果我们进行的是重载而不是覆盖操作时,编译器就会报错
注意:
@Override 并不是关键字,只是用法与关键字类似
7.5资源回收
Java没用析构函数,所有的清理都由垃圾回收器负责,但是垃圾回收器回收垃圾时的顺序是不确定的,所以对于一些有依赖关系的资源,需要我们手动进行回收。但是由于存在依赖关系(例如:继承),我们必须要先将本类的资源回收后,再去调用基类的资源回收涵数。
7.6向上转型
在c++中基类类型可以接受派生类的赋值,但是我们只能使用派生类中继承自基类的变量和方法。这也是Java的向上转型的原理。
我们在使用Java时,应该更多的去使用组合,而不是使用继承。除非你需要进行向上转型。
注意:
如果继承类复写了基类的方法,在向上转型之后,使用基类调用的却是继承类复写的方法,而不是基类方法
7.7 final关键字
final与c++中的const本质上一致
修饰基本数据类型: 数据的值不能改变,必须在定义时进行初始化。
修饰对象的引用: 该引用不能再指向别的对象,即不能再作为左值,但是该引用所指向的对象的值是可以改变的
修饰行参: 在方法中不能对final修饰的行参进行修改
修饰方法 1.防止继承类修改方法的定义,即在继承类中不能覆盖被final修饰的方法。实际上private方法本身就隐含的final关键字的功能,因为private方法对继承类不可见(但确实存在于继承类中),所以也就不存在覆盖的概念。
2.优化效率,相当于c++中的inline关键字(内联函数),在Java早期的实现中,对方法使用final修饰,就意味着同意编译器将针对该方法的所有调用都转换为内嵌调用(内嵌调用,直接将方法的代码嵌入调用程序代码中,而不使用函数调用机制)。但是当final修饰的方法过长时,会使得调用程序过于冗长。
修饰类: 该类不能被继承和改变。
第八章、多态
8.1什么是多态
向上专型可以将继承类当做基类使用,但是当一个基类有多个继承类时,怎么判断有引用指向的是哪个继承类对象所对应的基类呢?
8.2动态绑定
Java中除了static方法和final方法外,其他所有的方法都是动态绑定。
注意:
当在继承类中对基类的private方法进行覆盖操作时,虽然编译器不会报错,但是我们使用继承类实例调用该方法时,调用的却是基类的private方法。
例如:
class A{ private fun(){ print("private fun"); }}
class B extends A{ public fun(){print("public fun");}}
void main (){ A a; a.fun();} // output~ : private fun
当继承类向上转型为基类时,任何的域访问操作都将由编译器解析,因此不是多态的。但是如果继承类对基类的方式实现了覆盖,在向上转型之后基类的应用调用的却是继承类的复写方法。
例如:
class A {int field=1; public int getfield(){return field;} }
class B extends A{int field=2; public int getfield(){return field;}}
void main(){A a = new B();
print("a.field = " + a.field);
print("a.getfield = " + a.getfield); }
//output: a.field = 1 a.getfield = 2
静态方法是与类,而并不是与对象相关联的,因此不具有多态性。特别注意,Java中的构造函数也属于静态方法。因为构造函数往往在实例被构件之前就被调用。
8.3继承条件下的初始化顺序
先递归对基类进行初始化,然后初始化本类对象。
注意:
static成员是在应用建立时初始化,而其他成员和构造器是在new操作时才进行初始化。
例如:
A a , A.(static)fun(); //静态域初始化
a = new A(); //自动初始化 + 构造器初始化
猜想:
前面说过,赋值初始化是先于构造器初始化的。所以继承可能使用的是组合的原理,即编译器隐晦的在继承类中创建了基类的实例
8.4继承环境下的资源回收
如果我们需要使用dispose()函数手动对一些资源进行回收时,必须要先回收继承类然后再回收基类,因为继承类可能会用到基类中的成员。
super关键字:指定调用的是基类的成员或方法。
例如:
super.dispose();
8.5构造器内部的多态方法行为
在构造器中调用普通方法时,方法中所使用的变量可能还未经初始化,从而产生意料之外的结果。
例如:
class A{int fA= 1; void getfield(){print("funA=" + fA);} void A(){ getfield();} }
class B extends A{int fB=2; void getfield(){print("funB=" + fB);}; void B(){getfield();}}
void main(){ new B();}
//output:
funB = 0 funB = 2 //因为函数覆盖,所以两次输出的对象都是 fB
//在类A的构造器中,调用的是类B的getfield(),但是此时 fB 进行了系统默认初始化被初始化(即内存空间清0),但是还没有进行赋值初始化。
//在类B的构造器中,FB已经完成了赋值初始化。
注意:
从以上例子中可以看出,函数的覆盖在在编译时就已经完成了,因此在对基类的初始化中被覆盖的函数就已经是不可见了。但是对于被覆盖的函数,我们仍然可以使用super关键字调用。如:super.dispose()
因此在构造器中唯一可以安全调用的是final方法和private方法。
8.6协变返回类型
使函数的返回值也可以接受向上转型。
class A{ } class B extends A{}
class C{A process(){return A;} } class D extends B{B process(){return B;}}
void main(){ C c = new C(); A a=c.process(); c = new D(); a=c.process();}
8.7运行时类型识别
当我们在执行向上转型之后,如果要使用继承类定义的扩展接口,就需要对实例进行向下转型。向下转型时Java会自动进行类型检查。
class A{void f()} class B extends A{void f()}
void main(){
A a=new A();
A b=new B(); //upcast
((B)a).f(); //ClassCastException
((B)b).f(); //downcast
}
第九章、接口
9.1
抽象方法:
abstract void f();
抽象类:
包含一个或多个抽象方法的类(并不要求所有的方法都是抽象方法)。如果继承类没有实现基类的抽象方法,那么继承类任然是抽象类。
abstract class A{ abstract void f();}
接口:
interface A{void f();}
--接口中的所有方法都是抽象方法,其中抽象方法不需要使用abstract修饰。
--可以在interface前使用public修饰,但是要求接口名称必须与源文件的名称相同。如果不使用public关键字,那么它只具有包访问权限。
--接口也可以包含域,但这些域隐式的是static和final的,例如:接口中定义的成员变量。
--接口中的方法默认是public的,而且只能使用public修饰,虽然那并没有意义。
接口的继承:
class B Implements A{}
9.2完全解耦
策略设计模式:
原理:
利用向上转型,使一个接口可以兼容不同的类型
利用覆盖的原理,在通用接口中调用同一个函数,会有不同的实现方法
利用接口解耦:
如果将基类声明为接口,就可以那么只要是继承了(直接或间接)该接口的类,都可以通过该接口进行耦合。
例如:
interface A{ void get();void set(); }
class B Implements A{void get(){} void set(){}}
class C Implements B{void get(){} void set(){}}
void apply(A a){ a.get();a.set();} //通过接口,对不同的类型进行耦合
适配器:
通过代理的方法对,使用的对象进行匹配和扩展
class B implements A{
Filter filter;
public FilterAdapter(Filter filter){
this.filter = filter; //对继承自Filter的继承类进行适配
}}
9.3Java中的多继承
--多继承只能继承一个抽象类和多个接口类
--多继承类可以向上转型为每一个接口
例如:
interface fight{public void fight();}
interface fly{void fly();}
class ActoinCharactor{void fight(){}}
class Hero extends ActionCharacter implements fight,fly{ void fly(){}}
注意:
fight类中的fight()和ActoinCharactor类中的fight()具有相同的特征签名,所以Hero类并没有提供fly()的定义,其定义也因ActoinCharactor随之而来,这也就让Hero类创建对象成为可能。
但是如果fight类中将fight()定义为private,那么Hero中任然需要对fight()进行定义,因为继承类对基类的private方法不可见。
9.4接口扩展
interface A{ void f();}
interface B{void s();}
interface C extends A,B{ void g();}
注意:
接口继承接口,类继承类使用: extends
类继承接口使用: implements
9.5.1接口扩展时的名字冲突
原因:因为重载无法通过返回值区分
例如:
interface A{void f();}
interface B{int f();}
interface C extends A,B{} //error
class D implements A{void f(){}}
class E extends D implements B{} //error
9.6接口适配
--Java中接口的主要作用就是可以将一个接口适配到任何想使用它的类上,然后我们就可以在一个地方调用所有的接口方法。
--Java中的类与类之间只有单继承,不能进行多继承
9.7接口中的域
--因为接口中的域都是static和final的,所以在Java SE5之前,接口常常作为创建常量组的工具,类似于c/c++中的enum
--接口中的域可以使用表达式进行初始化,但是不能为空
9.8嵌套接口
接口之间的嵌套:
嵌套在接口中的所有接口元素都自动并且必须是public的
接口与类的嵌套:
嵌套在类中的接口可以是private、public和包访问权限的,而且private的接口,只能在其嵌入的类中定义。但是private接口的实现类只能被自身所使用,而且不接受向上转型
嵌套接口的实现:
class A {interface B{void f();}}
class C implements A.B{ void f(){}}
第十章、内部类
10.1创建内部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类,那么必须要使用全名
注意:
当创建外围类对象时或者继承中,只会对外围类进行初始化,内部类的构造函数不会被调用。即外围类的初始化流程不包含内部类。
10.2链接外部类
内部类的对象可以访问创建它的外部类的变量和方法。
例如:
class outer{ class inner{}}
void main(){A a; outer.inner out=out.inner()} //b可以访问a的成员
10.3使用this与new
如果你想要在内部类中生成对外部类的引用,可以使用外部类的名字后面紧跟圆点和this。
例如:
outer.this.f();
如果要创建某个内部类对象,则你必须在new表达式中提供对其外部类对象的引用。
例如:
outer out
outer.inner in = out.new Inner();
//在拥有外部类之前是不可能创建内部类对象,因为内部类对象会链接到创建到他的外部类对象上
10.4内部类与向上转型
interface A{void f()}
class outer{
private class inner implements A{void f(){}}
void g(){ return new inner();}
}
void main(){
outer out = new outer();
A a = out.g(); //upcast,且a不能再向下转型为inner
}
因为private的内部类在外部不可访问,通过这种方式,即可以为内部类提供接口,也完全隐常内部类的实现细节。
10.5在方法和作用域内的内部类
方法中的内部类:
--方法中的内部类的编译是同其它类一起编译的,,但是却只能在定义该类的局部作用域内访问。
--同一目录下的其它类中的同名内部类不会有名字冲突。
--定义在方法中的内部类在方法执行完之后任然可以使用。
10.6匿名内部类
class outer{
static inner getInner(int a,final int b){ //匿名类只能使用final修饰的外部定义对象
return new inner(int a){ //a是传递给基类使用的,所以不要求是final
private cost;
{ cost = b } //在创建实例时,进行初始化操作
}; //分号必加,return语句的
}
static void main(){
inner in = new getInner(1,2);
}
}
匿名类的本质:
创建一个继承自inner的匿名对象,并在反回时对其进行向上转型。
注意:
匿名类只能继承一个类或者实现一个接口
10.7嵌套类
--申明为static的内部类
--嵌套类的创建不需要外围类对象
--嵌套类对象不能访问非静态的外围类对象,静态类中没有this指针
--内部类中不能有static成员,嵌套类中可以包含
10.8接口内部的类
因为放到接口中的任何类都自动的是public和static的,所以嵌套类可以放入接口中。因为类是static的,只是将嵌套类置于接口的名字空间内。你甚至可以在嵌套类中实现其外围接口。
如果你想创建某些公共代码,使得他们可以被某个接口的所有不同实现所共有,那么使用接口内部的嵌套类就会很方便。
10.8.1多层嵌套的内部类
一个类无论被嵌套多少层,它都能透明的访问所有它嵌入的外围类的所有成员。但是在创建对象时需要逐层创建。
10.9为什么需要内部类
--内部内提供类提供了进入外部类的窗口
--内部类很好的解决了多重继承的问题,因为Java的继承能单类和多接口,如果想要一个类继承多个类,那么内部类就提供了很好的解决方法
--内部类可以有多个实例,每个实例都有自己的状态信息,而且与其外围类的信息相互独立
--在单个外围类中,可以使用多种方式实现同一接口或继承同一个类。
例如:
class A { Object obj; } class B{ }
class C {
A getA1(final int i){ return new A(){ obj = i}; }
A getA2(final float f){ return new A(){obj = f};}
B getB(){ return new B{ }; }
}
10.10内部类与控制框架
闭包:
回调:
10.11内部类的继承
class outer{ inner { } }
class A extends outer.inner {
A(outer out){ out.super(); }
}
void main(){
outer out = new outer();
A a = new A(out);
}
注意:
out.super()是必须的,在执行内部类的构造函数时,那个指向外围类对象的“秘密的”引用必须被初始化。但是在导出类中不存在可连接的默认对象。所以必须要通过super()语法给内部类的构造函数传递一个外围类的引用。
10.12内部类的覆盖
class outer{ class inner{} }
class A extends outer{ class B extends outer{ }} //内部类不会被覆盖
class C extends outer{ class C extends outer.inner{ }} //内部类会被覆盖,方法的实现等
10.13局部内部类
--局部内部类的名字在方法外不可见
--局部内部类可以用于重载,而匿名内部类只能用于实例初始化
--当需要创建不只一个对象时可以使用局部内部类
10.14内部类型标识符
outer$inner.class
outer$1.class //匿名内部类
--由此可见内部类与局部内部类之间是存在名字冲突的。
第十一章、持有对象
11.1泛型和类型安全的容器
类型安全的容器:
@SuppressWarning("checked") //注解
ArrayList a = new ArrayList();
可以存储所有类型的对象,因为在存储时会将对象转换为Object类型的对象进行存储。因此在使用元素时必须需要进行强制类型转换。
如:((A)a.get(i)).id()
泛型容器:
ArrayList<A> a = new ArrayList<A>();
只能存储A类型的对象,在使用时不需要进行强制类型转换
11.2基本容器类型
Collection(序列)
Set(元素不重复)、List(按照插入顺序保存元素)、Queue按照排队规则保存元素
Map(键值对、映射表、关联数组)
ArrayList(将对象与数值关联,可以通过数值查找对象)
注意:
泛型容器也可以进行向上转型(容器也是类),这样我们在需要修改实现时,就只用在创建出进行修改。
例如:
List<A> a = new ArrayList<A>();
11.3添加一组数组
Array.asList():接受一个数组或是一个用逗号分隔的元素列表(可变参数),并将其转换为List对象。
Collections.addAll():接受一个Collection对象,以一个数组后或一个用逗号分割的列表。
例如:
Collection<Integer> collection = new ArrayList<Integer>(Array.asList(1,2,3,4,5));
Integer[] m = {1,2,3,4,5};
collection.addAll(Arrays.asList(m));
Collections.addAll(collection,6,7,8,9);
Collections.addAll(collection,m);
List<Integer> list = Array.asList(1,2,3,4);
//list.add(5) //error
注意:
Array.asList()返回的List在底层表示的是数组,因此不能修改它的尺寸。但真正的List是可以修改尺寸的。
class A{ } class B extends A{ }
class A1 extends A{ } class A2 extends A{ }
class B1 extends B{ } class B2 extends B{ }
void main(){
List<A> a1 = Arrays.asList(new A1(),new A2(),new B());
//List<A> a2 = Arrays.asList(new B1(),new B2());
//Collection.addAll()从第一个参数了解到它要创建的是List<B>而不是List<A>
List<A> a3 = new ArrayList<A>();
Collections.addAll(a3,new B1(),new B2());
List<A> a4 = Arrays.<A>asList(new B1(),new B2());
}
显示类型参数说明:
在Arrays.asList()中间添加插入一条"线索",以告诉编译器实际的目标类型是什么。
例如:Arrays.<A>asList
11.4 容器的打印
打印数组时必须使用Arrays.toString()来产生数组的可打印表示,但是容器打印无需任何帮助
例如:
print(list);
11.5 List
ArrayList:快速的随机访问,在中间插入和移动元素较慢
LinkedList:顺序访问,随机访问慢,较低代价的中间插入和删除操作
11.5.1 容器中的常用方法:
contains()、p.containsAll(q)
remove()、p.removeAll(q)
set()、replace()
isEmpty()、clear()
p.retainAll(q) 取 p,q的交集
subList(1,4) 允许你从较大的列表中创建一个片段,但是对返回列表的所有操作都会反映到初始列表
Collection.addAll(sub) 只能追加到末尾、List.addAll(2,sub) 可以在中间添加
toArray() 将任意的Collection转换为数组,如果数组太小,会新建大小合适的数组。可以指定返回数组的类型(默认是Object)。例如:A[] a = p.toArray(new A[0]);
11.6 迭代器
迭代器是一个对象,它的工作就是遍历并选择序列中的对象,而不必关心该序列的底层结构
11.6.1 Iterator:
只能单向移动
1>iterator():要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
2>next():Itertor自动后移
3>hasNext():Iterator不向后移
4>remove():删除最近返回的元素(即Interator-1指向的元素),而且remove()之前必须先执行next();
5>display(iterator()):显示容器中的所有元素
6>id():显示Iterator的下标
11.6.2 ListIterator:
只能用于访问List容器,可以双向移动。
1> hasPrevious()、it.previous()、it.id()
2> iterator()、iterator(5)
3> it.set(p)
11.7 LinkedList
LinkedList添加了可以使其用作栈、队列、双端队列的方法
1> getFirst()、element()、peek()
都可以返回序列的第一个元素,但是在序列为空时,getFirst()、element()返回异常,而peek()返回null。
2> removeFirst()、remove()、poll()、removeLast()
同上,删除第一个元素,但在序列为空时,removeFirst()、remove()返回异常,而poll()返回空。
3>addFirst()、add()、addLast()
11.7.1 LinkedHashList
LinkedHashList,在使用散列的同时,通过链表来维护元素的插入顺序
11.8 Stack
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用,但是通常都是将LinkedList类封装成栈再来使用(代理)。
1>push()、pop()、peek()
peek()只是返回元素,pop()返回并删除元素
注意:
在使用存在同名类的对象时,可以通过import关键字选择默认使用的类,但是使用它的同名类时就需要使用全名。
11.9 Set
因为Set不允许元素重复,最常使用的就是测试归属性。查找是Set中最重要的操作,因此常常常用的是HashSet。
11.9.1 HashSet
11.9.2 TreeSet
使用红-黑树存储元素
11.10 Map
Map可以和其他对象组合,扩展到多维。
例如:
Map<Person,List<? extends Pet>> petPeople = new HashMap<Person,List<? extends Pet>>();
petPeople.put(new Person("x"),Arrays.asList(new Cat("a"),new Dog("b")));
keySet():产生由petPeople中的所有键组成的Set,它可以用于foreach中遍历Map
例如:
for(People person : petPeople.keySet())
for(Pet pet : petPeople.get(person))
{ }
11.11 Queue
LinkedList提供了方法支持队列的行为,并且实现了Queue接口,因此LinkedList可以用作Queue的一种实现,通过将LinkedList向上转型为Queue。
offer():将一个元素插入到队尾
peek()、element()
poll()、remove()
自动包装机制:
void main(){
Queue<Integer> queue = new LinkedList<integer>();
Queue<Character> qc = new LinkedList<Character>();
int i = 1,char b = 'B';
queue.offer(i);
qc.offer(b);
}
自动包装机制会自动将i转换为queue所需的Integer对象,将b转换为qc所需的Character对象。
11.11.1 Priority Queue
弹出最高优先级的元素,默认是按照ASIIC编码,也可以指定其他的Compator。
例如:
PriorityQueue<Integer> PQ = new PriorityQueue<Integer>(size,Collections.reverseOrder());
// Collections.reverseOrder() 以ASIIC编码的逆序输出。
11.12 Collection和Iterator
Collection是描述所有序列容器的公共性的根接口,如果我们编写的方法接受一个Collection,该方法就可以 应用于任何实现了Collection的类。但是在标准c++类库中并没有其容器的任何公共基类--容器之间的所有共性都是通过迭代器达成的。而Java提供了对这两种方法的支持。
当需要处理的类是Iterable类型时,使用Collection更方便,因为可以使用foreach结构。但是当要处理的类没有实现Collection时,使用Iterator就显得更方便,因为我们不必自己去实现Collection接口。
AbstractCollection(抽象类)是Collection(接口)的默认实现。
注意:
在继承AbstractCollection、Iterator时,需要使用泛型
例如:
class A extends AbstractCollection<B>{ }
return new Iterator<B>{ };
11.13 Foreach与迭代器
foreach语法
11.13.1 适配器
?
第十二章、通过异常处理错误
12.1栈轨迹:
printStackTrace()返回的信息可以通过getStackTrace()获取,这个方法会返回一个由栈轨迹中的元素构成的数组,数组中的每一个元素都是一个栈帧,o是栈顶元素(即抛出异常的函数)
12.2重新抛出异常:
例如:
f() throws Exception
g() throws Exception { try f(); catch(Exception e) throw e; } //将当前异常对象直接抛出
h() throws Exception { try f(); catch(Exception e) throw e.fillInStackTrace() } //接受fillInStackTrace()返回的ThrowTable对象,然后将当前调用栈信息填入原来的的异常对象
void main(){
try g(); catch(Exception e) e.printStackTrace; //栈轨迹: f(),main()
try h(); catch(Exception e) e.printStackTrace; 栈轨迹 f(),h(),main()
}
12.3异常链:
在捕获一个异常后抛出另一个类型的异常,并保留原来异常的值。Throwable类的提供了接受cause对象作为参数的构造器,但是在Throwable的子类中只有三种基本的异常类提供了带cause参数的构造器。分别是:Error、Exception、RuntimeException
如果要把其他类的异常链接起来应该使用initCause()
12.4Java标准呢异常:
异常处理的目的:
用瞬时风格报告错误的语言(如:C)有一个主要的缺陷,就是在每次调用的时候都要进行条件检测。
Throwable用来表示任何可以作为异常被抛出的类,分为两种类型:
Error: 编译时和系统错误
Exception: 可以被抛出的基本类型
12.5 RuntimeException
不受检查的异常,JVM会自动捕获并抛出,不需要在异常说明中申明方法将抛出RuntimeException异常,也不需要使用try。如果没有对该异常进行捕获,则会被传递到main(),并在main()结束时自动调用printStackTrace()
12.6 使用finally进行清理
无论异常是否被捕获,finally语句都会被执行
作用:
1.释放内存
2.使资源恢复初始状态(如:打开的文件或网络链接)
3.和break和continue语句结合使用,可以取代goto语句
4.与return结合使用,再退出时进行清理工作
注意:
finally可能导致异常丢失,如:1.finally中的语句抛出的异常覆盖了原异常,2.finally中直接return导致异常不能继续向上传递。因此在finally中不要使用会抛出异常的语句。
12.7 异常的限制
1.如果在基类方法和接口中定义了同名函数
例如:
class A{ f()throws E1{return;} }
interface B { f()throws E2; g()throws E2; }
class C extends A implements B{
f() throws E2{}
g() throws E3()
}
JDK1.8:
1.继承类中的同名函数如果要抛出异常,那么这个异常必须时所有基类或接口的公共的异常接口(不抛出异常时没有要求),或者新抛出的异常类型是基类异常接口的派生类型(派生的异常对象可以匹配基类的异常对像)。
2.基类构造器的接口不会影响派生类构造器,派生类构造器也无法捕获基类构造器抛出的异常
12.8 嵌套try-catch
有些资源只有在使用完后才被清理(如:打开的文件),但是如果在构造器中抛出了异常,那么这些资源的清理工作就不会执行。但是构造器中抛出异常时,我们不确定对像是否被创建,而且清理工作也会产生异常代码,因此最好的解决办法是使用嵌套的try-catch
12.9 异常匹配
当你不知道怎么处理抛出的异常但是又不想"吞噬异常"(即编写一个空的异常处理函数)时,可以将异常包装到RuntimeException中。
例如:
void throwException(int type){
try{
switch(type){
case 1: throw new e1();
case 2: throw new e2();
default: return;
} }catch {
throw new RuntimeException(e);
}
}
void main(){
throwException(1); //抛出异常时不需要使用try-catch块
try{
throwException(2); //为了获取RuntimeException对象,而抛出异常,并且选择在一个合适的位置进行处理
}catch (RuntimeException re){
try{
throw re.getCouse();
}catch (e1 e){
...
}catch (e2 e){
...
}
}
}
//另一种方法是,创建RuntimeException的子类
第十三章、字符串
13.1 不可变String(只读属性)
String对象是不可变的,每一个看似修改String对象的方法,实际上都是创建了新的String对象。
13.2重载"+"与StringBuilder
"+"的本质是在重载函数中使用StringBuilder类的append对象连接字符串。因此每个"+"都会新建StringBuilder对象。所以在需要频繁拼接字符串时,我们最好是显示的创建StringBuilder对象,然后使用append函数。
注意:
JavaSE5之前使用的是StringBuffer对象,而且StringBuffer是线程安全的,而StringBuilder不是。
13.3 无意识的递归
使用this关键字打印对象的内存地址:
class A {
toString(){
return "this:" + this; // 会产生异常,因为这里会进行自动类型转换,A -> String ,实际上是通过调用this上的toString方法。
return Object.toString; // 以下两种才是负责任的打印地址的方法
return super.toString;
}}
13.4格式化输出
13.4.1 format()
例如:
System.out.format();
PrintStream.format();
PrintWriter.format();
String.format();
13.4.2 Formatter对象
在新建Format对象时,需要给它传递一个参数,指明输出位置。如:
new Formatter(System.out)
new new Formatter( PrintStream/OutputStream/File )
13.4.3 格式化说明符
%[argument_index$][flags][width][.precision]conversion
flags:"-" 用来改变输出的对齐方向(默认是右对齐)
width: 最小尺寸
.precision: 1.支付穿--输出字符的最大长度
2.浮点数--小数后的精确度
3.整数--报错(并不是所有的输出对象都可以使用.precision)
第十四章、类型信息
14.1 运行时类型检测(ETTI)
-->向上转型-->泛化引用
14.2 Class对象
类加载器:生成类的对象
原生类加载器:加载可信类(从本地磁盘加载),例如:API
额外的类加载器:例如:需要在网络上下载类
Class.forName("className"); //创建一个class的引用,需要使用全名
getName() | getSimpleName() | getCanonicalName() //获取( 全名 | 类名 | 全名)
isInterface() | getInterface()
getSuperClass()
newInstance() //虚拟构造器,创建的对象必须带有默认构造器(这里指无参构造器+默认构造器)
例如:
a.getSuperClass().newInstance(); //创建a的基类对象
14.2.1 类字面常量
类字面常量取代了forName(),用于生成对Class对象的引用
类的初始化流程:
1.加载(查找字节码,创建Class对象)
2.链接(为静态域分配空间,并解析类创建所引用的其他类)如:static final = New B();
3.初始化(执行构造函数)
例如:
A{
static final int sf1 = 1; //编译期常量,
static final int sf2 = ramdom(10); //非编译期常量,虽然有static final修饰
static { System.out.print("class A")};
}
B{
static int nof = 2;
static { System.out.print("class B")}; //
}
C{
static int nof = 3;
static { System.out.print("class C")};
}
void main(){
Class a = A.class; //通过类字面常量获取引用,不会引发类的初始化
Class c = Class.forName("C"); //通过forName()获取引用,会引发类的初始化
System.out.print("A: sf1 = " + A.sf1); //访问编译期常量成员,不会引发类的初始化(加载)
System.out.print("A: sf2 = " + A.sf2); //访问非编译期常量,会引发对象初始化(链接、初始化)
System.out.print("B: nof = " + B.nof); //访问静态成员变量,会引发类的初始化
}
14.2.2 泛化的class引用
泛型语法可以让编译器强制执行额外的类型检查
例如:
Class a = int.Class;
Class<Integer> b = int.Class;
a = double.Class;
//! b = double.Class; //error
Class<?> c = intClass; // 等价于 a
Class<? extends Number> d = int.Class;
d = double.Class;
14.2.3 引用转型语法
例如:
A {} B extends A{}
A a = new B();
B b = B.Class.cast(a); // == (B)a , 向下转型
14.4 反射:运行时的类信息
Mathod[] m = [Class].getMethods();
Constructor c = [Class].getConstructors();
14.5 动态代理
代理:
为了提供额外的或不同的工作,而插入的用来代替"实际"对象的对像。
例如:
interface methods{ f(); g(); inter(); }
class real implements methods{ f(){} g(){} inter(){} }
class DynamicProxyHandler implements InvocationHandler{
private Object proxied;
public DynamicProxyHandler(Object proxied){ this.proxied = proxied;}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ //所有的而代理对象的调用都会被重定向到该函数
if(method.getName().equals("inter")) { //用来过滤方法
return method.invoke(proxied,args); //将请求转发给被代理对象,调用被代理对象的处理函数
}
}
}
void main (){
methods m = (methods)Proxy.newProxyInstance(
methods.Class.getClassLoader(), //从已加载的类中获取类加载器
new Class[] { methods.Class }, //被代理的类
new DynamicProxyHandler(new real() ) //InvocationHandler接口的实现类
);
m.f();
m.g();
m.inter(); //只有该函数会被执行
}
14.6 空对象
1.通过空对象可以使用对象的所有成员方法
2.不必每次使用对象时都去判断对象是否为NULL
例如:
public interface Null {}
public class Person{ int a; String b; Person(int a,String b){ this.a = a; this.b = b; } }
public static class NullPerson extends Person implements Null{
super.(0,"None");
public static final Person NULL = new Person(); //单例模式
}
14.7 接口与类型信息
1. 使用向下转型调用继承类函数
例如:
interface A { f();} class B implements{ f(){} g(){} }
void main(){ A a = new B(); (B)a.g(); }
2.使用包访问权限
class C implements A{
protected g1(){}
private g2(){}
g3(){}
}
void main(){
A a = new B();
/!(B)a.g3(); // compile error, conot find symbol ‘C’
reflect(a,"g2"); //反射可以调用,任何类中的任何方法,但是不能修改final域的值
}
static void reflect(Object obj,String MethodName){
Method m = obj.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(a); //动态代理使用的是反射机制
}
14.8 RTTI与多态
多态:派生类对基类的不同实现方法,在向上转型之后,通过基类对象引用调用基类方法,实际上调用的是对象实际类型所指的实现方法,
RTTI: 为了扩张基类的接口,在派生类中定义新方法,在调用时,系统将对引用向下转型为实际的对象类型,然后调用对象所指的方法,
例如:
class A{ f(){ }} class B extends A{ f(){} g(){} }
void main(){
A a = new B();
a.f(); //多态,多态指的是函数的动态绑定
a.g(); //RTTI == (B)a.g(),RTTI指的是类型的自动识别
}
注意:
C++的RTTI 依赖 虚函数列表(首先去调用本类中的方法,没有再去调用父类中的方法),实际上是前期绑定
Java的RTTI 依赖 JVM对Class对象的动态加载,是后期绑定,
注意:
C++中的向上转型,相当于截取了子类中父类所占的那部分内存,但是由于虚函数列表是分布在对象内存的最顶端位置,因此被截取的内存中包含的是子类的虚函数列表,这也就导致了调用基类中的虚函数是,实际上调用的是子类中的函数
第十五章、泛型
15.2.1 元组(数据传送对象,信使
将一组对象打包存储于其中的对象,并且这个容器对象只能读取其中的元素,不能放入新的对象。元组可以具有任意长度,并且元素可以是任意不同的类型。
例如:
public class twoTuple<A,B>{ //可以持有两个对象的二维元组
public final A a;
public final B b;
public twoTuple(A a,B b){this.a = a; this.b = b;}
}
public class threeTuple<A,B,C> extends twoTuple<A,B>{
public final C c;
public threeTuple(A a,B b,C c){ super(a,b); this.c = c;}
}
15.2.2 Java SE5的自动打包和自动拆包功能:
???
15.3 泛型接口
例如;
public interface generator<T>{ T next();}
class A{} class B extends A{} class C extends A{}
public class A_generator implements generator<A>,Iterable<A>{
public Class[] type = {A.class, B.class, c.class }
public A next(){ return (A)type[i].newInstance(); } //注意:类 A 必须是public,并且具有默认构造函数
class A_Iterator implements Iterator<A>{
int count;
public boolean hasNext(){ return count > 0;}
public A next(){ count--; return A_generator.this.next();}
}
public Iterator<A> Iterator(){ return new A_Iterator();}
}
void main(){
A_generator a = new A_generator();
for(int i=0 ; i<5 ; i++ )
gen.next();
for(A a : new A_generator())
System.out.println(a);
}
15.4 泛型方法
1) 可以在泛型类中定义常用的参数化方法,而这个方法所在的类可以是泛型的也可以不是泛型的。
2) 尽量使用泛型方法取代整个类的泛型化
3) static方法必须泛型化,才能访问类型参数的值
例如:
public <T> T f(T x){}
15.4.1 类型参数推断
在使用泛型类时,必须在定义对象时指明类型参数的值。但是在使用泛型函数时,可以不指明。编译器会自动找出具体的类型。
当使用泛型时会产生很多多余的代码,如:Map<Person,List<? extends Pet>> p = new Map<Person,List<? extends Pet>>();
但是通过泛型我们可编写一个工具类,简化容器的创建。
例如:
pubic class New{
public static <T> List<T> list(
return new List<T>;
)}
void main(){
List<String> = new New.list(); //类型推断只对赋值操作有用
f(New.list()); //compile error,编译器认为:调用泛型方法后,其返回值被赋给了一个Object类型的变量
}
15.4.2 泛型方法的显示类型说明。
例如:
new.<A,B>f();
注意:
如果使用static方法,必须在点操作符之前加上类名
15.4.3 泛型方法与可变形参
<T> T f(T... args){ }
15.4.4 泛型的类型擦除
List<Integer>.getClass() == List<String>.getClass() // true, List = List
Class.getTypeParameters() // 返回一个TypeVariable对象数组,表示有泛型声明所声明的类型参数,但实际返回的却是参数的占位符
例如:
List<Integer>.getTypeParameters() // return [T]
Map<Integer,String>.getTypeParameters // return [K,V]
15.4.4 C++的模板
template<class T> class A{ T t; A(T x){t = x} f(){ t.g();} };
class B{ g(){} };
void main(){ B b; A<B> a(b); b.f(); }
注意:
C++编译器在实例化类模板时,会进行类型检查,判断B中是否有g()。因此C++在编译时,就知道了类型参数的具体类型。但是由于擦除的原因,Java无法知道参数类型所指的具体类型。因此编译不能通过
因此需要通过extends 给泛型类指定边界。如:class A<T extends B>{ ... };
15.4.5 迁移兼容性
在Java的早期没有泛型的概念,Java的类库使用的都是类型参数,应此在向泛型类库迁移的过程中,必须保持对非泛型类库的兼容性。因此引入擦除的概念就是为解决泛型代码与非泛型代码之间的兼容性问题。
原理:
泛型类型只有在静态类型检查期间才出现,在此之后,程序的所有泛型都会被擦除,替换为他们的非泛型上界。
List<T> --> list | T(类型变量) --> Object
15.4.6 擦除的问题
1.使用泛型并不是强制的,
例如:
class A{ set(T t){} }
class A1<T> extends A<T>()
class A2 extends A{} //no warning
class A3 extends A<?>{} //error,unexpected type found : ?
void main(){
A2 a2 = new A2();
a2.set(Object); //warning here
}
2.表示没有意义的事物
class A{
Class<T> kind;
A(Class<T> t){this.kind = t;}
T[] create(int size){
return (T[])A.newInstance(kind,size); //Class<T>被擦除为Class,实际上并没有记录任何类型信息,因此需要类型转换
}
}
3.在进入泛型对象时会进行类型检查,在离开泛型对象时,编译器会自动插入类型转换代码(不必显示指定),这也就保证了类型的内部一致性。
4.在泛型对象中任何在运行时需要知道确切类型信息的操作都将无法执行。
例如:
class A<T>{
static void f(){
if(org instanceof T){} //error,T不是被擦除为Object吗????
T var = new T(); //error
T array = (T)new Object[10] //unchecked warning
}
}
15.5 擦除的补偿
1.引入类型标签进行擦除补偿
例如:
class A{
Class<T> kind;
A(Class<T> t){this.kind = t;}
boolean f(Object obj){
return kind.isInstance(obj); //使用动态的isInstance()替代instanceof
}
}
2.创建类型实例
例如:
class A<T>{ T x; A(Class<T> kind){ x = kind.newInstance(); }
}
class B{}
void main(){
A<B> a1 = new A<B>(B.class);
A<Integer> a2 = new A<Integer>(Integer.class); //Integer没有默认构造函数,会出现运行时错误
}
3.使显示工厂创建实例
interface FactoryI<T>{ T create(); }
class Factory {
private T x;
public <F extends A<T>> Factory(F f){
x = f.create();
}
}
class IntegerFactory implements FactoryI<Integer>{
public Integer create(
return new Integer(0);
)
}
class E{
public static class F implements FactoryI<E>{
public E create(){
return new E();
}
}
}
void main (){
new Factory<Integer>(new IntegerFactory());
new Factory<E>(new E.F())
}
4.使用模板方法创建实例
abstract class A<T>{
final T x;
A(){ x = create();}
abstract T create();
}
class B{}
class Creator extends A<B>{
B create(){ return new B;}
void f(){ x.toString();}
}
void main(){
Creator c = new Creator();
}
15.6 泛型数组
对象数组可以转换为T,但是对象数组不可以转换为T[]
泛型数组在编译时会进行类型检查,但是在运行时都是Object[]
public class A<T>{}
void main{
static A<Integer>[] a1 = (A<Integer>[])new Object[100]; //compile error, A[] = Object[]
static A<Integer>[] a2 = (A<Integer>[])new A[100]; //A[],编译时会产生警告
a2[0] = new A<Integer>(); //A[] = Object,向下转型是隐式的
a2[1] = new Object(); //compile error,在编译时发现类型不匹配,虽然在运行时数组的实际类型是Object[]
a2[2] = new A<double>(); //compile error,在编译时发现类型不匹配,Object[] = A(即使能运行时,也会有错误)
}
解决办法:
先将数组转换为 T(任意类型)人,然后再将 T转换为具体类型
15.6 边界
class A<T extends E & F> extends B<T>{}
15.7 通配符
class A{}
class B extends A{}
class C extends A{}
void main(){
A[] a = new B[10];
a[0] = new B();
a[1] = new A(); //ArrayStoreException,编译时 a是A类型的引用,但是运行时的数组机制(RTTI)知道它处理的时B[],因此只接受B类型及其子类型。数组对象可以保留有关它们包含的对象类型的规则。
a[2] = new C(); //ArrayStoreException
}
holder<A> = holder<B> //泛型对象不能向上转型,
holder<?extends A> = holder<B> //ok
T get() // return A,
set(T) //error: ?extends A,意味着它可以是任何事物,而编译器无法保证它的安全性
equals(Object) //ok
15.8.1 逆变
超类型通配符:
<? super Myclass> //set(T)不允许任何的写入操作,<? extends A>,只允许写入A及其子类型
<? super T>
<T super myClass> //error
总结:
set(T) //执行严格类型检查,只允许T类型写入
get(T) //只能读取T类型
set(<? extends T>) //不能写入任何类型,包扩T
get(<? extends T>) //读取T及其子类型
set(<? super T>) //可以写入T及其子类型
get(<? super T>) //不可以返回任何类型
https://www.cnblogs.com/lucky_dai/p/5485421.html
15.8.2 捕获转换
如果向一个使用<?>的方法传递原生类型,编译器可以推断出实际的参数
f(A<T> a)
g(A<?> a){f(a);}
15.9 问题
1.泛型不能应用于基本类型,虽然自动包装机制,可以实现int和Integer的双向转换,因此List<Integer>就相当于List<int>,但是自动包装机制不能应用于数组。
2.实现参数化接口: 一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。
3.转型警告: 对象的强制类型转换可能会产生警告,可以使用泛型类来转型,如: List<A> la = List.class.cast(T);
4.继承: 由于擦除重载的方法将产生相同的类型签名。如:f(List<A> a) f(List<B> b)
5.
15.10 自限定类型
class SelfBounded<T extends SelfBounded<T>>{} //基类用导出类替代其参数,泛型基类变成了其所有导出类的公共功能的模板
class A extends SelfBounded<A>{}
class B extends SelfBounded<A>{}
class D{}
class E extends SelfBounded<D>{} //error,不能使用不是SelfBounded类型的参数
class F extends SelfBounded{} //自限定用法不是强制执行的
15.10.1 参数协变
协变参数类型: 方法参数类型会随子类而变化
15.11 动态类型安全
checkedCollection()、checkedList()、checkedMap、checkedSet()、checkedSortedMap()、checkedSortedSet()
受检查的容器在你试图插入类型不正确的对象时抛出 ClassCastException ,而原生容器在你从容器中取出对象时才报出异常。
例如:
List<Dog> dog = Collection.checkedList(new ArrayList<Dog>(),Dog.Class);
15.12 异常
Catch语句不能捕获泛型类型的异常,在编译期和运行期都必须知道异常的确切的类型。泛型也不能直接或间接继承Throwable。
15.13 混型
C++的多继承可以很容易实现混型,而且C++可以记住模板的参数类型。但是Java的擦除会忘记基类类型,Java可以通过接口实现混型,也可以通过组合实现混型。
与动态类型混合:
。。。。太难了!!
15.14 潜在类型机制
只要求实现某个方法的子集,而不是某个特定类或接口,可以横跨类继承结构,调用不属于某个公共接口的方法,从而产生更加泛化的代码。如:Paython、c++
Java可以使用反射实现更加泛化的代码
15.15 策略设计模式
。。。太太难了!!!
16.5 数组与泛型
A<B>[] a1 = new A<B>[10] //error,不能实例化具有参数化类型的数组,擦除机制会移除参数类型信息,但是数组必须知道它们的确切类型。
List[] a2 = new Lis[10] //
A<B>[] ls //可以定义参数化类型的数组引用
a3 = (List<B>[])ls //可以创建非泛型数组,然后将其转型为泛型数组
16.6 数组操作方法
equals() //Object对象实现了equals方法,我们可以直接使用
comparaTo() //继承自Comparable接口,接受一个Object参数,与其自身进行比较
Arrays.sort(); //如果没有实现Comparable接口,会抛出ClassCastException ,因为sort()需要把参数的类型转变为Comparable
Arrays.binarySearch(); //对已排序的数组进行查找,如果使用Comparator排序了数组,则必须提供同样的Comparator。
注意:
由于数组与泛型的搭配有诸多限制,我们应该尽量使用容器而不是数组
第十七章、容器深入探究
17.1 容器的分类
见图。。
17.2 可选操作
1.Collection接口中的插入和移除操都是可选的,可选接口在实现类中的定义没有强制要求。(本质就是函数的在基类中被定义为只抛出异常而不执行其他操作,继承类不使用这个接口时可以不定义,如果要使用这个接口,必须要自己实现这个接口。否则调用的就是基类抛出异常的方法)
2.可选操作的unSupportException异常抛出是在运行阶段
3.最常见的未获支持的操作,都源于背后由固定尺寸的数据结构支持的容器。
例如:
Arrays.asList(); //返回的list,底层上是一个固定大小的数组,仅支持不会改变数组大小的操作(任何对修改底层数据结构尺寸的方法都会产生unSupportException)。
17.3 容器实现类型
List:
ArrayList :底层由数组支持
LinkedList :底层由双向链表实现
Set:
HashSet :快速查找,元素必须被定义hashcode()(Object中的方法)
TreeSet :保持次序,元素必须实现Comparable接口,元素的顺序取决于ComparableTo()的实现,基于TreeMap
LinkedHasSet :HashSet的查找速度,且内部使用链表维护元素的顺序(插入顺序)
注意:
return i - j; //当i为正数,j为负数时,可能会产生溢出而返回负数。
return i < j ? -1 : ( i == j ? 0:1)
Queue:
双向队列: :Java没有提供用于双向队列的接口,LinkedList中包含支持双向队列的方法,因此我们可以通过代理的方式创建双向队列。
Map:
HashMap :使用HashCode进行快速查询,感觉象是对散列码进行排序后,进行快速查找。
TreeMap :基于红黑树的实现
LinkedHashMap
WeakHashMap :弱键映射,允许释放映射所指向的对象,如果映射之外没有引用指向某个"键",则这个键可以被回收
ConcurrentHashMap :线程安全的Map,没有同步加锁
IdentityHashMap :使用 == 取代 equals() 对键进行比较
注意:
Object实现的 hashCode(),默认是使用对象的地址计算散列码的,而equals()默认是使用对象的地址进行比较。因此如果你要判断当前的键是否存在于表中,就必须覆盖hashCode()、equals()。
散列码:
hashCode()计算的整数作为保存键值的数组下标。当散列码产生冲突时由外部链接处理(数组中的值为list)
剖析器:
性能分析工具。
17.4 Collection或Map的同步控制
Collections类可以自动同步整个容器
synchronized关键字:
new Collections.synchronizedList();
new Collections.synchronizedSet();
new Collection.synchronizedSortedSet();
new Collections.synchronizedMap();
快速报错机制:
当一个程序对另一个程序正在使用的容器进行修改时,抛出ConcurrentModificationException
例如:
Iterator<String> it = collection.iterator();
collection.add("any");
try{ String s = it.next(); //容器在取得迭代器之后,又修改了容器中的内容
catch(...){...}
17.5 持有引用
Reference类:
如果想继续持有对某个对象的引用,但是又允许垃圾回收期在内存耗尽时释放它。就需要使用Reference对象作为你和普通引用之间的媒介(代理)。
SoftReference类:
用来实现内存敏感的高速缓存
WeakReference类:
为“规范映射”而设计,它不妨碍垃圾回收器回收映射的“键”或“值”。“规范映射”中对象的实例可以在程序的多出被同时使用,以节省存储空间。
Phantomreference类:
调度回收前端清理工作
17.5.1 WeakHashMap
用来保存WeakReference,每个值只保存一份实例,但是添加的元素没有特别的要求,映射会自动使用WeakReference包装他们.
17.6 BitSet 和 EnumSet
BitSet : 存储大量的标志信息,最小的容量时Long(64位).支持扩容
EnumSet : 按照名字而不是数字的位置进行操作
第十八章、Java I/O系统
第二十一章、并发
21.1 基本线程机制
Java的线程机制基于来自C的低级的p线程方式
21.1.1 线程创建
1.Runnable()接口
实现Runnable接口并编写run()方法
例如:
public class Liftoff implements Runnable{
public void run(){
while(...){
...
Thread.yield(); //表示线程已执行完生命周期最重要的部分,可以切换给其他任务执行。但这只是对县城调度器的一种建议。
}}
}
2.Thread类
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器
例如:
Thread t=new Thread(new Liftoff()); t.start();
new Thread(new Liftoff()).start();
3.Executor(执行器)
管理Thread对象
例如:
ExecutorService exec=Executors.newCachedThreadPool(); //为每个任务分配一个线程,回收旧线程时停止创建新线程()
exec=Executors.newFixedThreadPool(5); //使用有限的线程数量
exec=SingleThreadExecutor //单线程,用于长期存活的任务或执行同步线程,如:监听套接字,多个任务时,排队执行(悬挂任务队列)
exec.execute(new Liftoff());
exec.shutdown(); //等待线程执行完成,并拒绝提交新线程
4.Callable接口
使用Callable接口从任务中产生返回值
例如:
class TaskWithResult implements Callable<String>{
public String cal1(){return ...} //接受返回值
}
void main(){
ExecutorService exec=Executors.newCachedThreadPool();
ArrayList<Future<String>> results =new ArrayList<Future<String>>();
results.add(exec.submit(new TaskwithResult(i)));
}
注意:
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化,可以用isDone()方法来查询Future是否已经完成,直接调用get()可能产生阻塞。
21.1.2 休眠
例如:
Thread.sleep(180); //o1d-style:
TimeUnit.MILLISECONDS.sleep(180); //Java SE5/6-style:
注意:
对sleepO的调用可以抛出InterruptedException异常,可以在run()中被捕获。但异常不能跨线程传播回main()。
21.1.3.优先级
getPriority() //读取现有线程的优先级
setPriority() //修改...
例如:
Thread.currentThread().setPriority(priority): //通过调用currentThread()来获得对驱动该任务的Thread对象的引用
注意:
尽管JDK有10个优先级,但它与多数操作系统都不能映射得很好。比如,Windows有7个优先级且不是固定的,所以这种映射关系也是不确定的。Sun的Solaris有2^31个优先级。
唯一可移植的方法是当调整优先级的时候,只使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三种级别。
721.1.4 让步
Thread.yield();
21.1.5 后台线程
当所有的非后台线程结束时,意味着程序运行结束,会杀死进程中的所有后台线程(即使finally中的程序也可能不会被执行)。
后台线程所创建出来的子线程,默认是后台线程。
例如:
Thread daemon=new Thread(new SimpleDaemons());
daemon.setDaemon(true); //Must call before start()
daemon. start();
21.1.6 加入线程
如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为假)。
可以在调用join()时带上一个超时参数。
对join()方法的调用可以被中断,做法是在调用线程上t调用interrupt()方法,这时需要用到try-catch子句。
例如:
class Sleeper extends Thread{...}
class Joiner extends Thread{
void run(){
try{异常被捕获时将清理这个标志
sleeper.join(); //sleeper可能定期返回也可能被中断
}catch(InterruptedException e){ //
print(getName() + "isInterrupted():" + isInterrupted()); //判断终端标志,但是异常被捕获时将清理中断标志,因此总为false
}
}
}
void main(){
...
sleeper.interrupt(); //将给sleeper线程设定一个标志,表明该线程已经被中断
}
21.1.7 线程组
线程组持有一个线程集合。一般不建议使用
21.1.8 捕获异常
异常逃出任务的run()方法,它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。
在JavaSE5之前,你可以使用线程组来捕获这些异常,但是有了Java SE5,就可以用Executor来解决这个问题,因此你就不再需要了解有关线程组的任何知识了。
例如:
Thread.UncaughtExceptionHandler是JavaSE5中的新接口,它允许你在每个Thread对象上都附着一个异常处理器。
Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。
例如:
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
public void uncaughtException(Thread t, Throwable e){
System. out. println("caught"+e);
}
}
class HandlerThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r){
Thread t= new Thread(r);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); //为每个线程设置不同的异常处理器
System.out.println("eh="+t. getUncaughtExceptionHandler());
return t;
}
}
void main(){
//Thread. etDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); //如果处处使用相同的异常处理器,可以将这个处理器设置为默认的未捕获异常处理器
ExecutorService exec=Executors.newCachedThreadPool(new HandterThreadFactory());
exec.execute(new ExceptionThread2());
}
21.2 序列化访问共享资源
21.2.1 synchronized关键字 (内建锁)
共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口。要控制对共享资源的访问,得先把它包装进一个对象。然后把所有要访问这个资源的方法标记为synchronized。
如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。
例如:
synchronized void f(){...}
synchronized void g(){...}
注意:
1.必须要将域设置为private。否则,synchronized关键字就不能防止其他任务直接访问域。
2.一个任务可以多次获得对象的锁。JVM负责跟踪对象被加锁的次数。
3.针对每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
21.2.2 使用显示的Lock对象
使用synchronized关键字时,无法进行异常处理,显式的Lock对象,可以使用finally子句将系统维护在正确的状态。
例如:
private Lock lock =new ReentrantLock();
public int next(){
1ock.1ock();
try{
...
return ...; //return语句必须在try子句中出现,以确保unlock0不会过早发生,从而将数据暴露给了第二个任务。
}finally{
1ock. unlock(); //
}
}
21.2.3 concurrent类库
用synchronized关键字不能尝试着获取锁且最终获取锁会失败,或者尝试着获取锁一段时间,然后放弃它。实现这些,要你必须使用concurrent类库。
例如:
public class AttemptLocking{
private ReentrantLock lock=new ReentrantLock();
public void untimed(){
boolean captured=1ock.tryLock();
//captured = Lock.trylock(Z,TimeUnit.SECONDS);
try{
System.out.println("tryLock():"+captured);
} finally{
if(captured)
1ock.unlock();
}
}
21.2.3 原子性与易变性
基本类型的读取和写入属于原子操作,但是long和double除外,JVM会将64位的读取和写入当做两个分离的32未操位执行(字撕裂)。
可视性:
在多处理器系统中,一个任务的修改对其它可能是不可视的(例如,修改只是暂时性地存储在本地处理器的缓存中,或程序从缓冲中读取数据,但实际数据已发生改变)
volatile 关键字:
1.保证可视性,所有的读取和修改都发生在内存中。
2.是long和double类型获得简单赋值与返回操作的原子性。
3.当一个域的值依赖于它之前的值时(例如递增一个计数器),volatile将无法工作。
4.告诉编译器,不要执行任何移除读取和写入操作的优化
例如:
i++,i+=2在c++中可能是原子性的(取决于编译器和处理器),但是在Java中一定不是原子性的(每条指令都会产生一个get和put)。
public class SeriallumberGenerator{
private static volatile int serialNumber=0;
public static int nextSerialNumber(){
return serialNumber++; // Not thread-safe
}
}
注意:
同步也会导致向主存中刷新,因此如果一个域完全由synchronized方法或语句块来防护,那就不必将其设置为是volatilef的。
21.2.4 原子类
Java SE5引入了诸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类
21.3.5 临界区
synchronized(syncObject){ //同步控制块,在进入此段代码前,必须得到syncObject对象的锁
//This code can be accessed
// by only one task at a time
}
1ock.1ock(); //使用显式的Lock对象来创建临界区:
try{
...
}finally{
1ock. unlock();
}
注意:
synchronized块必须给定一个在其上进行同步的对象。如:synchronized(this),如果获得了synchronized块上的锁,那么该对象其他的synchronized方法和临界区就不能被调用了。
21.3.6 线程本地存储(ThreadLocal类)
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。
例如:
public class ThreadLocalVariableHolder{
private static ThreadLocal<Integer> value=new ThreadLocal<Integer>(){
protected synchronized Integer initialValue(){
return rand.nextInt(10000);
}
};
public static void increment(){
value.set(value. get()+1); //get()返回与其线程相关联的对象的副本,set()将参数插入到为其线程存储的对象中,并返回存储中原有的对象
}
}
21.3 终结任务
21.3.1 任务取消
public class PrimeGenerator implements Runnable {
private volatile boolean cancel = false; //class Generator
run(){ while(!cancel){...} }
}
new Thread(generator).start(); // main()
try{
SECONDS.sleep(1);
}finally {
generator.cancel(); //cancelled = true,cance1方法由finally块调用,来保证即使在调用sleep被中断的情况下,素数生成器也能被取消。
}cancelled = false
21.3.1 在阻塞时终结
如果run()中调用了阻塞方法,那么任务永远不能被取消,因为永远不会执行cancel标志的判断。
1.阻塞-唤醒
sleep()、wait() - notify()、notifyAll()、signal()、signalAll()
2.阻塞状态下的中断
1.Thread类包含interrupt()方法,这个方法将设置线程的中断状态。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException。
当抛出该异常或者该任务调用Thread.interrupted()时,中断状态将被复位。正如你将看到的,Thread.interrupted()提供了离开run()循环而不抛出异常的第二种方式:while(!Thread.interrupted())。
2.Executor上调用shutdownNow(),那么它将发送一个interrupt0调用给它启动的所有线程
3.submit()会返回一个泛型Future<?>,它可以持有该任务的上下文,Future.cancel(true)赋予了Funture停止线程的权限。
4.sleep()可以被中断,但是不能中断正在试图获取synchronized锁或者试图执行I/O操作的线程。解决方法是,释放阻塞资源。而且nio类提供的nio通道可以自动的响应中断。
5.持有synchronized锁的方法可以调用其他synchronized方法。
6.interrupt()可以打断被互斥所阻塞的调用。
3.检查中断
所有需要清理的对象的创建操作后面,都必须紧跟try-finally子句,从而使得无论run()循环如何退出,清理都会发生。
例如:
run(){
...
NeedCleanup n1 = new NeedCleanup();
try{
...
NeedCleanup n2 = new NeedCleanup();
try{
...
}finally{
n2.clean();
}
}finally{
n1.clean();
}
}
21.4 线程协作
注意:
sleep()、yield()不会释放锁。
wait()会释放锁,但会产生忙等待(执行空循环,不断判断状态是否满足)
wait()、notify()、notifyAll()都属于Object类而不是Thread类,因为这些方法操作的锁是所有对象的一部分。但是如果在非同步控制方法或同步控制块里调用这些方法,会产生IllegalMonitorStateException。
例如:
synchronized(x){
x.notifyAl1(); //如果要向对象x发送notifyAll0,那么就必须在能够取得x的锁的同步控制块中这么做
}
21.4.1 使用notify0/waitO或notifyAll0/waitO进行协作
例如:
class Car{
private boolean waxOn=false;
public synchronized void waxed(){
waxOn= true; // 涂蜡完成
notify(); //因为只会有一个任务处于等待状态,因此可以是使用notify()替代notifyAll()
}
public synchronized void buffed(){
waxOn=false; // 抛光完成
notify();
}
public synchronized void waitForwaxing() throws InterruptedException{
while(waxOn==false) // 等待涂蜡完成
wait();
}
public synchronized void waitForBuffing() throws InterruptedException{
while(waxOn==true) // 等待抛光完成
wait();
}
}
class Waxon implements Runnable{
private Car car;
public Waxon(Car c){ car=c;)
public void run(){
try{
while(! Thread. interrupted()){
printnb("Wax On!"):
TimeUnit.MILLISECONDS.sleep(200);
car.waxed();
car.waitForBuffing(); // 等待抛光完成
}
} catch(InterruptedException e){
print("Exiting via interrupt");
print("Ending Wax On task");
}
}
class Maxoff implements Runnable(
private Car car;
public Waxoff(Car c){ car=c;}
public void run(){
try{
while(!Thread. interrupted()){
car.waitForwaxing(); // 等待涂蜡完成
printnb("Wax Off!");
TimeUnit.MILLISECONDS.sleep(200);
car.buffed();
}
) catch(InterruptedException e){
print("Exiting via interrupt");
print("Ending wax off task");
}
}
}
public static void main(String[] args) throws Exception{
Car car=new Car();
ExecutorService exec=Executors.newCachedThreadPool();
exec.execute(new Waxoff(car));
exec.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(5): //Run for a while...
exec.shutdownNow(); //Interrupt all tasks
}
21.4.2 信号错失:
while( condition = true){ //如果condition在进入while后发生改变,就会错失信号,而进入睡眠,原因是对condition的竞争
synchronized(sharedMonitor){
sharedMonitor.wait();
}
}
解决办法:
synchronized(sharedMonitor){
while(someCondition)
sharedMonitor.wait();
}
21.4.3 notify()与notifyAll()
x.notify() 唤醒一个等待x锁的线程
x.notifyAll() 唤醒等待x锁的所有线程
21.4.4 生产者与消费者
例如:
class WaitPerson implements Runnable{
private Restaurant restaurant;
public WaitPerson(Restaurant r){ restaurant=r;}
public void run(){
try{
while(!Thread.interrupted()){
...
synchronized(this){
while(restaurant.meal==null)
wait(); //等待生产者
}
...
synchronized(restaurant.chef){
restaurant.meal=nul1;
restaurant.chef.notifyAll(); //唤醒生产者
}
...
}
) catch(InterruptedException e){...}
class Chef implements Runnable{
private Restaurant restaurant;
private int count=0;
public Chef(Restaurant r){ restaurant=r;}
public void run(){
try{
while(!Thread.interrupted()){
...
synchronized(this){
while(restaurant.meal!=nul1)
wait(); //等待消费者
if(++count ==10){
print("Out of food, closing");
restaurant.exec.shutdownNow();
}
}
...
synchronized(restaurant.waitPerson){
restaurant.meal=new Meal(count);
restaurant.waitPerson.notifyAl1(); //唤醒消费者
}
...
}
} catch(InterruptedException e){...}
}
}
public class Restaurant{
Meal meal;
ExecutorService exec=Executors. newCached ThreadPool();
WaitPerson waitPerson=new WaitPerson(this);
Chef chef=new Chef(this);
public Restaurant(){
exec.execute(chef);
exec.execute(waitPerson);
}
}
21.4.5 使用显式的Lock和Condition对象
在Java SE5的java.util.concurrent类库中还有额外的显式工具可以用来重写WaxOMatic.java。
使用互斥并允许任务挂起的基本类是Condition,你可以通过在Condition上调用awaitO来挂起一个任务。
当外部条件发生变化,意味着某个任务应该继续执行时,你可以通过调用signal0来通知这个任务,从而唤醒一个任务,或者调用signalAll0来唤醒所有在这个Condition上被其自身挂起的任务(与使用notifyAlO相比,signalAllO是更安全的方式)。
例如:
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
同上上,使用lock()与unlock()替换synchronized,使用condition.signal()替换notify()
21.4.6 使用同步队列来解决任务协作问题
同步队列在任何时刻都只允许一个任务插入或移除元素。
在java.util.concurrent.BlockingQueue接口中提供了这个队列,这个接口有大量的标准实现。
通常可以使用LinkedBlockingQueue,它是一个无界队列,还可以使用ArrayBlockingQueue,它具有固定的尺寸,因此你可以在它被阻塞之前,向其中放置有限数量的元素。
例如:
class LiftOffRunner implements Runnable{
private BlockingQueue<Liftoff> rockets;
public LiftOffRunner(BlockingQueue<LiftOff>queue){ rockets=queue; }
public void add(Liftoff lo){
try{
rockets.put(1o);
} catch(InterruptedException e){...}
}
public void run(){
try{
while(!Thread.interrupted()){
Liftoff rocket = rockets.take();
rocket.run(); //通过显示的调用run()而是用自己的线程来运行,而不是启动新线程
}catch(InterruptedException e){...}
}
21.4.7 任务之间使用管道通信
管道基本上是一个阻塞队列,存在于多个引入BlockingQueue之前的Java版本中。
例如:
class Sender implements Runnable {
private Random rand = new Random(47);
private PipedWriter out = new Pipedwriter();
public Pipedwriter getPipedwriter(){ return out; }
public void run(){
try {
while(true)
for(char c='A'; c <= '2'; c++){
out.write(c);
TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
}
) catch(IOException e){...}
}
}
class Receiver implements Runnable {
private PipedReader in;
public Receiver(Sender sender) throws IOException{
in = new PipedReader (sender.getPipedWriter());
public void run(){
try {
while(true)
printnb("Read: "+ (char) in.read() +", "); //管道中没有数据时读阻塞
} catch(IOException e) {...}
}
}
}
public class PipedIo {
public static void main(String[] args) throws Exception {
Sender sender =new Sender();
Receiver receiver = new Receiver (sender);
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute (sender);
exec.execute (receiver);
TimeUnit.SECONDS.sleep(4);
exec.shutdownNow();
}
}
21.6 死锁
死锁条件:
资源互斥访问、持有等待、非抢占、循环等待。防止死锁最容易的方法是破坏第4个条件
21.7 新类库中的构件
Java SE5的java.util.concurrent引入了大量设计用来解决并发问题的新类。
21.7.1 CountDownLatch
它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用waito的方法都将阻塞,直至这个计数值到达0。其他任务在结束其工作时,可以在该对象上调用countDown0来减小这个计数值。
CountDownLatch被设计为只触发一次,计数值不能被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarriet
例如:
class TaskPortion implements Runnable {
private final CountDownLatch latch;
TaskPortion (CountDownLatch latch){
this.latch = latch;
}
public void run(){
try {
doWork(); //执行不同的初始化任务
latch.countDown();
} catch(InterruptedException ex){...}
}
class WaitingTask implements Runnable {
private final CountDownLatch latch;
WaitingTask(CountDownLatch latch) {
this.latch=latch
}
public void run(){
try {
latch.await(); //等待所有的初始化任务执行完成
...
} catch(InterruptedException ex){...}
}
}
void main(){
ExecutorService exec= Executors.newCachedThreadPool ():
CountDownLatch latch =new CountDownLatch(10);
for (int i=o: i< 10; i++){
exec.execute(new WaitingTask(latch));
for (int i =e; i < 10: i++)
exec.execute (new TaskPortion(latch));
exec.shutdown();
}
21.7.2 CyclicBarrier
CyclicBarrier非常像CountDownLatch,只是CountDownLatch是只触发一次的事件,而CyclicBarrier以多次重用。
21.7.3 DelayQueue
这是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。
这种队列是有序的,即队头对象的延迟到期的时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll()将返回null(正因为这样,你不能将nulit置到这种队列中)。
21.7.4 PriorityBlockingQueue
这是一个很基础的优先级队列,它具有可阻塞的读取操作
例如:
class PrioritizedTask implements unnable, Comparable<PrioritizedTask> { //定义要执行的任务
private final int priority;
protected static List<PrioritizedTask> sequence=new ArrayList<PrioritizedTask> ();
public PrioritizedTask(int priority) {
this.priority =priority;
sequence.add(this); //保存创建序列,用于和实际的执行顺序比较
}
public int compareTo(PrioritizedTask arg) { //自定义比较方式,用于取出任务时的优先级比较
return priority <arg.priority ? 1:(priority > arg.priority ?-1:0);
}
public void run() { ... } //print and sleep
public static class EndSentinel extends PrioritizedTask ( //内部类
private ExecutorService exec;
public EndSentinel(ExecutorService e){
super(-1); // Lowest priority in this program
exec = e;
}
public void run() {
for (PrioritizedTask pt: sequence) { ... } //遍历队列,输出队列的创建顺序
exec.shutdownNow();
}
}
class PrioritizedTaskProducer implements Runnable {
private Random rand = new Random (47);
private Queue<Runnable> queue;
private ExecutorService exec;
public PrioritizedTaskProducer{Queue<Runnable> q,ExecutorService e) {
queue = q;
exec = e; //Used for EndSentinel,同main()中的exec
}
public void run(){ //向队列中添加任务
for(int i=; i<20; i++){
queue.add(new PrioritizedTask(rand.nextInt(10)));
Thread.yield();
}
try {
for(int i = 0; i< 10; i++) {
TimeUnit.MILLISECONDS.sleep(250);
queue.add(new PrioritizedTask(10));
}
for(int 1 = 0; i< 10; i++)
queue.add(new PrioritizedTask(i));
queue.add(new PrioritizedTask.EndSentinel(exec));
} catch (InterruptedException e) {...}
}
}
class PrioritizedTaskConsumer implements Runnable {
private PriorityBlockingQueue<Runnable> q;
public PrioritizedTaskConsumer(PriorityBlockingQueue<Runnable> q){
this.q =q;
}
public void run() {
try {
while(!Thread.interrupted())
q.take().run(); //从队列中取出任务执行并使用当前线程执行
} catch(InterruptedException e){...}
}
}
public static void main(String[] args) throws Exception {
Random rand = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>(); //优先级队列,并且队列的阻塞属性,不需要我们去实现同步
exec.execute(new PrioritizedTaskProducer(queue,exec));
exec.execute(new PrioritizedTaskConsumer(queue));
}
21.7.5 ScheduledThreadPoolExecutor
通过使用schedule()(运行一次任务)或者scheduleAtFixedRate()(每隔规则的时间重复执行任务),你可以将Runnable对象设置为在将来的某个时刻执行。
21.7.6 Semaphore
正常的锁(来自concurrent.locks或内建的synchronized锁)在任何时刻都只允许一个任务访问一项资源,而计数信号量允许n个任务同时访问这个资源。