《编写高质量代码(改善Java程序的151个建议)》读书笔记

时间只是供我垂钓的溪流,在我喝着溪水的时候,我看的到它的沙床,它是那么浅啊,浅浅的洗漱流去了。永恒却留在原地。我愿痛饮,我愿在天上垂钓,在天空的底层,看着石子似的星星。—————《瓦尔登湖》

嗯嗯,昨夜难眠,与Y君讲,要我遇些困苦,是要注定不凡的,我会走完一生的,要给自己些勇气。
2018.11.22

时间只是供我垂钓的溪流,在我喝着溪水的时候,我看的到它的沙床,它是那么浅啊,浅浅的洗漱流去了。永恒却留在原地。我愿痛饮,我愿在天上垂钓,在天空的底层,看着石子似的星星。—————《瓦尔登湖》

建议

第一章,Java开发中通用的方法和准则

1. 不要在常量和变量中出现易混淆的字母,(“1l“,表示一个long型的值,容易看做“11”);
2. 莫让变量脱变成变量(public static final int RAND_CONST = Random().nextInt();)
3. 三元类型操作符的类型必须一致(转化规则,若不可转,返回值Object,为明确类型表达式(变量),正常转化,若为数字与表达式,转换为范围大的,若为字面量数字,类型转化为范围大的)
4. 避免带有变长参数的方法重载(从最简单开始)。
5. 别让null值和空值威胁到重载的变长方法(变长参数N>=0,必须把null定义为具体的类型)。
6. 覆写变长的方法也要循规蹈矩(可访问性一致/公开,参数列表相同(顺序等),返回类型相同/子类,不抛出新异常,或者超过父类的异常类)
7. 警惕自增的陷阱(i =0 ; while(true){i = i++;})
8. 不要让旧语法困扰。
9. 少用静态导入(规则:不使用*通配符,方法名为工具类)。
10. 不要在本类中覆盖静态导入的变量和方法(最短路径原则),一般在原始类重构而不是覆盖。
11. 养成良好的习惯,显示申明UID
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
public class Person implements Serializable{
//流标识符(Stream Unique Identfier)类的版本定义,可以显示定义可以隐式定义
private static final long serialVersionUID = 55799L;
private String name;
public Person() {
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Producer {

public static void main(String[] args) {
//序列化
Person person =new Person();
person.setName("李瑞龙");
SerializationUtils.writeObject(person);
System.out.println("序列化成功!!");
}
}
public class SerializationUtils {
private static String FILE_NAME = "C:/LIRUILONG.bin";
/*
* 序列化
*
*/
public static void writeObject(Serializable s) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oos.writeObject(s);
oos.close();
}catch(Exception e){
e.printStackTrace();
}
}
public SerializationUtils() {
// TODO Auto-generated constructor stub
}
/*
* 反序列化
* @return obj
*/
public static Object readObject() {
Object obj = null;
try {
ObjectInput input = new ObjectInputStream(new FileInputStream(FILE_NAME));
obj = input.readObject();
input.close();
}catch(Exception e) {
e.printStackTrace();
}
return obj;
}
}
public class Consumer {
public static void main(String[] args)throws Exception {
//反序列化
Person p = (Person)SerializationUtils.readObject();
System.out.println("name="+p.getName());
}
}

当序列化和反序列化的版本不一致时,反序列化会报一个InvalidClassException异常,原因是类版本发生变化,JVM不能把数据流转换为实例对象,JVM通过SerialVersionUID(流标识符),即类的版本定义的,可以显示定义可以隐式定义(编译器自动申明),JVM反序列化时,会比较数据流中的SerialVersionUID与类中的SerialVersionUID是否相同,不相同抛出异常。依靠显示申明,改变一端的Person后可以运行。即显示申明SerialVersionUID可以避免对象不一致。但尽量不要以这种方式向JVM”撒谎”。

12. 避免用序列化类在构造函数中为不变量赋值,(反序列化时构造函数不会执行)。
13. 避免为final变量复杂赋值:

(保存在磁盘上的对象文件包括两部分:

  • 1,类文件描述信息:包括类路径,继承关系,访问权限,变量描述,变量访问权限,方法签名,返回值,以及变量 的关联关系类信息。
  • 2,非瞬态(trtansient)和非静态(static)的的实例变量值
    反序列化时final变量在一下情况不会被赋值:通过构造函数赋值,通过方法返回值赋值,final修饰的属性不是基本类型
14. 使用序列化类的私有方法巧妙解决部分属性持久化的问题;
1
2
3
4
5
6
7
8
9
10
11
12
private void writeObject(ObjectOutputStream out)throws IOException{
//告诉JVM按照默认的规则写入对象,惯例的写法是写在第一句
out.defaultWriteObject();
//写入相应的值
out.writeInt(salary.getBasePay());
}
private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
//告诉JVM按照默认规则读入对象,也写在第一句
in.defaultReadObject();
//独处相应的值
salary = new Salary(in.readInt(),0);
}

在序列化类中增加writeObject和readObject两个方法,使用序列化的独有机制,序列化回调,Java调用ObjectOutputStream类把一个对象转换为流数据时,会通过反射(Reflection)检查被序列化的类是否有writeObject方法,并且检查其是否为私有,无返回值的特性,若有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化,在反序列化的时候也会检查是否有私有方法readObject。如果有会通过该方法读取属性。

15. break 万万不可以忘;
16. 易变业务使用脚本语言编写(特性:灵活,便捷,简单),JCP(Java Community Prosess)提出JSR223规范,JavaScript默认支持,

脚本语言可以随时发布而不用重新部署,即脚本语言改变,也能提供不间断的业务服务。

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
public class main {

public main() {
// TODO Auto-generated constructor stub
}

public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
//获取JAVacript的执行引擎(engine)。
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
//建立上下文变量
Bindings bind = engine.createBindings();
bind.put("factor", 1);
//绑定上下文,作用域为当前引擎范围。
engine.setBindings(bind, ScriptContext.ENGINE_SCOPE);
Scanner input = new Scanner(System.in);
while(input.hasNextInt()) {
int first = input.nextInt();
int sec = input.nextInt();
System.out.println("输入参数为:"+first+","+sec);
//执行js代码
engine.eval(new FileReader("C:/model.js"));
//是否可调用方法
if(engine instanceof Invocable) {
Invocable in = (Invocable)engine;
Double result = (Double)in.invokeFunction("formula",first,sec);
System.out.println("运行结果:"+result.intValue());
}
}
}
}
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
 public interface Bindings extends Map<String,Object>
所有键均为 String 的键/值对映射关系。
void put(String key, Object value)
设置 ScriptEngine 的状态中的键/值对,它创建一个将在脚本执行中使用或者以其他方式
使用的 Java Language Binding,具体取决于该键是否被保留
public interface ScriptEngine
ScriptEngine 是基础接口,该接口的方法在此规范的每个实现中都必须具有完整的功能。
这些方法提供基本的脚本功能。为这个简单接口编写的应用程序应该对每个实现稍做修改就能够运行。
这包括执行脚本的方法,以及设置和获取值的方法。这些值是两种类型的键/值对。组成第一种类型的键/值对中的
键是此规范或个别实现中保留和定义的键。包含保留键的键/值对中的值具有指定的含义。
另一种类型由那些创建 Java 语言 Bindings 的键/值对组成,值通常通过相应键或其装饰的形式用脚本表示。
Bindings createBindings()
返回一个未初始化的 Bindings
void setBindings(Bindings bindings, int scope)
设置将由脚本使用的指定值的范围。

public class ScriptEngineManager extends ObjectScriptEngineManager

ScriptEngine 类实现一个发现和实例化机制,还维护一个键/值对集合来存储所有 Manager 创建的引擎所共享的状态。
此类使用服务提供者机制枚举所有的 ScriptEngineFactory 实现。
ScriptEngineManager 提供了一个方法,可以返回一个所有工厂实现和基于语言名称、
文件扩展名和 mime 类型查找工厂的实用方法所组成的数组。 键/值对的 Bindings(即由管理器维护的 "Global Scope")
对于 ScriptEngineManager 创建的所有 ScriptEngine 实例都是可用的。Bindings 中的值通常公开于所有脚本中。
ScriptEngine getEngineByName(String shortName)
查找并创建一个给定名称的 ScriptEngine
public interface Invocable
ScriptEngines 实现的可选接口,该 ScriptEngines 的方法允许在以前执行过的脚本中调用程序。
Object invokeFunction(String name, Object... args)
用于调用脚本中定义的顶层程序和函数。
  1. 慎用动态编译(注意:在框架中谨慎使用,不要在要求高性能的项目中使用,动态编译要考虑安全问题,记录动态编译过程)
  2. 避免instanceof非预期的结果。(instanceof操作符的左右必须有继承或实现关系)
    1
    2
    3
    4
    5
    6
    7
    8
    "String" instanceof String //返回值为true
    new String() instanceof String //返回值为true
    new Object() instanceof String //false(可以编译)
    'A' instanceof Character //编译不通过,A为基本类型,Character为封装类,前边必须为对象。
    null instanceof String //false,特殊规则,如果左操作数是null,结果就直接返回false,不在运运算右操作数,
    (String)null instanceof String //false,null是一个万用类型,可以说它没有类型,即使类型转换也是null。
    new Date() instanceof String //编译不通过,没有继承实现关系。
    T(T为泛型String类变量) instanceof Date; //通过,false,T被编译为Object类,传递String类的值,所以 "Object instanceof Date";
19. 断言绝不是鸡肋:
1
2
3
4
5
assert <boolean_expression>
assert <boolean_expression>:<erroe>
当布尔表达式为假时,抛出一个AssertionError错误,是继承自Error的,并附带错误信息,默认不启动。
不可用:在对外的方法中不可用,在执行逻辑代码时不可用。因为生产中断言不会执行。
可用:在私有方法中放置assert作为输入参数校验。流程控制中不可能达到的区域。建立程序探针,即判断程序中不可变的量是否改变。
20. 不要只替换一个类,(发布应用系统时禁止使用类文件替换方式,整体的WAR包发布才是万全之策)

对于final修饰的基本类型和String类型,编译器会认为他是稳定态(Immutable Status),所以编译期间之间把值编译到字节码中,避免运行期引用(Run-time-reference),提高代码执行效率对于final修饰的基本类型和String类型,编译器会认为他是稳定态(Immutable Status),所以编译期间之间把值编译到字节码中,避免运行期引用(Run-time-reference),提高代码执行效率,对于final类来讲编译器认为它是不稳定的,在编译期建立则是引用关系,即到final修饰一个类或实例时,不重新编译也会是最新值。


第二章,基本类型

21. 用于偶判断,不用奇判断,(取模运算,)
1
2
3
4
i%2 == 1?"奇数":"偶数";//输入-1为偶数
public static int remainder(int dividend,int divisor){
return dividend - dividend/divisor*divisor;
}
22. 用整数类型处理货币(使用BigDecimal类,与数据库映射方便。使用整形(扩大倍数))
23. 不用让类型默默转换,(java 为先运算后进行类型转化的)当运算结果过界就会从头开始(负值),在运算中加入数据范围大的值,基本类型转换时,使用主动声明方式减少不必要的bug.
24. 边界,边界,还是边界(判断数值范围时,要考虑超过类型边界后为负数)。
25. 不要让四舍五入亏了一方(银行家舍入):
26. 堤防包装类型的null值:包装类型参与运算时,要做null值校验。
27. 谨慎包装类型的大小比较:”==”对于基本类型比较的值,对象比较是否为同一个引用。<>不能比较对象大小。
28. 优先使用整形池:装箱生成的对象,装箱动作通过valueOf实现的,chache是IntegerCatch内部类的静态数组,容纳-128—127之间的Integer对象,不在该范围的int类型通过new生成包装对象。即127这个数字的包装每次都是同一个对象,而128不是同一个对象。整形池存在不仅提高了系统性能,节约内存空间。
1
2
3
4
5
6
7
8
9
10
11
public static Integer valueOf(int){
final int offset= 128;
if(i>=-128&&i<=127){
return IntegerCache[i+offset];}
return new Ingeger(i);
}
static final Integer cache[] = new Integer[-(-128)+127+1];
static{
for(int i =0; i < cache.length;i++)
cache[i] = new Integer(i-128)
}
29. 优先选择基本类型:(自动装箱的重要原则,基本类型可以先加宽,再转变成宽类型的包装类型,但不能直接转换为宽类型的包装类型)
30. 不要随便设置随机种子:

在java中,随机数的产生取决于种子,随机数和种子之间的关系(种子不同,产出的随机数不同,种子相同,即使实例不同也产生相同的随机数)

1
2
3
4
5
6
7
public static void main(String[] args) {
//Random r= new Random();默认种子
Random r= new Random(1000);//设置种子
for(int i=1;i<4;i++) {
System.out.println("随机数字为:"+r.nextInt());
}
}

获得随机数:Math.random()方法,通过java.util.Random;


第三章,类对象及方法

31. 在接口中不要存在实现代码(接口中声明一个匿名内部类的实例对象的静态常量)。
32. 静态变量一定要先声明后赋值

静态变量是类加载时被分配到数据区(Data Area)的,它在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址则保持不变。JVM初始化变量是先声明空间,然后在赋值(int i= 12;==>int i ; i =12;)
静态变量在类初始化时首先被加载的,JVM会查找类中所有的静态申明,然后分配空间,只是完成地址空间分配,还没有赋值,之后会根据类中的静态赋值(包括静态类型赋值和静态块赋值)的先后顺序执行。变量先申明后使用。

33. 不要(重写)覆写静态方法,覆写是针对非静态方法(实例方法)的,不针对静态方法(类方法),但是可以隐藏静态方法(属于该类,与父类没有关系)。通过实例对象访问静态方法和属性是不好的习惯。

实例对象有两个类型:表面类型(Apparent Type)实际类型(Actual Type),表面类型是声明时的类型,实际类型是对象产生时的类型,对于非静态方法,它是根据对象的实际类型来执行的,对于静态方法来说,不依赖实例对象,通过类名访问,通过对象访问静态方法,JVM会通过对象的表面类型查找到静态方法的入口,然后执行,
在子类中构建与父类相同的方法名,输入参数,输出参数,访问权限(权限可以扩大),并且父类子类都是静态方法,此种行为称之为隐藏(Hide),它与覆写有两点不同。

  • 1,表现形式不同:隐藏用于静态方法,覆写用于非静态,@OVerride可以用于覆写(写上自动检测是否合要求),不能用于隐藏。
  • 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
    34
    public class Base {
    //父类静态方法
    public static void doSomething() {
    System.out.println("我是父类的静态方法");
    }
    //父类非静态方法
    public void doAnything() {
    System.out.println("我是父类的非静态方法");
    }
    }
    public class Sub extends Base{
    //子类同名,同参数的静态方法
    public static void doSomething() {
    System.out.println("我是子类的静态方法");
    }
    //覆写父类的非静态方法
    @Override
    public void doAnything() {
    System.out.println("我是子类的非静态方法");
    }
    }
    public class Client1 {
    public static void main(String[] args) {
    Base base1 = new Sub();
    Sub base = new Sub();
    base.doAnything();
    base1.doSomething();
    base.doSomething();
    }
    }
    //结果
    //我是子类的非静态方法
    //我是父类的静态方法
    //我是子类的静态方法
34. 构造函数尽量简化。(子类实例化时,会先初始化父类(初始化,不是生成父类对象),就是初始化父类的对象,调用父类的构造函数,然后才会初始化子类的变量,调用子类的构造函数,最后生成一个实例对象)
35. 避免在构造函数中初始化其他类。
36. 使用构造代码精炼程序,

代码块(Code Block):{}包裹的数据体,实现特定算法,一般不能单独运行,要有运行主体,java 中有四种:

  • 1,普通代码块:方法名后面{ }部分。
  • 2,静态代码块:在类中使用static修饰的{ },用于静态变量的初始化和对象创建前的环境初始化。类中的静态块会在整个类加载过程中的初始化阶段执行,而不是在类加载过程中的加载阶段执行初始化阶段是类加载过程中的最后一个阶段,该阶段就是执行类构造器方法的过程,方法由编译器自动收集类中所有类变量(静态变量)的赋值动作和静态语句块中的语句合并生成 ,一个类一旦进入初始化阶段,必然会执行静态语句块。所以说,静态块一定会在类加载过程中被执行,但不会在加载阶段被执行
  • 3,同步代码块:使用synchronized修饰的{ },表示同一时间只能有一个线程进入到该方法块,一种多线程保护机制。
  • 4,构造代码块:在类中没有人任何前缀和后缀的{ },编译器会把构造代码块插入到构造函数的最前端。
    在通过new关键字生成一个实例时会先执行构造代码块,然后在执行其他构造函数代码,依托于构造函数运行,不是在构造函数之前运行。应用:
  • 1,初始化实例变量(Instance Variable):如果每个构造函数都需要初始化变量,可以通过构造代码块实现。
  • 2,初始化实例环境:当一个对象必须在适的场景才能存在,jee中要产生HTTP Request,必须要建立HTTP session ,可以在创建HTTP Request时通过构造代码块检查HTTP Session是否存在,不存在就创建。
37. 构造代码块会想你所想。(当构造代码块遇到this关键字时(构造函数调用自身其他无参构造函数),则不插入代码块,遇到super时,会放到super方法之后执行)
38. 使用静态内部类提高封装性:

java中的嵌套类(Nesetd Class):分为两种,静态内部类(也叫静态嵌套类,Static Nested Class)和内部类(Inner Class),
静态内部类:加强了类的封装性,提高了代码的可读性。
静态内部类不持有外部类的引用(普通内部类可以访问外部类的方法,属性,即使是private类型也可以访问,静态内部类只可以访问外部类的静态方法和静态属性),静态内部类不依赖外部类(普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,同生同死,一起声明,一起被拉圾回收器回收,静态内部类可以独立存在,即使外部类消亡,静态内部类还是可以存在),普通内部不能声明static的方法和变量(常量可以修饰,静态内部类没有限制)

39. 使用匿名类的构造函数
1
2
3
4
5
6
//声明一个ArrayList对象。
List la = new ArrayList();
//一个继承了ArrayList的匿名类的声明和赋值,没有任何覆写方法。
List lb = new ArrayList(){};
//在上面的基础上增加了构造函数块。可以有多个。
List lc = new ArrayList(){{}{}};
40. 匿名类的构造函数很特殊:(一般类(具有显示名字的类)的所有构造函数默认都是调用父类的无参构造函数的,而匿名类没有名字,只能有构造代码块代替,它在初始化时直接调用父类的同参构造函数,然后在调用自己的构造代码块)
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
enum Ops{ADD,SUB }
public class Calculator {
private int i,j,result;

public Calculator() {

}
public Calculator(int i,int j) {
this.i =i;
this.j = j;
}
protected void setOperator(Ops _op) {
result = _op.equals(Ops.ADD)?i+j:i-j;
}
public int getResult() {
return result;
}
public static void main(String[] args) {
//定义一个匿名内部类,使用构造代码块初始化
Calculator c1 = new Calculator(1,2) {
{
setOperator(Ops.ADD);
}
};
System.out.println(c1.getResult());
}
}
41. 让多重继承成为现实(内部类的重要特征,内部类可以继承一个与外部类无关的类,保证了内部类当然独立性)多重继承考虑内部类。
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 interface Father {
public int strong();
}
//母亲
public interface Mother {
public int Kind();
}
//父亲实现类
public class FatherImpl implements Father {
@Override
public int strong() {
return 8;
}
}
//母亲实现类
public class MotherImpl implements Mother{
@Override
public int Kind() {
return 8;
}
}
//儿子
public class Son extends FatherImpl implements Mother{
@Override
public int strong() {
return super.strong()+1;
}
@Override
public int Kind() {
return new MotherSpecial().Kind();
}
public class MotherSpecial extends MotherImpl {
public int kind() {
return super.Kind() -1;
}
}
}
42. 让工具类不可实例化(一般为静态):在构造函数设置私有,抛出异常。
1
2
3
4
5
public class UtilsClass{
private UtilsClass(){
throw new Error("不要实例化我哎");
}
}
43. 避免对象的浅拷贝(浅拷贝是java的一种简单的机制,不便于直接使用)

一个类在实现了Cloneable接口就表示它具备了被拷贝的能力,如果在覆写clone()方法就会完全具备拷贝能力。拷贝在内存中进行,所以在性能方面比直接通过new生成对象要快的多,存在缺陷:浅拷贝(Shadow Clobe,也称影子拷贝)存在对象属性拷贝不彻底的问题。拷贝规则:

  • 1,基本类型拷贝其值,。
  • 2,实例对象,拷贝地址引用,就是说此时新拷贝的对象与原有对象共享该实例变量,不受访问权限的限制。
  • 3,String字符串,拷贝的也是地址,但是在修改时,会从字符串池(String Pool)中重新生成字符串,原有的字符串保持不变
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
public class Person implements Cloneable{
//姓名
private String name;
//父亲
private Person father;
public Person(String name) {
this.name = name;
}
public Person(String name,Person parent) {
this.name = name;
this.father = parent;
} //getter与setter方法省略
public Person clone() {
Person p = null;
try {
p = (Person)super.clone();
//p.setFather(new Person(p.getFather().getName())实现深拷贝
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
public class Client2 {
public static void main(String[] args) {
Person f = new Person("父亲");
Person s1 = new Person("大儿子",f);
Person s2 = s1.clone();
s2.setName("小儿子");
//s2.getFather().setName("干爹");
System.out.println(s1.getName()+"的父亲为:"+s1.getFather().getName());
System.out.println(s2.getName()+"的父亲为:"+s2.getFather().getName());
}
}
44. 推存使用序列化实现对象的拷贝,

被拷贝的类只要实现Serializable接口,不需要任何实现,需要加上SerialVersionUID常量,使用需要注意:

  • 1,对象的内部属性都是可序列化的。
  • 2,注意方法和属性的特殊修饰符。final,static变量的序列化问题会被 引入到拷贝对象中,瞬态变量(trtansient)不能进行序列化。可一采用Apache下的commons工具包中的SerializationUtils类。
    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 CloneUtils {
    //拷贝一个对象
    //@SuppressWarnings。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。
    /*
    * 关键字 用途
    deprecation 使用了不赞成使用的类或方法时的警告
    unchecked 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
    fallthrough 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
    path 在类路径、源文件路径等中有不存在的路径时的警告。
    serial 当在可序列化的类上缺少 serialVersionUID 定义时的警告。
    finally 任何 finally 子句不能正常完成时的警告。
    all 关于以上所有情况的警告。
    @SuppressWarnings 批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,
    您将调查它,如果您确定它不是问题,您就可以添加一个 @SuppressWarnings 批注,以使您不会再看到警告。
    虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 —
    您看到的每一个警告都将值得注意。
    */
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) {
    //拷贝产生的对象
    T clonedObj = null;
    try {
    //读取对象字节数据
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);//写操作
    oos.close();//关闭流
    //分配内存空间,写入原始对象,生成新对象
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    clonedObj = (T)ois.readObject();//读操作
    ois.close();
    }catch(Exception e) {
    e.printStackTrace();
    }
    return clonedObj;
    }
    }
45. 覆写equals方法时不要识别不出自己,equals方法的自反原则(对于任何非空引用x,x.equals(x)应该返回true)
46. equals应该考虑null值情景。对称性原则,对于任何引用x和y的情形,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
47. 在equals中使用getClass进行类型判断,注意equals的传递性原则,对于实例对象s,y,z,如果如果s.equals(y)为true,y.equals(z)返回true,那么x.equals(z)也返回true。
48. 覆写equals方法必须覆写hashCode方法。

HashMap的底层处理机制是以数组的方式保存Map条目的,链表保存val,依据传入元素的hashCode方法返回的哈希值决定数组下标,如果该位置已有Map条目了,且与传入的键值相等则不要处理,若不相等则则覆盖,如果数组位置没有条目则插入。并加入到Map条目的链表中。即在检查相等时也是由哈希吗确定位置。
哈希码:由Object方法本地生成,确保每一个对象有一个哈希码(哈希算法,输入任意L,通过一定算法f(L),将其转化为非可逆的输出,一对一,多对一成立),重写hashCode方法:

1
2
3
public int hashCode(){
return new HashCodeBuilder().append(),toHashCode();//HashCodeBuilder哈希码生成工具。
}
49. 推存覆写toString方法,正常输出:类名+@+hashCode;
50. 使用package-info类为包服务:用于描述和记录包信息的,会被编译,不能声明类,不能继承,没有类关系,用于申明友好类和包内访问常量,为在包上标注一个注解(Annotation)提供方便,即将定义的注解写到这里,提供包的整体注释说明。
51. 不要主动进行垃圾回收。

第四章,字符串

52. 推存使用String直接量赋值,

Java为了避免在一个系统中产生大量的String对象,设计了一个字符串池(也称字符串常量池,String pool 或 String Constant Pool,存在于JVM常量池(Constant Pool)中),在字符串池中容纳的都是String对象,当创建一个字符串时,首先检查池中是否有字面值相等的字符串,有不在创建,直接返回池中该对象的引用,没有则创建,然后放到池中,返回新建对象的引用,所以当用“==”判断相等。当使用new String(“”) 时不会检查池,也不会放入池。不相等,intern方法(会检查池里,有返回)处理相等。String类是一个不可变(Immutable)的类,final不可继承,在String提供的方法中,如果返回字符串一般为新建的String对象,不对原对象进行修改。

53. 注意方法中传递的参数要求,replaceAll传递的是第一个参数为正则表达式。
54. 正确使用String ,StringBuffer , StringBuilder :

CharSequence接口有三个实现类:String , StringBuffer , StringBuilder ,

