Java编程思想阅读笔记

第一章(对象导论)

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello every one,I'm cpacm");
}
}

这一章是java的整体介绍,让我们先熟悉了java是什么。其具体的内容都会在后面介绍。

面向基本语言的五大特性

  • 万物皆为对象。
  • 程序是对象的集合,它们通过发送消息来告知彼此所要做的。
  • 每个对象都拥有其类型。
  • 某一特定类型的所有对象都可以接收同样的消息。

第二章(一切都是对象)

一、存储

栈(堆栈):存放基本类型变量和对象引用变量。位于RAM区
堆:存放new得到的对象和数组。也位于RAM区
常量存储:存放常量,包括静态变量。

二、基本类型

基本数据类型在没有初始化的时候会获得一个默认值。

基本数据类型 默认值 大小
boolean false 未确定
char null 16bits
byte 0 8bits
short 0 16bits
int 0 32bits
float 0f 32bits
long 0L 64bits
double 0d 64bits

tip1:String不是基本数据类型
tip2:上面的初始默认值并不适用于方法内部变量。其默认初始化值未知。当使用未初始化的变量编译器会返回错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BaseType {
static boolean b;
static char c;
static byte bt;
static short s;
static int i;
static float f;
static long l;
static double d;

public static void main(String[] args) {
System.out.println("类变量——"+"boolean:"+b+" char:"+c+" byte:"+bt+" short:"+s+" int:"+i+" float:"+f+" long:"+l+" double:"+d);
}

}
//类变量——boolean:false char: byte:0 short:0 int:0 float:0.0 long:0 double:0.0

第三章(操作符)

一、别名现象

当两个变量包含的是同一个引用时,修改其中一个变量的值另一个变量的值也同时改变。记住new出来的对象的=赋值都是只传递引用。
Tip:减少为对象赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class T{
int i;
}

public class Assigment {

public static void main(String[] args) {
T t1 = new T();
T t2 = new T();
t1.i = 5;
t2.i = 8;
t1 = t2;
t1.i = 10000;
System.out.println(t2.i);
}

}
//10000

二、equals方法

在自定义的对象中使用equals方法时需要覆盖此方法,否则默认是比较引用

三、短路

其现象本质为:当已经确定一个逻辑表达式的结果时不会再计算剩余的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ShortCircuit {

public static void main(String[] args) {
// TODO Auto-generated method stub
boolean flag1 = test1()&&test2()||test3()&&test4();
System.out.println("\n");
boolean flag2 = test1()&&test3()||test2()&&test4();
}

static boolean test1(){
System.out.println("test1");
return true;
}
static boolean test2(){
System.out.println("test2");
return false;
}
static boolean test3(){
System.out.println("test3");
return true;
}
static boolean test4(){
System.out.println("test4");
return false;
}

}
/*
test1
test2
test3
test4

test1
test3
*/

boolean flag2 = test1()&&test3()||test2()&&test4();
若test1为true,test3为true时,因为前面这部分已经确定为true,所以后面部分不会被调用。

四、e

科学与工程领域中,”e”代表自然对数的基数,为2.718。
而在C,C++和java(或者更多的语言)中,”e”代表“10的幂次”
$1.39e-43f = 1.39*10^{-43}$

五、位操作

与,或,异或,非 &,|,^,~
与:所有的位都为1则输出1,否则输出0;
或:只要有一个位是1就输出1;
异或:两个位值相等时输出1;
非:1输出0,0输出1.
位运算不会出现短路现象。
移位操作符:
$<<$:操作数向左移动,低位补0;
$>>$:操作数向右移动,(1)符号为正时,高位补0,(2)符号为负时,高位补1;
$>>>$:java独有操作符,操作数向右移动,高位统一补0。
char,byte,short进行移位操作时先会转成int类型,即32位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class URShift {

public static void main(String[] args) {
int i = 1024;
System.out.println(Integer.toBinaryString(i));
i >>= 10;
System.out.println(Integer.toBinaryString(i));
i = -1;
System.out.println(Integer.toBinaryString(i));
i >>>= 10;
System.out.println(Integer.toBinaryString(i));
i <<= 1;
System.out.println(Integer.toBinaryString(i));
short s = -1;
s >>>= 10;//s移位后得到的结果在赋值时会强行转为int,所以移位后的s已经是int型
System.out.println(Integer.toBinaryString(s));
s = -1;
System.out.println(Integer.toBinaryString(s>>>10));
}

}
/*
10000000000
1
11111111111111111111111111111111
1111111111111111111111
11111111111111111111110
11111111111111111111111111111111
1111111111111111111111
*/

六、截尾

将float或double转型为整数值时,总是对数字进行截尾,不会进行四舍五入。如果想要得到舍入的结果可以使用Math.round()

1
2
3
4
5
6
7
8
9
10
11
12
public class CastingNumbers {

public static void main(String[] args) {
double d = 1.7d;
int i = (int)d;
System.out.println(i);
i = (int) Math.round(d);
System.out.println(i);
}
}
//1
//2

第四章(控制执行流程)

一、goto 标签

goto关键词,在java中则是使用标签代替臭名昭著的goto。其实在java中也是最好不要用标签来跳转语句,太伤智商。。

写法
outer:
并放在迭代语句前。
(1)continue 标签
跳出所有循环,并到标签位置的语句,但会重新进入紧接后面的循环里。
(2)break 标签
跳出所有循环,且不再进入紧接后面的循环里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class LabeledFor {

public static void main(String[] args) {
System.out.println("测试continue————————————");
label:
for(int i=0;i<3;i++){
System.out.println("外部for循环"+i);
for(int j=0;j<3;j++){
if(j==1){
continue label;
}
System.out.println("内部循环"+j);
}
}
System.out.println("测试break————————————————");
label2:
for(int m=0;m<3;m++){
System.out.println("外部for循环"+m);
for(int n=0;n<3;n++){
if(n==1){
break label2;
}
System.out.println("内部循环"+n);
}
}
}

}
/*
测试continue————————————
外部for循环0
内部循环0
外部for循环1
内部循环0
外部for循环2
内部循环0
测试break————————————————
外部for循环0
内部循环0*/

第五章(初始化与清理)

一、重载

重载主要以传入参数及顺序来区别。不能通过返回值来区别
以基本数据类型传入时:自动包装机制
(1)若传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会提升。
byte->short->int->long->float->double
(2)如果传入的实际参数较大,就得通过类型转换执行窄化转换。

二、垃圾回收机制

标记-清扫(Android中使用这个技术)
从堆栈和存储区出发,遍历所有的引用,进而找出所有存活的对象,并给与标记。遍历完成后,清理所有未被标记的对象。
停止-复制
先暂停程序的运行,然后将所有存活的对象复制到另一个堆,而没有复制的是可回收的内存。

三、初始化顺序

所有的变量都会在任何方法(包括构造器)被调用之前得到初始化。
无论创建多少对象,静态数据都只占用一份存储区域,static不能作用于局部变量。且静态初始化动作只执行一次。

四、可变参数列表。

void method(Object... args){}

调用:
method();或method(new Object[]{1,2,3,4});

第六章(访问权限控制)

一、访问权限途径

取得某一成员的访问权限途径:

  • (1)该成员访问权限为public,这样谁都可以访问该成员。
  • (2)不加访问权限修饰词并将其他类放置于同一个包内的方式给成员赋予包访问权。于是包内的其他类也可以访问。
  • (3)继承的类可以访问public和protected方法。
  • (4)通过提供访问器set,get来读取和改变值。

第七章(复用类)

一、toString()的自动调用

有时候编译器会自动帮你调用toString()方法。
“source”+ source;这时候会自动调用source对象的toString方法。

二、构造函数调用顺序

构造函数总是从基类开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Insert{
Insert(){
System.out.println("Insert");
}

}

public class Beetle extends Insert{

Beetle(){
System.out.println("Beetle");
}

public static void main(String[] args) {
// TODO Auto-generated method stub
Beetle b = new Beetle();
}

}
//Insert
//Beetle

三、final

对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变,但对象本身是可以改变的。
private 属于 final 方法
static final是属于类属性,即能被类调用,不用实例化
final则需要实例化。

四、组合模式

组合模式可以看做是一颗树,每个枝干都可以长出新的枝干,它们的结构都是相同的。
将枝干抽象为一个类,里面包含链接下一个节点的方法,若需要不断的链接下一个节点只需要继承这个方法并实现。
示例:导航菜单
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class Tab {
private String title;

public Tab(String title) {
this.title = title;
}

protected abstract void add(Tab tab);

protected abstract void romove(Tab tab);
}

public class CardTab extends Tab{

public CardTab(String title) {
super(title);
// TODO Auto-generated constructor stub
}

private List<Tab> tabs;

@Override
protected void add(Tab tab) {
// TODO Auto-generated method stub
tabs.add(tab);
}

@Override
protected void romove(Tab tab) {
// TODO Auto-generated method stub
tabs.remove(tab);
}

}

public class TabView {
public static void main(String[] args) {
// TODO Auto-generated method stub
CardTab rootTab = new CardTab("roottab");
CardTab tab1 = new CardTab("tab1");
CardTab tab2 = new CardTab("tab2");
CardTab tab3 = new CardTab("tab3");
rootTab.add(tab1);
rootTab.add(tab2);
rootTab.add(tab3);
CardTab tab4 = new CardTab("tab1-1");
CardTab tab5 = new CardTab("tab1-2");
tab1.add(tab4);
tab1.add(tab5);
}
}

