时空主站

嗨,我是时空,一名来自中国的开发者。

Java 笔记

数据类型和运算符

数据类型 字节数
int 4
long 8
char 2
float 4
double 8
bool 1

常量

普通常量

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

final int A = 20;
public static void main(String[] args) {
Test1 test1 = new Test1();
System.out.println(test1.A);
}

}

类常量

1
2
3
4
5
6
7
public class Test1 {

static final int B = 20;
public static void main(String[] args) {
System.out.println(B);
}
}

常量一般都定义为大写字母

运算符

1
2
3
4
5
6
7
public class Test2 {
public static void main(String[] args) {
int a = 30;
System.out.println(a++);
System.out.println(++a);
}
}

输出结果

30
32

数组

binarySearch(), 使用二分搜索法来搜索指定数组,以获得指定对象。

在进行此调用之前,必须根据元素的自然顺序对数组进行升序排序(通过 sort() 方法)

参数:

  • a - 要搜索的数组
  • key - 要搜索的值

返回:

  • 如果它包含在数组中,则返回搜索键的索引;
  • 否则返回 (-(插入点) - 1) 。
  • 插入点被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为a.length
  • 注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SearchArray
{
public static void main(String[] args)
{
int[] array = {2, 4, 6, 1, 3, 5, 9, 11, -5, 30, 100};
Arrays.sort(array);
int index = Arrays.binarySearch(array, 11);
System.out.println(Arrays.toString(array));
System.out.println("The index of 11 is "+index);
index = Arrays.binarySearch(array, 10);
System.out.println("The index of 10 is "+index);
}
}

输出结果

[-5, 1, 2, 3, 4, 5, 6, 9, 11, 30, 100]
The index of 11 is 8
The index of 10 is -9

String

  1. 串连接:+concat()
  2. 提取子字符串:substring()
  3. 提取字符:charAt()
  4. 获取字符串长度:length
  5. 判断字符串是否相等:equal()

有的登陆系统密码忽略大小写。此时java语言中有个方法就是equalsIgnoreCase(String str),这个方法忽略字符串大小写。

常见疑难: equals 和 == 的区别

java中的数据类型,可分为两类:

  1. 基本数据类型,也称原始数据类型。

byte,short,char,int,long,float,double,boolean

他们之间的比较,应用双等号(==),比较的是他们的值。

  1. 复合数据类型(类)

当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。 JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。

对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

StringBuffer

  1. 追加:append()
  2. 指定位置插入字符串:insert()
1
2
3
4
5
6
7
8
9
public class StringBufferTest {

public static void main(String[] args) {

StringBuffer strbuf1 = new StringBuffer();
System.out.println(strbuf1.capacity());
System.out.println(strbuf1.length());
}
}

输出结果

16
0

常见疑难 :String 和 StringBufffer 的区别

StringBuffer对象的内容可以修改;String对象一旦产生后就不可以被修改,重新赋值其实是两个对象。

StringBuffer的内部实现方式和String不同,StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

String

在String类中没有用来改变已有字符串中的某个字符的方法,由于不能改变一个java字符串中的某个单独字符,所以在JDK文档中称String类的对象是不可改变的。然而,不可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。

StringBuffer

StringBuffer类属于一种辅助类,可预先分配指定长度的内存块建立一个字符串缓冲区。这样使用StringBuffer类的append方法追加字符 比 String使用 + 操作符添加字符 到 一个已经存在的字符串后面有效率得多。因为使用 + 操作符每一次将字符添加到一个字符串中去时,字符串对象都需要寻找一个新的内存空间来容纳更大的字符串,这无凝是一个非常消耗时间的操作。添加多个字符也就意味着要一次又一次的对字符串重新分配内存。使用StringBuffer类就避免了这个问题。
StringBuffer是线程安全的,在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。

类和对象

访问控制符:public ,private ,protected, default

private default protected public
同一类中可见
同一包中对子类可见
同一包中对非子类可见
不同包中对子类可见
不同包中对非子类可见

基础类 :Math,Date

重载和包

重载

重载的实质

  • 方法名相同
  • 参数个数可以不同
  • 参数类型可以不同
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
class overload {
// /一个普通的方法,不带参数
void test() {
System.out.println("No parameters");
}

// /重载上面的方法,并且带了一个整型参数
void test(int a) {
System.out.println("a: " + a);
}

// /重载上面的方法,并且带了两个参数
void test(int a, int b) {
System.out.println("a and b: " + a + " " + b);
}

// /重载上面的方法,并且带了一个双精度参数,与上面带一个参数的重载方法不一样
double test(double a) {
System.out.println("double a: " + a);
return a * a;
}

public static void main(String args[]) {
overload o = new overload(); //创建了对象o
o.test();
o.test(2);
o.test(2, 3);
o.test(2.0);
}
}

输出结果

No parameters
a: 2
a and b: 2 3
double a: 2.0

包 package

import com.stx8.test,相当于C#里的using语句,C++里的头文件及namespace

继承和多态

继承

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
class People {
int a;

People() {
a = 1;
}

People(int a) {
this.a = a;
}
}

public class men extends People {
int b;

men(int a, int b) {
super(a);
this.b = b;
}

public static void main(String[] args) {
men m = new men(10, 20); //通过带参构造函数创建men类对象
System.out.println(m.a + " " + m.b); //调用对象的成员变量a和b
}
}

设计类的继承时的建议

不要使用受保护字段,即protected字段

如果不希望自己的类再被扩展,可以在类的声明之前加上final关键字。

构造函数

  • 构造函数名字与类名相同(包括大小写)
  • 一个类可以有多个构造函数
  • 构造函数没有返回值,也不用写void关键字
  • 构造函数总和new运算符一起被调用
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
class Test3_super{
public Test3_super() {
// TODO 自动生成的构造函数存根
System.out.println("父类构造函数");
}
public void fun1(){
System.out.println("父类F1");
}
protected void fun2() {
System.out.println("父类F2");
}
private void fun3() {
System.out.println("父类F3");
}

}

public class Test3 extends Test3_super{

Test3(){
super();
System.out.println("子类构造函数");
}
public void fun2() {
System.out.println("子类F2");

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

Test3 test3 = new Test3();
test3.fun1();
test3.fun2();

}
}

输出结果

父类构造函数
子类构造函数
父类F1
子类F2

java 的单继承性

针对同一方法,子类的访问控制权限只能等于或大于父类。

类之间的关系

多态

多态一定要遵守两个规则:

  • 方法名称一定要一样
  • 传入参数的类型一定要不一样

多态的两种表现形势

  • 重载
  • 覆盖

接口 interface

接口中只有方法名,没有具体实现的方法体。

接口的声明默认是 public,有时候也可以省略。

类实现接口是要使用 implements关键字

在类实现接口时要注意:

  • 声明类需要实现指定接口
  • 提供接口中所有方法的定义
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
package com.interfacetest;

interface Alarm {
void alarm();
}

class AlarmDoor implements Alarm {

void open() {
System.out.println("door open");
}
void close() {
System.out.println("door close");
}
public void alarm() {
System.out.println("a door with alarm");
}
}

public class InterfaceTest {

public static void main(String[] args) {

AlarmDoor alarmDoor = new AlarmDoor();
alarmDoor.alarm();
}
}

接口的多重实现

为解决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
package com.interfacetest;

interface Alarm {
void alarm();
}

interface Window {
void window();
}

class AlarmDoor implements Alarm, Window {

public void window() {
System.out.println("a door with window");
}

public void alarm() {
System.out.println("a door with alarm");
}
}

public class InterfaceTest2 {

public static void main(String[] args) {

AlarmDoor alarmDoor = new AlarmDoor();
alarmDoor.alarm();
}
}

接口的属性

接口不是一个类,不可以使用关键字new来生成一个接口的实例。但是可以声明一个接口变量 “school sc”.

如果要生成一个接口的实例,可以让接口变量指向一个已经实现该接口的类的对象。

1
school sc = new student();

在接口中,不能声明实例字段及静态方法,但可以声明常量。接口不一定要有方法,可以全是常量。

接口的继承

和类一样,使用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
package com.interfacetest;

interface Lock {
void lock();
}

interface HighLock extends Lock{
void hignlock();
}

class Door implements HighLock {

@Override
public void hignlock() {
// TODO 自动生成的方法存根
System.out.println("WOW high lock");
}
@Override
public void lock() {
// TODO 自动生成的方法存根
System.out.println("just lock");
}
}

public class InterfaceTest3 {

public static void main(String[] args) {

Door door = new Door();
door.lock();
}
}

内部类

内部类就是在类的内部再创建一个类。

内部类的基本结构

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
package com.interclass;

//外部类
class Out {
private int age = 12;

//内部类
class In {
public void print() {
System.out.println(age);
}
}
}

public class InterclassTest1 {
public static void main(String[] args) {

Out.In in = new Out().new In();
in.print();

Out out = new Out();
Out.In in2 = out.new In();
in2.print();

}
}

匿名内部类

匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口

不使用匿名内部类来实现抽象方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.interclass;

abstract class Person {
public abstract void eat();
}

class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}

public class InterclassTest2 {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}

运行结果:

eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用,但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.interclass;

abstract class Person1 {
public abstract void eat();
}

public class InterclassTest3 {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}

运行结果:

eat something

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口

