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里的实现继承下来。html
使用装饰者模式:ForwardingSet implements Set,该类有成员private final Set s s,构造器里就是传入一个Set ,该类不具体实现Set的任何方法,例如:java
public boolean add(E e) {
return s.add(e);
}
复制代码
InstrumentedSet extends ForwardingSet,构造器super父类便可,在这个类里添加一些功能,例如:android
@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,所以私有的静态成员类是最佳的选择。bash
两者的不一样点:数据结构
若是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,就使用<? extends T>,若是表示消费者T,就使用<? super 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注解后写错了编译器就会报错。
若是类的成员是可变的,为了保护内部信息变化,对于构造器的每一个可变can'shu参数进行保护性拷贝是必要的,使用被封对象做为实例的组件,而不使用原始的对象。但注意,保护性拷贝是在检查参数的有效性以前进行的,而且有效性检查是针对拷贝以后的对象而不是原始对象。
慎用clone。若是对于非final的成员,不能保证clone方法必定返回一样的类的对象,它有可能返回专门出于恶意目的而设计的不可信子类的实例,例如这样的子类能够在每一个实例被建立时把指向该实例的引用记录到一个私有的静态列表中,而且容许攻击者访问这个列表,这将使得攻击者能够自由地控制全部的实例。为了阻止这种攻击,对于参数类型能够被不可信任方子类话的参数,请不要使用clone方法进行保护性拷贝。
另外须要修改访问方法,返回可变内部域的保护性拷贝:
public Data end() {
return new Data(end.getTime());
}
复制代码
只要可能,都应该使用不可变的对象做为对象内部的组件,这样就没必要再为保护型拷贝操心。
类型仍是父类,虽然调用父类方法指向子类引用。
安全而保守的策略是:永远不要导出两个具备相同参数数目的重载方法。若是方法使用可变参数,保守的策略是根本不要重载它。
若是客户端调用这个方法时并无传递参数进去,它就会在运行时而不是编译时失败。
//带两个参数,避免没有传参致使的问题
static init min(int firstArg, int... remainingArgs) {
int min = firstArg;
for(int arg : remainingArgs) {
...
}
}
复制代码
在重视性能的状况下,使用可变参数要特别小型,可变参数方法的每次调用都会致使进行一次数组分配和初始化。可使用多个重载方法,每一个重载方法带有0至3个普通参数,当参数数目超过3个时,就使用可变参数方法。
伪随机数生成器
//错误
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接口。