/**
* 这样子Tab组成了一个导航列表,这就是一个简单的组合模式.
*/

第八章(多态)

多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。

一、多态缺陷

缺陷1:只有非private方法才可以被覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Super{
public int field = 0;
public int getField(){return field;};
}

class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
}

public class FiledAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getField() = " +
sub.getField() +
", sub.getSuperField() = " +
sub.getSuperField());
}
}

输出:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0

缺陷2:域和静态方法直接在编译时候进行解析,所以多态不会对其产生作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}

class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}

public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
} /* Output:
Base staticGet()
Derived dynamicGet() */

二、断言

@Override作用:
断言,如果我们使用了这种annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示

三、构造器构造顺序

构造器在多态时的构造顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Meal {
Meal() {
P.print("Meal()");
}
}

class Bread {
Bread() {
P.print("Bread()");
}
}

class Cheese {
Cheese() {
P.print("Cheese()");
}
}

class Lettuce {
Lettuce() {
P.print("Lettuce()");
}
}

class Lunch extends Meal {
Lunch() {
P.print("Lunch()");
}
}

class PortableLunch extends Lunch {
PortableLunch() {
P.print("PortableLunch()");
}
}

public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();

public Sandwich() {
P.print("Sandwich()");
}

public static void main(String[] args) {
new Sandwich();
}

}

class P {
public static void print(String s){
System.out.println(s);
}
}
输出:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

解释说明:
一个继承类实例化的时候必须要确保所使用的成员已经构建完毕,所以必须先调用基类的构造器,所以当实例化Sandwich对象时先调用其基类的构造方法:
Meal()
Lunch()
PortableLunch()
其次对成员变量进行初始化
Bread()
Cheese()
Lettuce()
最后调用构造器
Sandwich()

三、构造器初始化

初始化的过程:
(1)在所有事物发生之前,将分配给对象的存储空间初始化为二进制的零。
(2)调用基类构造器。
(3)按照声明顺序调用成员的初始化方法。
(4)调用导出类(本体)的构造器主体。

第九章(接口)

任何抽象性都应该是应真正的需求而产生的。

访问权限

interface如果不加public关键字,则只具有包访问权限。

重名

可以通过extends来扩展接口,但在实现多重继承时要注意不能实现签名或返回类型不同的接口方法,除非其传入参数不一样。最好避免使用同一个方法名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface M{
void menace();
void kill();
}

interface Danger{
void menace(int s);
void kill();
}

interface Vampire extends Danger,M{

}

public class Monster implements Vampire{
public static void main(String[] args) {
System.out.println("Hello,every one,I'm cpacm");
}

public void menace(int s) {
// TODO Auto-generated method stub

}

public void kill() {
// TODO Auto-generated method stub

}

public void menace() {
// TODO Auto-generated method stub

}

}

tip:切勿过渡设计

策略模式

去商店去买东西,可以选择不同的出行方式但都能达到买东西的目的,这种选择模式就是策略模式。
把出行方式抽象为一个接口,实现公交车,自行车,走路,出租车等实例,最后自己决定使用哪种方式。

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。
策略模式

适配器模式

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
在Android中常用到的各种Adapter就是用的适配器模式思想。
适配器模式

工厂模式

工厂模式主要用以下几种形态:

  1. 简单工厂(Simple Factory): 主要代码由一个Factory管理,每添加一个产品都需要在factory类中修改代码;
  2. 工厂方法(Factory Method):一种产品对应一个工厂,增加一种产品就同时增加一个工厂;
  3. 抽象工厂(Abstract Factory):通常用于多种产品且每种都有不同型号的情况下,针对型号建立工厂,每个工厂只生产该型号的产品。

抽象工厂和工厂可以根据不同情况下相互转化。

第十章(内部类)

一、访问权

内部类拥有其外围类的所有元素的访问权。
意思是通过内部类能够获得其外部类的内存信息(参数值)。
故不能直接通过创建普通对象的方法创建内部类,必须要通过外部类才能创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DotThis {
void f() {
System.out.println("DotThis.f()");
}

public class Inner {
public DotThis outer() {
return DotThis.this;
// A plain "this" would be Inner's "this"
}
}

public Inner inner() {
return new Inner();
}

public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}

二、.new

.new语法,可以直接创建其内部类,但前提也是要提供其外部类的引用。

1
2
3
4
5
6
7
8
9
public class DotNew {

public class Inner{};

public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dti = dn.new Inner();
}
}

当内部类向上转型为基类或接口时,往往是为了隐藏实现细节。

三、局部

内部类可以嵌入在任何堆作用域内,但只在其作用域内可用。称作为局部内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Parcel6 {

private void inTrack(boolean b){
if(b){
class TrackSlip{
private String id;
TrackSlip(String s){
id = s;
}
String getSlip(){return id;}
}
TrackSlip ts = new TrackSlip("slip");
String s = ts.getSlip();
}
}
//TrackSlip ts = new TrackSlip("slip"); //run error

public void track(){
inTrack(true);
}

public static void main(String[] args) {
// TODO Auto-generated method stub
Parcel6 p = new Parcel6();
p.track();
}

}

四、匿名内部类

匿名内部类是一个没有名字的内部类,通常使用它来简化代码编写,同时必须继承一个父类或实现一个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Parcel7 {

public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}

}
//1 2 3 4 5

tip:常用的扩展是将只使用一次的工厂类作为匿名内部类放在生产类上。

五、嵌套类

将内部类声明为static时,此时就作为嵌套类使用。创建嵌套类的对象并不需要外围对象,同时也不能从嵌套类的对象中访问非静态的外围对象。

六、接口内部类

在接口中可以放入嵌套类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface ClassInInterface {

void howdy();

class Test implements ClassInInterface{

@Override
public void howdy() {
// TODO Auto-generated method stub
System.out.println("Howdy!");
}

public static void main(String[] args) {
new Test().howdy();
}

}

}

七、内部类作用

使用内部类的一些好处

  1. 可以解决多重继承的问题,因为内部类可以访问外围类的信息,所以获得内部类相当于获得外围类和内部类两种信息。
  2. 在一个外围类中,可以让多个内部类以不同的方式实现同一个接口或类从而实现不同的特性。

八、模版模式

模板方法模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板模式需要一个抽象类和继承该抽象类的子类来实现。同样使用出门买东西的情景:出门->买东西->回来。

我们不关心怎么出去和回来,所以把出门和回来的方法写在抽象类中,然后将买东西这个方法抽象化放在抽象类中以便子类实现。
接着创建子类继承抽象类,买一种东西可以创建一个新类,从而实现抽象方法。

而策略模式是使用委托方法实现的,需要一个用来实现
方法的接口存在。
模板模式和策略模式通常可以互相替换。

九、命令设计模式

命令模式的结构
顾名思义,命令模式就是对命令的封装,首先来看一下命令模式类图中的基本结构:
Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令。
ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。
Client类:最终的客户端调用类。
以上三个类的作用应该是比较好理解的,下面我们重点说一下Invoker类和Recevier类。
Invoker类:调用者,负责调用命令。
Receiver类:接收者,负责接收命令并且执行命令。
命令模式的精髓所在:把命令的调用者与执行者分开,使双方不必关心对方是如何操作

十、继承内部类

当继承内部类的时候必须在构造器中传入外围类的引用和外围类的super;

第十一章(持有对象)

一、各个容器

ArrayList LinkedList 都是按插入顺序存放数据
ArrayList在随机访问速度上比较快,而LinkedList在插入和删除数据比较有优势,具有Queue,Stack的特性。
HashSet TreeSet LinkedHashSet
HashMap TreeMap LinkedHashMap
通用点:Hash开头的容器都是通过Hash值来查找数据,所以特点是无序但速度快;
Tree开头的容器都会将存入的数据进行升序排列;
Linked则是按插入的顺序进行排序。
(后面17章会介绍其原理)

二、迭代器

迭代器具有以下特性:
1)创建代价小
2)单向移动
3)next()获取下一个对象,hasNext()判断是否具有下一个对象,remove()移除当前对象。

(ListIterator作为List特有的迭代器,具有双向移动功能,其对应方法为hasPrevious(),previous())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;

public class CrossContainerIteration {

public static void display(Iterator<Pet> it) {
while (it.hasNext()) {
Pet p = it.next();
System.out.println(p.id() + ":" + p + " ");
}
System.out.println();
}

public static void main(String[] args) {
// TODO Auto-generated method stub
List<Pet> pets = new ArrayList<>();
pets.add(new Pet("cat"));
pets.add(new Pet("dog"));
pets.add(new Pet("bird"));
pets.add(new Pet("fish"));
pets.add(new Pet("pig"));
LinkedList<Pet> petsLL = new LinkedList<>(pets);
HashSet<Pet> petsHS = new HashSet<>(pets);
TreeSet<Pet> petsTS = new TreeSet<>(pets);

display(pets.iterator());
display(petsLL.iterator());
display(petsHS.iterator());
display(petsTS.iterator());
}

}
/*0:Pet cat
1:Pet dog
2:Pet bird
3:Pet fish
4:Pet pig

0:Pet cat
1:Pet dog
2:Pet bird
3:Pet fish
4:Pet pig

3:Pet fish
1:Pet dog
4:Pet pig
2:Pet bird
0:Pet cat

2:Pet bird
0:Pet cat
1:Pet dog
3:Pet fish
4:Pet pig */