Thread类的匿名内部类实现:

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
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();
}
}

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
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) { System.out.print(i + " "); } } }; Thread t = new Thread(r); t.start(); } } ``` 内部类的好处: - 内部类的对象能够访问创建他的对象的所有方法和属性,包括私有数据 - 对于同一个包中的其他类来说,内部类是隐形的 。 - 匿名内部类可以很方便的定义回调 - 使用内部类可以方便的编写时间驱动的程序。 作为一个单独的类,只能有 `default` 和 `public` 两种访问控制符,但是作为内部类,可以使用 `private` 控制符。当内部类设置为 `private` 时,包含此内部类的外部类的方法才可以访问它。 ### 使用内部类来访问对象 内部类这个机制之所以出现,是因为存在如下两个目的: - 可以让程序中逻辑上相关的类结合在一起 - 内部类可以直接访问外部类的成员。 ## 常见疑难 接口与继承的区别: - 属性:接口中的所有属性都是公开静态常量,继承则无所谓 - 方法:接口中所有方法都是公开抽象方法,继承中所有方法不一定是抽象的 - 接口方法:接口没有构造器,继承有构造器 ## 抽象和封装 抽象就是讲拥有共同方法和属性的对象提取出来,提取后,重新设计一个更加通用、更加大众的类,这个类成为抽象类。 ### 抽象类 `abstract` 具有一个或多个抽象方法的类,本身就要被定义为抽象类。含有抽象方法的类一定是抽象类。 抽象类不仅可以有抽象方法,也可以又具体的方法,一个类中只要有一个抽象方法,就是抽象类。 抽象类中不一定含有抽象的方法,也可以全部都是具体的方法。 抽象类是可以继承的,如果子类没有实现抽象类的全部抽象方法,那么子类也是抽象类。如果实现了抽象类的全部抽象方法,那么子类就不是抽象类。 抽象类不可以被实例化。但是可以声明一个抽象类的变量指向具体子类的对象。 抽象类的好处在于,有的方法在父类中不想实现时,可以不用实现。 ```java package com.abstracttest; abstract class Door{ abstract void open(); abstract void close(); } class MyDoor extends Door{ @Override void open() { // TODO 自动生成的方法存根 System.out.println("i can open"); } @Override void close() { // TODO 自动生成的方法存根 System.out.println("i can close"); } } public class AbstractTest1 { public static void main(String[] args) { MyDoor myDoor = new MyDoor(); myDoor.close(); } } ``` ## 常见疑难 抽象与接口的区别: 共同点 - 都不能创建实例对象 - 可以声明变量,通过指向子类或实现类的对象类,来创建对象实例。 不同点 - Java 不支持多重继承,即一个子类只能有一个父类,但一个子类可以实现多个接口。 - 接口内不能有实例字段,只能有静态变量,抽象类可以拥有实例字段 - 接口内方法自动设置为 `public`的,抽象类中的方法必须手动声明访问控制符。 ## 枚举 `enum` ## 反射 程序自己能够检查自身信息。反射使得java语言具有了“动态性”,即程序首先会检查某个类中的方法、属性等信息,然后再动态的调用或动态的创建该类或该类的对象。 ### 反射类的基石 ――`Class`类 任何事物都可以用类表示,那么java中的类可以用一个什么类表示呢? 从JDK1.2开始,就出现了Class类,该类描述Java中的一切事物,该类描述了关于类事务的类名字、类的访问属性、类所属的包名等。 ### 反射的基本应用 所谓反射就是把java类中的各种成分映射成相应的java类。 不仅java类,可以用`Class`类的对象表示,而java类的各种成员:成员变量、方法、构造方法、包等也可以用相应的类表示。 Java反射机制主要提供了以下功能: - 在运行时判断任意一个对象所属的类; - 在运行时构造任意一个类的对象; - 在运行时判断任意一个类所具有的成员变量和方法; - 在运行时调用任意一个对象的方法。 反射一般会设计如下类 : - `Class` : 表示一个类的类 - `Filed` : 表示属性的类 - `Method` : 表示方法的类 - `Constructor` : 表示类的构造方法的类 &gt; `Class`类位于java.lang包中,而后面3个的类都位于java.lang.reflect包中

编写Java反射程序的步骤:

- 必须首先获取一个类的Class对象
- 然后分别调用Class对象中的方法来获取一个类的属性/方法/构造方法的结构

现有一个类:

​```java
package com.reflection;
public class TestReflection {

private String username;
private String password;
private int[] age;

public void setUserName(String username) {
this.username = username;
}

private void setPassWord(String password) {
this.password = password;
}
}
</code></pre>

获取一个类的Class对象:

<pre><code class="java">TestReflection tfReflection = new TestReflection();
Class c1 = TestReflection.class;
Class c2 = Class.forName("com.reflection.TestReflection");
Class c3 = tfReflection.getClass();
</code></pre>

获取指定的包名:

<pre><code class="java">String package01 = c1.getPackage().getName();
</code></pre>

获取类的修饰符:

<pre><code class="java">int mod = c1.getModifiers();
String modifier = Modifier.toString(mod);
</code></pre>

获取指定类的完全限定名:

<pre><code class="java">String className = c1.getName();
</code></pre>

获取指定类的父类:

<pre><code class="java">Class superClazz = c1.getSuperclass();
String superClazzName = superClazz.getName();
</code></pre>

获取实现的接口:

<pre><code class="java">Class[] interfaces = c1.getInterfaces();
for (Class t : interfaces) {
System.out.println("interfacesName = " + t.getName());
}
</code></pre>

获取指定类的成员变量:

<pre><code class="java">Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
modifier = Modifier.toString(field.getModifiers()); // 获取每个字段的访问修饰符
Class type = field.getType(); // 获取字段的数据类型所对应的Class对象
String name = field.getName(); // 获取字段名
if (type.isArray()) { // 如果是数组类型则需要特别处理
String arrType = type.getComponentType().getName() + "[]";
System.out.println("" + modifier + " " + arrType + " " + name + ";");
}
else {
System.out.println("" + modifier + " " + type + " " + name + ";");
}
}
</code></pre>

获取类的构造方法:

<pre><code class="java">Constructor[] constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName(); // 构造方法名
modifier = Modifier.toString(constructor.getModifiers()); // 获取访问修饰符
System.out.println("" + modifier + " " + name + "(");
Class[] paramTypes = constructor.getParameterTypes(); // 获取构造方法中的参数
for (int i = 0; i < paramTypes.length; i++) { if (i > 0) {
System.out.print(",");
}
if (paramTypes[i].isArray()) {
System.out.println(paramTypes[i].getComponentType().getName() + "[]");
} else {
System.out.print(paramTypes[i].getName());
}
}
System.out.println(");");
}
</code></pre>

获取成员方法:

<pre><code class="java">Method[] methods = c1.getDeclaredMethods();
for (Method method : methods) {
modifier = Modifier.toString(method.getModifiers());
Class returnType = method.getReturnType(); // 获取方法的返回类型
if (returnType.isArray()) {
String arrType = returnType.getComponentType().getName() + "[]";
System.out.print("" + modifier + " " + arrType + " " + method.getName() + "(");
} else {
System.out.print("" + modifier + " " + returnType.getName() + " " + method.getName() + "(");
}
Class[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) { if (i > 0) {
System.out.print(",");
}
if (paramTypes[i].isArray()) {
System.out.println(paramTypes[i].getComponentType().getName() + "[]");
} else {
System.out.print(paramTypes[i].getName());
}
}
System.out.println(");");
}
</code></pre>

反射调用方法,可以通过Method类的invoke方法实现动态方法的调用:

<code>public Object invoke(Object obj, Object... args)</code>
第一个参数代表对象
第二个参数代表执行方法上的参数

若反射要调用类的某个私有方法,可以在这个私有方法对应的Mehtod对象上先调用<code>setAccessible(true)</code>

<pre><code class="java">public static void test02() throws InstantiationException, IllegalAccessException, SecurityException,
NoSuchMethodException, IllegalArgumentException, InvocationTargetException {

Class c1 = TestReflection.class;
TestReflection t1 = (TestReflection) c1.newInstance(); // 利用反射来创建类的对象

Method method = c1.getDeclaredMethod("setUserName", String.class);
method.invoke(t1, "Java反射的学习");
method = c1.getDeclaredMethod("setPassWord", String.class);
method.setAccessible(true);
method.invoke(t1, "反射执行某个Private修饰的方法");

}

</code></pre>

<h3>反射的一些应用</h3>

既然String是不可变字符串对象,如何才能改变让其可变?[反射的一些应用][1]

<pre><code class="java">public static void stringReflection() throws Exception {

String s = "Hello World";

System.out.println("s = " + s); //Hello World

//获取String类中的value字段
Field valueField = String.class.getDeclaredField("value");

//改变value属性的访问权限
valueField.setAccessible(true);

char[] value = (char[]) valueField.get(s);

//改变value所引用的数组中的第5个字符
value[5] = '_';

System.out.println("s = " + s); //Hello_World
}
</code></pre>

既然String对象中没有对外提供可用的public setters等方法,因此只能通过Java中的反射机制实现。因此,前文中说到的String是不可变字符串对象只是针对“正常情况下”。而非必然。

<blockquote>
Java的反射机制的概念:
在Java运行时环境中,对于任意一个类,能否知道这个类的哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制。

Reflection是Java被视为动态(准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括modifiers(诸如static、public等等)、superclass(例如Object)、实现interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
</blockquote>

<h2>标注 Annotation</h2>

在实际的应用中期可以部分或全部的取代传统的XML等部署描述文件。之所以要出现标注特性,是因为部署描述文件很复杂,在具体编写时很容易出错。

<h3>标注的简单使用</h3>

<code>@SuppressWarning</code>

<pre><code class="java">public class SimpleAnnotation {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
System.runFinalizersOnExit(true);
}
}
</code></pre>

<h3>几个简单的内置标注</h3>

<code>@Override</code>

<pre><code class="java">package com.Annotation;

class People{
public String toString() {
return "people name";
}
}

class Student extends People{
@Override
public String toString() {
return "student name";

}
}
public class Annotation_Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.toString());
}

}
</code></pre>

<blockquote>
<code>@Override</code> 是方法标注,只能作用于方法,在覆盖父类方法却又写错了方法名的时候发挥作用。
</blockquote>

<code>@Deprecated</code>

很多时候,设计了一个包含<code>sayHello()</code>方法的类<code>Hello.java</code>,但是经过一段时间发现,<code>sayHello1()</code>可以更好更快的实现相同的功能。但是这个时候如果去掉方法<code>sayHello()</code>,那么调用该方法的类就会出现错误。为了兼容之前的类,而又不建议新设计的类使用方法<code>sayHello()</code>,就需要把<code>Hello.java</code>中的方法<code>sayHello()</code>做<code>@Deprecated</code>标注。

<pre><code class="java">package com.Annotation;

class Hello{

@Deprecated
public void sayHello(){
System.out.println("已经过时的方法");
}
public void sayHello1() {
System.out.println("现在的方法");
}
}
public class Deprecated_Test {
public static void main(String[] args) {
Hello hello = new Hello();
hello.sayHello();
hello.sayHello1();

}

}
</code></pre>

当一个类或者类成员使用<code>@Deprecated</code>修饰的话,编译器将不鼓励使用这个被标注的程序元素,而且这种修饰具有一定的“延续性”,即在代码中通过继承或者覆盖使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型并不是被声明为<code>@Deprecated</code>,但是编译器仍要报警。

<blockquote>
<code>@Deprecated</code>标注不仅可以用在方法前面,而且可以用在参数或类的前面。
</blockquote>

<code>@SuppressWarning</code>

可以用作标注类,属性、方法等成员,主要用于屏蔽警告。该标注于前面两个标注最大不同点在于其带有参数,并且参数可以是一个,可以是多个。参数的值为警告的类型。如:

<ul>
<li>已过时的警告 : deprecation</li>
<li>没有使用警告 : unused</li>
<li>类型不安全警告 : unchecked</li>
</ul>

<blockquote>
当<code>@SuppressWarning</code> 接收的参数为多个值得时候,必须使用数组的方式为参数赋值。例如@SuppressWarning({"deprecation","unused","unchecked"})
</blockquote>

<h2>泛型 <code>&lt;E&gt;</code></h2>

所谓泛型,其本质就是实现参数化类型,也就是说所操作的数据类型被指定为一个参数。

<h3>泛型概念的提出:</h3>

<pre><code class="java">public class GenericTest {

public static void main(String[] args) {
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);

for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } } ``` 运行结果 name:qqyumidi name:corn Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.fan.GenericTest.main(GenericTest.java:15) &gt; Java2的集合框架,抽其核心,主要有三种:List、Set和Map。
需要注意的是,这里的 Collection、List、Set和Map都是接口(Interface),不是具体的类实现。 List lst = new ArrayList(); 这是我们平常经常使用的创建一个新的List的语句,在这里, List是接口,ArrayList才是具体的类。
常用集合类的继承结构如下:
Collection&lt;--List&lt;--Vector
Collection&lt;--List&lt;--ArrayList
Collection&lt;--List&lt;--LinkedList
Collection&lt;--Set&lt;--HashSet
Collection&lt;--Set&lt;--HashSet&lt;--LinkedHashSet
Collection&lt;--Set&lt;--SortedSet&lt;--TreeSet
Map&lt;--SortedMap&lt;--TreeMap
Map&lt;--HashMap

定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

在如上的编码过程中,我们发现主要存在两个问题:

1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。

2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。

### 什么是泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

​```java
public class GenericTest {

public static void main(String[] args) {
/*
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
*/

List<string> list = new ArrayList<string>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 1 提示编译错误</string></string>

for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 2
System.out.println("name:" + name);
}
}
}

自定义泛型接口、泛型类和泛型方法

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 GenericTest1 {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> num = new Box<integer>(4);
System.out.println("name:" + name.getData());
System.out.println("num:" + num.getData());
}</integer></integer></string></string>

}

class Box<t> {</t>

private T data;

public Box() {

}

public Box(T data) {
this.data = data;
}

public T getData() {
return data;
}
}

运行结果

name:corn
num:4

在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

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

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);</integer></integer></string></string>

System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box
System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box
System.out.println(name.getClass() == age.getClass()); // true

}

}

运行结果

name class:class com.fan.Box
num class:class com.fan.Box
true

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

关于泛型通配符

接着上面的结论,我们知道,Box和Box实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box<Number>Box<Integer>是否可以看成具有父子关系的泛型类型呢?

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类Number的子类。

关于泛型的一些特性

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

public static void main(String[] args) {

Box<number> name = new Box<number>(99);
Box<integer> age = new Box<integer>(712);</integer></integer></number></number>

getData(name);
getData(age); // 1

}

public static void getData(Box<number> data){
System.out.println("data :" + data.getData());
}</number>

}

通过提示信息,我们知道Box<Number>在逻辑上不能视为Box<Integer>的父类.

我们需要一个在逻辑上可以用来表示同时是Box<Integer>Box<Number>的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ?代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>Box<Number>…等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

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

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);
Box<number> number = new Box<number>(314);</number></number></integer></integer></string></string>

getData(name);
getData(age);
getData(number);
}

public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}

}

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

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 GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);
Box<number> number = new Box<number>(314);</number></number></integer></integer></string></string>

getData(name);
getData(age);
getData(number);

//getUpperNumberData(name); // 1
getUpperNumberData(age); // 2
getUpperNumberData(number); // 3
}

public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}

public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
}

}

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。

封装

线程

程序是计算机指令集合,它以文件形式存储在磁盘上

进程就是一个执行中的程序,每一个进程都有一个独立的内存空间和资源系统。

线程是CPU调度和分配的基本单位,一个进程可以由多个线程组成,而这多个线程共享同一个存储空间,这使得线程间的通信比较容易。

线程的创建

创建线程的方法有两种 :

  • 通过实现Runnable接口的方式创建线程
  • 通过集成Threa类来创建线程

通过实现Runnable接口的方式创建线程

在java中,线程是一种对象,而不是所有的对象都可以被称为线程,只有实现了Runnable接口的对象才可以被称为线程。

Runnable接口的定义:

public interface new Runnable() {
public abstract void run() ;
}

只有实现了该接口的类才有资格被称为线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.threadtest;

class ThreadTest implements Runnable {
public void run() {
System.out.println("thread 1");
}
}
class ThreadTest2 implements Runnable{
public void run() {
System.out.println("thread 2");
}
}
public class ThreadTest1 {
public static void main(String[] args) {
ThreadTest test = new ThreadTest();
ThreadTest2 test2 = new ThreadTest2();
Thread thread = new Thread(test);
Thread thread2 = new Thread(test2);
thread.start();
thread2.start();
}

}

在java技术中,线程通常是通过调度模块来执行的。所谓抢占式调度模式是指:许多线程处于可以运行状态,即等待状态,但实际只有一个线程在运行。该线程一直运行直到他终止或是另一个优先级更高的线程变成可运行状态。

通过继承Thread类的方式创建线程

其实Thread本身也实现了Runnable接口,所以只要让一个类继承Thread类,并覆盖run()方法,也会创建进程。

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
package com.threadtest;

class Threadtest_3 extends Thread {
public void run() {
System.out.println("thread 1");

}
}
class ThreadTest_4 extends Thread{
public void run() {
System.out.println("thread 2");

}
}
public class ThreadTest2 {
public static void main(String[] args) {
Threadtest_3 threadtest_3 =new Threadtest_3();
ThreadTest_4 threadTest_4 = new ThreadTest_4();

Thread thread3 = new Thread(threadtest_3);
Thread thread4 = new Thread(threadTest_4);

thread3.start();
thread4.start();

}
}

线程的状态

线程包括5种状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。

新建状态

线程对象通过new关键字已经建立,在内存中有一个活跃的对象,但是没有启动该线程,所以它仍不能做任何事情,此时线程处于新建状态,程序中没有运行线程中的代码,如果线程要运行需要处于就绪状态。

就绪状态

一个线程一旦调用了start()方法,该线程就处于就绪状态。此时线程等待CPU时间片,一旦获得CPU时间周期,一旦获得CPU周期,线程就可以执行。这种状态下的任何时刻,线程是否执行完全取决于系统的调度程序。

运行状态

一旦处于就绪状态的线程获得CPU执行周期,就处于运行状态,执行多线程代码部分的运算。线程一旦运行,只是在CPU周期内获得执行权利,而一旦CPU的时间片用完,操作系统会给其他的线程运行的机会,而剥夺当前线程的执行。在选择哪个线程可以执行时,操作系统的调度程序会考虑现成的优先级,该内容后续讲解。

阻塞状态

该状态下线程无法运行,必须满足一定条件条件后才可以执行。如果线程处于阻塞状态,JVM调度机不会为其分配CPU周期。而线程满足一定条件就被解除阻塞,线程处于就绪状态,此时就获得了被执行的机会。当发生以下情况的时候线程会进入阻塞状态:

  • 线程正在等待一个输入输出操作,该操作完成前不会返回其调用者。
  • 线程调用了wait()方法或是sleep()方法。
  • 线程需要满足某种条件之后可以继续执行。

死亡状态

线程一旦退出run()方法就处于死亡状态。

线程的优先级

线程的执行顺序是一种抢占方式,优先级高的比优先级低的要获得更多的执行时间,如果想让一个线程比其他线程有更多的运行时间,可以通过设置线程的优先级解决。

具体方法如下:

public final void setPriority(int newPriority);

其中,newPriority是一个1~10的正整数,数值越大,优先级别越高。系统定义了一些常用的数值如下:

  • public final static int MIN_PRIORITY = 1 :表示最低优先级
  • public final static int MAX_PRIORITY = 10 :表示最高优先级
  • public final static int NORM_PRIORITY = 5 :表示默认优先级
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
package com.threadtest;

import org.omg.CORBA.PUBLIC_MEMBER;

class Threadtest_3 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}

}
}
class ThreadTest_4 extends Thread{
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello"+ i);
}

}
}
public class ThreadTest2 {
public static void main(String[] args) {

Threadtest_3 threadtest_3 =new Threadtest_3();
ThreadTest_4 threadTest_4 = new ThreadTest_4();

Thread thread3 = new Thread(threadtest_3);
Thread thread4 = new Thread(threadTest_4);

thread3.setPriority(3);
thread4.setPriority(Thread.MIN_PRIORITY);

thread3.start();
thread4.start();

}
}

线程的休眠与唤醒

线程的休眠 sleep()

线程处于等待状态。

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
package com.cjgong.avd;
///这是一个主运行类
///创建一个线程对象,让其运行
public class thread5
{
public static void main(String[] args)
{
compute27 t=new compute27();
t.start();
}
}
///创建一个线程类,在这个类中,通过休眠来输出不同结果
class compute27 extends Thread
{
int i=0;
public void run()
{
System.out.println("在工作中,不要打扰");
try
{
sleep(1000000);
}
catch(Exception e)
{
System.out.println("哦,电话来了");
}
}
}

线程唤醒 interrup()

当一个线程处于休眠状态,如果开始设置了休眠时间是1000ms,但是想在休眠了500ms的时候,让它继续执行,这时候就可以使用线程唤醒功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.cjgong.avd;
public class thread6
{
public static void main(String[] args)
{
compute28 t=new compute28();
t.start();
t.interrupt();
}
}
///创建一个线程类,在这个类中,通过休眠,让线程运行输出不同的结果
class compute28 extends Thread
{
int i=0;
public void run()
{
System.out.println("在工作中,不要打扰");
try
{
sleep(1000000);
}
catch(Exception e){System.out.println("哦,电话来了");}
}
}

线程让步 yield()

所谓线程让步,就是使当前正在运行的线程对象退出运行状态,让其他线程运行。

这个方法不能讲运行权让给指定的线程,只是允许这个线程把运行权让出来,至于给谁,就看谁能抢到。

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
package com.cjgong.avd;
///这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread7
{
public static void main(String[] args)
{
compute29 t=new compute29();
compute30 t1=new compute30();
t.start();
t1.start();
}
}
///创建一个线程类
///通过循环语句来输出十个整型数据
///通过让步程序让此线程停止运行
class compute29 extends Thread
{
int i=0;
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println(i);
yield();
}
}
}
///创建一个线程类
///通过循环语句来输出说明语句
class compute30 extends Thread
{
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println("这个数字是:"+i);
}
}
}

线程同步

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
package com.cjgong.avd;
//这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread8
{
public static void main(String[] args)
{
compute31 t=new compute31('a');
compute31 t1=new compute31('b');
t.start();
t1.start();
}
}
///创建一个线程类
///在这线程类中,使用循环语句输出字符
class compute31 extends Thread
{
char ch;
compute31(char ch)
{
this.ch=ch;
}

public void run()
{
for(int i=0;i<10;i++)
{
System.out.print(ch);
}
}
}

两个线程循环输出,就会出现抢占现象。解决这个问题的办法是可以使用线程同步,解决同步的两个方法:

  • 同步块
  • 同步化方法

同步块

同步块是使具有某个对象监视点的线程,获得运行权限的一种方法,每个对象只能在拥有这个监视点的情况下,才能获得运行权限。

同步块的结构如下:

synchronized (bObject) {
程序段
}

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.cjgong.avd;
//这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread8
{
public static void main(String[] args)
{
compute31 t=new compute31('a');
compute31 t1=new compute31('b');
t.start();
t1.start();
}
}
///创建一个线程类
///在这线程类中,使用循环语句输出字符
class compute31 extends Thread
{
char ch;
static Object bObject = new Object();
compute31(char ch)
{
this.ch=ch;
}

public void run()
{
synchronized (bObject) {

for(int i=0;i<10;i++) { System.out.print(ch); } } } } ``` ### 同步化方法 同步化方法就是对整个方法进行同步: 结构如下: synchronized void f() { 代码 } ```java //这是一个主运行类 ///在主运行方法中,通过创建三个线程对象,让其交替执行 public class thread11 { public static void main(String[] args) { compute34 t=new compute34(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } ///创建一个线程类 ///在这线程类中,使用循环语句输出字符 ///在run方法中,使用同步块来给线程加一把锁 class compute34 extends Thread { int i=10; static Object obj=new Object(); public void print() { System.out.println(Thread.currentThread().getName()+":"+i); i--; } public void run() { while(i&gt;0) { synchronized(obj) { print(); } try { sleep(1000); } catch(Exception e){} } } } ``` ## 异常的处理和内存管理 ## Java 输入和输出 Java程序类库中包含大量的输入输出类,提供不同情况的不同功能。其中包括: - 关于文件操作的类 `File` - 关于以字节方式访问文件的类 `InputStream`和类`OutStream` - 关于以字符方式访问文件的类`Reader`和类`Writer` &gt; 在编写程序的过程中,若是要使用输入输出类的方法和属性值,就需要引入`java.io`类

## Java 的I/O操作

Java中I/O操作主要是指使用Java进行输入,输出操作.

Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。

总结的基本概念如下:

数据流:

一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。

输入流:

程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道

输出流:

程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。

采用数据流的目的就是使得输出输入独立于设备。

数据流分类:

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
1) 字节流:数据流中最小的数据单元是字节
2) 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

