标签:语句 for循环 同步方法 ... uil 对象创建 类型检查 进阶 坚持
public static final int[] VALUES={...} //错误
//正确1:增加一个公有的不可变列表
private static final int[] VALUES=...
public static final List< intergeR > VALUES=
{Collections.unmodifiableList(Arrays.adList(PRIVATE_VALUES));
//正确2:返回私有数组的拷贝
private static final int[] VALUES=...
public static final int[] values() {
return VALUES.clone();
}
public class Point{ //错误
public int x;
public int y;
}
public class Point{ //正确
private int x;
private int y;
public int getX() { return x;}
}
结合上面说到的,HashSet是implement Set类的,在HashSet里重写了Set接口定义的add,addAll等方法。因此新的子类继承Hashset重写add、addAll就不可避免会将HashSet里的实现继承下来。
使用装饰者模式:ForwardingSet implements Set,该类有成员private final Set s s,构造器里就是传入一个Set ,该类不具体实现Set的任何方法,例如:
public boolean add(E e) {
return s.add(e);
}
InstrumentedSet extends ForwardingSet,构造器super父类即可,在这个类里添加一些功能,例如:
@Override
public boolean add(E e){
count++;
return super.add(e);
}
这种模式下,InstrumentedSet 只是一个包装类,只是对其成员Set进行修饰,为它增加计数特性。包装类并不实现具体功能,构造器里传入的就是实现具体功能的Set,可以是HaseSet或者自己实现的Set。
另可参考阅读:
Android源码学习之装饰模式应用
public class Son extends Father {
public Son() {
// super(); //没加默认调用父类无参构造方法
super("from son");
Log.e("zyz", "son-constructor");
}
public Son(String str) {
// super(); //没加默认调用父类无参构造方法
Log.e("zyz", str + " son-constructor-with-params");
}
@Override
public void print() {
Log.e("zyz", "son-print");
}
}
public class Son extends Father {
public Son() {
// super(); //没加默认调用父类无参构造方法
super("from son");
Log.e("zyz", "son-constructor");
}
public Son(String str) {
// super(); //没加默认调用父类无参构造方法
Log.e("zyz", str + " son-constructor-with-params");
}
@Override
public void print() {
Log.e("zyz", "son-print");
}
}
抽象类可以写实例方法,通过派生继承,实现代码复用(子类可直接调用父类方法),但由于重用方法增加了耦合度,接口的方法一定需要重写,最大程度实现了解耦。
标签类:
例如使用枚举或常量定义了圆和矩形,成员里有半径、长、宽。在公共方法 计算面积里,使用switch来判断是那种形状,再分别计算。类似的把多个实现乱七八糟地挤在单个类中,破坏可读性,又增加了内存占用,因为实例承担着属于其他类型的域。
应该使用类层次来优化:
定义一个抽象类,包含抽象方法:将共有的方法(计算面积),如果有公有的成员还可以将其放在抽象类中。之后不同的类圆和矩形继承公共抽象类,另外添加自己的参数,并重写自己的计算面积的方法。
如果成员类不要求访问外围实例,就要定义成静态内部类。非静态内部类始终要保持外围对象的引用,不仅消耗内存,还将导致外围实例无法被垃圾回收。
例如Map实现的内部都有Entry对象,每个Entry都与Map关联,但是entry的方法(getKey/getValue)等并不需要访问Map,因此私有的静态成员类是最佳的选择。
- 如果一个嵌套类需要在单个方法之外可见,或者它太长了不适合放在方法内部,就使用成员类。
- 如果成员类的每个实例都需要一个指向外围实例的应用,就使用非静态成员类。否则就使用静态成员类。
- 如果嵌套类属于一个方法的内部,且你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就使用匿名类。否则就使用局部类。
二者的不同点:
如果B是A的子类,那么B[]就是A[]的子类型。
//编译时不报错,运行时报错ArrayStoreException
Object[] test = new Long[1];
test[0] = "test";
而两个不同的类型A、B,List既不是List的子类也不是超类。
List<Object> test2 = new ArrayList<Long>(); //编译时报错
test2.add("123");
数组在运行时才知道并检查他们的元素类型约束。泛型则是通过擦除(erasure)来实现的。泛型只在编译时强化类型信息,在运行时擦除元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意互用。
PECE producer-extends,consumer-siper
如果参数化类型表示生产者T,就使用
//src产生E实例供使用,是生产者
public void pushAll(Iterable<? extands E> src) {
for (E e : src) push(e);
}
//dst消费E实例,是消费者
public void popAll(Collection<E> dst) {
while(!isEmpty()) {
dst.add(pop());
}
}
不要用通配符类型作为返回参数
(android不推荐使用enum)
public enum Test {
APPLE("test1", 2),
pen("test2", 1);
private final String name;
private final int num;
Test(String name, int num) {
this.name = name;
this.num = num;
}
public void print() {
Log.e("zyz", APPLE.name + APPLE.num);
}
}
//遍历枚举
Test[] values = Test.values();
@Retention(RetentionPolicy.RUNTIME) //运行时保留
@Target(ElementType.METHOD) //只在方法声明中才是合适的
public @interface MyTest {
}
覆盖equals时的参数是Object类型的,否则则变成了重载。但如果使用@Override注解后写错了编译器就会报错。
另外需要修改访问方法,返回可变内部域的保护性拷贝:
public Data end() {
return new Data(end.getTime());
}
类型还是父类,虽然调用父类方法指向子类引用。
安全而保守的策略是:永远不要导出两个具有相同参数数目的重载方法。如果方法使用可变参数,保守的策略是根本不要重载它。
如果客户端调用这个方法时并没有传递参数进去,它就会在运行时而不是编译时失败。
//带两个参数,避免没有传参导致的问题
static init min(int firstArg, int... remainingArgs) {
int min = firstArg;
for(int arg : remainingArgs) {
...
}
}
伪随机数生成器
//错误
Math.abs(new Random().nextInt());
//正确
Random.nextInt(int)
了解和使用标准类库提供的便利工具,而不用浪费时间为那些与工作不太相关的问题提供特别的解决方案。标准类库太庞大了,以至于不可能去学习所有文档,但是每个程序员都应该熟悉java.lang,java.util,某种程度上还有java.io种的内容。有两种工具值得特别一提。
总而言之,不要重新发明轮子,如果你要做的事情看起来是十分常见的,有可能类库中已经有某个类完成了这样的工作。
float和double类型尤其不适合用于货币计算,因为要让一个float或double精确地表示0.1(或者10的ren’he’qi’ta任何其他负数次方值)是不可能的。
使用BigDecimal代替double:
BigDecimal bigDecimal = new BigDecimal(0.1);
BigDecimal允许你完全控制舍入,每当一个操作设计舍入的时候,它允许你从8种舍入模式中选择其一。但是缺点是与基本运算类型比,不仅不方便,而且很慢。如果性能非常关键,并且又不介意自己记录是金子小数点,而且涉及的数值又不太大,就可以使用int或long(例如0.1改变单位计作10)。如果数值范围没超过9位十进制数字,就可以使用int。如果不超过18位数值,就可以使用long。如果数值超过18位数字,就必须使用BigDecimal。
当程序装箱了基本类型值时,会导致高开销和不必要的对象创建。
连接操作符不适合运用在大规模的场景中,为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。这是由于字符串不可变,当两个字符串被连接在一起时,它们的内容都要被拷贝。
使用StringBuilder:
StringBuilder test = new StringBuilder("test");
test.append("test2")
如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明。只有当你利用构造器创建某个对象的时候,才真正需要引用这个对象的类。
List<String> list = new ArrayList<>();
这样会使程序更灵活,当你决定更换实现时,只需要改变构造器中类的名称:
List<String> list = new Vector<>();
所有的代码都可以继续工作,代码并不知道原来的实现类型,所以对于这种变化并不在意。
反射机制允许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在,然而这种能力也是要付出代价的:
//Dont‘t do this
try {
int i = 0;
while (true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
}
不要优先使用基于异常的模式:
一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性(failure atomic)。有几种途径可以实现这种效果:
忽略一个异常非常容易,只需将方法调用通过try语句包围起来,并包含一个空的catch块。空的catch块会使异常达不到应有的目的,至少,catch块也应该包含一条说明,解释为什么可以忽略这个异常。
正确地使用同步可以保证没有任何方法会看到对象处于不一致的状态中。它还可以保证刚进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护的之前所有的修改效果。换句话说,读取一个非long或double类型的变量,可以保证返回的值是某个线程保存在该变量中的,即使多个线程在没有同步的情况下并发地修改这个变量也是如此。
不要使用 Thread.stop方法。要阻止一个线程妨碍另一个线程,建议做法是让第一个线程轮训一个boolean域,这个域一开始为false,但是可以通过第二个线程设置为true,以表示第一个线程将终止自己。由于boolean域的读写操作都是原子的,程序员在访问这个域的时候不再使用同步。
实际上,如果读和写操作没有都被同步,同步就不会起作用。
如果变量修饰符是volatile,则读取变量时不需要锁,虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值。
使用volatile的时候务必要小心。
//错误
private static volatile int number = 0;
//需要使用synchronization
public static int getNumber() {
return number++;
}
虽然number是原子的,但是增量操作符不是原子的,它首先读取值,然后写回一个新值。如果第二个线程在第一个线程读取旧值和返回新值期间读取这个域就会出错。
在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客户端以函数对象的形式提供的方法。这样的方法是外来的,这个类不知道方法会做什么事情,也无法控制它,从同步区域中调用它很可能会导致异常、死锁或者数据损坏。
通常,你应该在同步区域内做尽可能少的工作。如果你必须要执行某个很耗时的动作,应该设法把这个动作移到同步区域的外面。
Java1.5增加了java.util.concurrent,这个包中包含了一个Executor Framework:
ExecutorService executorService = Executors.newSingleThreadExecutor();
//执行提交一个runnable方法
executorService.execute(runnable);
//告诉executor如何优雅地终止
executor.shutdonw();
你可以利用executor service完成更多的事情。例如,可以等待一个任务集合中的任何任务或所有任务完成(invokeAny或invokeAll),你可以等待executor service优雅地完成终止(awaitTermination),可以在任务完成时逐个地获取这些任务的结果(ExecutorCompletionService)等。
自从java1.5发型版本开始,java就提供了更高级的并发工具,他们可以完成以前必须在wait和notify上手写代码来完成的各项工作。其分成三类:
同步器(Synchronizer)是一些使线程能够等待另一个线程的对象,允许他们协调动作。最常用的同步器是CountDownLatch和Semaphore。
倒计数锁存器(CountDown Latch)是一次性的障碍,允许一个或者多个线程等待一个或者多个其他线程来做某些事情。CountDownLatch是唯一构造器带有一个int类型的参数,这个int参数是指允许所有在等待的线程被处理之前,必须在锁存器上调用countDown方法的次数。
例如:一个方法带有一个执行该动作的executor,一个并发级别(表示要并发执行该动作的次数),以及表示该动作的runnable。所有的工作线程自身都准备好,要在time线程启动时钟之前运行该动作(为了实现准确的定时)。当最后一个工作线程准备好运行该动作时,timer线程就“发起头炮”,同事允许工作线程执行该动作,一旦最后一个工作线程执行完该动作,timer线程就立即停止计时。直接在wait和notify上实现这个逻辑至少来说会很混乱,而在CountDownLatch之上实现则相当简单:
public long getTime(Executor executor, int councurrency, final Runnable action) throws InterruptedException {
final CountDownLatch ready = new CountDownLatch(councurrency);
final CountDownLatch start = new CountDownLatch(1);
final CountDownLatch done = new CountDownLatch(councurrency);
for (int i = 0; i < councurrency; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
ready.countDown();
try {
start.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
done.countDown();
}
}
});
}
ready.await();
long startNano = System.nanoTime();
start.countDown();
done.await();
return System.nanoTime() - startNano;
}
用ready来告诉timer线程他们已经准备好了。然后工作线程会在start上等待。当最后一个工作线程调用ready.countDown时,timer线程记录下起始时间,并调用start.countDown,允许所有的工作线程继续进行。然后timer线程在done上等待,直到最后一个工作线程运行完该动作,并调用donw.countDown。一旦调用这个,timer线程就会苏醒过来,并记录下结束时间。
wait方法的标准模式:
synchronized(obj) {
while() {
obj.wait(); //release lock, and reacquires on wakeup
}
}
始终应该使用wait循环模式来调用wait方法;永远不要在循环之外调用wait方法。循环会在等待之前和之后测试条件。
线程安全性的几种级别。(这份列表并没有涵盖所有的可能,而只是些常见的情形:
//私有锁对象
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
...
}
}
私有锁对象模式只能用在无条件的线程安全类上。有条件的线程安全类不能使用这种模式,因为它们必须在文档中说明:在执行某些方法调用序列时,它们的客户端程序必须获得哪把锁。
私有锁对象模式特别适用于那些专门为继承而设计的类。如果这种类使用它的实例作为锁对象,之类可能很容易在无意中妨碍基类的操作,反之亦然,出于不同的目的而使用相同的锁,子类和基类很可能会“互相绊住对方的脚”。
有条件的线程安全类必须在文档中指明哪些方法调用序列需要外部同步,以及在执行这些序列的时候需要获得哪把锁。如果你编写的是无条件的线程安全类,就应该考虑使用私有锁对象来代替同步的方法以防止客户端程序和子类的不同步干扰。
如果处于性能的考虑需要对静态域使用延迟初始化:
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldHolder getField() {
如果处于性能的考虑需要对实例域使用延迟初始化:
private volatile FieldType field;
FieldTpye getField() {
FieldType result = field;
if(result == null) { //First check(no locking)
synchronized (this) {
result = field;
if(result == null) //Second check(with locking)
field = result = computeFieldValue();
}
}
return result;
}
如果需要延迟初始化一个可以接受重复初始化的实例域:
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if(result == null) {
field = result = computeFiedlValue();
}
return result;
}
线程不应该一直处于忙-等状态,即反复地检查一个共享对象,以等待某些事情的发生。
不要让应用程序的正确性依赖于线程调度器,否则结果得到的应用程序将既不健壮,也不具有可移植性。不要依赖Thread.yield或者线程优先级。线程优先级可以用来提高一个已经能够正常工作的程序的服务质量,但永远不应该用来“修正”一个原本能不能工作的程序。
实现Serializable接口而付出的巨大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。
如果一个类实现了Serializable接口,它的字节流编码(序列化形式)就变成了它的导出的API的一部分,一旦这个类被广泛使用,往往必须永远支持这种序列化形式。
第二个代价是,它增加了出现bug和安全漏洞的可能性。你可能会忘记确保:反序列化过程必须也要保证所有“由真正的构造器建立起来的约束关系”,并且不允许攻击者访问正在构造过程中的对象的内部信息。
第三个代价是,随着类发行新的版本,相关的测试负担也增加了。可序列化的类被修订后,你必须既要确保“序列化-反序列化”过程成功,也要确保结果产生的对象真正是原始对象的复制品。
内部类不应该实现Serializable。
如果一个类为了继承而设计,要更加小心。对于这样的类而言,在“允许子类实现Serializable接口”或者“禁止子类实现serialzable”两者间的一个折衷方案是:提供一个可访问的无参构造器,这种方案允许(但不要求)子类实现Serializable接口。
标签:语句 for循环 同步方法 ... uil 对象创建 类型检查 进阶 坚持
原文地址:http://blog.csdn.net/yazhi1992/article/details/54767094