二、栈

后进先出
通常可以使用LinkedList来实现Stack的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Stack<T> {
private LinkedList<T> storge = new LinkedList<>();
public void push(T v){
storge.addFirst(v);
}
public T peek(){
return storge.getFirst();
}

public T pop(){
return storge.removeFirst();
}

public boolean empty(){
return storge.isEmpty();
}

}

三、队列

Queue 先进先出
同样可以使用LinkedList来实现功能,由于其实现了Queue接口,可以将其向上转型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class QueueDemo {
public static void printQ(Queue queue) {
while (queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
}

public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for (int i = 0; i < 10; i++)
queue.offer(rand.nextInt(i + 10));
printQ(queue);
Queue<Character> qc = new LinkedList<Character>();
for (char c : "Brontosaurus".toCharArray())
qc.offer(c);
printQ(qc);
}
} /*
* Output: 8 1 1 1 5 14 3 1 0 1 B r o n t o s a u r u s
*/

三、优先队列

PriorityQueue
简单来说就是具有排序功能的队列,下一个弹出的元素是在队列中优先级最高的一个。使用Comparator比较器来进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>();
Random rand = new Random(47);
for (int i = 0; i < 10; i++)
priorityQueue.offer(rand.nextInt(i + 10));
QueueDemo.printQ(priorityQueue);

/*
* Output: 0 1 1 1 1 1 3 5 8 14 1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25 25
*/

List<Integer> ints = Arrays.asList(25, 22, 20, 18, 14, 9, 3, 1, 1, 2,
3, 9, 14, 18, 21, 23, 25);
priorityQueue = new PriorityQueue<Integer>(ints);
QueueDemo.printQ(priorityQueue);
priorityQueue = new PriorityQueue<Integer>(ints.size(),
Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.printQ(priorityQueue);

/*
* 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1
*/


String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
List<String> strings = Arrays.asList(fact.split(""));
PriorityQueue<String> stringPQ = new PriorityQueue<String>(strings);
QueueDemo.printQ(stringPQ);
stringPQ = new PriorityQueue<String>(strings.size(),
Collections.reverseOrder());
stringPQ.addAll(strings);
QueueDemo.printQ(stringPQ);

Set<Character> charSet = new HashSet<Character>();
for (char c : fact.toCharArray())
charSet.add(c); // Autoboxing
PriorityQueue<Character> characterPQ = new PriorityQueue<Character>(
charSet);
QueueDemo.printQ(characterPQ);
}
/*
* A A B C C C D D E E E F H H I I L N
* N O O O O S S S T T U U U W W U U U T T S S S O O O O N N L I I H H F E E E D
* D C C C B A A A B C D E F H I L N O S T U W
*/

}

总结:四种容器List Set Queue Map

第十二章(通过异常处理错误)

Java的基本理念是“结构不佳的代码不能运行

自定义异常

我们可以继承Exception类来自定义异常,在自定义异常类中可以添加一些动作,比如写入日志等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class MyException extends Exception {
public MyException() {}
public MyException(String msg) { super(msg); }
}

public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch(MyException e) {
e.printStackTrace(System.out);
}
try {
g();
} catch(MyException e) {
e.printStackTrace(System.out);
}
}
} /* Output:
Throwing MyException from f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at FullConstructors.main(FullConstructors.java:19)
Throwing MyException from g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at FullConstructors.main(FullConstructors.java:24)
*///:~

异常也是类的一种,所以我们可以扩展使其获得更强大的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class MyException2 extends Exception {
private int x;

public MyException2() {
}

public MyException2(String msg) {
super(msg);
}

public MyException2(String msg, int x) {
super(msg);
this.x = x;
}

public int val() {
return x;
}

public String getMessage() {
return "Detail Message: " + x + " " + super.getMessage();
}
}

public class ExtraFeatures {
public static void f() throws MyException2 {
System.out.println("Throwing MyException2 from f()");
throw new MyException2();
}

public static void g() throws MyException2 {
System.out.println("Throwing MyException2 from g()");
throw new MyException2("Originated in g()");
}

public static void h() throws MyException2 {
System.out.println("Throwing MyException2 from h()");
throw new MyException2("Originated in h()", 47);
}

public static void main(String[] args) {
try {
f();
} catch (MyException2 e) {
e.printStackTrace(System.out);
}
try {
g();
} catch (MyException2 e) {
e.printStackTrace(System.out);
}
try {
h();
} catch (MyException2 e) {
e.printStackTrace(System.out);
System.out.println("e.val() = " + e.val());
}
}
} /*
Throwing MyException2 from f()
chapter12.MyException2: Detail Message: 0 null
at chapter12.ExtraFeatures.f(ExtraFeatures.java:32)
at chapter12.ExtraFeatures.main(ExtraFeatures.java:47)
Throwing MyException2 from g()
chapter12.MyException2: Detail Message: 0 Originated in g()
at chapter12.ExtraFeatures.g(ExtraFeatures.java:37)
at chapter12.ExtraFeatures.main(ExtraFeatures.java:52)
Throwing MyException2 from h()
chapter12.MyException2: Detail Message: 47 Originated in h()
at chapter12.ExtraFeatures.h(ExtraFeatures.java:42)
at chapter12.ExtraFeatures.main(ExtraFeatures.java:57)
e.val() = 47
*/// :~

所有的异常都可以由Exception进行捕获,可以通过Exception打印发生错误时所获取的信息。

Rethrow