### 标准I/O

### java.IO层次体系结构

在整个Java.io包中最重要的就是5个类和一个接口。

5个类指的是`File`、`OutputStream`、`InputStream`、`Writer`、`Reader`;

1个接口指的是`Serializable`.

掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。

主要的类如下:

File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。

Reader(文件格式操作):抽象类,基于字符的输入操作。
Writer(文件格式操作):抽象类,基于字符的输出操作。
RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

### 文件或目录信息的处理 `File`

`File` 类提供了与文件或目录相关的信息

文件处理方法:

​```java
package com.filetest;

import java.io.File;

public class FileTest1 {
public static void main(String[] args) {

File file = new File("d:\\","file.txt");
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getPath());
}
}

运行结果:

file.txt
d:
d:\file.txt

文件和目录的操作:

在Java中,目录被当做一种特殊的文件使用。类File是唯一代表磁盘文件对象的类。

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
package com.cjgong.chaozuo;

import java.io.*;

///通过print方法来判断这个文件类对象的性质
///通过print1方法来获取文件对象的信息
///通过print2方法来获取文件对象的信息
public class file1 {
public void print(File f) {
//通过print方法来判断这个文件类对象的性质
if (f.isDirectory()) {
//判断f对象是否为目录
System.out.println("这是一个目录!");
} else {
System.out.println("这不是一个目录!");
}
if (f.exists()) {
//判断f对象是否存在
System.out.println("这个文件存在的!");
} else {
System.out.println("抱歉,这个文件不存在的!");
try {
f.createNewFile();
//当文件不存在时,创建一个文件
} catch (Exception e) {
}
}
}

public void print1(File f) {
//通过print1方法来获取文件目录对象的信息
System.out.println(f.getName());
System.out.println(f.getParent());
System.out.println(f.getPath());
}

public void print2(File f) {
//通过print2方法来获取文件对象的信息
if (f.isFile()) {
System.out.println(f.lastModified());
System.out.println(f.length());
}
}

public static void main(String[] args) {
file1 f1 = new file1(); //创建一个f1对象
File f = new File("d:\\filetest");
//调用相应的方法
f1.print(f);
f1.print1(f);
f1.print2(f);
}
}

运行结果

这不是一个目录!
抱歉,这个文件不存在的!
filetest
d:
d:\filetest
1452923459623
0

使用文件字节输入流读取文件 FileInputStream

FileInputStream类是是InputStream的子类,并且不是抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

///创建一个文件类f
///创建一个输入流对象fis,并且以f作为参数
///使用循环语句将文本文件中的字符读出
public class file3 {
public static void main(String[] args) throws Exception {
File f = new File("d:\\filetest\\file.txt");
//创建一个文件类f
FileInputStream fis = new FileInputStream(f);
//创建对象f的文件输入流
char ch;
//声明一个字符串对象ch
for (int i = 0; i < f.length(); i++) {
//通过循环读取文件类f所对应的文件
ch = (char) fis.read();
System.out.print(ch);
}
fis.close();
}
}

使用文件字节输出流输出文件 FileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;

///创建一个文件类f
///创建一个输入流对象fis,并且以f作为参数
///将所有的字节都保存到一个字节数组b中。
///使用循环语句将b中的字符读出
public class file4 {
public static void main(String[] args) throws Exception {
File f = new File("d:\\filetest\\file.txt");
//创建一个文件类f
FileInputStream fis = new FileInputStream(f);
//创建一个输入流对象fis,并且以f作为参数
byte[] b = new byte[(int) f.length()];
//创建一个字节数组对象b
fis.read(b);
//读取到的内容存储到字节数组对象b
for (int i = 0; i < f.length(); i++) {
//通过循环输出相应内容
System.out.print((char) b[i]);
}
fis.close();
}
}

Java中对数据的处理

基本数据类型和对象数据类型的转换

为什么要将基本类型转换成对象数据类型:

Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)

基本数据类型 包装类
byte Byte
boolean Boolean
short Short
char Character
int Integer
long Long
float Float
double Double

由于所有的包装类具有比较相似的成员,这里以Integer类为例:

Integer类的构造方法:

public integer(int value):将整型值value包装成持有此值的Integer类对象。
public integer(string s):将由数字字符组成的串s包装成持有此值的Integer类对象,若s不是数字构成的话,则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
public class file1 {
public static void main(String[] args) {
int x = 12; //创建一个整形类型变量x
String str = "13579"; //创建一个字符串类型变量str
//把变量x,str转换成对象类型t1和t2
Integer t1 = new Integer(x);
Integer t2 = new Integer(str);
//通过tostring()把对象转换成字符串。
System.out.println(t1);
System.out.println(t2);
}
}

如何处理随机性数据 Random

如何对数据进行排列、整理 Arrays

数据结构接口

Collection接口

Collection接口是数据集合接口,它位于数据结构API的最上部,构成Collection的单位被称为元素。可将Collection接口分为三个部分,分别是Map接口、Set接口和List接口。

Python学习

第一个Python程序

1
2
>>> 100+200
300

如果要让Python打印出指定的文字,可以用print()函数,然后把希望打印的文字用单引号或者双引号括起来,但不能混用单引号和双引号:
第一个hello world!

1
2
>>> print('hello, world')
hello, world

这种用单引号或者双引号括起来的文本在程序中叫字符串,今后我们还会经常遇到。

最后,用exit()退出Python,我们的第一个Python程序完成!唯一的缺憾是没有保存下来,下次运行时还要再输入一遍代码。

输入和输出

用print()在括号中加上字符串,就可以向屏幕上输出指定的文字。比如输出’hello, world’,用代码实现如下:

1
>>> print('hello, world')

输出

print()函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出:

1
2
>>> print('The quick brown fox', 'jumps over', 'the lazy dog')
The quick brown fox jumps over the lazy dog

print()也可以打印整数,或者计算结果:

1
2
3
4
>>> print(300)
300
>>> print(100 + 200)
300

因此,我们可以把计算100 + 200的结果打印得更漂亮一点:

1
2
>>> print('100 + 200 =', 100 + 200)
100 + 200 = 300

输入

现在,你已经可以用print()输出你想要的结果了。但是,如果要让用户从电脑输入一些字符怎么办?Python提供了一个input(),可以让用户输入字符串,并存放到一个变量里。比如输入用户的名字:

1
2
3
4
>>> name = input()
Michael
name = input()
print('hello,', name)

数据类型和变量

数据类型

整数 浮点数 字符串

字符串:’I'm "OK"!’表示的字符串内容是:I’m “OK”!

布尔值

布尔值和布尔代数的表示完全一致,一个布尔值只有True、False两种值,要么是True,要么是False,在Python中,可以直接用True、False表示布尔值(请注意大小写),也可以通过布尔运算计算出来:

1
2
3
4
5
6
7
8
>>> True
True
>>> False
False
>>> 3 > 2
True
>>> 3 > 5
False

布尔值可以用and、or和not运算

1
2
3
4
5
6
7
8
>>> True and True
True
>>> True and False
False
>>> False and False
False
>>> 5 > 3 and 3 > 1
True

not运算是非运算,它是一个单目运算符,把True变成False,False变成True:

1
2
3
4
5
6
>>> not True
False
>>> not False
True
>>> not 1 > 2
True

布尔值经常用在条件判断中,比如:

1
2
3
4
if age >= 18:
print('adult')
else:
print('teenager')

常量

所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量:

PI = 3.14159265359

最后解释一下整数的除法为什么也是精确的。在Python中,有两种除法,一种除法是/:

1
2
>>> 10 / 3
3.3333333333333335

除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:

1
2
>>> 9 / 3
3.0

还有一种除法是//,称为地板除,两个整数的除法仍然是整数:

1
2
>>> 10 // 3
3

Python的字符串

1
2
>>> print('包含中文的str')
包含中文的str

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

1
2
3
4
5
6
7
8
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'

要计算str包含多少个字符,可以用len()函数:

1
2
3
4
>>> len('ABC')
3
>>> len('中文')
2

len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数:

1
2
3
4
5
6
>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6

格式化

在Python中,采用的格式化方式和C语言是一致的,用%实现,举例如下:

1
2
3
4
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串:

1
2
>>> 'Age: %s. Gender: %s' % (25, True)
'Age: 25. Gender: True'

有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个%:

1
2
>>> 'growth rate: %d %%' % 7
'growth rate: 7 %'

使用list和tuple

list

Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。

比如,列出班里所有同学的名字,就可以用一个list表示:

1
2
3
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

变量classmates就是一个list。用len()函数可以获得list元素的个数:

1
2
>>> len(classmates)
3

用索引来访问list中每一个位置的元素,记得索引是从0开始的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
​```</module></stdin>

