0815实习培训随记

Smile_slime_47

魔鬼数字

用有意义的常量代替字面量数字

  • 便于理解和替换

常用枚举类型代替枚举含义的变量

  • 典型的反例是C中习惯用return 0表示正确执行

对于局部的字面量可以只用注释说明,如单位换算没必要用常量替换10/100/1000等

反例

  • int ONE=1

不推荐但允许的情况

  • int HOUR_12 = 12 //12小时制

并发工具

单例模式

1
2
3
4
5
6
7
8
public class Single {
private static Single inst = new Single();
public static Single getInstance() {
return inst;
}
private Single() {
}
}

lazyinit

1
2
3
4
5
6
7
8
9
10
11
public class Single {
private static Single inst = null;
public static synchronized Single getInstance() {
if (inst == null) {
inst = new Single();
}
return inst;
}
private Single() {
}
}

getInstance整个方法都同步,实际应用中抢占并初始化单例对象并非常见情况,这么做会严重影响性能

DoubleCheck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Single {
private static volatile Single inst = null;//可见性和重排序:保证所有线程共享同一个类,而非各自的副本
public synchronized Single getInstance() {
if (inst == null) {
synchronized (Single.class) {
if (inst == null) {
inst = new Single();
}
}
}
return inst;
}
private Single() {
}
}

只锁静态类,保证了日常调用的性能,同时doublecheckinst == null保证了不会初始化多个对象

利用classLoader的特性(更推荐)

1
2
3
4
5
6
7
8
9
public class Single {
private static class SingleHolder {
static final Single foo = new Single();
}

public static Single getInstance() {
return SingleHolder.foo;
}
}

利用类加载的特性,只有在调用getInstance的时候才会加载SingleHolder内部类,同样实现了lazyinit的效果

验证Volatiole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StopThread {
//如果取消volatile的话,下面的线程就会不能正常中止
private static volatile boolean stopRequested;

public static void main(String[] args) {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested) {
i++;
}
});
backgroundThread.start();

TimeUnit.SECONDS.sleep(1);
stopRequested=true;
System.out.println("after set stopRequested");
}
}

基本型偏执

1
2
3
4
5
6
7
8
9
public int deposit(int money)

public class Money{
private int yuan;
public Money(int yuan){
this.yuan=yuan;
}
}
public int deposit(Money money)
  • 便于理解
  • 编译器的语义检查
  • 提供了对Money的行为放置场所
  • 便于扩展,例如精度修改等

线程不安全的对象

DateFormat等不一定是线程安全的

位运算

有符号右移:>>

无符号右移:>>> 负数补0而非1

1
2
3
4
byte i = (byte) 0xf3;   //1111 0011
byte j = (byte) (i>>2); //??11 1100
byte k = (byte) (i>>2); //??11 1100
System.out.print("%x %x",j,k)//fc fc

对于>>>运算左边如果是byte/char/short,会先转换为int

1
2
3
4
byte i = (byte) 0xf3;               //1111 0011
byte j = (byte) ((i & 0xff) >> 2); //??11 1100
byte k = (byte) ((i & 0xff) >>> 2); //??11 1100
System.out.println(String.format("%x %x", j, k));//3c 3c

非短路运算

短路与:&&

位与运算:&

常量右置:左侧倾向于变化、右侧倾向于不变

  • 在C中会采用常量左置,如if(null == a),此时如果写成=编译器报警
  • Java中的例外"foo".equals(var),反之则需要if(var != null)...

静态成员类

非静态成员

  • 可以访问外围类的成员,等效于外围类的一个非静态方法
  • 必须依附于外围类的实例对象
  • 需要维护和实例对象的关联关系

静态成员(更加常用)

  • 只能访问静态成员
  • 不需要维护关联关系,少了空间和时间开销

equals覆写

覆写equals时也应当覆写hashcode

Hash集合依赖于hashCode决定索引和返回

  • 保证equals的对象,hashcode必须相等
  • 否则会出现找不到值的情况