可以通过throw将catch到的异常重新抛出,不过异常里面是原来异常抛出的调用栈信息。可以使用 fillInStackTrace()更新,调用 fillInStackTrace那一行就成了异常的新发生地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Rethrowing {
public static void f() throws Exception {
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args) {
try {
g();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.h(Rethrowing.java:20)
at Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:24)
at Rethrowing.main(Rethrowing.java:35)
*///:~

异常链

Throwable的子类可以接受一个cause对象作为参数,cause表示原始异常,这样可以通过把原始异常传递给新的异常使得可以追踪所有连接起来的异常。
在Throwable子类中,只有三种基本的异常类提供了带cause参数的构造器,分别为Error,Exception和RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法。

return

return 与 fianl共用时,即使在try里面实行return,finally里面的代码还是会运行,
而在final里面使用热力return 的话,即使抛出了异常也不会产生任何输出。

异常使用指南

  1. 在恰当的级别处理问题。
  2. 解决问题并且重新调用产生异常的方法。
  3. 进行少许修补,然后绕过异常发生的地方继续执行。
  4. 用别的数据进行计算,以代替异常发生的地方继续执行。
  5. 吧当前运行环境下能做的事情尽量做完,然后将相同的异常重抛到更高层。
  6. 吧当前运行环境下能做的事情尽量做完,然后将不同的异常重抛到更高层。
  7. 终止程序
  8. 进行简化。
  9. 蓝类库和程序更安全。

第十三章(字符串)

字符串具有不可变性。

格式修饰符

对字符串进行可变操作时尽量使用StringBuilder.

在插入数据时,如果想要控制空格与其对其,则需要更精细复杂的格式修饰符。

%[argument_index$][flags][width][.precision]conversion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Receipt {
private double total = 0;
private Formatter f = new Formatter(System.out);

public void printTitle() {
f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
f.format("%-15s %5s %10s\n", "- - - -", "- - -", "- - - - -");
}

public void print(String name, int qty, double price) {
f.format("%-15.15s %5d %10.2f\n", name, qty, price);
total += price;
}

public void printTotal() {
f.format("%-15s %5s %10.2f\n", "Tax", "", total + 0.06);
f.format("%-15s %5s %10s\n", "", "", "- - - - -");
f.format("%-15s %5s %10.2f\n", "Total", "", total * 1.06);
}

public static void main(String[] args) {
Receipt receipt = new Receipt();
receipt.printTitle();
receipt.print("Jack's Magic Beans", 4, 4.25);
receipt.print("Princess Peas", 5, 5.1);
receipt.print("Three Bears Porridge", 1, 14.29);
receipt.printTotal();
}

/*
*
Item Qty Price
- - - - - - - - - - - -
Jack's Magic Be 4 4.25
Princess Peas 5 5.10
Three Bears Por 1 14.29
Tax 23.70
- - - - -
Total 25.06
* */
}

Formatter转换表格
| 符号 | 定义 |
|:—————:|:—————:|
| d | 整数型(十进制) |
| c | Uncode字符 |
| b | Boolean值 |
| s | String |
| f | 浮点数(十进制) |
| e | 浮点数(科学计数) |
| x | 整数(十六进制) |
| h | 散列码(十六进制) |
| % | 字符“%” |

正则表达式

正则表达式
使用Pattern和Matcher实现正则表达式

这里列了正则表达式简单的表格,详细的可以在网上找找教程。

Matcher.find()用来查找多个匹配,可以像迭代器一样不断向前搜索匹配的字符串。

组是用括号划分的正则表达式,可以根据组的编号来引用某个组,组号为0表示整个表达式,组号1表示被第一对括号括起的组,依次类推。比如说:A(B(C))D有三个组
组0是ABCD,组1是BC,组2是C。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class Groups {
static public final String POEM =
"Twas brillig, and the slithy toves\n" +
"Did gyre and gimble in the wabe.\n" +
"All mimsy were the borogoves,\n" +
"And the mome raths outgrabe.\n\n" +
"Beware the Jabberwock, my son,\n" +
"The jaws that bite, the claws that catch.\n" +
"Beware the Jubjub bird, and shun\n" +
"The frumious Bandersnatch.";
public static void main(String[] args) {
Matcher m =
Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
.matcher(POEM);
while(m.find()) {
for(int j = 0; j <= m.groupCount(); j++)
System.out.println("[" + m.group(j) + "]");
}
}
}
/*
[the slithy toves]
[the]
[slithy toves]
[slithy]
[toves]
[in the wabe.]
[in]
[the wabe.]
[the]
[wabe.]
[were the borogoves,]
[were]
[the borogoves,]
[the]
[borogoves,]
[mome raths outgrabe.]
[mome]
[raths outgrabe.]
[raths]
[outgrabe.]
[Jabberwock, my son,]
[Jabberwock,]
[my son,]
[my]
[son,]
[claws that catch.]
[claws]
[that catch.]
[that]
[catch.]
[bird, and shun]
[bird,]
[and shun]
[and]
[shun]
[The frumious Bandersnatch.]
[The]
[frumious Bandersnatch.]
[frumious]
[Bandersnatch.]
*/

Matcher的详细用法,推荐看看这篇博文
http://www.itzhai.com/java-notes-regex-matches-and-lookingat.html

第十四章(类型信息)

运行时类型信息使得你可以在程序运行时发现和使用类型信息。

RTTI:RunTime Type Identification.

Class类

类是程序的一部分,每个类都有一个Class对,每一个类都会被保存在一个同名的.class文件中。

所有的类都是在对其第一次使用时,动态加载到JVM中的。static初始化是在类加载时进行的。此时类加载器就会去检查这个类的Class是否被加载。

Class类包含很多有用的方法:(所有Class对象都属于这个类)

1
2
3
4
5
6
7
8
Class.forName("")://取得Class对象引用的一种方法。
Class cc;
cc.getName();//获取全限定的类名
cc.isInterface();//是否表示为接口
cc.getSimpleName();//不含包名的类名
cc.getInterfaces();//返回Class对象,表示cc对象中包含的接口
cc.getSuperclass();//查询基类
cc.newInstasnce();//实现“虚拟构造器”来创建自己

使用 .class 来创建对Class对象的引用。此时并不会初始化该对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或非常熟静态域进行首次引用时才执行。

类使用

类使用的三个步骤:
1、加载,由类加载器执行,查找字节码,并从这些字节码中创建一个Class对象。
2、链接,验证类中的字节码,为静态域分配存储空间
3、初始化,执行静态初始化器和静态初始化块(包括超类)

静态常量

如果一个 static final 值是 “编译期常量”(像 static final int Y = 1),那么这个值不需要对这个类进行初始化就可以被读取。但是,如果static final 是一个非常数静态域还是会进行类的初始化。

如果一个static 域不是 final的,那么访问前必须先进行链接和初始化行为。

可以使用Class<?> 和 Class<? extends 具体类> 来限制类型,提供编译期的检查。

利用 instanceif 在类型转换前进行检查

Java之外观模式(Facade Pattern)

1.概念
为子系统中的一组接口提供一个统一接口。Facade模式定义了一个高层接口,这个接口使得这子系统更容易使用。

2.代码
下面是一个具体案例的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package facade;  
class DrawerOne {
publicvoid open(){
System.out.println("第一个抽屉被打开了");
getKey();
}
publicvoid getKey(){
System.out.println("得到第二个抽屉的钥匙");
}
}
class DrawerTwo{
publicvoid open(){
System.out.println("第二个抽屉被打开了");
getFile();
}
publicvoid getFile(){
System.out.println("得到这个重要文件");
}
}
class DrawerFacade{
DrawerOne darwerOne=new DrawerOne();
DrawerTwo darwerTwo=new DrawerTwo();
publicvoid open(){
darwerOne.open();
darwerTwo.open();
}
}
publicclass DrawerClient{
publicstaticvoid main(String []args){
DrawerFacade drawer=new DrawerFacade();
drawer.open();
}
}

4.应用场景
1)为一个复杂子系统提供一个简单接口。
2)提高子系统的独立性。
3)在层次化结构中,可以使用Facade模式定义系统中每一层的入口。

RTTI和反射的区别

RTTI和反射的根本区别:
RTTI是在编译时打开和检查.class文件,而对于反射来说.class文件在编译时期是不可获取的,所以是在运行时打开和检查.class文件

class内部方法

利用Class的getMethods()和getConstructors()方法获得对象的方法和构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ShowMethods {

private static Pattern p = Pattern.compile("\\w+\\.");

public static void main(String[] args) {
try {
Class<?> c = Class.forName("chapter13.Groups");
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();

for (Method method : methods)
print(
p.matcher(method.toString()).replaceAll(""));
for (Constructor ctor : ctors)
print(p.matcher(ctor.toString()).replaceAll(""));

} catch (ClassNotFoundException e) {
print("No such class: " + e);
}
}

public static void print(String s) {
System.out.println(s);
}
/*
public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public Groups()
*/
}

代理模式

使用代理角色来操作真实角色,并对外声明统一接口。

比如说操作数据库,我们可以声明代理类,实现数据库的增删改查操作。

所谓代理模式就是让使用者与真实对象耦合开来,使用代理者来做中间沟通。比如说一个消费者购买商品,其中商店就是代理者,生产商就是真实对象,但消费者和生产商其实并没有联系。

真实角色和代理角色继承同一个接口,代理角色调用真实角色,使用者调用真实角色。

反射修改

人类已经无法阻止反射了。。

1
2
3
4
5
6
7
8
9
10
//调用method方法
Method g = a.getClass().getDeclaredMethod(methodname);
g.setAccessible(true);
g.invoke(a);

//修改field值
Field f = pf.getClass().getDeclaredField(fieldname);
f.setAccessible(true);
f.set(pf,"");// or
f.setInt(pf,233);

但final域是安全的,不会遭到修改。

第十五章(泛型)

概念

泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。

元组

利用泛型创建元组,可以将一组对象直接打包存储于其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TwoTuple<A, B> {
public final A first;
public final B second;

public TwoTuple(A a, B b) {
first = a;
second = b;
}

public String toString() {
return "(" + first + ", " + second + ")";
}
}

可以使用继承机制实现长度更长的元组。

1
2
3
4
5
6
7
8
9
10
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}
public String toString() {
return "(" + first + ", " + second + ", " + third +")";
}
}

局限1

基本类型不能作为泛型的类型参数

泛型方法

1
2
3
    public <T> void f(T a) {
System.out.println(a.getClass().getName());
}

局限2-擦除

在泛型代码内部,无法获得任何有关泛型参数类型的信息。
在c++中,当模板被实例化时,模板代码知道其模板参数的类型。java泛型由于擦除局限存在无法做到,所以必须利用<? extends >来限定边界以便实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

template<class T> class Manipulator {
T obj;
public:
Manipulator(T x) { obj = x; }
void manipulate() { obj.f(); }
};

class HasF {
public:
void f() { cout << "HasF::f()" << endl; }
};