如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:

​```python
>>> classmates[-1]
'Tracy'

以此类推,可以获取倒数第2个、倒数第3个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> classmates[-2]
'Bob'
>>> classmates[-3]
'Michael'
>>> classmates[-4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
​```</module></stdin>

list是一个可变的有序表,所以,可以往list中追加元素到末尾:
​```python
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

要删除list末尾的元素,用pop()方法:

1
2
3
4
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

也可以把元素插入到指定的位置,比如索引号为1的位置:

1
2
3
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

list元素也可以是另一个list,比如:

1
2
3
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

tuple

另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:

1
>>> classmates = ('Michael', 'Bob', 'Tracy')

现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。

1
2
3
>>> t = (1, 2)
>>> t
(1, 2)

如果要定义一个空的tuple,可以写成():

1
2
3
>>> t = ()
>>> t
()

但是,要定义一个只有1个元素的tuple,如果你这么定义:

1
2
3
>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。

1
2
3
>>> t = (1,)
>>> t
(1,)

Python在显示只有1个元素的tuple时,也会加一个逗号,,以免你误解成数学计算意义上的括号。

最后来看一个“可变的”tuple:

1
2
3
4
5
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

条件判断

1
2
3
4
age = 20
if age >= 18:
print('your age is', age)
print('adult')

当然上面的判断是很粗略的,完全可以用elif做更细致的判断:

1
2
3
4
5
6
7
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')

循环

Python的循环有两种,一种是for…in循环,依次把list或tuple中的每个元素迭代出来,看例子:

1
2
3
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

再比如我们想计算1-10的整数之和,可以用一个sum变量做累加:

1
2
3
4
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)

如果要计算1-100的整数之和,从1写到100有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:

1
2
3
4
sum = 0
for x in range(101):
sum = sum + x
print(sum)

使用dict和set

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。

举个例子,假设要根据同学的名字查找对应的成绩,如果用list实现,需要两个list:

1
2
3
4
5
6
7
8
9
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]

``
如果用dict实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个dict如下:
​```python
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:

1
2
3
>>> d['Adam'] = 67
>>> d['Adam']
67

由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:

1
2
3
4
5
6
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88

要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:

1
2
>>> 'Thomas' in d
False

要删除一个key,用pop(key)方法,对应的value也会从dict中删除:

1
2
3
4
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

和list比较,dict有以下几个特点:

1.查找和插入的速度极快,不会随着key的增加而增加;
2.需要占用大量的内存,内存浪费多。
而list相反:

1.查找和插入的时间随着元素的增加而增加;
2.占用空间小,浪费内存很少。

set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。

要创建一个set,需要提供一个list作为输入集合:

1
2
3
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。

重复元素在set中自动被过滤:

1
2
3
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果:

1
2
3
4
5
6
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

通过remove(key)方法可以删除元素:

1
2
3
>>> s.remove(4)
>>> s
{1, 2, 3}

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:

1
2
3
4
5
6
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

不可变对象

对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:

1
2
3
4
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而对于不可变对象,比如str,对str进行操作:

1
2
3
4
5
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

函数

调用函数

调用abs函数:

1
2
3
4
5
6
>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34

而max函数max()可以接收任意多个参数,并返回最大的那个:

1
2
3
4
>>> max(1, 2)
2
>>> max(2, 3, 1, -5)
3

数据类型转换

Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

1
2
3
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

定义函数

我们以自定义一个求绝对值的my_abs函数为例:

1
2
3
4
5
def my_abs(x):
if x >= 0:
return x
else:
return -x

返回多个值

比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:

1
2
3
4
5
6
import math

def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny

然后,我们就可以同时获得返回值:

1
2
3
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0

函数的参数

位置参数

我们先写一个计算x*x的函数:

1
2
def power(x):
return x * x

当我们调用power函数时,必须传入有且仅有的一个参数x:

1
2
3
4
>>> power(5)
25
>>> power(15)
225

现在,如果我们要计算x3怎么办?可以再定义一个power3函数,但是如果要计算x4、x5……怎么办?我们不可能定义无限多个函数。

你也许想到了,可以把power(x)修改为power(x, n),用来计算xn,说干就干:

1
2
3
4
5
6
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

对于这个修改后的power(x, n)函数,可以计算任意n次方:

1
2
3
4
>>> power(5, 2)
25
>>> power(5, 3)
125

递归函数

举个例子,我们来计算阶乘n! = 1 x 2 x 3 x … x n,用函数fact(n)表示,可以看出:

fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n

于是,fact(n)用递归的方式写出来就是:

1
2
3
4
def fact(n):
if n==1:
return 1
return n * fact(n - 1)

上面就是一个递归函数。可以试试:

1
2
3
4
5
6
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

1
2
3
4
5
6
7
def fact(n):
return fact_iter(n, 1)

def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

在git bash中执行

1
find . -name ".git" -exec sed -i 's/旧地址/新地址/g' {}/config \;

将替换所有子目录的git远端地址

环境变量GNUPGHOME:C:\Program Files (x86)\GnuPG\bin\gpg.exe

1
2
3
4
5
git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"

git config --global user.signingkey 你的公钥

git config --global commit.gpgsign true

敏捷的意思就是反应迅速,为什么要反应迅速? 看看那么多996公司就知道了,

市场变化越来越快,客户要求越来越高,为了满足用户的需求, 人家一个星期发一个版本, 我们仨月才能憋出一个来 ,那还不被打的满地找牙?

问题是如何才能反应迅速? 先来看一个场景:

1、残酷的现实

软件开发有一大难题就是客户脑子中的需求难于描述出来, 我们通常的应对方法是这样:

先花上几个月整理需求, 天天和客户座谈, 画出几百页的流程图, 写出上千页的文档, 最后把客户都快搞晕了。