其中equals的原型为:

1
public boolean equals(Object o)

重写HashCode

  1. 把某个非零常数保存在一个叫result的的变量(通常是质数,如17)
  2. 对于每一个成员字段,完成以下步骤
  • result = 31 * result +c
  • boolean f?0:1
  • byte/char/short/int (int)f
  • long (int)(f^(f>>>32))
  • float Float.floatToIntBits(f)
  • double Double.doubleToIntBit

Locale

对于不同语言的大小写可能有不同规则,要指定Locale转换方式

调用toUpperCase()时,默认调用toUpperCase(Locale.getDefault())

例如String name=aquickbrownfoxjumpoverthelazydog

应当调用name.toUpperCase(Locale.ENGLISH)

size

  • byte - 1字节
  • char - 2字节
  • String - char串而非byte串,二进制协议编解码时应当注意

常量池

Integer a = -128Integer a=Integer.valueOf(-128)都会应用常量池

只有new出来的会在堆里创建新对象

永远用valueOf和equals进行包装类的创建和比较

不要对包装类使用synchronized,否则会因为常量池意外获得同一把锁

Long/Short

  • -128~127

Byte/Boolean

  • 全部缓存

Float/Double

  • 全部不缓存

String.intern()

  • 永远返回常量池的String对象
  • 当且仅当s.equals(t)时,有s.intern()==t.intern()

异常关闭

在fianlly中关闭资源

  • close某个资源时如果close方法本身也抛异常,如果不捕获会导致后面的资源无法关闭

建议使用try-with-resource写法

  • 多个资源抛出异常时会保留第一个异常,并将后续异常作为Supressed Exceptions,可以通过getSuppressed()捕获
1
2
3
4
5
6
7
8
try(...){
...
} catch(Exception e) {
e.printStackTrace();
for(Throwable t:e.getSuppressed()) {
t.printStackTrace();
}
}

暴露和拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
public void getA(){
return A//暴露了内部成员
}
public void setA(A a){
this.A=a//绑定了外部引用,可能会意外改变外部对象
}

public void getA(){
return A.clone()
}
public void setA(A a){
this.A=a.clone()
}

根据实际情况设计采用暴露还是拷贝

substring在Java7之后变为深拷贝

引用和指针

Java中的引用在行为上更接近C中的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args){
B b = new B();
func(b);
}

void func(B b){
...
b= b2;//不会影响main中的b
}

void func(B *b){
...
b= b2;//不会影响main中的b
}

void func(B &b){
...
b= b2;//会同步影响main中的b,实际上相当于b.set(b2)
}

类的访问修饰符

  • public > protected > default > private
  • protected除内部文件的类可见外,外部文件的子类也可见
  • default仅有内部文件的类可见

对于同一个package的default类,他们互为友元,可以让同一个package内的类互相访问private对象

  • default打通了package的类
  • protected打通了继承树的类

暴露增加了修改的困难程度,暴露n个方法就会被外界调用n个方法,想要修改逻辑就要同时考虑n个对外的接口

多态的优先级

不建议利用如下优先级

  1. this.func(args)
  2. super.func(args)
  3. this.func((super)args)
  4. super.func((super)args)

建议多使用@Override增加可读性

多态的设计模式——策略模式

SAP Breaker——破坏软件抽象

最稳定的类/被依赖的类应当是最抽象的类,反之亦然

  • 依赖倒置

业务->基础设施

业务->基础设施接口<-基础设施

正方形悖论

正方形 is a 长方形

SOLID原则

  • SIngle Responsibility Principle 单一职责
  • Open-Closed Principle 开闭原则
    • 只是一个期望,往往难以实现
  • Liskov Substitution Principle 里氏替换原则
    • 最重要的,不可违背
  • Interface Segregation Principle 接口隔离原则
  • Dependency Inversion Principle 依赖倒置原则
Comments