  • String为不可改变量,修改要么创建新的字符串对象,要么返回自己(str.substring(0)),
  • StringBuffer是一个可变字符序列,他与String一样,在内存中的都是一个有序的字符序列,不同点是值可以改变,
  • StringBuilder 与 StringBuffer在性能上基本相同,都为可变字符序列。不同点为StringBuffer为线程安全的,而StringBuilder为线程不安全。

使用场景

  • String:常量的声明,少量的变量运算。
  • StringBuffer:频繁的字符串运算,多线程(xml,Http解析)。
  • StringBuilder:频繁的字符串运算,单线程(SQL语句拼接)。
55. 注意字符串的位置:在+号处理中,String字符串具有最高的优先级。
56. 自由选择字符串拼接方式:
  • 1,“+”号拼接,时间最长,等价于srt = StringBuilder(srt).append(“c”).toString();
  • 2,concat方法拼接,时间中等,每次都会新建一个String对象;
  • 3,append方法拼接字符串,时间最快,只生成一个String对象。
57. 推存在复杂字符串操作中使用正则表达式来完成复杂处理。查找单词数,\b\w+\b
58. 强烈建议使用UTF编码。
59. 对字符串排序持一种宽容的心态,如果排序不是关键算法,用Collator类即可。

第五章,数组和集合

60. 性能考虑,数组是首选,集合类的底层都是通过数组实现的,基本类型在栈内存中操作的(速度快,容量小),而对象则是在堆内存中操作的(速度慢,容量大)。性能要求较高的时候用数组代替集合。
61. 若有必要,使用变长数组,

数组扩容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class demo {
public demo() {
// TODO Auto-generated constructor stub
}
public static <T> T[] expandpacity(T[] datas, int newLen) {
newLen = newLen<0?0:newLen;
return Arrays.copyOf(datas, newLen);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
demo []de= new demo[30];
de = expandpacity(de,60);
}

}

集合的长度自动维护功能的原理与此类似。

62. 警惕数组的浅拷贝,数组的copyOf方法产生的数组为一个浅拷贝,与序列化的浅拷贝相同,基本类型拷贝值, 其他拷贝地址,集合的clone方法也为浅拷贝,
  • 浅拷贝(浅复制、浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。 换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
  • 深拷贝(深复制、深克隆):被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。继承自java.lang.Object类的clone()方法是浅复制。
63. 在明确的场景下,为集合指定初始容量,ArrayList中java实现长度的动态管理。如果不设置初始容量,系统就按照1.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
public boolean add(E e) {
ensureCapacity(size + 1);//扩展长度。
elementData[size++] = e;//追加元素
return true;
}
public void ensureCapacity(int minCapacity){
modCount++;//修改计数器
int oldCapacity = elementData.length;//上次数组长度
if(minCapcity>oldCapacity) {
Object oldDate[] = elementDate;
//计算新数组长度。
int newCapacity = (oldCapacity*3)/2+1;
if(newCapacity<minCapacity)
newcapacity = minCapacity;
//数组拷贝,生成新数组
elementDate = Arrays.copyof(elementDate,newCapacity);
}

}
//并不是增加一个元素elementDate的长度就加1,而是在达到elementDate长度的临界点,才将elementDate元素扩充1.5倍。
//无参构造函数
public ArrayList() {
this(10);
}
//指定长度的有参构造函数
public ArrayList(int initialCapacity) {
super();
if(initialCapacity<0)
this.elementDate = new Object[initialCapacity];
}

vector的处理方式与ArrayList的长度处理相似,不同的地方是提供递增步长(capacityIncrement变量)。不设置容量翻倍。HashMap是按照倍数增加的。

64. 多种最值算法,适时选择,使用集合最简单,使用数组性能最优。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static int max(int [] data) {
int max = data[0];
for(int i:data) {
max = max >i?max:i;
}
return max;
}//自行实现,速度最快
public static int max(int [] data) {
Arrays.sort(data.clone());
return data[data.length-1];
}//先排序后取值
//查找不重复的仅次于最大值的。
public static int getSecond(Integer[] data) {
List<Integer> dataList = Arrays.asList(data);//转换为列表
TreeSet<Intger> ts = new TreeSet<Integer>(dataList);//删除重复元素并升序排序,由TreeSet类实现。
return ts.lower(ts.last());//用lower获取小于最大值的值。
}
65. 避开基本类型数组转化为列表陷阱,原始数据类型数组不能做为asList的输入参数,否则会引起程序逻辑混乱。
1
2
3
4
5
public static void main(String[] args ){
int [] data = {1,2,3,4,5};
List<Integer> dataList= Arrays.asList(data);
System.out.println("列表中的元素个数为:"+list.size());
//asList方法输入的为泛型变长参数,在java中数组为一个对象,它是可以泛型化的,即把int型数组当做T的类型,所以转换后长度为1,调用getclass方法返回:class [I,jvm不能输出Arrays类型,因为Arrays是属于java.lang.reflect包的,它是通过反射访问数组元素的工具类,在java中任何一个数组的类都是“[I”数组类并没有定义,是在编译器编译时生成的,是一个特殊的类,
66. asList方法产生的List对象不可更给,

调用add方法会抛不支持的操作的异常,基于Arrays的ArrayList是一个静态私有内部类,除了Arrays能访问以外,其他类都不能访问,且add方法为ArrayList的父类提供,但是没有具体的实现,Arrays的内部类没有覆写add方法。
ArrayList静态内部类实现了5 个方法。s
ize(元素数量),toArray(转化为数组),get(获取指定元素),set(重置某一元素),container(是否包含某个元素),方法 ,没有实现add和remove方法,
即asList返回的为一个长度不可变的列表,数组为多长转换为列表为多长,即不在保持列表动态变长的特性。Listnames = Arrays.asList(“”,””,””);不可取,列表长度不可修改;

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
package java_151;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.RandomAccess;

public class asList_Demo {
/*
* 报不支持 的操作异常, java.lang.UnsupportedOperationException
* List的
*/
enum Week {Sun,Mon,Tue,Wed,Thu,Fri,Sat}
public static void main(String[] args) {
// TODO Auto-generated method stub
Week[] workDays = {Week.Mon,Week.Tue,Week.Wed,Week.Thu,Week.Fri};
List<Week> list = Arrays.asList(workDays);
list.add(Week.Sat);
}
/*
* asList method 源码,直接new一个ArrayList对象并返回,
* 此ArrayList非java.util.ArrayList,而不是Arrays的工具类。
*/
public static <T> List<T>asList(T...a){
return new ArrayList<T>();
}
/*
* ArrayList类为Arrays的内置类,构造函数:为一个静态私有的内部类,没有提供add方法,即只能是
* AbstractList提供,但父类提供的add方法没有提供具体的实现,需要覆写。
*
*/
private static class ArrayList<E> extends AbstractList<E> implements
RandomAccess, Serializable{
private final E[];
ArrayList(E[] array){
if(array ==null) {
throw new NullPointerException();
}
a = array;
}
}
}

67.不同的列表选择不同的遍历方法,

对于 ArrayList数组for循环下标方式要比foreach遍历快,
对于LinkedList类讲,foreach方法要比fo循环方法快,
ArrayList数组为随机存取列表,LinkedList为有序存取列表(实现了双向链表,每个数据节点有三个数据项前节点引用(Previons),本节点元素(Node element),后继节点的引用(Next Node ))。ArrayList数组实现了RandomAccess接口(随机存取接口),即为一个随机存取的列表,数据元素之间没有关联,没有依赖和索引关系,
Java中,RandomAccess(随机存取)和Cloneble(可以拷贝)。Serializable(可以序列化)接口一样,为标志性接口,不需要任何实现,只是用来表明实现类具有某种性质,
java中foreach语句为iterator(迭代器)的变形使用,即迭代器为23种设计模式的一种,提供一种方法访问一个容器中对象的各个元素,同时又无需暴露内部细节,对于ArrayList来讲,创建一个迭代器,然后屏蔽内部遍历细节,提供hasnext,next等方法,

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
package java_151;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.RandomAccess;

public class asList_Demo {

public static void main(String[] args) {
int stuNum = 80*10000;
//实例化List集合,并指定初始容量。
List<Integer> scores = new ArrayList<Integer>(stuNum);
for(int i = 0; i <stuNum ;i++) {
//返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值。
scores.add(new Random().nextInt(150));
}
long start = System.currentTimeMillis();
System.out.println("平均分是:"+average(scores));
System.out.println("执行时间:"+(System.currentTimeMillis()-start)+"ms");

}
//foreach语句遍历
public static int average(List<Integer> list) {
int sum = 0;
for(int i:list) {
sum = sum+i;
}
return sum/list.size();
}
//下标遍历
public static int average(List<Integer> list) {
int sum = 0;
for(int i=0,size=list.size();i<size;i++) {
sum += list.get(i);
}
return sum/list.size();

/*
* foreach 原理
*/
for(Iterator<Integer>i = list.iterator();i.hasNext();) {

}
}
}
//适用用于有序存取列表和随机存取列表。
public static int average(List<Integer> list) {
int sum =0;
if(list instanceof RandomAccess) {
for(int i = 0,size = list.size();i<size;i++) {
sum += list.get(i);
}
}else {
for(int i:list) {
sum +=i;
}
}
return sum/list.size();
}
}
//LinkList 下标遍历法;
public E get(int index) {
return entry(index).element;
}
private Entry(E) entry( int index){
Entry<E> e = heder;
if(index < (size>>1)) {
for(int i =0 ; i<= index; i++)
e = e.next;
}else {
for(int i = size; i>index; i++) {
e = e.previous;
}
return e;
}

68.频繁插入和删除时使用LinkedList,

LinkedList的插入效率要比ArrayList快 50倍,删除要比ArrayList快40倍,修改要慢ArrayList许多,ArrayList在写操作时要慢Lnkedlist;

69.列表相等只需关心元素数据,

在java中,列表只是一个容器,只要是同一种类型的容器(List),不会关心容器的细微差(ArrayList和Kinkedlist两者都实现了LIst接口继承了AbstractList抽象类,equals方法在抽象类中定义。),只要确定所有元素数据,个数相等即可,Set,Map与此相同。判断集合是否相等,只需要判断元素是否相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean equals(Object o) {
//是否为自身
if (o == this)
return true;
//是否为List的实现类,即是否为List列表
if (!(o instanceof List))
return false;
//返回列表元素的迭代器,访问所有的元素进行比较。
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
//当都为空不执行,一个为空false,都不为空equals比较。
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
//判断长度是否相等。
return !(e1.hasNext() || e2.hasNext());
}

70.子列表只是原列表的一个视图,

List提供一个subList方法,与String的subString有点类似,subList方法是由AbstractList实现的,它会根据是不是随机存储提供不同的实现方法,SubList返回的类也是AbstractList的子类,其所有的方法(get,add,set,remove等)都是在原始列表上操作的,它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图(View),所有的修改都反映在原列表上。

1
2
3
4
5
6
7
8
List <String> c = new ArrayList<String>();
c.add("A");
c.add("B");
List<String> c1 = new ArrayList<String>(c);
List<String> c2 = c.subList(0, c.size());
c2.add("C");
System.out.println("c==c1"+c.equals(c1));
System.out.println("c==c2"+c.equals(c2));
71.推存使用subList处理局部列表,
1
2
3
4
5
6
7
8
9
10
11
List<Integer> initate = Collections.nCopies(100, 0);
//public static <T> List<T> nCopies(int n, T o)
//返回由指定对象的 n 个副本组成的不可变列表。
List<Integer> list = new ArrayList<Integer>(initate);
//转换为可变列表。
//list.subList(20, 30).clear();
for(int i = 0,size=list.size();i<size;i++) {
if(i>=20&&i<30) {
list.remove(i);
}
}
72.生成子列表后不要在操作原列表

,操作抛出java.util.ConcurrentModificationException并发修改异常,因为subLis取出的列表只是原列表的一个视图,原数据集修改了,但是subList取出的子列表不会重新生成一个新列表,后面的对子列表继续操作时,就会检测到修改计数器与预期的不相同,会抛出并发修改异常,subList的其他方法也会检测修改计数器,例如Set,get,add方法,如果生成子列表在操作原列表,必然会导致视图不稳定,有效的办法是通过Collections.unmodifiableList方法设置列表为只读状态当有多个字列表时,任何一个子列表就都不能修改啦。生成子列表后,原列表保持只读状态。防御式编程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
List<String>subList = list.subList(0, 2);
list.add("D");
System.out.println("原列表的长度:"+list.size());
System.out.println("子列表偶的长度:"+subList.size());
抛出异常:Exception in thread "main" java.util.ConcurrentModificationException;
//设置列表为只读状态。
List<String>list = new ArrayList<String>();
List<String> subList = list.subList(0, 2);
//unmodifiableList(List<?y extends T> list)
//返回指定列表的不可修改视图。
list = Collections.unmodifiableList(list);
73.使用Comparator进行排序,

在java中,要想给数据进行排序,有两种事项方式,一种为实现Comparable接口,一种是实现Comparator接口,

1
public interface Comparable<T>

此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
int compareTo(T o)比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

1
2
@FunctionalInterface
public interface Comparator<T>

比较功能,对一些对象的集合施加了一个整体排序 。 可以将比较器传递给排序方法(如Collections.sort或Arrays.sort ),以便对排序顺序进行精确控制。 比较器还可以用来控制某些数据结构(如顺序sorted sets或sorted maps ),或对于不具有对象的集合提供的排序natural ordering ,与Comparable不同,比较器可以可选地允许比较空参数,同时保持对等价关系的要求。
int compare(T o1, T o2) 比较其两个参数的顺序。

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
* @Description : 集合排序整理
* @Author: Liruilong
* @Date: 2019/7/31 22:21
*/
public class Compara implements Comparable<Compara>{

private Integer data;

public Integer getData() {
return data;
}

public void setData(Integer data) {
this.data = data;
}

/**
* @Author Liruilong
* @Description public interface Comparable<T>
* 此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,
* 类的 compareTo 方法被称为它的自然比较方法。
* 实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。
* 实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
* int compareTo(T o)比较此对象与指定对象的顺序。
* 如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
* @Date 22:54 2019/7/31
* @Param [o]
* @return int
**/
@Override
public int compareTo(Compara o) {
// (x == 1) ? -1 : ((x == 2) ? 0 : ((x == 3) ? 1:((x == 4) ? 3 : ))) 代替 if -else if。
return (this.data < o.data) ? -1 : ((this.data == o.data) ? 0: 1);
}

public interface Comparator<T> {
int compare(T o1, T o2);
}

public static void main(String[] args) {

List<Compara> comparaList = new ArrayList<>();
//方法一 集合元素实现了Comparable 接口,直接用工具类排序
Collections.sort(comparaList);
// 方法二 在排序时将比较器传入
Collections.sort(comparaList, (Compara o1, Compara o2) ->o1.compareTo(o2) );
//方法三,通过Comparator传入一个比较器
comparaList.sort((Compara o1,Compara o2) ->o1.compareTo(o2));
comparaList.sort(new java.util.Comparator<Compara>() {
/**
* @Author Liruilong
* @Description 针对一些本身没有比较能力的对象(数组)为它们实现比较的功能,
* 所以它叫做比较器,是一个外部的东西,通过它定义比较的方式,
* 再传到Collection.sort()和Arrays.sort()中对目标排序,
* 而且通过自身的方法compare()定义比较的内容和结果的升降序;
* @Date 23:16 2019/7/31
* @Param [o1, o2]
* @return int
**/
@Override
public int compare(Compara o1, Compara o2) {
if (o1.getData()== o2.getData()){
return 0;
}else if(o1.getData() < o2.getData()) {
return -1;
}else {
return 1;
}
}
});

}



}

74.不推存使用binarySearch对队列进行检索。一般使用indexOf方法进行检索,binarySearch使用二分搜索法搜索指定列表,以获得指定的对象,二分法查询,数据要已经实现了升序排序。但是binarySearch在性能上要比indexOf好。
75.集合中的元素必须 做到compareTo和equals同步:实现了Comparable接口的元素就可以排序,compareTo方法是Comparable接口要求必须实现的。当返回0时,表示进行比较的两个元素是相等的,indexOf检索方法是通过equals方法判断的,binarySearch则依赖compare方法查找,不懂?????
76.集合运算时使用更优雅的方式,并集,交集,差集。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<String> list1 = new ArrayList<>();
list1.add("A");
list1.add("B");
List<String> list2 = new ArrayList<>();
list2.add("C");
list2.add("D");
//并集
list1.addAll(list2);
//交集
list1.retainAll(list2);
list1.removeAll(list2);
//无重复的并集
list2.removeAll(list1);
list1.addAll(list2);
77.使用shuffle打乱列表,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int tagCloudNum = 10;
List<String> tagClouds = new ArrayList<String>(tagCloudNum);
Random random = new Random();
for(int i = 0;i <tagCloudNum;i++) {
//获取随机位置;
int randPotion = random.nextInt(tagCloudNum);
//当前元素与随机位置元素交换。
String temp = tagClouds.get(i);
tagClouds.set(i, tagClouds.get(randPotion));
tagClouds.set(randPotion, temp);
}

int tagCloudNum = 10;
List<String> tagClouds = new ArrayList<String>(tagCloudNum);
Random random = new Random();
for(int i = 0;i <tagCloudNum;i++) {
int randPotion = random.nextInt(tagCloudNum);
//当前元素与随机位置元素交换。
Collections.swap(tagClouds, i, randPotion);
}
int tagCloudNum = 10;
List<String> tagClouds = new ArrayList<String>(tagCloudNum);
Collections.shuffle(tagClouds);
78.减少HashMap中的元素的数量

HashMap在底层也是以数组的方式保存元素的,每一个键值对就是一个元素,HashMap把键值对封装为Entry对象,然后把Entry放到数组中,HashMap的底层数组变量为table,是Entry类型的数组,保存一个一个的键值对,HashMap也可以动态的增加,大于等于阈值,数组增大一倍,阈值为当前长度与加载因子的乘机,默认加载因子为 0.75,即HashMap的size大于等于数组长度的0.75倍,就开始扩容。

79.集合中的哈希码不要重复

随机存取的列表是遍历查找,顺序存储列表是链表查找,或者Collections的二分法查找,HashMap等set集合要快于List集合,HashMap每次增加元素都会先计算器哈希码,然后使用hash方法再次对hashCode进行抽取和统计,同时兼顾哈希码的高位和低位的信息产生唯一值,之后通过indexFor方法与数组长度做一次与运算,计算数组位置,hash的方法和iindexFor方法就是把哈希码转化为数组,

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 boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}

final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int size = 10000;
List<String> list = new ArrayList<String>(size);
for(int i = 0;i < size; i++) {
list.add("value"+i);
}
long statc = System.nanoTime();
//开始查找
list.contains("value"+(size-1));
long end = System.nanoTime();
System.out.println("List时间为:"+(end-statc)+"ns");
//Map 的查找时间
Map map = new HashMap<String, String>(size);
for(int i = 0;i < size; i++) {
map.put("key"+i, "value"+i);
}
statc = System.nanoTime();
//开始查找
map.containsKey("key"+(size-1));
end = System.nanoTime();
System.out.println("Map 时间为:"+(end-statc)+"ns");

}
80.多线程使用Vector或HashTable,

Vector是ArrayList的多线程版本,HashTable是HashMap的多线程版本,线程安全和同步修改异常是两个概念,基本上所有集合类都有一个叫做快速失败的校验机制(Ffail-Fast),当一个集合在被多个线程修改并访问时,就可能会出现ConcurrentModificationException 异常,这是为了确保集合方法一致设置对的保护措施,实现原理为modCount修改统计器,当读取列表是发生变化(其他线程也在操作),则会抛出异常,也与 线程同步不同,线程同步是为了保护数据不被脏写,脏读而设置的,Vector的每个方法都加上了synchronized,两个线程进行同样的操作才可以讨论线程同步,一个线程删除一个线程增加,不属于多线程范畴。

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
package java_151;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Vector {

public static void main(String[] args) {

//火车票列表
final List<String>tickets = new ArrayList<String>();
//线程安全:final Vector<String>tickets = new Vector<String>();
//初始化数据池,
for(int i = 0; i < 10000 ;i++) {
tickets.add("火车票"+i);
}
//退票
Thread returnThread = new Thread() {
public void rnu() {
while(true) {
tickets.add("车票"+new Random().nextInt());
}
}
};
//售票
Thread saleThread = new Thread() {
public void run() {
for(String ticket:tickets)
tickets.remove(ticket);
}
};
//启动退票线程
returnThread.start();
saleThread.start();
}

}
Exception in thread "Thread-1" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
at java_151/java_151.Vector$2.run(Vector.java:28)
//10个窗口售票
for(int i=0; i <10 ; i++) {
new Thread() {
public void run() {
while(true) {
System.out.println(Thread.currentThread().getId()+"---"+tickets.remove(0));
}
};
}.start();
}
//为线程同步问题

81.非稳定排序推:

存使用List,Set与List的最大区别为Set中的元素不可以重复(基于equals的返回值),Set的实现类有一个比较常用的类TreeSet,该类实现了类默认排序为升序Set集合,插入一个元素,默认按照升序排序,SortedSet(TreeSet实现了该接口)接口只是定义了在给集合加入元素时将其进行排序,不能保证修改后的排序结果。所以TreeSet适合不变量的集合数据排序。Sstring或Integer等

82.由点几面,集合大家族:
  • `List:实现List的集合主要有ArrayList(动态数组),LinkedList(双向链表),Vector(线程安全的动态数组),Stack(对象栈,先进后出)。

  • Set: 不包含重复元素的集合,其主要的实现类有:EnumSet(枚举类型专用,所有为枚举类型),HashSet(以哈希码决定元素位置,与HashMap相似,提供快速插入和查找),TreeSet(自动排序Set,实现了SortedSet接口)

  • Map:分为排序Map和非排序Map,排序Map主要是TreeMap类,根据Key值进行自动排序,非排序Map主要包括:HashMap,HsahTable,Properties,EnumMap等,其中Properties是HashTable的子类,它的主要用途从property文件中加载数据,并提供方便的读写操作:EnumMap则要求Key必须为某一个枚举类型。Map中还有一个weakHashMap类,采用弱键方式实现的Map类,WeakHashMap对象的存在并不会阻止垃圾回收器对键值对的回收,即不用担心内存溢出问题。

  • Query:队列,分为阻塞式队列和非阻塞式队列,阻塞式队列主要包括:ArrayBlockingQuery(以数组方式实现的有借阻塞数组),PrinonityBoockingQuery(依照优先级组建的队列),LinkedBlockingQuery(通过链表实现的阻塞队列),非阻塞式队列,PrinonityQuery类.

  • 数组:数组与集合的最大区别就是数组能够容纳基本类型,而集合不行,且数组为非动态,集合的底层都是数组。

  • 工具类:数组的工具类时java.util.Arrays和java.lang.reflect.Array,集合的工具类是java.util.Collections.

第六章, 枚举和注解

83.推存使用枚举定义常量,

JLS(Java Language Specification,java语言规范),提倡枚举全部大写,枚举常量简单,属于稳定型,具有内置方法,可以自定义方法,

84.使用构造函数协助描述枚举项

枚举描述,通过枚举的构造函数,声明每个枚举项必须具有的属性的行为。

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

public enum Season {
Spring("春"),Summer("夏"),Autumn,Winter;
private String desc;
Season(String desc){

this.desc = desc;
}
Season(){

}
public String getDesc() {
return desc;

}

}

85.小心Switch带来的空值异常,

java中Switch可以判断byte,short,int,char即String类型,枚举也可以更在Sewitch后面,原因是switch先计算变量的排序值,然后与枚举常量的每个排序值进行对比,当变量为空时,调用ordinal方法报空指针异常。所以需要判断。switch(s) =s.ordinal;

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

public class Enums {
public static void main(String[] args) {
for(Season s: Season.values())
System.out.println(s);
}
public void describe(Season s) {

switch(s) {
case Spring:
System.out.println(Season.Spring);break;
case Winter:
System.out.println(Season.Winter);break;
case Summer:
System.out.println(Season.Summer);break;
}

}

}

86.在switch的default代码块中增加AssertionError错误。当有不存在枚举时需要。
87.使用valueOf前必须进行校验,

枚举类都是java.lang.Enum的子类,可以访问hashCode,name,valueOf等方法,valueOf会在枚举项中查找出字面值与参数相等的枚举项,当不存在时会报错。rllegalArrumentExcep。可以抛出异常

88.用枚举实现工厂方法模式更简洁。

:工厂模式(Factory Method Pattern)即创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其子类,

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
package java_151;
//抽象产品
interface Car{

}
//具体产品类
class FordCar implements Car{

}
//具体产品类
class BulickCar implements Car{

}
//工厂类
public class CarFactory {
//生产汽车
public CarFactory() {
// TODO Auto-generated constructor stub
}
public static Car createCar(Class<? extends Car> c) {
try {

return (Car)c.newInstance();
}catch(Exception e) {
e.printStackTrace();
}
return null;
}

public static void main(String[] args) {
// TODO Auto-generated method stub
Car car = CarFactory.createCar(FordCar.class);
}
}
//枚举方法:
enum CarFactory{
//定义工厂类能生产汽车的类型
FordCar,BulickCar;
public Car create() {
switch(this) {
case FordCar:
return new FordCar();
case BulickCar:
return new BulickCar();
default:
throw new AssertionError("无效参数");
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Car car = CarFactory.BulickCar.create();
}
}
//抽象方法生成产品,eclipse报错误
enum CarFactory{
FordCar{
public Car create() {
return new FordCar();
}
},
BulickCar{
public Car create() {
return new BulickCar();
}


};
public abstract Car create();
}

使用枚举类的工厂方法模式,可以避免错误调用的发生,性能好,使用便捷,降低类的耦合性最少知识原则(一个对象应该对其他对象有最少的了解,LoD)。

89.枚举项的数量限制在64个以内

java提供个两个枚举项,EnumSet和EnumMap,Enum表示元素必须是某一枚举的枚举项,EnumMap表示Key值必须是某一枚举项,Java的处理机制当枚举项数量小于等于64时,创建一个RegularEnumSet实例对象,大于64小时则创建一个jumboEnumSet 实例对象。枚举项的排序值ordinal是从0.1.2.3……依次递增的,没有重号,没有跳号,RegularEnumSet利用这点吧每个枚举项的ordinal映射到一个long类型的每个位上,long类型为64 位,所以RegularEnumSet类型就只能负责不大于64位。

90.小心注解的继承,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
*
*/
/**
* @author li rui long
*
*/

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.Inherited;


@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)//保留策略
@Target(java.lang.annotation.ElementType.TYPE)//注解的使用范围
@Inherited//父类被子类继承,子类自动修饰。
@interface Desc{
enum Color{
White,Grayish,Yellow;
}
Color c() default Color.White;
}
91.枚举与注解结合使用威力更强大。分析ACM(Access Control List,访问控制列表),ACM要求,资源,权限级别,控制器。
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
package java_151;



import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.Inherited;


@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)//保留策略
@Target(java.lang.annotation.ElementType.TYPE)//注解的使用范围

@interface Access{
CommonIdentifier level() default CommonIdentifier.Admin;
}
interface Identifier{
//
String PEFUSE_WORD = "您无权访问";
//鉴定
public boolean identify();
}
enum CommonIdentifier implements Identifier{
Reader,Author,Admin
;

@Override
public boolean identify() {
// TODO Auto-generated method stub
return true;
}

}
@Access(level = CommonIdentifier.Author)
public class Foo {

public Foo() {
// TODO Auto-generated constructor stub
}

public static void main(String[] args) {
Foo b= new Foo();
//获取注释
Access access = b.getClass().getAnnotation(Access.class);
//没有注释或鉴定失败
if(access == null|| access.level().identify()) {
//没有access注解或者鉴定失败
System.out.println(access.level().PEFUSE_WORD);
}
}
}
92.注意@Override不同版本的区别,在1.6版本中可以在实现接口中加上@Override,而以下版本父类必须是一个类,子类方法和父类方法必须具有相同的方法名,输入参数,输出参数(允许缩小),访问权限(允许类扩大)。

第七章,泛型和反射

泛型可减少强制类型的转换,规范集合的元素类型,提高代码的安全性和可读性,优先使用泛型

93.Java的泛型是类型擦除的

Java泛型(Generic)的引入加强了参数类型的安全性,减少了乐行的转化,Java泛型在编译期有效,在运行期被删除,即泛型参数类型在编译后都会被清除掉。
转化规则:
List< String>,List< Integer>,List擦除后的类型为List
List< String>[],类型擦除后是List[];
List<? extends E>,List<? super E>擦除后的类型为List.
List<T extends Serializable & Cloneable>擦除后为List< Serializable>
Java中泛型的class对象都是相同的,泛型数组初始化时不能声明泛型类型,instanceof不允许存在泛型参数,
即`List< String >[] listArray = new List< String>[];’编译不成功。
System.out.prinln(list instanceof List< String>);编译不通过

1
2
3
4
5
6
public void arrayMethod(String[] strArray) {}
public void arrayMethod(Integer[] intArray ) {}
public void listMethod(List<String> stringList) {}
public void listMethod(List<Integer> intList) {}
//无法通过编译
Erasure of method listMethod(List<Integer>) is the same as another method in type Generic
94.不能初始化泛型参数和数组,

new T();,new T(5)都不能通过,new ArrayList< T>()可以通过。ArrayList表面是泛型,其实已经在编译期转型为Object了,数组允许协变(Covariant),即可以容纳所有对象,类的成员变量是在类初始化前初始化的,所有要求在初始化前它必须具有明确的类型,否则则就只能申明,不能初始化。

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 ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class acces
private int size;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//使用泛型数组
class Foo<T>{
//由构造函数初始化
private T t;
private T[] tArray;
private List<T> list = new ArrayList<T>();
public Foo() {
try {
Class<?> tType = Class.forName("");
t = (T)Array.newInstance(tType, 5);
//创建一个具有指定的组件类型和长度的新数组。
//Array 类提供了动态创建和访问 Java 数组的方法。 Array 允许在执行 get 或 set 操作期间进行扩展转换,但如果发生收缩转换,则抛出 IllegalArgumentException。


}catch(Exception e) {
e.printStackTrace();
}
}
}
95.强制声明泛型的实际类型,强制类型转换,

List< Object >与List< Integer >没有继承关系,不能进行强制转换,强制声明泛型类型,List< Integer > list2 = ArrayUtils.< Integer >asList();即通过声明 强制确定类型。

96.不同的场景使用不同的泛型通配符,

Java泛型支持通配符,可以使用“?”,表示任意类,也可以使用extends 关键字表示某一个类的(接口)的子类型,可以使用super关键字表示某一个类(接口)的父类。
如果一个泛型结构即用作读操作,又用作写操作,使用确切的泛型类型即可。