int main() {
HasF hf;
Manipulator<HasF> manipulator(hf);
manipulator.manipulate();
} /* Output:
HasF::f()
///:~

在java中实现

1
2
3
4
5
class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
}

“?”代表未知类型

extends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类

super关键字声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object

参数化接口

一个类不能实现同一个泛型接口的两种泛型,由于擦除的特性,这两个接口变体会被视为相同的接口。

1
2
3
4
5
6
7
8
//: generics/MultipleInterfaceVariants.java
// {CompileTimeError} (Won't compile)

interface Payable<T> {}

class Employee implements Payable<Employee> {}
class Hourly extends Employee
implements Payable<Hourly> {} ///:~

同时重载,转型等也会受到影响。

装饰器模式

定义

  1. 又名包装(Wrapper)模式,装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

  2. 装饰模式以对客户端透明的方式动态的给一个对象附加上更多的责任。换言之客户端并不会觉的对象在装饰前和装饰后有什么区别。

  3. 装饰模式可以在不创造更多的子类的模式下,将对象的功能加以扩展。

  4. 装饰模式与类继承的区别:

    4-1. 装饰模式是一种动态行为,对已经存在类进行随意组合,而类的继承是一种静态的行为,一个类定义成什么样的,该类的对象便具有什么样的功能,无法动态的改变。

    4-2. 装饰模式扩展的是对象的功能,不需要增加类的数量,而类继承扩展是类的功能,在继承的关系中,如果我们想增加一个对象的功能,我们只能通过继承关系,在子类中增加两个方法。

    4-3. 装饰模式是在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真是的对象。

  5. 装饰模式把对客户端的调用委派给被装饰的类,装饰模式的关键在于这种扩展完全透明的。

特点

1. 装饰对象和真实对象具有相同的接口,这样客户端对象就可以以真实对象的相同的方式和装饰对象交互。

2. 装饰对象包含一个真实对象的引用(reference).

3. 装饰对象接受所有来自客户端的请求,它把这些请求转发给真实的对象。

4. 装饰对象可以在转发这些请求以前或者以后增加一些附加的功能。这样就能确保在运行时,不用修改给定对象结构就可以在外部增加附加的功能。在面向对象的程序设计中,通常是使用继承的关系来扩展给定类的功能。

tip:使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。

潜在类型机制

上面说到的c++在编译时期检查参数类型,以及python运行时期检查参数类型,所以可以判断类型。这就形成了潜在类型机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Dog:
def speak(self):
print "Arf!"
def sit(self):
print "Sitting"
def reproduce(self):
pass

class Robot:
def speak(self):
print “Click!”;
def sit(self):
print "Clank"
def oilChange(self)
pass

def perform(anything):
anything.speak()
anything.sit()

a = Dog()
b = Robot()
perform(a)
perform(b)

第十六章(数组)

特性

数组是一个简单的线性序列,访问速度快,但数组对象的大小被固定。

数组的初始化

1
2
3
4
5
6
BerylliumSphere[] a; // Local uninitialized variable
a = new BerylliumSphere[]{
new BerylliumSphere(), new BerylliumSphere(),};
BerylliumSphere[] b = new BerylliumSphere[5];
BerylliumSphere[] d = {new BerylliumSphere(),
new BerylliumSphere(), new BerylliumSphere()};

对象数组保存的是引用,基本类型数组是直接保存基本类型的值。

多维数组

粗糙数组:数组中构成矩阵的每个向量都可以具有任意的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class RaggedArray {
public static void main(String[] args) {
Random rand = new Random(47);
// 3-D array with varied-length vectors:
int[][][] a = new int[rand.nextInt(7)][][];
for(int i = 0; i < a.length; i++) {
a[i] = new int[rand.nextInt(5)][];
for(int j = 0; j < a[i].length; j++)
a[i][j] = new int[rand.nextInt(5)];
}
System.out.println(Arrays.deepToString(a));
}
} /* Output:
[[], [[0], [0], [0, 0, 0, 0]], [[], [0, 0], [0, 0]], [[0, 0, 0], [0], [0, 0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0], []], [[0], [], [0]]]
*///:~
```

### deepToString
Arrays.deepTotring()可以输出数组,对象数组和基本类型数组都其作用。

### fill()
Arrays.fill() 可以用数字填充数组。

### 常用数组操作
* **System.arraycopy()** 用于复制数组,注意这只是浅复制。
* **Arrays.equals()** 用来比较数组是否相等,两者个数不仅相等,且每个位置上的每个元素都要相等。
* **Comparable** 利用此接口进行比较,compareTo()方法中,当前对象小于参数返回负值,等于返回零。
* **Arrays.sort()** 进行排序,前提是数组内对象实现了Comparable 接口。使用内置的排序方法,可以对任意的基本类型数组排序。
```java
public class StringSorting {
public static void main(String[] args) {
String[] sa = Generated.array(new String[20],
new RandomGenerator.String(5));
print("Before sort: " + Arrays.toString(sa));
Arrays.sort(sa);//字典排序,大写排在小写前面
print("After sort: " + Arrays.toString(sa));
Arrays.sort(sa, Collections.reverseOrder());//反向
print("Reverse sort: " + Arrays.toString(sa));
Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);忽略大小写
print("Case-insensitive sort: " + Arrays.toString(sa));
}
}

Arrays.binarySearch() 排序后才可以使用该方法进行快速查找。找到目标返回值,大于等于零,否则返回负值。负值 = -(插入点)-1;
若使用了自定义的Comparable,则需要将Comparable作为参数传入方法中。

优先使用容器而不是数组

第十七章(容器深入研究)

java容器

除此以外还添加了 queue , ConcurrentMap 接口以及相应实现的 PriorityQueue,BlockingQueueConcurrentHashMap等容器。
同时在 Collections 类中有着对容器进行操作方便的方法。

Collection是集合类的上级接口,子接口主要有Set 和List、Map。Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

List

retainAll()方法:取得两个List的交集

1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> list1 = new ArrayList<String>();
List<String> list2 = new ArrayList<String>();
list1.add("g");
list1.add("s");
list1.add("a");
list1.add("f");
list2.add("g");
list2.add("c");
list2.add("b");
list2.add("a");
list1.retainAll(list2);
System.out.print(list1);
//[g, a]

ListIterator.add()方法
把新元素插入由next()所返回的那个元素之前,previous()所返回的元素之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<String> alpha = new ArrayList<>();
alpha.add("a");
alpha.add("b");
alpha.add("c");
alpha.add("d");
alpha.add("e");

ListIterator<String> listIterator = alpha.listIterator();
listIterator.next();
listIterator.add("1");
listIterator.add("2");
System.out.println(alpha.toString());
listIterator.next();
listIterator.add("3");
System.out.println(alpha.toString())

Set

存入 Set 的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equal()方法以确保对象的唯一性。
HashSet 为快速查找而设计的Set,存入HashSet的元素必须定义hashCode()
TreeSet 保持次序的Set,底层为树结构,使用它可以从Set中提取有序的序列,元素必须实现Comparable接口
LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序,元素也必须定义hashCode()方法

Queue

LinkedListPriorityQueue
其他的还有
ArrayBlockingQueue,ConcurrentLinkedQueue,LinkedBlockingQueue,PriorityBlockingQueue

优先级队列PriorityQueue 需要元素继承 Comparable 接口。返回+,表示被比较的排在当前的后面,0表示相等,返回-则相反

Map

Map中最重要的是用散列码代替对象的int值,散列表是通过将对象的某些信息进行转换而生成。hashCode()是根类Object中的方法,而HashMap就是使用对象的hashCode()进行快速查询。
任何键都必须具有一个equals()方法,如果键被用于散列Map,则必须具有恰当的hashCode()方法,如果用于TreeMap,还必须实现Compareable。

注意:基本常量,特别是new String(),看上去像是生成了一个新的对象,但内存地址可能是一样的。

通过一个特定容量的数组来保存键,但不是保存键本身,而是通过键对象生成的一个数字,将这个数字作为数组的下标,而数组本身是保存值的list。这样我们就可以通过键对象的hashCode获得数组的下标,根据下标找到保存在数组里值list的位置,再通过对象的equals方法去线性比较list的值。

容器选择

ArrayList便于查询,内部使用数组实现。因为增加的时候会涉及到扩容,会导致数值的复制操作所以效率低。
LinkedList便于增加和删除

HashSet的性能基本上总是比TreeSet好,特别是添加和查询元素时,而需要排序功能时才会考虑使用TreeSet

HashMap是第一选择,需要排序时使用TreeMap,而需要保持插入顺序时使用LinkedHashMap

负载因子 尺寸/容量

同步控制

1
2
List<String> list = Collections.synchronizedList(new ArrayList<String>());
//其他容器同理

直接将新生成的容器容器传递给了适当的“同步”方法,这样就不会有任何机会暴露出不同步的版本。

持有引用

普通的引用即为强引用,GC不会回收。

SoftReference
软引用是当内存不足时GC回收,构建缓存系统

WeakReference
弱引用是每次GC时都会回收,减少人工清理引用
WeakHashMap被用来保存weakReference

虚引用在监视垃圾回收时使用

第十八章(Java IO 系统)

File

File 不仅代表一个特定文件的名称,也能代表一个目录下的一组文件的名称

File的list()方法可以传入 FilenameFilter()对文件进行筛选。
renameTo()用来把一个文件重命名(或移动)到由参数所指示的另一个完全不同的新路径(也就是另一个File对象)下面。
mkdirs()可以产生任意复杂的目录路径。

I/O

字节流类 功能简单介绍
DataInputStream 包含了读取Java标准数据类型的输入流
DataOutputStream 包含了写Java标准数据类型的输出流
ByteArrayInputStream 从字节数组读取的输入流
ByteArrayOutputStream 写入字节数组的输出流
FileInputStream 从文件读入的输入流
FileOutputStream 写入文件的输出流
PrintStream 包含最常见的Print()和Println()的输出流
PushbackInputStream 返回一个字节到输入流,主要用于编译器的实现 PipedInputStream 输出管道
PipedOutputStream 输入管道
SequenceInputStream 将n个输入流联合起来,一个接一个按一定顺序读取
RandomAccessFile 随机访问文件
BufferInputStream 缓冲输入流
BufferOutputStream 缓冲输出流
FilterInputStream 实现了InputStream Interface
FilterOutputStream 实现了OutputStream Interface
InputStream 抽象类,描述流的输入
OutputStream 抽象类,描述流的输入

使用RandomAccessFile写入文件

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws IOException {
String fileName="D:"+File.separator+"hello.txt";
File f=new File(fileName);
RandomAccessFile demo=new RandomAccessFile(f,"rw");
demo.writeBytes("asdsad");
demo.writeInt(12);
demo.writeBoolean(true);
demo.writeChar('A');
demo.writeFloat(1.21f);
demo.writeDouble(12.123);
demo.close();
}

关于字节流和字符流的区别

实际上字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的,但是字符流在操作的 时候下后是会用到缓冲区的,是通过缓冲区来操作文件的。

读者可以试着将上面的字节流和字符流的程序的最后一行关闭文件的代码注释掉,然后运行程序看看。你就会发现使用字节流的话,文件中已经存在内容,但是使用字符流的时候,文件中还是没有内容的,这个时候就要刷新缓冲区。