img

然后是详细设计, 开发, 测试, 我们强悍的技术团队开始发动, 一切都严格按照计划进行, 一切看起来都很完美, 看来项目马上成功结束了!

但是客户的验收测试给了我们当头一棒: 这个界面怎么少了一个选项 ? 那个界面怎么不能跳转 , 那个功能需要给领导一个后门, 还有, 我的业务规则怎么不能改? 什么? 在代码中写死了? 唉,你们做的系统啊, 根本就不能用 !

每个人都很郁闷, 几个月的辛苦开发看来要付诸东流了。

从这个场景中能看出的是, 我们从客户那里得到的需求描述和需求文档, 其实离客户真正想要的软件还差的很远。

在瀑布式的开发模式下,验收测试发现的问题,要想改正代价是非常高昂的。

2、改进

一个想法自然而然就浮现出来: 为了避免到最后习惯性崩盘,能不能让客户经常性的做验收测试?

让他们经常性的去使用一个可以工作的软件, 从而告诉我们那些地方还有欠缺 ? 那些地方做错了? 这样我们可以迅速的修改, 这样我们就会轻松多了 !

我们可以把软件开发划分成一个个小的开发周期, 例如每个周期就两三周时间, 在这两周之内我们完成一个或几个功能, 然后就让用户去试用, 有问题立刻反馈,在下一个开发周期马上改掉。 这样就可以逐步逼近客户的最终目标。

这还带来了一个额外的好处, 不用花费巨长的时间来分析,整理冗长的需求文档了。

听起来很美是不是? 但是仔细想想这里边的问题很多。

1. 抛弃了冗长的需求文档, 但还是得描述需求啊

需要发明一个简单的、主要用来促进客户和开发团队沟通的描述形式, 这个新的形式叫做用户故事, 这里有个用户故事的例子:

img

这是一个卡片, 背面还会记录下针对需求的讨论和验收标准。

用户故事主要彰显的是: 谁做了什么事, 带来什么商业价值。

2. 怎么决定每个小开发周期(我们称之为迭代吧)要开发的东西?

用户故事得有估算, 得有大小, 太大了一个迭代开发不完 , 还得拆分一下。

我们需要对需求按照优先级进行排序, 按照优先级从高到低的原则来开发。

3. 不要架构设计了吗?

一上来就按优先级选择需求, 直接进入迭代开发, 把架构师撂在一边,合适吗?

架构工作肯定还是需要的,在正式的迭代周期开始之前需要架构设计, 但是和设计出面面俱到的架构设计不同, 我们更需要演进式的架构, 随着迭代的推进而进化。

4. 那详细设计怎么办?

在每个迭代开始的时候,团队在一起把这些用户故事给拆分成一个个小的任务, 这个拆分的过程就相当于详细设计了。 对于一些特别复杂的,例如算法, 当然可以写文档,帮助大家理解。

5. 由于是迭代式开发, 这个迭代周期修改上一个迭代周期的代码在所难免, 怎么保证不破坏原有的功能? 总不能每次都手工重测一遍吧?

这个绝对是一大难点, 答案就是自动化的回归测试, 包括单元测试和功能测试。

开发人员写代码的同时,也要写下自动化的单元测试, 测试人员需要开发自动化的功能测试, 这样一旦有了代码的修改,就可以运行它们, 检查现有功能有没有被破坏。

像持续集成这样的基础设施是必不可少的, 每天,每小时,甚至每次代码提交都会触发编译,打包、部署、测试这样的过程。

6. 这么短的开发周期, 测试人员怎么测试啊?

开发和测试需要同步进行, 当开发在澄清需求的时候, 测试需要参与, 当开发在编码的时候, 测试人员在编写测试用例,等到一个用户故事开发完,马上就可以投入测试。

7. 看来开发、测试之间需要紧密的协作, 它们之间怎么沟通?

肯定是面对面的沟通, 有问题就跑到对方的座位那里去问,大家的座位最好在一起, 扭头就可以讨论, 尽可能减少效率不高的电话、QQ/微信等工具的使用。

开发团队每天都开一个15分钟左右的站会, 展示自己的进展和计划, 让进度保持透明, 及时暴露问题,解决问题。

8. 客户什么时候可以做验收测试?

随时欢迎, 但是我们更倾向于迭代结束以后, 这时候功能会稳定下来, 我们会给客户做一个演示, 告诉他这个迭代完成的工作, 邀请他也测试一下软件, 给我们反馈。

当然客户可能会发现问题, 甚至提出新的需求, 我们表示欢迎, 我们要和客户合作,而不是对抗。

除了给客户演示之外,我们自己还会反思一下,看看有那些地方做的好,要继续保持; 那些地方做的不好, 要持续改进。

估计你也明白了,这种看起来很美的迭代化开发方法实施起来挺不容易的, 如果我们给它起个名字的话, 可以叫做:敏捷软件开发。

原作者:码农翻身

原文链接:http://mp.weixin.qq.com/s/tJzEQ87eaUdx7F2nR23oyw

码农翻身公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。

码农翻身二维码:

img

原文:有没有已经被证实存在的超能力?有能够后天习得的超能力吗?

作者:张天一
链接:https://www.zhihu.com/question/32235004/answer/57412334
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

there is no spoon.
我回来补充了!!
———————————乱七八糟的分割线—————————

知乎上一位信基督的程序员朋友说,“就好比你玩游戏,不能因为在游戏里找不到职业为程序员的 NPC,又无法用游戏中的规则造一个程序员角色出来,就说程序员是假的,因为他有可能在游戏之外的某个维度 debug 。换个说法是,游戏是虚拟的,程序员的存在才是真实。”(侵删致歉)私以为说的非常好。
如果代入这个理论来看的话,世界就有意思了。
一神教和多神教的区别就是这个世界的程序是一位程序员写的还是多位程序员写的,不同教派的区别就是因为自己所处的数据片段不一样所以脑补的神的形象也不一样。
还记得Neo去见先知的那个片段吗?看见那几个孩子,他们每个人都做了违反物理规律的事情,那个意念弯曲勺子的小僧侣对着他说,不要掰它,要看透它。
他说,“There is no spoon.”
很巧的是魔兽争霸3有一个作弊码,There is no spoon,输入后你的英雄魔法是无限的。
如果他们有自己的意识的话他们会意识到世界的规律变化了,而对你而言,你也不过是在利用代码而已。再举一个例子如Minecraft这种沙盒游戏,你可以通过合理的资源配置来完成一些科技,组成与门或门非门来完成牌子电路或者红石电路,甚至做出CPU,而有些你作弊也可以。科学就像是你在摸索mc的世界规律来完成科技,开作弊码就像是直接利用了代码的后门,“看透”了数据的本质(其实并没有真正看透,譬如你使用作弊码只是知其然不知其所以然,只能算作“使用”而非“破解”)。
这就是科学力量和超自然力量的关系。更直接的说,一个是掰勺子,一个是看穿勺子。两者实际上都是在“使用”它,而所谓的唯物世界不过是按照规律运行的一组数据,你可以像在mc里做电路一样发展科技,完成很厉害的事,也可以理解程序破解程序直接的为自己谋福利,两者有区别吗?有。基础一样吗?基础都是这一组数据,问题的关键是,你要怎么去理解它,使用它。你没有玩过mc做不出电路,只知道作弊码也算不得破解程序,你要发展你要趋利避害你要往好的地方走你要活着,你就要学习规律。
你把世界当做一个沙盒游戏,很多的哲学问题都可以迎刃而解。
你面前的勺子,There is no spoon.

(你们再说地球OL我就要报警了!!!)
接下来要给大家介绍一位始祖级程序员,老子。
以及在宗教界独树一帜的宗教,道教。
把上面的话替换几个字比如把规律和程序换成“道”,就基本是道门的要旨了。
下面的话我的评论只涉及纯粹标准的各宗教徒,个人评价无攻击意图。第一,道教确实非常奇特,第二,我对道教了解较深,所以将以道教尤其是早期道教作为一种特异的哲学和宗教现象来讨论,并非由于我是道教徒。道门独树一帜的地方就在于,别的宗教学习世界系统的程序员,我们学习这个程序的运行原理。当然,所有的宗教都引人向善给人以内心的平静,仅仅从利益角度的狭义功利主义而言,撇开信仰不提,其他的宗教大多目的都是希望通过遵从使得程序员对他们有所偏好来获得特殊的权限,而道教敬天地人神,最尊敬的就是道。理解道,参悟道,学习道,使用道,道如同涛涛江河,他们学习水流的方向和规律顺着水流游的游刃有余左右逢源,这是道教徒的本意。亚伯拉罕一神系的三大宗教犹太教伊斯兰教基督教都是一神教,即使有主神和他们的小伙伴们的故事说到底拜的还是一个神,掌管万物;佛教在入中国之前也基本算是释迦牟尼一神主万世法;希腊、美洲、北欧、玛雅那是各自神系下的神话体系,不算宗教。
再回头看看道教就耐人寻味起来。道教没有统领一切的神,而诸神各有职司。耐人寻味之处就在于,绝大多数的神,都是由人而化神,很少有比如火气水气之精或天地交感化而为神。现在,我们回到那个有趣的理论基础上去,即是:
道教徒跪拜的不是设立世界程序的程序员,而是在这个程序里生出来的AI极高高到足以看透数据世界的本质并突破了数据限制的人,并且希望学习他们。
这种人被称为什么呢?神?仙?
都不是。道教徒叫他们为,【真人】。
interesting.
非常非常有趣啊。这些人被叫做真人,那么普通人是什么?
脑补一下Neo突破自己然后在最后飞起来的时候,普通人的表情。于此处黑客帝国全剧终,导演给他们的表情留了白。
当你突破这个世界的时候,不管你能在这个世界有多么厉害,摇山撼海夸神通,目上无尘目下空,你都不会再去珍视了,因为那于你而言不过是…盒子里的世界。
There is no spoon.