  • 在泛型结构中,只参与“读”操作则限定上界(extends):
1
2
3
4
5
public static <E> void read(List<? extends list>) {
for( E e:list) {

}
}
  • 泛型结构只参与”写“操作则限定下界(super)。
1
2
3
4
public  static void write(List<? super  Number> lsit){
lsit.add(123);
lsit.add(33.14);
}

JDK的Collections.comp方法实现了把源列表中的所有元素拷贝到目标列表对应的索引上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");

if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (i)nt i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
97.警惕泛型是不能协变和逆变的。协变,即指窄类型替换宽类型的,逆变即宽类型覆盖窄类型。泛型不支持协变,但可以使用通配符(Wildcard)模拟协变。泛型不支持逆变,即不能把一个父类对象赋值给一个子类类型变量,可以使用super实现。
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
//子类的doStuff()方法返回值的类型比父类方法要窄,即该方法为协变方法,也称多态。
class Bean{
public Number doStuff(){
return 0;
}
}
class sub extends Bean{
@Override
public Integer doStuff(){
return 0;
}
}
//子类的doSutff方法返回值的类型比父类方法宽,此时为逆变方法,
class Bean{
public Number doStuff(Integer i ){
return 0;
}
}
class sub extends Bean{
public Integer doStuff(Number i){
return 0;
}
}
//数组支持协变
Number [] n = new Integer[10];
//编译不通过
//java 为了保证运行期的安全性
//必须保证泛型参数类型是固定的,所以它不允许一个泛型参数可以同时包含两种类型,即使为父子关系也不行。
List<Number>ln = new ArrayList<Integer>();
//即Number的子类型都可以为泛型类型参数,即允许NUmber所有的子类作为泛型参数类型,在运行期为一个具体的值,
//通配符在编译期有效,在运行期必须为一个明确的类型。,协变
List< ? extends Number > ln = new ArrayList<Integer>();
//Integer的父类型(包括Integer)都可以为泛型参数类型,逆变
List< ? super Integer> li = new ArrayList<Number>();

带泛型参数的子类型定义

Integer 是Number的子类型
ArrayList< Integer>是List< Integer>的子类型
Integer[] 是Number[]的子类型
List< Integer>是List< Number> 的子类型
List< Integer> 是List<? extends Integer>的子类型
List< Integer>是List< ? super Integer>的子类型
98.建议采用的顺序为List< T>,List< ?>,List< Object>,原因为:
  • 1,List< T >:表示List集合中的元素都为T类型,具体类型在运行期决定,List< ?>表示为任意类型,List< Object >表示为所以元素为Object类型,因为Object为所有类型的父类,所以List< Objerct>可以容纳所有 的类型。
  • 2,List:可以进行读写操作,add或remove等操作,因为是固定的T类型,在编码期不需要进行类型转化。
  • 3,List< ? >:是只读类型,不能进行增加修改操作,因为编译器不知道List中容纳的是什么类型的元素,而且读取的类型为Object类型,需要主动转型,所以经常采用泛型方法的返回值,可以执行删除类型,因为删除动作与泛型类型无关。
  • 4,List< Object>也可以读写操作,但是它执行写入操作时需要向上转型(Up cast),在读取数据时需要向下转型(Docwncast),
99.严格限定泛型类型采用多重界限,

使用“&”设定多重边界(Multi Bounds),在java中的泛型中,可以使用 & 符号关联多个上界并实现多个边界的限定,只有上界才有限定,下界没有多重限定的情况,指定泛型类型T必须为Staff和passenger的共有的子类型,此时变量t就具有了所以限定的属性和方法。

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
interface staff{
public int getSa();
}
interface Passeng{
public boolean isSa();
}
class Me implements staff,Passeng{

@Override
public boolean isSa() {
// TODO Auto-generated method stub
return true;
}

@Override
public int getSa() {
// TODO Auto-generated method stub
return 2000;
}
public static<T extends staff&Passeng>void dis(T t){
if(t.getSa()<2250 &&t.isSa() ) {
System.out.println("李瑞龙");
}
}

}
public static void main(String[] args) {
dis(new Me());
}
//策略模式
public class UserHandler <T extends User>{
public boolean permit(T user,List<Job> jobs) {
List<Class<?>> iList =Arrays.asList(user.getClass().getInterfaces());
//判断用户是否是 管理员
//返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
if(iList.indexOf(Admin.class) >-1){
Admin admin = (Admin)user;
//判断管理员权限
}else {
//判断普通用户权限
}
return false;
}
100.数组的真实类型必须是泛型类型的子类型,

List接口的toArray()方法可以将一个集合转换为一个数组,返回的是一个Object数组,所以需要自行转变,yoArray(T[] a)虽然返回的事T类型的数组,但是还需要传入一个T类型的数组。

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 java_151;

import java.util.Arrays;
import java.util.List;
public class genericDemo {

public static <T> T[] toArray(List<T> list) {
T[] t = (T[]) new Object[list.size()];
for(int i = 0,n = list.size();i < n;i++) {
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A","B");
for(String str:toArray(list)) {
System.out.println(str);
}
}
}
//异常:
Exception in thread "main" java.lang.ClassCastException: java.base/[Ljava.lang.Object; cannot be cast to java.base/[Ljava.lang.String;
at java_151.genericDemo.main(genericDemo.java:21)

类型转化异常,不能把一个Object数组转换为一个String数组,数组是一个容器,只有确保容器内的所有元素与期望的类型有父子关系时才能转换,Object数组只能保证数组内的元素是Object类型,却不能保证他们都是String的父类型,所以转换失败。为什么在main方法中抛出异常,因为泛型是类型擦除的,toArray方法经过编译后的代码。

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 static  Object[]  toArray(List<T> list) {
Object [] t = (Object[]) new Object[list.size()];
for(int i = 0,n = list.size();i < n;i++) {
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A","B");
for(String str:(String[])toArray(list)) {
System.out.println(str);
}
}
//改进办法。即把实际类型也变为要转换的类型。
//objectArray的实际类型和表面类型都是String类型
Object[] objectArray = {"A","B"};
String [] stringArray = (String [])objectArray;

String[] ss = {"A","B"};
//objs的真实类型为String数组,显示类型为Object数组。
Object [] objs = ss;
String [] stas = (String[])objs;

public static <T> T[] toArray(List<T> list,Class<T> tClass) {
//newInstance(Class<?> componentType, int length) 创建一个具有指定的组件类型和长度的新数组
T[] t = (T[]) Array.newInstanc(tClass,list.size())
for(int i = 0,n = list.size();i < n;i++) {
t[i] = list.get(i);
}
return t;
}
//通过反射类Array申明了一个T类型的数组,调用者传入Class类型参数,获取数组的实际类型,

当一个泛型类(泛型集合)转变为泛型数组时,泛型数组的真实类型不能是泛型类型的父类型,只能是泛型类型的子类型,否则就会出现异常。

101.注意Class类的特殊性:

Java语言把Java源文件编译为后缀为class的字节码文件,然后通过ClassLocale机制把类文件加载到内存中,最后生成实例执行,Java使用元类(MetaClass)来描述加载到内存中的类数据,即Class类,描述类的类对象,

  1. 无构造函数,不能主动实例化,Class对象在加载时由java虚拟机通过类加载器中的defineClass自动构造。
  2. 可以描述基本类型 Class as=int.class;8个基本类型执行JVM中并不是一个对象,一般存在于栈中,通过Class可以描述它们,可以使用int.calss描述int类型的类对象。
  3. 对象都是单例模式,一个Class对象描述一个类,只描述一个类,即一个类只有一个Class对象。
    Class是java 的反射入口,只有在获得一个类的动态描述时才能动态的加载调用。获得Class对象有三种方法,类属性方法,对象的getClass方法,forName()方法。
102.适时选择getDeclaredXXX和getXXX;

getDeclaredMethod方法获得的是所有public访问级别的方法,包括从父类继承来的方法,而getDeclareMethod获得自身类的所有方法,包括公有的(public),私有(private),方法等,不受访问权限限制。如果需要列出所有继承自父类的方法,可以先获得父类,然后使用getDeclareMethods,之后持续递归。

103.反射访问属性或方法时将Accessible设置为true,

java中通过反射执行方法的步骤,获取一个对象的方法,然后根据isAccessible返回值确定是否能执行,如果返回false,则需要调用`setAccessible(true),在调用invoke执行方法。
Access并不是语法层次理解的访问权限,而是指是否更容易获得,是否进行安全检查。动态的修改一个类或方法或执行方法时都会受到Java安全体系的制约,而安全处理非常消耗资源,所以对于运行期要执行的方法或修改的属性就提供了Accessible可选项,由开发者决定是否要逃避安全体系的检查。
AccessibleObject是field,Method,constructor的父类,决定其是否可以快速访问而不进行访问控制检查,AccessobleObject类中是以override变量保存该值的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Method  method= genericDemo.class.getMethod("toArray");
if(!method.isAccessible())
method.setAccessible(true);
method.invoke(obj, args);

public class Foo {
public final void doStuff() {
System.out.println("Do liruilong ___$# ^_^");
}
public static void main(String[] args) throws Exception, Throwable {
Method method = Foo.class.getMethod("doStuff");
System.out.println("可以访问吗!!"+method.isAccessible());
method.invoke(new Foo());
}

Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,可以提升系统性能,AccessibleObject的其他两个子类field和 constructor也相似,所以要设置Accessible为true。

104.使用forName动态加载类文件,动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时决定是否要加载一个类,一个类文件只有在被加载到内存中才可能生成实例对象,即加载到内存中,生成Class对象,通过new关键字生成实例对象。
105.动态加载不适合数组,当使用forName加载一个类时,8个基本类型排除,它不是一个具体的类,还要具有可追索的类路径,否则包ClassNotFoundException异常。数组虽然是一个类,但没有定义类路径,可以加载编译后的对象动态动态加载一个对象数组,但是没有意义。在java中数组是定长的,没有长度的数组是不允许存在的。可以使用Array数组反射类来动态加载一个数组。
1
2
3
//动态创建一个数组
String [] strs = (String[]) Array.newInstance(String.class,8);
int[][] ints = (int [][])Array.newInstance(int.class,2,3);
元素类型 编译后的类型
byte[] [B
char[] [C
Double[] [D
Float[] [F
Int[] [I
Long[] [J
Short[] [S
Boolean [Z
引用类型(如String) [L引用类型
106.动态可以让代理模式更灵活,java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标对象生成代理,静态代理:通过代理主题角色和具体主题角色共同实现抽象主题角色的逻辑的,只是代理主题角色把相关的执行逻辑委托给了具体主题角色而已。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface  subject{
public void request();
}
class RealSubject implements subject{
public void request(){

}
}
class Proxy implements subject{
private subject subjects = null;
public Proxy(){
subjects = new RealSubject();
}
public Proxy(subject subjects){
this.subjects =subjects;
}
public void request(){
befoer();
subjects.request();
afert();
}
public void befoer(){}
public void afert(){}
}

java基于java.lang.reflect.Proxy用于实现动态代理,使SubjectHandler作为主要的逻辑委托对象,invoke是必须要实现的,完成对真实方法的调用。即通过InvocationHandler接口的实现类来实现,所有被代理的方法都是由InvocationHandler接管实际的处理任务。

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
interface  subject{
public void request();
}
class RealSubject implements subject{
public void request(){

}
}
class SubjectHandler implements InvocationHandler{
private subject subjects;

private SubjectHandler(subject subjects) {
this.subjects = subjects;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("预处理");
Object obj = method.invoke(subjects,args);
System.out.println("后处理");
return obj;
}
}
//场景类
public static void main(String[] args) {

subject subjects = new RealSubject();
InvocationHandler handler = new SubjectHandler(subjects);
//当前加载器
ClassLoader cl = subjects.getClass().getClassLoader();
//动态代理
subject proxy = (subject) Proxy.newProxyInstance(cl,subjects.getClass().getInterfaces().request(),handler);
//执行具体的角色方法
proxy.request();
}
107.使用反射增加装饰模式的普遍性,装饰模式:动态的给一个对象添加一些额外的职责。使用动态代理可以实现装饰模式。
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
//动物
interface Animal{
public void doStuff();
}
//老鼠
class Rat implements Animal{
@Override
public void doStuff(){
System.out.println("Jetty Tom");
}
}
//定义某种能力
interface Featuer{
public void load();
}

class FlyFeatuer implements Featuer{
public void load(){
System.out.println("增加 一支翅膀");
}
}
class DigFeatuer implements Featuer{
public void load(){
System.out.println("增加钻地能力!");
}
}

class DecorateAnimal implements Animal{
private Animal animal;
private Class<? extends Featuer> clz;
public DecorateAnimal(Animal animal,Class<? extends Featuer> clz){
this.animal = animal;
this.clz = clz;
}
@Override
public void doStuff(){
InvocationHandler handler = new InvocationHandler() {
//具体的包装行为
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
//设置包装
// public class Modifier
// extends ObjectModifier类提供了static方法和常量来解码类和成员访问修饰符。 修饰符集合被表示为具有表示不同修饰符的不同位位置的整数。
//method.getModifiers()返回由该对象表示的可执行文件的Java语言modifiers 。
if (Modifier.isPublic(method.getModifiers())){
obj = method.invoke(clz.newInstance(),args);
}
animal.doStuff();
return obj;
}
};
ClassLoader cl = getClass().getClassLoader();
Featuer Proxy = (Featuer) java.lang.reflect.Proxy.newProxyInstance(cl,clz.getInterfaces(),handler);
Proxy.load();
}
}

public class Demo {
public static void main(String[] args) {
//定义Jerry老树
Animal Jerry = new Rat();
Jerry = new DecorateAnimal(Jerry,FlyFeatuer.class);
Jerry = new DecorateAnimal(Jerry,DigFeatuer.class);
Jerry.doStuff();

}
}
108.反射让模板方法模式更强大,模板方法模式:定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构即可定义该算法的某些特定的步骤,即父类定义抽象模板为骨架,其中包括基本方法(由子类实现的方法,并且在模板方法被调用)和模板方法(实现对基本方法的调用,完成固定的逻辑)
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 abstract class AbsPopulator{
public final void dataInitialing() throws Exception{
doInit();
}
protected abstract void doInit();
}
public class UserPopulator extends AbsPopulator{
protected void doInit(){}
/*初始化用户表,如创建,加载数据等*/
}
//基于反射增强的模板方法
public abstract class AbsPopulator{
public final void dataInitialing() throws Exception{
doInit();
//获得所有public方法
Method[] methods = getClass().getMethods();
for(Method m:methods){
//判断是否是数据初始化方法
if(isIinitDataMethod(m)){
m.invoke(this);
}
}
}

private boolean isIinitDataMethod(Method m) {
return m.getName().startsWith("init")&&//init开始
Modifier.isPublic(m.getModifiers())&&//公开的方法
m.getReturnType().equals("Void.Type")&&//返回类型为void
!m.isVarArgs()&&//输入参数不为空
!Modifier.isAbstract(m.getModifiers());//不能为抽象方法
}
public class UserPopulator extends AbsPopulator{
public void Inituser(){}
public void InitPassword(){}
public void InitJobz(){}
public void Inituser(){}
/*初始化用户表,如创建,加载数据等*/

}

使用反射后,不需要定义任何抽象方法,只需要定义一个基本的方法鉴别器,即可加载否和规则的基本方法,模板方法根据鉴别器返回执行相应的方法。

109.不需要太多的关注反射效率,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Utils{
//获得一个泛型类的实际泛型类型
public static <T> Class<T> getGenricClassType(Class clz) {
//返回 Type表示此所表示的实体(类,接口,基本类型或void)的直接超类 类
Type type = clz.getGenericSuperclass();
//ParameterizedType表示一个参数化类型,如Collection <String>。
if(type instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType){
ParameterizedType pt = (ParameterizedType)type;
Type[] types = pt.getActualTypeArguments();
if (types.length>0&&types[0] instanceof Class){
return (Class)types[0];
}
}
return (Class)Object.class;
}
}
}

第八章 异常

110.提倡异常的封装,可以提供按系统的友好性,提高系统的可维护性,对异常进行分类,解决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
class MyException extends Exception{
private List<Throwable> causes = new ArrayList<>();
public MyException(List<? extends Throwable> causes){
this.causes.add((Throwable) causes);
}
public List<Throwable> getException(){
return causes;
}
public static void doStuff() throws MyException {
List<Throwable> list = new ArrayList<>();
try{
//异常代码
}catch(Exception e){
list.add(e);
}
try{
//异常代码
}catch (Exception e){
list.add(e);
}
if (list.size() > 0)
throw new MyException(list);
}
}
111.采用异常链传递异常,设计模式中有一种叫责任链意识,它的目标是将多个对象连成一条链,并沿着这条链传递该请求,直到有对象处理为止,即先封装,然后传递,把FileNotFoundException封装为MyException,抛到逻辑层,逻辑层根据异常代码确定后续的处理,然后抛出到视图层,在视图层,如果为管理员展现低层次异常,如果为普通用户则展现为封装后异常。
112.受检异常尽可能转换为非受检异常。
113.不要在finally块中处理返回值,
  • 会覆盖try块中的返回值(当没有定义变量时,会先返回try中的返回值,然后执行finally会重置返回值),当定义变量时,不会重置try的返回值,异常代码加上try语句就标志着运行时会有一个Throwable线程监视该方法的运行,当出现异常时,交由异常逻辑处理,方法在栈内存中运行的,会按照“先进后出”的原则执行,main方法调用异常方法,main方法在下层,异常方法在上层,当异常方法执行完毕return a后,此方法的返回值以确定为固定值(基本类型为值拷贝),此后finally代码块修改已经没有意义(类似值传递),当为引用类型时,因为是地址拷贝,所以会改变。
  • 屏蔽异常,无法捕捉到异常,当异常线程在监视到有异常发生时,就会登记当前的异常类型为DateFormatException,但当执行器执行finally语句时,则会重新为doSome方法赋值,告诉调用者没有异常产生,返回值为1。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static void doSome(){
    try{
    throw new RuntimeException();
    }finally {
    return
    }
    }
    public static void main(String[] args) {
    try{
    doSome();
    }catch (RuntimeException e){
    System.out.println("这里永远不会到达!!");
    }
    }
    与return语句类似的System.exit(0)或Runtime().exit(0)出现在异常代码块也会产生异常。不要在finally块中出现return语句。
114.不要在构造函数中抛出异常,java的异常机制有三种,
  • Erroe类及其子类表示为错误,它是不需要程序员处理也不能处理的异常,比如VirtualMachineError虚拟机错误,Thread线程僵死等。
  • RuntimeException类及其子类表示非受检异常,是系统可能会抛出的异常,编译器不要求强制处理的异常,常见的有NullPointException异常和IndexOutBoundException越界异常,
  • Exception类及其子类中除RuntimeException异常,表示受检异常,是程序员必须处理的异常,不处理程序不能通过,IOException和SQLException异常。
    一个对象的创建要经过内存分配,静态代码初始化,构造函数执行等过程,一般不在构造函数中抛出异常,是程序员无法处理的,会加重上层代码的编写者的负担,后续代码不会执行,违背了里氏替换原则,父类能出现的地方子类就可以出现,而且将父类替换为子类也不会产生任何异常, java的构造函数允许子类的构造函数抛出更广泛的异常类(正好与类方法的异常机制相反:子类方法的异常类型必须为父类方法的子类型,覆写要求),当替换时,需要增加catch块,构造函数没有覆写的概念,只有构造函数之间的引用调用而已,
  • 子类构造函数扩展受限。
115.使用Throwsable获得栈信息,AOP编程可以亲松的控制一个方法调用那些类,也能控制那些方法允许别调用,一般来讲切面编程只能控制到方法级别,不能实现代码级别的植入(Weave),即不同类的不同方法参数相同调用相同方法返回不同的值。即要求被调用者具有识别调用者的能力,可以使用Throwable获得栈信息,然后鉴别调用者信息。

JVM在创建一个Throwable类及其子类时会把当前线程的栈信息记录下来,以便在输出异常时准确定位异常原因,在出现异常时,JVM会通过fillInStackTrace(填写执行堆栈跟踪。)方法记录下栈帧的信息,然后生成一个Throwable对象,可以知道类间的调用顺序,方法名称及当前行号。

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 Foo{
public static boolean m(){
//堆栈跟踪中的一个元素,由Throwable.getStackTrace()返回。 每个元素表示单个堆栈帧。 堆栈顶部除堆栈之外的所有堆栈都表示方法调用。 堆栈顶部的帧表示生成堆栈跟踪的执行点。 通常,这是创建与堆栈跟踪相对应的throwable的点。
//取得当前栈信息
StackTraceElement [] stackTraceElements = new Throwable().getStackTrace();
//检查是否是m1方法调用
for(StackTraceElement st: stackTraceElements){
if(st.getMethodName().equals("m1"))
return true;
}
return false;
//throw new RuntimeException("除m1方法外,该方法不允许其他方法调用");
}
}
class Invoker{
//该方法打印true
public static void m1(){
System.out.println(Foo.m());
}
//该方法打印true
public static void m2(){
System.out.println(Foo.m());
}
}

public class Throwable implements Serializable {
//出现异常的栈记录
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
//构造函数记录栈帧
public Throwable() {
fillInStackTrace();
}
//本地方法,抓取执行时的栈信息。
public synchronized Throwable fillInStackTrace() {

}
116.异常只为异常服务,即异常块里不添加逻辑,添加逻辑,会造成异常判断降低了系统性能,降低了代码可读性,隐藏了运行期可能产生的异常
117.多使用异常,把性能放一边,异常是主逻辑的例外逻辑,比如 在马路上走路(主逻辑),突然开过一辆车,我要避让(受检异常,必须处理),继续走路,突然一架飞机从我头顶飞过(非受检异常),我可以选择走路(不捕捉),也可以选择职责其噪音污染(捕捉,主逻辑的补充处理),在继续走下去,突然一棵流星砸下类,这是没有选择,属于错误,不能做任何处理。
1
2
3
4
5
6
7
8
9
public void Login(){
try{
//正常登陆,
}catch (InvalidLonginException e){
//用户名无效
}catch (InvalidPasswordException e){
//密码错误异常
}
}
118.不推存覆写start方法.
119.启动线程前stop方法是不可靠的。
120.不要使用stop方法停止线程。
121.线程优先级只是用三个等级,priority只是表示线程获得CPU运行的机会,不代表强制符号。优先级相同,由操作系统决定,基本上按照FiFO(先入先出)原则,不能完全保证。
122.使用线程异常处理器提升系统可靠性,在java1.5版本后在Thread类中增加了setUncaughhtExceptionHandle方法,实现了异常的捕捉和处理,
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 Tcpserver implements Runnable {

//创建后及运行
public Tcpserver() {
Thread t = new Thread(this);
//设置当该线程由于未捕获的异常而突然终止时调用的处理程序。 线程可以完全控制如何通过明确设置其未捕获的异常处理来响应未捕获的异常。 如果没有设置这样的处理程序,那么线程的ThreadGroup对象将作为其处理程序。
t.setUncaughtExceptionHandler(new TcpServerExceptionHandler());
t.start();
}
@Override
public void run(){
for (int i =0; i < 3;i++){
try{
Thread.sleep(1000);
System.out.println("系统正常运行:"+i);
//线程在等待,睡眠或以其他方式占用时抛出,线程在活动之前或活动期间中断。 偶尔,一个方法可能希望测试当前线程是否已被中断,如果是,立即抛出该异常。 可以使用以下代码来实现这一效果: if (Thread.interrupted()) throw new InterruptedException();
}catch (InterruptedException e){
e.printStackTrace();
}
}
throw new RuntimeException();
}

}
//当Thread由于未捕获的异常而突然终止时,处理程序的接口被调用。
//当一个线程要终止由于未捕获到异常的Java虚拟机将使用查询线程其UncaughtExceptionHandler Thread.getUncaughtExceptionHandler() ,将调用处理程序的uncaughtException方法,将线程和异常作为参数。 如果一个线程一直没有其UncaughtExceptionHandler明确设置,那么它ThreadGroup对象充当其UncaughtExceptionHandler。 如果ThreadGroup对象没有处理异常的特殊要求,则可以将调用转发到default uncaught exception handler 。

//异常处理器
private static class TcpServerExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e);{
System.out.println("线程"+t.getName()+"出现异常,自行启动,分析原因");
e.printStackTrace();
new Tcpserver();
}
}

第九章 线程安全

123,volatile不能保证数据同步,

每个线程都运行在栈内存中,每个线程都有自己的工作内存(Working Memory),比如寄存器Register,高速缓存存储器Cache等,线程的计算一般是通过工作内存进行交互的,线程在初始化时从主内存中加载所需要的变量值到工作内存中,然后在线程运行时,如果读取内存,则直接从工作内存中读取,若是写入则先写入到工作内存中,之后在刷新到主内存中,在多线程情况下,可能读到的不是最新的值,可以使用synchronized同步代码块,或使用Lock锁来解决该问题,java可以使用volatile解决,在变量前加volatile关键字,可以保证每个线程对本地变量的访问和修改都是直接与主内存交互的,而不是与本线程的工作内存交互。但是Volatile关键字并不能保证线程安全,它只能保证当前线程需要该变量的值能够获得最新的值,而不能保证多个线程修改的安全性。

124.异步运算考虑使用Callable接口:

多线程应用有两种实现方式,一种是实现Runnable接口,另一种是继承Thread类,run方法没有返回值。不能抛出异常,使用Callable可以实现多线程任务, Executors 是静态工具类,提供异步执行器的创建能力,一般是异步计算的入口类,Future关注的是线程执行后的 结果。
public class Executors extends Object
工厂和工具方法Executor , ExecutorService , ScheduledExecutorService , ThreadFactory和Callable在此包中定义的类。 该类支持以下几种方法:
创建并返回一个ExecutorService设置的常用的配置设置的方法。
创建并返回一个ScheduledExecutorService的方法, 其中设置了常用的配置设置。
创建并返回“包装”ExecutorService的方法,通过使实现特定的方法无法访问来禁用重新配置。
创建并返回将新创建的线程设置为已知状态的ThreadFactory的方法。
创建并返回一个方法Callable出的其他闭包形式,这样他们就可以在需要的执行方法使用Callable 。

public interface FutureFuture
表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。
V get() 如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true。
boolean isDone() 如果任务已完成,则返回 true
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
class TaxCallabletor implements Callable<Integer>{
private int seedMoney;
TaxCallabletor(int seedMoney){
this.seedMoney = seedMoney;
}
@Override
public Integer call()throws Exception{
//TimeUnit 表示给定单元粒度的时间段,它提供在这些单元中进行跨单元转换和执行计时及延迟操作的实用工具方法。
TimeUnit.MICROSECONDS.sleep(10000);
return seedMoney/10;
}
}

public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//生成一个单线程的异步执行器,
ExecutorService es = Executors.newSingleThreadExecutor();
//线程执行后的期望值
//Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果
Future<Integer> future = es.submit(new TaxCallabletor(100));
while(!future.isDone()){
//还没有运算完成,等待200毫秒
TimeUnit.MICROSECONDS.sleep(200);
//输出进度符号
System.out.print("#");
}
System.out.println("计算完成。税金是:"+future.get()+"yun");
es.shutdown();
}
}

126,.适时选择不同的线程池来实现,java线程池实现从根本讲,ThreadPoolExecuto类和ScheduleThreadPoolException类。还是父子关系。

线程池的优点:
重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;
能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
能够多线程进行简单的管理,使线程的使用简单、高效。

线程池框架Executor
java中的线程池是通过Executor框架实现的,Executor 框架包括类:Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable和Future、FutureTask的使用等。
Executor: 所有线程池的接口,只有一个方法。

1
2
3
public interface Executor {        
void execute(Runnable command);
}

ExecutorService: 增加Executor的行为,是Executor实现类的最直接接口。
Executors: 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
ThreadPoolExecutor:线程池的具体实现类,一般用的各种线程池都是基于这个类实现的。
构造方法如下:

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

参数介绍:
corePoolSize:线程池的核心线程数,线程池中运行的线程数也永远不会超过 corePoolSize 个,默认情况下可以一直存活。可以通过设置allowCoreThreadTimeOut为True,此时 核心线程数就是0,此时keepAliveTime控制所有线程的超时时间。
maximumPoolSize:线程池允许的最大线程数;
keepAliveTime: 指的是空闲线程结束的超时时间;
unit :是一个枚举,表示 keepAliveTime 的单位;
workQueue:表示存放任务的BlockingQueue<Runnable队列。
BlockingQueue:阻塞队列(BlockingQueue)是java.util.concurrent下的主要用来控制线程同步的工具。如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒。同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。具体的实现类有LinkedBlockingQueue,ArrayBlockingQueued等。一般其内部的都是通过Lock和Condition(显示锁(Lock)及Condition的学习与使用)来实现阻塞和唤醒。

3.线程池的工作过程
线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
当调用 execute() 方法添加一个任务时,线程池会做如下判断:
如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
当一个线程完成任务时,它会从队列中取下一个任务来执行。
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

4.线程池的创建和使用
生成线程池采用了工具类Executors的静态方法,以下是几种常见的线程池。
1)SingleThreadExecutor:单个后台线程 (其缓冲队列是无界的)

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {        
return new FinalizableDelegatedExecutorService (
new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

创建一个单线程的线程池。这个线程池只有一个核心线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2)FixedThreadPool:只有核心线程的线程池,大小固定 (其缓冲队列是无界的) 。

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {         
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3)CachedThreadPool:无界线程池,可以进行自动线程回收。

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {         
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。SynchronousQueue是一个是缓冲区为1的阻塞队列。
4)ScheduledThreadPool:核心线程池固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

1
2
3
4
5
6
public static ExecutorService newScheduledThreadPool(int corePoolSize) {         
return new ScheduledThreadPool(corePoolSize,
Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}

5.线程池实现的原理
线程池的实现过程没有用到Synchronized关键字,用的都是volatile,Lock和同步(阻塞)队列,Atomic相关类,FutureTask等等,因为后者的性能更优。理解的过程可以很好的学习源码中并发控制的思想。
在ThreadPoolExecutor主要Worker类来控制线程的复用。看下Worker类简化后的代码,这样方便理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final class Worker implements Runnable {
final Thread thread;
Runnable firstTask;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}

final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
while (task != null || (task = getTask()) != null){
task.run();
}
}

Worker是一个Runnable,同时拥有一个thread,这个thread就是要开启的线程,在新建Worker对象时同时新建一个Thread对象,同时将Worker自己作为参数传入TThread,这样当Thread的start()方法调用时,运行的实际上是Worker的run()方法,接着到runWorker()中,有个while循环,一直从getTask()里得到Runnable对象,顺序执行。getTask()又是怎么得到Runnable对象的呢?

1
2
3
4
5
6
7
8
9
    private Runnable getTask() {
if(一些特殊情况) {
return null;
}

Runnable r = workQueue.take();

return r;
}

这个workQueue就是初始化ThreadPoolExecutor时存放任务的BlockingQueue队列,这个队列里的存放的都是将要执行的Runnable任务。因为BlockingQueue是个阻塞队列,BlockingQueue.take()得到如果是空,则进入等待状态直到BlockingQueue有新的对象被加入时唤醒阻塞的线程。所以一般情况Thread的run()方法就不会结束,而是不断执行从workQueue里的Runnable任务,这就达到了线程复用的原理了。

控制最大并发数:
那Runnable是什么时候放入workQueue?Worker又是什么时候创建,Worker里的Thread的又是什么时候调用start()开启新线程来执行Worker的run()方法的呢?有上面的分析看出Worker里的runWorker()执行任务时是一个接一个,串行进行的,那并发是怎么体现的呢?
很容易想到是在execute(Runnable runnable)时会做上面的一些任务。看下execute里是怎么做的。

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 void execute(Runnable command) {
if (command == null)
throw new NullPointerException();

int c = ctl.get();
// 当前线程数 < corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 直接启动新的线程。
if (addWorker(command, true))
return;
c = ctl.get();
}

// 活动线程数 >= corePoolSize
// runState为RUNNING && 队列未满
// workQueue.offer(command)表示添加到队列,如果添加成功返回true,否则返回false
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次检验是否为RUNNING状态
// 非RUNNING状态 则从workQueue中移除任务并拒绝
if (!isRunning(recheck) && remove(command))
reject(command);// 采用线程池指定的策略拒绝任务
// 两种情况:
// 1.非RUNNING状态拒绝新的任务
// 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
} else if (!addWorker(command, false))
reject(command);
}

根据代码再来看上面提到的线程池工作过程中的添加任务的情况:
如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
线程知识参考知乎:https://zhuanlan.zhihu.com/p/33725107;

127.Lock与Synchronized是不一样的

对于同步资源来讲,显示锁是对象级别的锁,而内部锁是类级别的锁,Lock定义为多线程类的私有属性是起不到资源互斥的作用的,除非包Lock定义为所以线程的共享变量。Lock是无阻塞锁,synchronized是阻塞锁,Lock可实现公平锁,synchronized只能为非公平锁,Lock是代码级的,synchronized是JVM级的。

128.预防线程死锁,

ThreadPoolExecutor提供了四个构造方法:
我们以最后一个构造方法(参数最多的那个),对其参数进行解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ThreadPoolExecutor(int corePoolSize, // 1
int maximumPoolSize, // 2
long keepAliveTime, // 3
TimeUnit unit, // 4
BlockingQueue<Runnable> workQueue, // 5
ThreadFactory threadFactory, // 6
RejectedExecutionHandler handler ) { //7
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

序号 名称 类型 含义

  • 1 corePoolSize int 核心线程池大小 2 maximumPoolSize int 最大线程池大小

  • 3 keepAliveTime long 线程最大空闲时间 4 unit TimeUnit 时间单位

  • 5 workQueue BlockingQueue 线程等待队列

  • 6 threadFactory ThreadFactory 线程创建工厂

  • 7 handler RejectedExecutionHandler 拒绝策略

如果对这些参数作用有疑惑的请看 ThreadPoolExecutor概述。
知道了各个参数的作用后,我们开始构造符合我们期待的线程池。首先看JDK给我们预定义的几种线程池:

一、预定义线程池

FixedThreadPool

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

corePoolSize与maximumPoolSize相等,即其线程全为核心线程,是一个固定大小的线程池,是其优势;
keepAliveTime = 0 该参数默认对核心线程无效,而FixedThreadPool全部为核心线程;
workQueue 为LinkedBlockingQueue(无界阻塞队列),队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。是其劣势;
FixedThreadPool的任务执行是无序的;

适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。

CachedThreadPool

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制;
keepAliveTime = 60s,线程空闲60s后自动结束。 workQueue 为 SynchronousQueue
同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue;

适用场景:快速处理大量耗时较短的任务,如Netty的NIO接受请求时,可使用CachedThreadPool。

SingleThreadExecutor

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

dome来解释一下:

1
2
3
4
5
6
7
8
9
10
 public static void main(String[] args) {
ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
System.out.println(threadPoolExecutor.getMaximumPoolSize());
threadPoolExecutor.setCorePoolSize(8);

ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
// 运行时异常 java.lang.ClassCastException
// ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) singleExecutorService;
}

对比可以看出,FixedThreadPool可以向下转型为ThreadPoolExecutor,并对其线程池进行配置,而SingleThreadExecutor被包装后,无法成功向下转型。因此,SingleThreadExecutor被定以后,无法修改,做到了真正的Single。

ScheduledThreadPool

1
2
3
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

newScheduledThreadPool调用的是ScheduledThreadPoolExecutor的构造方法,而ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,构造是还是调用了其父类的构造方法。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

二、自定义线程池

以下是自定义线程池,使用了有界队列,自定义ThreadFactory和拒绝策略的demo:

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

```java
public class ThreadTest {

public static void main(String[] args) throws InterruptedException, IOException {
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
ThreadFactory threadFactory = new NameTreadFactory();
RejectedExecutionHandler handler = new MyIgnorePolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
workQueue, threadFactory, handler);
executor.prestartAllCoreThreads(); // 预启动所有核心线程

for (int i = 1; i <= 10; i++) {
MyTask task = new MyTask(String.valueOf(i));
executor.execute(task);
}

System.in.read(); //阻塞主线程
}

static class NameTreadFactory implements ThreadFactory {

private final AtomicInteger mThreadNum = new AtomicInteger(1);

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
System.out.println(t.getName() + " has been created");
return t;
}
}

public static class MyIgnorePolicy implements RejectedExecutionHandler {

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
doLog(r, e);
}

private void doLog(Runnable r, ThreadPoolExecutor e) {
// 可做日志记录等
System.err.println( r.toString() + " rejected");
// System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
}
}

static class MyTask implements Runnable {
private String name;

public MyTask(String name) {
this.name = name;
}

@Override
public void run() {
try {
System.out.println(this.toString() + " is running!");
Thread.sleep(3000); //让任务执行慢点
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public String getName() {
return name;
}

@Override
public String toString() {
return "MyTask [name=" + name + "]";
}
}
}

嗯,好累,因为要准备一些考试,这本书就刷到这里啦!哎,小秘密被发现的感觉真不爽。加油生活!!^ _ ^ !!

2019.3.17

发布于

2018-11-22

更新于

2023-06-21

许可协议

评论
加载中,最新评论有1分钟缓存...
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×