OutputStreramWriter 和 InputStreamReader

OutputStreramWriter将输出的字符流转化为字节流
InputStreamReader将输入的字节流转换为字符流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 将字节输出流转化为字符输出流
* */
import java.io.*;
class StreamTest{
public static void main(String[] args) throws IOException {
String fileName= "d:"+File.separator+"hello.txt";
File file=new File(fileName);
Writer out=new OutputStreamWriter(new FileOutputStream(file));
out.write("cpacm");
out.close();
}
}

class StreamTest2{
public static void main(String[] args) throws IOException {
String fileName= "d:"+File.separator+"hello.txt";
File file=new File(fileName);
Reader read=new InputStreamReader(new FileInputStream(file));
char[] b=new char[100];
int len=read.read(b);
System.out.println(new String(b,0,len));
read.close();
}
}

SequenceInputStream

SequenceInputStream主要用来将2个流合并在一起,比如将两个txt中的内容合并为另外一个txt。

1
2
// 合并流
SequenceInputStream sis = new SequenceInputStream(input1, input2);

java内部执行其他操作系统的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class OSExecute {
public static void command(String command) {
boolean err = false;
try {
Process process =
new ProcessBuilder(command.split(" ")).start();
BufferedReader results = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String s;
while((s = results.readLine())!= null)
System.out.println(s);
BufferedReader errors = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
// Report errors and return nonzero value
// to calling process if there are problems:
while((s = errors.readLine())!= null) {
System.err.println(s);
err = true;
}
} catch(Exception e) {
// Compensate for Windows 2000, which throws an
// exception for the default command line:
if(!command.startsWith("CMD /C"))
command("CMD /C " + command);
else
throw new RuntimeException(e);
}
if(err)
throw new OSExecuteException("Errors executing " +
command);
}
} ///:~

NIO

通道缓冲区 是 NIO 中的核心对象
通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
唯一直接与通道交互的缓冲器是ByteBuffer。
FileInputStream,FileOutputStreamRandomAccessFile这三个类中修改,用于产生FileChannel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Write a file:
FileChannel fc =
new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
// Add to the end of the file:
fc =
new RandomAccessFile("data.txt", "rw").getChannel();
fc.position(fc.size()); // Move to the end
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
// Read the file:
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while(buff.hasRemaining())
System.out.print((char)buff.get());
}

当 buffer 一旦被通道存储数据,就要调用flip()方法做好被读取的准备。
clear() 方法重设缓冲区,使它可以接受读入的数据。 flip() 方法让缓冲区可以将新读入的数据写入另一个通道。

使用transferTotransferFrom来将两个通道相通。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class GetData {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
// Allocation automatically zeroes the ByteBuffer:
int i = 0;
while(i++ < bb.limit())
if(bb.get() != 0)
print("nonzero");
print("i = " + i);
bb.rewind();
// Store and read a char array:
bb.asCharBuffer().put("Howdy!");
char c;
while((c = bb.getChar()) != 0)
printnb(c + " ");
print();
bb.rewind();
// Store and read a short:
bb.asShortBuffer().put((short)471142);
print(bb.getShort());
bb.rewind();
// Store and read an int:
bb.asIntBuffer().put(99471142);
print(bb.getInt());
bb.rewind();
// Store and read a long:
bb.asLongBuffer().put(99471142);
print(bb.getLong());
bb.rewind();
// Store and read a float:
bb.asFloatBuffer().put(99471142);
print(bb.getFloat());
bb.rewind();
// Store and read a double:
bb.asDoubleBuffer().put(99471142);
print(bb.getDouble());
bb.rewind();
}
} /* Output:
i = 1025
H o w d y !
12390
99471142
99471142
9.9471144E7
9.9471142E7
*///:~

向ByteBuffer插入基本类型数据最简单的方法:利用asCharBuffer()、asShortBuffer等获得该缓冲器上的视图,然后使用视图的put()方法,此适用于所有基本数据类型。(short要进行类型转换)。

具体详情:
NIO

序列化

Externalizable 可用来代替实现 Serializable 接口——对序列化过程进行控制。这个 Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal()readExternal()
对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个Externalizable对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用readExternal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Blip implements Externalizable {

private int i;
private String s;

public Blip() {
System.out.println("blip construct");
}

public Blip(int i, String s) {
System.out.println("blip construct with i and s");
this.i = i;
this.s = s;
}

@Override
public String toString() {
return s + i;
}


@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("blip writeExternal");
out.writeInt(i);
out.writeObject(s);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("blip readExternal");
i = in.readInt();
s = (String) in.readObject();
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Blip blip = new Blip(1, "blip");
System.out.println(blip);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("blip.out"));
o.writeObject(blip);
o.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("blip.out"));
Blip blip2 = (Blip) in.readObject();
System.out.println(blip2);
in.close();

}


/*
输出:
blip construct with i and s
blip1
blip writeExternal
blip construct
blip readExternal
blip1
*/
}

writeExternal()readExternal()两个方法中数据写入和数据读取的顺序要一致,否则会报OptionalDataException异常

可以实现Serializable接口,并添加 writeObjct()readObject两个方法(即Externalizable接口下的两个方法),这样一旦对象被序列化或被反序列化还原就会自动地分别调用这两个方法。

1
2
3
private void writeObject(ObjectOutputStream stream) throws IOException;

private void readObject(OjectInputStream stream) throws IOException,ClassNotFoundException

在调用ObjectOutputStream.writeObject()时,会检查所传递的Serializable对象,看看是否实现了它自己的writeObject()。如果是这样,就跳过正常的序列化过程并调用它的writeObject(),readObject()同理。
在你的writeObject()内部,可以调用defaultWriteObject()来选择执行默认的writeObject(),此时就等同于常规下的Serializable

transient (瞬时)关键字

用于控制某个特定子对象不想让Java的序列化机制自动保存于恢复。
比如说一个用户对象的密码字段不想被序列化保存下来就可以使用 transient 来声明该字段。

使用“持久化”

只要将任何对象序列化到单一流中,就可以恢复出与之前写出时一样的对象网,并不会出现重复的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class House implements Serializable {}

class Animal implements Serializable {
private String name;
private House preferredHouse;
Animal(String nm, House h) {
name = nm;
preferredHouse = h;
}
public String toString() {
return name + "[" + super.toString() +
"], " + preferredHouse + "\n";
}
}

public class MyWorld {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
House house = new House();
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal("Bosco the dog", house));
animals.add(new Animal("Ralph the hamster", house));
animals.add(new Animal("Molly the cat", house));
print("animals: " + animals);
ByteArrayOutputStream buf1 =
new ByteArrayOutputStream();
ObjectOutputStream o1 = new ObjectOutputStream(buf1);
o1.writeObject(animals);
o1.writeObject(animals); // Write a 2nd set
// Write to a different stream:
ByteArrayOutputStream buf2 =
new ByteArrayOutputStream();
ObjectOutputStream o2 = new ObjectOutputStream(buf2);
o2.writeObject(animals);
// Now get them back:
ObjectInputStream in1 = new ObjectInputStream(
new ByteArrayInputStream(buf1.toByteArray()));
ObjectInputStream in2 = new ObjectInputStream(
new ByteArrayInputStream(buf2.toByteArray()));
List
animals1 = (List)in1.readObject(),
animals2 = (List)in1.readObject(),
animals3 = (List)in2.readObject();
print("animals1: " + animals1);
print("animals2: " + animals2);
print("animals3: " + animals3);
}
} /* Output: (Sample)
animals: [Bosco the dog[Animal@addbf1], House@42e816
, Ralph the hamster[Animal@9304b1], House@42e816
, Molly the cat[Animal@190d11], House@42e816
]
animals1: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals2: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals3: [Bosco the dog[Animal@10d448], House@e0e1c6
, Ralph the hamster[Animal@6ca1c], House@e0e1c6
, Molly the cat[Animal@1bf216a], House@e0e1c6
]

Java序列化是不能序列化static变量的,因为其保存的是对象的状态,而static变量保存在全局数据区,在对象未实例化时就已经生成,属于类的状态。

第十九章(枚举类型)

在Android避免使用Enum,因为与静态常量相比,它对内存的占用要大很多。

values()

values()方法是由编译器插入到enum定义的static方法(同时还有valueof()方法),所以若将enum实例向上转型为Enum,那么values方法就不可访问。不过,在Class中有一个getEnumConstants()方法,即使Enum接口中没有values()方法,我们仍可以通过Class对象取得所有enum实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UpcastEnum {

enum Sample{TEXT,CONTENT}

public static void main(String[] args) {

//Sample[] samples = Sample.values();
Enum e = Sample.TEXT;
for(Enum en :e.getClass().getEnumConstants()){
System.out.println(en);
}

}
/*
输出:
TEXT
CONTENT
*/
}

当然对于不是枚举的类调用此方法只会返回null.

Enum 泛型使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Enums {

private static Random random = new Random();

public static <T extends Enum<T>> T random(Class<T> ec) {
return random(ec.getEnumConstants());
}

public static <T> T random(T[] values) {
return values[random.nextInt(values.length)];
}

enum Activity {create, start, resume, pause, stop, destroy}

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.print(Enums.random(Activity.class) + " ");
}
}
//输出:resume destroy start destroy start destroy stop resume create start
}

在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。

EnumSet 和 EnumMap