来源:知乎

一人我饮酒醉 醉把佳人成双对 两眼 是独相随只求他日能双归

娇女轻扶琴 燕嬉紫竹林 痴情红颜心甘情愿 千里把君寻.

说红颜我痴情笑 曲动琴声妙 我轻狂高傲懵懂无知只怪太年少

弃 江山 忘天下 斩断情丝无牵挂 千古留名传佳话 两年征战以白发

一生征战何人陪 谁是谁非谁相随 戎马一生为了谁 能爱几回恨几回

败 帝王 斗苍天 夺得皇位以成仙 豪情万丈天地间 续写另类帝王篇

红尘事我以斩断 久经战场人心乱 当年扬名又力万 是这一战我无遗憾

相思 我愁断肠 眼中我泪两行 我多年为君一统天下 戎马把名扬

烟雨 我平凡事 此生 我怀大志 我为了家人回眸一笑立下这毒誓

百花 我出芬芳 回首 我曲流觞 我回眸沧海一首忧歌感触梨花香

将军出征人在外 归来之日谁还在 兄弟把酒论豪迈 驰骋疆场求一败

这次走我何时归 寒 风起 心似灰 冷风吹起樱花飞 触景生情心伤悲

琴声悠悠在回荡 一首幽歌为你唱 沧海桑田难遗忘 酒醒燕归来无恙

百花绽放出芬芳 再次回首曲流伤 回眸感触昙花香 一首忧歌笑沧桑

仰望窗外烟雨朦胧 想你当初那笑容 佳人一别月下逢 牡丹花开百花丛

情 已过 义难断 提笔写下江山乱 孤枕难眠梦相伴 花飞花舞花飘散

紫金楼 风起玄 挥舞霸刀几万年 悠悠岁月心缠绵 如何忘记你容颜

恍然如梦烟雨间 雨碎落叶舞翩翩 烟雨飘渺断崖边 待我转世化成仙

为何你要说离别 留我一人寻彩蝶 浮生若梦三字决 留下三字这心结

这 情花 为你摘 一颗心门为你开 平凡事我了尘埃 寂寥之心谁能猜

一别红颜多年后 多年情 在守候 断情之曲谁人奏 思念红颜人消瘦

海誓山盟烟雨楼 苦苦相思两处愁 三声誓言的缘由 只因一生无所求

梦里梦外千百年 可曾记得那誓言 今生情 来世缘 难忘二字为红颜

千 百里 相思雨 相 思雨 落谷底 红尘往事梦一曲 期盼三字风云起

脑中浮现一幕幕 一幕幕 江湖路 谁懂我心痛苦处 三声誓言以颠覆

梦里 谁独相思 为何要寻一相知 梦外 我望明月 只为一人情难却

企业版

vs_enterprise.exe –layout c:\vs2017offline

专业版

vs_professional.exe –layout c:\vs2017offline

社区版

vs_community.exe –layout c:\vs2017offline

企业版 Enterprise:NJVYC-BMHX2-G77MM-4XJMR-6Q8QF

专业版 Professional:KBJFW-NXHK6-W4WJM-CRMQB-G3CDH

来自 OSCHINA

昨天,Gitlab.com发生了一个大事,某同学误删了数据库,这个事看似是个低级错误,不过,因为Gitlab把整个过程的细节都全部暴露出来了,所以,可以看到很多东西,而对于类似这样的事情,我自己以前也干过,而在最近的两公司中我也见过(Amazon中见过一次,阿里中见过至少四次),正好通过这个事来说说一下自己的一些感想和观点吧。我先放个观点:你觉得有备份系统就不会丢数据了吗?

事件回顾

整个事件的回顾Gitlab.com在第一时间就放到了Google Doc上,事后,又发了一篇Blog来说明这个事,在这里,我简单的回顾一下这个事件的过程。 首先,一个叫YP的同学在给gitlab的线上数据库做一些负载均衡的工作,在做这个工作时的时候突发了一个情况,Gitlab被DDoS攻击,数据库的使用飙高,在block完攻击者的IP后,发现有个staging的数据库(db2.staging)已经落后生产库4GB的数据,于是YP同学在Fix这个staging库的同步问题的时候,发现db2.staging有各种问题都和主库无法同步,在这个时候,YP同学已经工作的很晚了,在尝试过多个方法后,发现db2.staging都hang在那里,无法同步,于是他想把db2.staging的数据库删除了,这样全新启动一个新的复制,结果呢,删除数据库的命令错误的敲在了生产环境上(db1.cluster),结果导致整个生产数据库被误删除。(陈皓注:这个失败基本上就是 “工作时间过长” + “在多数终端窗口中切换中迷失掉了”) 在恢复的过程中,他们发现只有db1.staging的数据库可以用于恢复,而其它的5种备份机制都不可用,第一个是数据库的同步,没有同步webhook,第二个是对硬盘的快照,没有对数据库做,第三个是用pg_dump的备份,发现版本不对(用9.2的版本去dump 9.6的数据)导致没有dump出数据,第四个S3的备份,完全没有备份上,第五个是相关的备份流程是问题百出的,只有几个粗糙的人肉的脚本和糟糕的文档,也就是说,不但是是人肉的,而且还是完全不可执行的。(陈皓注:就算是这些备份机制都work,其实也有问题,因为这些备份大多数基本上都是24小时干一次,所以,要从这些备份恢复也一定是是要丢数据的了,只有第一个数据库同步才会实时一些) 最终,gitlab从db1.staging上把6个小时前的数据copy回来,结果发现速度非常的慢,备份结点只有60Mbits/S,拷了很长时间(陈皓注:为什么不把db1.staging给直接变成生产机?因为那台机器的性能很差)。数据现在的恢复了,不过,因为恢复的数据是6小时前的,所以,有如下的数据丢失掉了:

  • 粗略估计,有4613 的项目, 74 forks,  和 350 imports 丢失了;但是,因为Git仓库还在,所以,可以从Git仓库反向推导数据库中的数据,但是,项目中的issues等就完全丢失了。
  • 大约有±4979 提交记录丢失了(陈皓注:估计也可以用git仓库中反向恢复)。
  • 可能有 707  用户丢失了,这个数据来自Kibana的日志。
  • 在1月31日17:20 后的Webhooks 丢失了。

因为Gitlab把整个事件的细节公开了出来,所以,也得到了很多外部的帮助,2nd Quadrant的CTO – Simon Riggs 在他的blog上也发布文章 Dataloss at Gitlab 给了一些非常不错的建议:

  • 关于PostgreSQL 9.6的数据同步hang住的问题,可能有一些Bug,正在fix中。
  • PostgreSQL有4GB的同步滞后是正常的,这不是什么问题。
  • 正常的停止从结点,会让主结点自动释放WALSender的链接数,所以,不应该重新配置主结点的 max_wal_senders 参数。但是,停止从结点时,主结点的复数连接数不会很快的被释放,而新启动的从结点又会消耗更多的链接数。他认为,Gitlab配置的32个链接数太高了,通常来说,2到4个就足够了。
  • 另外,之前gitlab配置的max_connections=8000太高了,现在降到2000个是合理的。
  • pg_basebackup 会先在主结点上建一个checkpoint,然后再开始同步,这个过程大约需要4分钟。
  • 手动的删除数据库目录是非常危险的操作,这个事应该交给程序来做。推荐使用刚release 的 repmgr
  • 恢复备份也是非常重要的,所以,也应该用相应的程序来做。推荐使用 barman (其支持S3)
  • 测试备份和恢复是一个很重要的过程。

看这个样子,估计也有一定的原因是——Gitlab的同学对PostgreSQL不是很熟悉。 随后,Gitlab在其网站上也开了一系列的issues,其issues列表在这里 Write post-mortem (这个列表可能还会在不断更新中)

从上面的这个列表中,我们可以看到一些改进措施了。挺好的,不过我觉得还不是很够。

相关的思考

因为类似这样的事,我以前也干过(误删除过数据库,在多个终端窗口中迷失掉了自己所操作的机器……),而且我在amazon里也见过一次,在阿里内至少见过四次以上(在阿里人肉运维的误操作的事故是我见过最多的),但是我无法在这里公开分享,私下可以分享。在这里,我只想从非技术和技术两个方面分享一下我的经验和认识。

技术方面

