摘要:如果采用抽象类,则属性组合可能导致子类的组合爆炸。内部类的设计静态成员类用修饰的内部类,可以不依赖于外部实例进行创建。如下所示其构造函数默认是,并且无法修改。
对所有对象都通用的方法 equals和hashCode方法的关系
重写equals方法必须也要重写hashCode方法。
equals用的属性没变,则多次调用hashCode返回值也必须保持不变。
equals比较相等的对象,hashCode也必须相同。反之不然。
所处相同hash bucket的对象,hashCode可能不同,因为在求解bucket位置时会对hashCode进行截断,根据bucket大小采用后段值。
clone方法的设计原则Cloneable接口并不提供接口方法clone,clone是Object类实现的基本方法。
实现Cloneable接口的类应该提供一个public的clone函数覆盖原来protect的方法。
clone方法首先调用super的clone方法,然后再处理需要深层拷贝的内部属性。
道行不深不要使用该接口。
Comparable接口的设计原则不可变对象线程安全,可以被自由的共享。不可变类不应该提供clone和拷贝构造器,直接共享最好,但是String还是提供了拷贝构造器。
不可变类也有一定的劣势,因为一个操作可能涉及多个临时的不可变类,而导致大量对象的创建和销毁,所以此时应该采用不可变类配套的可变类。如String类对应的StringBuilder。
应该提供尽可能小的可变状态。
复合优先于继承继承会破坏封装性,实现继承需要对父类的实现进行充分的了解。所以父类和子类应该实现在同一个包内,由同一个开发团队维护。
一个类在设计时应该明确指明是不是为了可被继承而设计的。
一个类如果没有考虑自己可能被继承,有些方法可能会被重写,则其内部调用这些可被重写方法的方法就可能会出现意想不到的异常行为。继承该方法的子类会调用父类的内部方法,而父类内部方法的更新可能会导致子类的异常。
接口优于抽象类现有的类可以很容易的加入新的接口,因为接口可以实现多个。
类只允许有一个父类,如果用抽象类描述共性,则需要该共性的类必须为该抽象类的后代,即使这些子类并没有很显然的关系。
接口可以让我们实现非层次结构的类框架。如果采用抽象类,则属性组合可能导致子类的组合爆炸。
接口的缺点是扩展新方法时,所有实现该接口的类都要重新添加该方法,而抽象类可以提供默认实现。不过,现在Java8提供了default描述默认实现方法,似乎这种弊端可以避免。
接口中定义属性接口中定义的属性默认为final static。
最好不要用接口来定义属性,因为实现该接口的类会引入这些属性,造成类的命名空间污染。接口应该用来描述类可以执行的动作。
内部类的设计静态成员类:用static修饰的内部类,可以不依赖于外部实例进行创建。
非静态成员类:创建时依赖于外部类的实例,并且创建后与外部实例绑定。无静态属性。
匿名内部类:作为参数,只会被实例化一次。和非静态成员类似。在非静态环境中,会与外部类的实例绑定。
局部类:声明局部变量的地方可以声明局部类,它有名字,可以在局部被多次实例化。在非静态环境中,会与外部类的实例绑定。
泛型 不要在代码中使用原生类型如下代码使用原生类型:
ArrayList a=new ArrayList(); a.add(new Object());
以上代码编译和运行都可以通过,但是埋下了很多隐患。
List
List中add任何对象都对。List>的变量可以引用任何参数化(非参数也可以)的List,但是无法通过该变量添加非null元素。
假设Men extends Person, Boy extends Men:
extends T>表示上界,<? super T>表示下界。
ArrayList extends Men> ml=new ArrayList
ArrayList super Men> ml=new ArrayList
总结2和3条,可知 extends T>和<? super T>是对等号右边实参数化ArrayList的限制,而不是对ArrayList中可存入元素的描述。因为从引用ml中无法得知其实际指向的是那种参数化的ArrayList实例,所以再往其中添加元素时会采用最谨慎的选择。
列表和数组的区别数组是协变的,也就是Fruit[] fs= new Apple[5];是合法的,因为Apple是Fruit的子类,则数组也成父子关系,而列表则不适用于该规则。数组的这种关系容易引发错误,如fs[0]= new Banana(),编译时没错,这在运行时报错。
创建泛型数组是非法的,如new E[]; new List
如下代码在编译时不会出错,在运行时出错java.lang.ClassCastException。
ArrayListlist=new ArrayList (); for (int i = 0; i < 10; i++) { list.add(""+i); } //该行报错 String[] array= (String[]) list.toArray(); }
原因很迷,toArray返回的是Object[]数组,但是不能强制转化为String[],明明元素实际类型是String。有的解释说,在运行时只有List的概念,而没有List
泛型是在整个类上采用泛型,这样可以在类内部方便的使用泛型参数。泛型方法是更精细的利用参数类型,将泛型参数设定在每个方法上。
比较下面两个接口,体会其中不同:
public interface Comparable递归类型限制{ public int compareTo(T o); } public interface Comparable2 { public int compareTo2(T o); } public class Apple implements Comparable , Comparable2{ @Override public int compareTo(Apple o) { return 0; } @Override public int compareTo2(T o) { //T 可以为任何类,所以Apple可以和任何类比较 return 0; } }
有类:
class Apple implements Comparable{ } class RedApple extends Apple{ }
有方法:
public static> T get(T t){ return t; }
该方法就采用了递归的类型限制,因为泛型T被限制为 Comparable
RedApple ra=new RedApple(); Apple a= get(ra); //正确 RedApple b=get(ra); //错误
原因是在调用泛型函数时,会自动进行类型推断,第一个get函数根据左边参数,推断T为Apple,符合条件。在第二个get公式中,推断T为RedApple,不符合get函数的泛型限制条件。
一个比较复杂的泛型函数public static> T max(List extends T> list)
其中
一个容器如Set只有1个类型参数,Map只有2个类型参数,但是有时候需要多个类型参数。下面是一个设计巧妙、可以容纳多个类型参数的类。
public static void main(String[] args){ Favorites f =new Favorites(); f.putFavorite(String.class, "Java"); f.putFavorite(Integer.class, 1111); f.putFavorite(Class.class, Favorites.class); int fi=f.getFavorite(Integer.class); } public class Favorites{ private Map, Object> favorites=new HashMap , Object>(); public void putFavorite(Class type, T instance){ if(type==null) throw new NullPointerException("Type is null"); favorites.put(type, instance); } public T getFavorite(Class type){ return type.cast(favorites.get(type)); } }
String.class为Class
其中Map实例favorites并没有限制value一定是key描述的类的实例,而方法putFavorite通过类型参数T,巧妙的限制了两者的关系。
枚举和注解 枚举类型举例枚举类型更像是一个不能new的类,只能在定义时就实例化好需要的固定数目的实例。如下所示:
public enum Planet { VENUS(2), EARTH(3), MARS(5); int data; Planet(int i){ this.data=i; } public int getData(){ return data; } }
其构造函数默认是private,并且无法修改。在枚举中还可以定义抽象方法,如下:
public enum Operation{ PLUS { double apply(double x, double y){return x+y;}}, MINUS { double apply(double x, double y){return x-y}}; abstract double apply(double x, double y); }自定义注解的例子
定义一个用来测试方法是否能抛出目标异常的注解。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ExceptionTest{ Class extends Exception>[] value(); }
元注解指明了该注解在运行时保留,并且只适用于注解方法。
使用如下:
@ExpectionTest({IndexOutOfBoundsException.class, NullPointerException.class}) public static void doublyBad(){ Listlist=new ArrayList (); //该方法会抛出IndexOutOfBoundsException list.addAll(5,null); }
测试过程实现如下:
public static void main(String[] args) throws Exception{ int tests=0; int passed=0; Class testClass=Class.forName(args[0]); for(Method m : testClass.getDeclaredMethods()){ if(m.isAnnotationPresent(ExceptionTest.class)){ tests++; try{ m.invoke(null); System.out.printf("Test failed: no exceptions"); }catch( Throwable wrappedExc){ Throwable exc = wrappedExc.getCause(); Class extends Exception>[] excTypes=m.getAnnotation(ExceptionText.class).value(); int oldPaassed=passed; for(Class extends Exception> excType:excTypes){ if(excType.isInstance(exc)){ passed++; break; } } if(passed==oldPassed) System.out.printf("Test failed"); } } } }方法 必要时进行保护性拷贝
构造函数在接受外来参数时,必要时需要拷贝参数对象,而不是直接将参数对象赋值给自己的属性。因为直接采用外部传入的对象,外部可以任意的修改这些对象,从而导致你自己设计的类内部属性变化。如果不想让使用你设计的类的客户有修改其内部属性的权利,除了设置为private外,还应该注意采用拷贝的方式使用外部传入的数据。选择copy而不是clone,是因为传入对象可能是客户定制过的参数子类,该子类仍然可能将其暴露在外面。
需要保护内部属性不被修改,除了关注构造函数的参数,还需要关注get类似的方法。这些返回内部属性的方法,应该返回拷贝过的属性对象。
慎用重载重载方法是静态的,在编译时就已经选择好,根据参数的表面类型,如Collection
而方法的重写选择时动态的,在运行时根据调用者的实际类型决定哪个方法被调用。
慎用可变参数可变参数可以让用户灵活的填入不同数量的参数,但是该方法本质上是将参数组织成数组,所以每次调用这些方法时都会涉及数组的创建和销毁,开销较大。
并发 同步的意义保证数据从一个一致状态转一到另一个一致状态,任何时候读取该数据都是一致的。
保证对数据的修改,其它线程立即可见。
读写变量是原子性的除了double和long以外,读写变量是原子性的。但是Java无法保证一个线程的修改对另一个线程是可见的。
在同步模块中小心调用其它方法如果一个同步方法在其中调用了一个不由自己控制的方法,比如客户传入的方法,客户可能在实现方法时申请同步锁,或者启动新线程申请锁,这可能会导致死锁。
并发工具优先于wait和notifyjava.util.concurrent包提供了执行框架、并发集合和同步器三种工具,应该尽量使用这些工具来实现并发功能,而不是使用wait、notify。
如果使用wait,notify则应该采用如下模式:
public void waitA(){ synchronized(a){ //获得锁 while(a>10) //放在while循环中保证满足条件 try { a.wait(); //释放锁、如果被唤醒则需要重新获得锁 } catch (InterruptedException e) { e.printStackTrace(); } } } //其它线程调用该方法唤醒等待线程 public void notifyA(){ a.notifyAll(); }
notifyAll方法相较于notify方法更安全,它保证唤醒了所有等待a对象的线程,被唤醒不代表会被立即执行,因为还需要获得锁。
不要对线程调度器有任何期望Tread yield(让步)即当前线程将资源归还给调度器,但是并不能保证当前线程下面一定不会被选中。线程的优先级设置也是不能保证按你预期的进行调度。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69409.html
摘要:构造器的参数没有确切地描述其返回的对象,适当名称的静态工厂方法更容易使用,也易于阅读。在文档中,没有像构造器那样明确标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要查明如何实例化一个类,有点困难。 第二章 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 两者创建对象的形式,例如:构造器是new Boolean();静态工厂方法是 public static Bool...
摘要:这本书是我第一次买的,从买来至今整本书还没有看完,只看了一半,原因是个人比较懒,而且玩的心比较大,经过这么多年的沉淀,终于可以偷点时间写下对于这本书的观后感了整本书给我的感觉不像是一个技术书,更多的是讲解一些实用技巧,而对于我这个职场菜鸟来 effective Java 这本书是我第一次买的, 从买来至今整本书还没有看完, 只看了一半, 原因是个人比较懒,而且玩的心比较大,经过这么多年...
摘要:第二章创建和销毁对象何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。表示工厂方法所返回的对象类型。 第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。 1 考虑用静态工厂方法代替构造器 一般在某处获取一个类的实例最...
阅读 1441·2019-08-29 17:14
阅读 1648·2019-08-29 12:12
阅读 728·2019-08-29 11:33
阅读 3263·2019-08-28 18:27
阅读 1444·2019-08-26 10:19
阅读 908·2019-08-23 18:18
阅读 3526·2019-08-23 16:15
阅读 2541·2019-08-23 14:14