Enum能说明一个二进制是否存在时,具有更好的表达能力。
EnumSet的基础是long,一个long值有64位,而一个enum实例只需一位bit表示其是否存在。

EnumMap要求其中的key必须来自一个enum ,EnumMap 内部由数组实现。

第二十章(注解)

注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。

基本语法

1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserCase {
public int id();
public String description() default "no description";
}

@Target 用来定义你的注解将应用于什么地方,
@Rectetion用来定义该注解在哪一个级别可用,在源代码(SOURCE)、类文件(CLASS)或者运行时(RUNTIME).

注解 用途
@Target 表示该注解可以用于什么地方,可能的ElementType参数包括:CONSTRUCTOR:构造器的声明,FIELD:域声明(包括enum实例),LOCAL_VARIABLE:局部变量声明,METHOD:方法声明,PACKAGE:包声明,PARAMETER:参数声明,TYPE:类、接口(包括注解类型)或enum声明
@Retention 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:SOURCE:注解将被编译器丢弃,CLASS:注解在class文件中可用,但会被VM丢弃,RUNTIME:VM将在运行期也被保留注解,因此可以通过反射机制读取注解的信息。
@Documented 将此注解包含在Javadoc中
@Inherited 允许子类继承父类中的注解

编写注解处理器

有了处理器才能使注解发挥作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PasswordUtils {
@UserCase(id = 47, description = "Password must contain at least one numeric")
public boolean vaildatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}

@UserCase(id = 48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}