人肉运维 一直以来,我都觉得直接到生产线上敲命令是一种非常不好的习惯。我认为,一个公司的运维能力的强弱和你上线上环境敲命令是有关的,你越是喜欢上线敲命令你的运维能力就越弱,越是通过自动化来处理问题,你的运维能力就越强。理由如下: 其一,如果说对代码的改动都是一次发布的话,那么,对生产环境的任何改动(包括硬件、操作系统、网络、软件配置……),也都算是一次发布。那么这样的发布就应该走发布系统和发布流程,要被很好的测试、上线和回滚计划。关键是,走发布过程是可以被记录、追踪和回溯的,而在线上敲命令是完全无法追踪的。没人知道你敲了什么命令。 其二,真正良性的运维能力是——人管代码,代码管机器,而不是人管机器。你敲了什么命令没人知道,但是你写个工具做变更线上系统,这个工具干了什么事,看看工具的源码就知道了。 另外、有人说,以后不要用rm了,要用mv,还有人说,以后干这样的事时,一个人干,另一个人在旁边看,还有人说,要有一个checklist的强制流程做线上的变更,还有人说要增加一个权限系统。我觉得,这些虽然可以work,但是依然不好,再由如下: 其一、如果要解决一个事情需要加更多的人来做的事,那这事就做成劳动密集型了。今天我们的科技就是在努力消除人力成本,而不是在增加人力成本。而做为一个技术人员,解决问题的最好方式是努力使用技术手段,而不是使用更多的人肉手段。人类区别于动物的差别就是会发明和使用现代化的工具,而不是使用更多的人力。另外,这不仅仅因为是,人都是会有这样或那样的问题(疲惫、情绪化、急燥、冲动……),而机器是单一无脑不知疲惫的,更是因为,机器干活的效率和速度是比人肉高出N多倍的。 其二、增加一个权限系统或是别的一个watch dog的系统完全是在开倒车,权限系统中的权限谁来维护和审批?不仅仅是因为多出来的系统需要多出来的维护,关键是这个事就没有把问题解决在root上。除了为社会解决就业问题,别无好处,故障依然会发生,有权限的人一样会误操作。对于Gitlab这个问题,正如2nd Quadrant的CTO建议的那样,你需要的是一个自动化的备份和恢复的工具,而不是一个权限系统。 其三、像使用mv而不rm,搞一个checklist和一个更重的流程,更糟糕。这里的逻辑很简单,因为,1)这些规则需要人去学习和记忆,本质上来说,你本来就不相信人,所以你搞出了一些规则和流程,而这些规则和流程的执行,又依赖于人,换汤不换药,2)另外,写在纸面上的东西都是不可执行的,可以执行的就是只有程序,所以,为什么不把checklist和流程写成代码呢?(你可能会说程序也会犯错,是的,程序的错误是consistent,而人的错误是inconsistent) 最关键的是,数据丢失有各种各样的情况,不单单只是人员的误操作,比如,掉电、磁盘损坏、中病毒等等,在这些情况下,你设计的那些想流程、规则、人肉检查、权限系统、checklist等等统统都不管用了,这个时候,你觉得应该怎么做呢?是的,你会发现,你不得不用更好的技术去设计出一个高可用的系统!别无它法。

关于备份

一个系统是需要做数据备份的,但是,你会发现,Gitlab这个事中,就算所有的备份都可用,也不可避免地会有数据的丢失,或是也会有很多问题。理由如下: 1)备份通常来说都是周期性的,所以,如果你的数据丢失了,从你最近的备份恢复数据里,从备份时间到故障时间的数据都丢失了。 2)备份的数据会有版本不兼容的问题。比如,在你上次备份数据到故障期间,你对数据的scheme做了一次改动,或是你对数据做了一些调整,那么,你备份的数据就会和你线上的程序出现不兼容的情况。 3)有一些公司或是银行有灾备的数据中心,但是灾备的数据中心没有一天live过。等真正灾难来临需要live的时候,你就会发现,各种问题让你live不起来。你可以读一读几年前的这篇报道好好感受一下《以史为鉴 宁夏银行7月系统瘫痪最新解析》 所以,在灾难来临的时候,你会发现你所设计精良的“备份系统”或是“灾备系统”就算是平时可以工作,但也会导致数据丢失,而且可能长期不用的备份系统很难恢复(比如应用、工具、数据的版本不兼容等问题)。 我之前写过一篇《分布式系统的事务处理》,你还记得下面这张图吗?看看 Data Loss 那一行的,在Backups, Master/Slave 和 Master/Master的架构下,都是会丢的。 所以说,如果你要让你的备份系统随时都可以用,那么你就要让它随时都Live着,而随时都Live着的多结点系统,基本上就是一个分布式的高可用的系统。因为,数据丢失的原因有很多种,比如掉电、磁盘损坏、中病毒等等,而那些流程、规则、人肉检查、权限系统、checklist等等都只是让人不要误操作,都不管用,这个时候,你不得不用更好的技术去设计出一个高可用的系统!别无它法。(重要的事,得再说一篇) 另外,你可以参看我的另一篇《关于高可用系统》,这篇文章中以MySQL为例,数据库的replication也只能达到 两个9。 AWS 的 S3 的的高可用是4个加11个9的持久性(所谓11个9的持久性durability,AWS是这样定义的,如果你存了1万个对象,那么丢一个的时间是1000万年),这意味着,不仅仅只是硬盘坏,机器掉电,整个机房挂了,其保证可以承受有两个设施的数据丢失,数据还是可用的。试想,如果你把数据的可用性通过技术做到了这个份上,那么,你还怕被人误删一个结点上的数据吗?

非技术方面

故障反思 一般说来,故障都需要反思,在Amazon,S2以上的故障都需要写COE(Correction of Errors),其中一节就是需要Ask 5 Whys,我发现在Gitlab的故障回顾的blog中第一段中也有说要在今天写个Ask 5 Whys。关于Ask 5 Whys,其实并不是亚马逊的玩法,这还是算一个业内常用的玩法,也就是说不断的为自己为为什么,直到找到问题的概本原因,这会逼着所有的当事人去学习和深究很多东西。在Wikipedia上有相关的词条 5 Whys,其中罗列了14条规则:

  1. 你需要找到正确的团队来完成这个故障反思。
  2. 使用纸或白板而不是电脑。
  3. 写下整个问题的过程,确保每个人都能看懂。
  4. 区别原因和症状。
  5. 特别注意因果关系。
  6. 说明Root Cause以及相关的证据。
  7. 5个为什么的答案需要是精确的。
  8. 寻找问题根源的步骤,而不是直接跳到结论。
  9. 要基础客观的事实、数据和知识。
  10. 评估过程而不是人。
  11. 千万不要把“人为失误”或是“工作不注意”当成问题的根源。
  12. 培养信任和真诚的气氛和文化。
  13. 不断的问“为什么”直到问题的根源被找到。这样可以保证同一个坑不会掉进去两次。
  14. 当你给出“为什么”的答案时,你应该从用户的角度来回答。

工程师文化 上述的这些观点,其实,我在我的以住的博客中都讲过很多遍了,你可以参看《什么是工程师文化?》以及《开发团队的效率》。其实,说白了就是这么一个事——如果你是一个技术公司,你就会更多的相信技术而不是管理。相信技术会用技术来解决问题,相信管理,那就只会有制度、流程和价值观来解决问题。 这个道理很简单,数据丢失有各种各样的情况,不单单只是人员的误操作,比如,掉电、磁盘损坏、中病毒等等,在这些情况下,你设计的那些流程、规则、人肉检查、权限系统、checklist等等统统都不管用,这个时候,你觉得应该怎么做呢?是的,你会发现,你不得不用更好的技术去设计出一个高可用的系统!别无它法。(重要的事得说三遍) 事件公开 很多公司基本上都是这样的套路,首先是极力掩盖,如果掩盖不了了就开始撒谎,撒不了谎了,就“文过饰非”、“避重就轻”、“转移视线”。然而,面对危机的最佳方法就是——“多一些真诚,少一些套路”,所谓的“多一些真诚”的最佳实践就是——“透明公开所有的信息”,Gitlab此次的这个事给大家树立了非常好的榜样。AWS也会把自己所有的故障和细节都批露出来。 事情本来就做错了,而公开所有的细节,会让大众少很多猜测的空间,有利于抵制流言和黑公关,同时,还会赢得大众的理解和支持。看看Gitlab这次还去YouTube上直播整个修复过程,是件很了不起的事,大家可以到他们的blog上看看,对于这样的透明和公开,一片好评。 (全文完)

来源:酷壳

阿里他爸?

旧金山——雅虎(Yahoo)或将自己的互联网业务以48亿美元的价格卖给威瑞森通讯公司(Verizon Communications),这笔交易的命运可能尚不确定。但是如果它成为现实,雅虎亦为公司剩下的业务做好了计划。

周一,该公司在一份监管备案文件中称,交易完成后,它将更名为Altaba。

此外,该公司目前半数以上的董事会成员将退出,包括首席执行官玛丽莎·梅耶尔(Marissa Mayer)在内。

为什么叫Altaba呢?

如果与威瑞森公司的交易能够完成,雅虎所剩的最大单笔资产就是在中国电子商务巨头阿里巴巴公司(Alibaba)所持的15%股份,Altaba这个名字其实就是在“阿里巴巴”的基础上稍作修改。Altaba还将持有雅虎日本35.5%的股份(雅虎的一名发言人拒绝作出评论)。

不过,Altaba当然是一个不同寻常的名字。它碰巧还很接近Al-Taba,那是巴基斯坦的一家剪刀制造公司的名字。

该公司在监管备案文件中称,更名后仍将留下的董事包括帮助推动公司改革的激进投资人杰弗里·史密斯(Jeffrey Smith);前投资银行家托尔·布雷厄姆(Tor Braham)和凯瑟琳·J·弗里德曼(Catherine J. Friedman);芯片制造公司博通(Broadcom)的前首席财务官埃里克· 勃兰特(Eric Brandt);以及媒体公司IAC的前首席财务官托马斯·麦金纳尼(Thomas McInerney)。

即将退位的董事包括梅耶尔;雅虎董事长梅纳德·韦伯(Maynard Webb);以及雅虎创始人之一大卫·费罗(David Filo)。韦伯将成为更名后的Altaba的荣退主席。

当然,所有这些变化取决于雅虎能否真的把自己最重要的互联网业务卖给威瑞森,要知道,雅虎曾两次遭遇黑客袭击,导致信息泄露,第二次袭击影响了10多亿用户的帐户。

威瑞森的高管曾公开表示,他们在权衡自己的选择,包括支付的金额可能会少于已达成一致的48亿美元。上周,威瑞森的产品创新和新业务主管玛妮·沃尔登(Marni Walden)在谈起这项交易的命运时说,“今天,我不能坐在这里确定地说会发生什么,因为我们还不知道。”

不过,隶属于威瑞森的美国在线(AOL)的首席执行官蒂姆·阿姆斯特朗(Tim Armstrong)在接受CNBC采访时称,他感到乐观。

“我依然对达成这项交易充满希望,我觉得,与此同时,我们会看看雅虎调查的结果,”他说。

来源:纽时中文

0%