@UserCase(id = 49, description = "Not equal")
public boolean checkForNewPassword(List<String> prevPasswords, String password) {
return !prevPasswords.contains(password);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class UserCaseTracker {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for (Method m : cl.getDeclaredMethods()) {
UserCase uc = m.getAnnotation(UserCase.class);
if (uc != null) {
System.out.println("Found Use Case:" + uc.id() + " " + uc.description());
}
useCases.remove(new Integer(uc.id()));
}
for (int i : useCases) {
System.out.println("Warning:Missiong use case-" + i);
}
}

public static void main(String[] args) {
List<Integer> useCases = new ArrayList<>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
/*
Found Use Case:47 Password must contain at least one numeric
Found Use Case:48 no description
Found Use Case:49 Not equal
Warning:Missiong use case-50
* */

getAnnoation()方法返回指定类型的注解对象,若没有则返回null值

注解元素可用的类型

  • 所以基本类型(int,float,boolean等)
  • Class
  • enum
  • Annotation(注解可以嵌套)
  • 以上类型的数组

默认值限制

元素不能有不确定的值,元素必须要么具有默认值,要么在使用注解时提供元素的值。
其次,对于非基本类型的元素,无论是在源代码中声明时或是在注解接口中定义默认值时,都不能以null作为其值。

apt

APT(Annotation processing tool) 是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。

Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件.使用APT主要的目的是简化开发者的工作量。

因为APT可以编译程序源代码的同时,生成一些附属文件(比如源文件类文件程序发布描述文件等),这些附属文件的内容也都是与源代码相关的,换句话说,使用APT可以代替传统的对代码信息和附属文件的维护工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

/**
* 修饰表属性
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Persistent {
String table();
}

/**
* 修饰标识属性
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface IdProperty {
String column();
String type();
String generator();
}

/**
* 修饰普通成员变量的Annotation
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Property {
String column();
String type();
}

建立一个java类 使用以上注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 普通的Java类
*/
@Persistent(table = "persons_table")
public class Person {
@IdProperty(column = "person_id", type = "integer", generator = "identity")
private int id;
@Property(column = "person_name", type = "string")
private String name;
@Property(column = "person_age", type = "integer")
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

下面我们为这三个Annotation提供了一个Annotation处理器该处理器的功能是根据注释来生成一个Hibernate的映射文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class HibernateAnnotationProcessor implements AnnotationProcessor {
// Annotation处理器环境是该处理器与APT交互的重要途径
private AnnotationProcessorEnvironment env;

// 构造HibernateAnnotationProcessor对象时获得处理器环境
public HibernateAnnotationProcessor(AnnotationProcessorEnvironment env) {
this.env = env;
}

// 循环处理每个对象
public void process() {
// 遍历每个class文件
for (TypeDeclaration t : env.getSpecifiedTypeDeclarations()) {
// 定义一个文件输出流用于生成额外的文件
FileOutputStream fos = null;
// 获取正在处理的类名
String clazzName = t.getSimpleName();
// 获取类定义前的Persistent Annotation
Persistent per = t.getAnnotation(Persistent.class);
// 当per Annotation不为空时才继续处理
if (per != null) {
try {
// 创建文件输出流
fos = new FileOutputStream(clazzName + ".hbm.xml");
PrintStream ps = new PrintStream(fos);
// 执行输出
ps.println("<?xml version=\"1.0\"?>");
ps.println("<!DOCTYPE hibernate-mapping");
ps.println(" PUBLIC \"-// Hibernate/Hibernate Mapping DTD 3.0//EN\"");
ps.println(" \"http:// hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">");
ps.println("<hibernate-mapping>");
ps.print(" <class name=\"" + t);
// 输出per的table()的值
ps.println("\" table=\"" + per.table() + "\">");
for (FieldDeclaration f : t.getFields()) {
// 获取指定FieldDeclaration前面的IdProperty Annotation
IdProperty id = f.getAnnotation(IdProperty.class);
// 如果id Annotation不为空
if (id != null) {
// 执行输出
ps.println(" <id name=\"" + f.getSimpleName() + "\" column=\"" + id.column() + "\" type=\"" + id.type() + "\">");
ps.println(" <generator class=\"" + id.generator() + "\"/>");
ps.println(" </id>");
}
// 获取指定FieldDeclaration前面的Property Annotation
Property p = f.getAnnotation(Property.class);
// 如果p Annotation不为空
if (p != null) {
// 执行输出
ps.println(" <property name=\"" + f.getSimpleName() + "\" column=\"" + p.column() + "\"type=\"" + p.type() + "\"/>");
}
}
ps.println(" </class>");
ps.println("</hibernate-mapping>");
} catch (Exception e) {
e.printStackTrace();
} finally { // 关闭输出流
try {
if (fos != null) {
fos.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
}

上面的Annotation处理器比较简单与前面通过反射来获取Annotation信息不同的是,这个Annotation处理器使用AnnotationProcessorEnvironment来获取Annotation信息AnnotationProcessorEnvironment包含了一个getSpecifiedTypeDeclarations方法,可获取所有需要处理的类声明,这个类声明可包括类,接口,和枚举等声明,由TypeDeclaration对象表地示与Classc对象的功能大致相似,区别只是TypeDeclaration是静态,只要有类文件就可以获得该对象而Class是动态的必须由虚拟机装载了指定类文件后才会产生。

TypeDeclaration又包含了如下三个常用方法来获得对应的程序元素

  • getFields: 获取该类声明里的所有成员变量声明返回值是集合元素FieldDeclaration的集合
  • getMethods: 获取该类声明里的所有成员声明返回值是集合元素MethodDeclaration的集合
  • getPackage: 获取该类声明里的包声明返回值是TypeDeclaration.

上面三个方法返回的TypeDeclaration,FieldDeclaration,MethodDeclaration都可调用getAnnotation方法来访问修饰它们的Annotation,上面程序中就是获取不同程序元素的Annotation的代码。
提供了上面的Annotation处理器类之后,还应该为该Annotation处理器提供一个处理工厂,处理工厂负责决定该处理器支持哪些Annotation,并通过getProcessorFor方法来生成一个Annotation处理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;
import java.beans.*;
import java.io.*;
import java.util.*;
public class HibernateAnnotationFactory implements AnnotationProcessorFactory {
// 所有支持的注释类型
public Collection<String> supportedAnnotationTypes() {
return Arrays.asList("Property", "IdProperty", "Persistent");
}
// 返回所有支持的选项
public Collection<String> supportedOptions() {
return Arrays.asList(new String[0]);
}
// 返回Annotation处理器
public AnnotationProcessor getProcessorFor( Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) {
return new HibernateAnnotationProcessor(env);
}
}

提供了上面的处理器工厂后就可以使用APT工具来处理上面的Person.java源文件。并根据该源文件来生成一个XML文件。 APT工具位于JDK的安装路径的bin路径下。
运行APT命令时,可以使用-factory选项来指定处理器工厂类。
如下所示 run.cmd,使用前注意配置Java环境变量:

1
apt -factory HibernateAnnotationFactory Person.java

第二十一章(并发)

Executor

newCachedThreadPool() 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。新的线程加入后,如果正在运行的线程达到了上限,则会阻塞,直到有了空闲的线程来运行。

Executor.shutdown() 这个方法调用可以防止新任务被提交给这个Executor,当前线程将继续运行在shutdown()被调用之前提交的所有任务。

利用 Callable 接口接收 Runnable 运行后返回的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CallableDemo {

public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(exec.submit(new TaskWithResult(i)));
}
for(Future<String> future: results){
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/*
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
*/
}

class TaskWithResult implements Callable<String> {
private int id;

public TaskWithResult(int id) {
this.id = id;
}

@Override
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}

1
2
3
4
// old-style
// Thread.sleep(100);
// Java SE5/6-style
TimeUnit.MILLISECONDS.sleep(100);

setPriority()优先级是在run()的开头部分设定的,在构造器中设置不会有任何好处,因为Executor在此刻还没有开始执行任务。
调整优先级时,最好只使用MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY

1
thread.setDaemon(true);//设置后台线程,只有当所有非后台线程终止时,后台线程才会结束

ThreadFactory可以定制由Executor创建的线程的属性(后台、优先级、名称)。
后台线程在所有非后台终止时,会“突然”终止,一旦main()退出,JVM就会立即关闭所有的后台进程,而不会有任何形式的确认行为(比如说不会执行finally语句)。

一个线程可以在其他线程上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将会被挂起,直到目标线程t结束才恢复(即t.isActive()返回为假)。

可以通过Thread.UncaughtExceptionHandler来捕获线程上溢出的异常。

1
2
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
Thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());//静态域,全局使用

共享受限资源

使用synchronized标记来防止资源冲突。
所有对象都自动含有单一的锁(也成为监视器)

  • 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  • 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
  • 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
  • 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
  • 以上规则对其它对象锁同样适用.

synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块,规则同上

可以synchronized(xx.class)来锁住一个类而不是对象。

使用java.util.concurrent.locks中的显式互斥机制。Lock对象必须被显式地创建、锁定和释放。

1
2
3
4
5
6
7
8
9
10
11
private Lock lock = new ReentrantLock();
public void next(){
lock.lock();
try{
//do something
Thread.yield();
return;
}finally {
lock.unlock();
}
}

对Lock()的调用,必须放在finally子句中带有unlock()的try-finally语句中。return语句要在try子句中出现,以确保unlock()不会过早发生。

ReentrantLock允许你尝试着获取但最终未获取锁,如果其他人已经获取了这个锁,那你可以决定离开去执行其他一些事情,而不是等待这个锁被释放。

1
2
3
lock.tryLock();

lock.tryLock(2,TimeUnit.SECONDS);

正确使用Volatile变量

要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

对变量的写操作不依赖于当前值。
该变量没有包含在具有其他变量的不变式中。

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

结合使用 volatile 和 synchronized 实现“开销较低的读-写锁”

1
2
3
4
5
6
7
8
9
10
11
public class CheesyCounter {
// Employs the cheap read-write lock trick
// All mutative operations MUST be done with the 'this' lock held
@GuardedBy("this") private volatile int value;

public int getValue() { return value; }

public synchronized int increment() {
return value++;
}
}

通过原子类(AtomicInteger、AtomicLong、AtomicReference)可以有效减少synchronized关键字和Lock显式锁的使用。但一般减少使用,只需在特殊情况下使用即可。

ThreadLocal对象通常当作静态域存储,它为每个单独的线程分配了自己的存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
private Random rand = new Random(47);

protected synchronized Integer initialValue() {
return rand.nextInt(10000);
}
};

public static void increment() {
value.set(value.get() + 1);
}

public static int get() {
return value.get();
}

public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new Accessor(i));
}
TimeUnit.SECONDS.sleep(3);
exec.shutdownNow();
}
/*
#0:9259
#1:556
#2:6694
#3:1862
#4:962
#0:9260
#1:557
#2:6695
#3:1863
#4:963
*/

}
class Accessor implements Runnable {
private final int id;
public Accessor(int id) {
this.id = id;
}

@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
ThreadLocalVariableHolder.increment();
System.out.println(this);
Thread.yield();
}
}

public String toString() {
return "#" + id + ":" + ThreadLocalVariableHolder.get();
}
}

终结任务

线程状态:新建就绪阻塞死亡

进入阻塞的情况:

  • 调用sleep()使任务进入休眠状态
  • 调用wait()线程挂起,需要notify()或notifyAll()来唤醒
  • 等待输入输出
  • 调用对象的同步控制方法,但对象锁不可用

Thread类包含interrupt()方法,因此可以终止被阻塞的任务,但若线程已经被阻塞或正试图执行一个阻塞操作那么设置中断状态将会抛出InterruptedException。
可以通过Executor的shutdownNow() 来中断它启动的线程。也可以通过submit()方法获得线程的Future,再通过 Future.cancel() 来中断特定线程。

Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

因此最好的方法是设置共享变量,并调用interrupt(),双重检查下确保线程能准确退出。

线程之间协作

对于wait()而言

  • 在wait()期间对象锁是释放的
  • 可以通过notify()、notifyAll(),或者令时间到期,从wait()中恢复执行。

wait()的标准写法,防止错失竞争信号

1
2
3
4
5
synchronized(sharedMonitor){
while(someCondition){
sharedMonitor.wait();
}
}

当notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。

死锁

从哲学家问题可以看出,要发生死锁必须要满足以下4个条件:

  • 互斥条件。任务使用的资源至少有一个是不能共享的。
  • 至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。
  • 资源不能被抢占,任务必须把资源释放当作普通事件。
  • 必须要有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源。

新类库 concurrent

CountDownLatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class CountDownLatchDemo {  
final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch=new CountDownLatch(2);//两个工人的协作
Worker worker1=new Worker("zhang san", 5000, latch);
Worker worker2=new Worker("li si", 8000, latch);
worker1.start();//
worker2.start();//
latch.await();//等待所有工人完成工作
System.out.println("all work done at "+sdf.format(new Date()));
}


static class Worker extends Thread{
String workerName;
int workTime;
CountDownLatch latch;
public Worker(String workerName ,int workTime ,CountDownLatch latch){
this.workerName=workerName;
this.workTime=workTime;
this.latch=latch;
}
public void run(){
System.out.println("Worker "+workerName+" do work begin at "+sdf.format(new Date()));
doWork();//工作了
System.out.println("Worker "+workerName+" do work complete at "+sdf.format(new Date()));
latch.countDown();//工人完成工作,计数器减一

}

private void doWork(){
try {
Thread.sleep(workTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


}

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。CountDownLatch很像一个线程闸,通过await()方法阻塞主线程等待闸内的线程完成,当计数器为0时主线程恢复。

CyclicBarrier

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class CyclicBarrierTest2 {

static CyclicBarrier c = new CyclicBarrier(2, new A());

public static void main(String[] args) {
new Thread(new Runnable() {

@Override
public void run() {
try {
c.await();
} catch (Exception e) {

}
System.out.println(1);
}
}).start();

try {
c.await();
} catch (Exception e) {

}
System.out.println(2);
}

static class A implements Runnable {

@Override
public void run() {
System.out.println(3);
}

}

}
/*
3
1
2
*/

CyclicBarrier和CountDownLatch的区别

  • CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
  • CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。
  • CyclicBarrier中线程到达“屏障”时会被阻塞。

Semaphore

Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证。还可以用tryAcquire()方法尝试获取许可证。

1
2
3
4
5
6
7
try {
s.acquire();
System.out.println("save data");
s.release();
}
catch (InterruptedException e) {
}

DelayQueue

DelayQueue是一个BlockingQueue,其特化的参数是Delayed。(不了解BlockingQueue的同学,先去了解BlockingQueue再看本文)
Delayed扩展了Comparable接口,比较的基准为延时的时间值,Delayed接口的实现类getDelay的返回值应为固定值(final)。DelayQueue内部是使用PriorityQueue实现的。

DelayQueue = BlockingQueue + PriorityQueue + Delayed

Exchanger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Producer extends Thread {
List<Integer> list = new ArrayList<>();
Exchanger<List<Integer>> exchanger = null;
public Producer(Exchanger<List<Integer>> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
Random rand = new Random();
for(int i=0; i<10; i++) {
list.clear();
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
try {
list = exchanger.exchange(list);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

class Consumer extends Thread {
List<Integer> list = new ArrayList<>();
Exchanger<List<Integer>> exchanger = null;
public Consumer(Exchanger<List<Integer>> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
list = exchanger.exchange(list);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(list.get(0)+", ");
System.out.print(list.get(1)+", ");
System.out.print(list.get(2)+", ");
System.out.print(list.get(3)+", ");
System.out.println(list.get(4)+", ");
}
}
}

public class ExchangerDemo {

public static void main(String[] args) throws Exception {
Exchanger<List<Integer>> exchanger = new Exchanger<>();
new Consumer(exchanger).start();
new Producer(exchanger).start();
}
}

Exchanger是在两个任务之间交换对象。

当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行

ReadWriteLock

1
2
3
4
5
6
private ReentrantReadWriteLock lock =
new ReentrantReadWriteLock(true);

Lock wlock = lock.writeLock();//获取写锁
Lock rlock = lock.readLock();//获取读锁
lock.getReadLockCount();//获取读锁的个数

ReadWriteLock 对向数据结构相对不频繁写入,但是有多个任务要经常读取这个数据结构的这类情况进行了优化。ReadWriteLock使得你可以同时有多个读取者,只要它们都不试图写入即可。如果写锁已经被其他任务持有,那么任何读取者都不能访问。

总结

关于最后的图形化用户界面章节,基本上讲的是如何使用Swing上的UI控件,所以略过。

断断续续的看完这本《Java编程思想》,只能说不愧是java语言最经典的教材,除去阅读时不断巩固自己在java上的知识,还能时不时的发现之前没有用到过的java技术,让人获益匪浅啊!

文章作者: cpacm
文章链接: http://www.cpacm.net/2016/11/29/Java编程思想阅读笔记/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 cpacm
打赏
  • 微信
  • 支付宝

评论