公用类若是暴露了本身的域,则会致使客户端滥用该域,而且该公用类在之后的升级中没法灵活的改变属性的表达方式。java
设计不可变类应该遵循如下准则:数组
以下代码使用原生类型:安全
ArrayList a=new ArrayList<String>(); a.add(new Object());
以上代码编译和运行均可以经过,可是埋下了不少隐患。并发
List<String>是List的子类,而不是List<Object>的子类。List这种原生类型逃避了类型检查。app
List中add任何对象都对。List<?>的变量能够引用任何参数化(非参数也能够)的List,可是没法经过该变量添加非null元素。框架
假设Men extends Person, Boy extends Men
:ide
<? extends T>
表示上界,<? super T>
表示下界。ArrayList<? extends Men> ml=new ArrayList<Boy>();
,等号右边部分能够是Men与其子类的参数化ArrayList,或者是ArrayList<>()和ArrayList()。初始化时能够在new ArrayList<Boy>()
中填入Boy对象,此后不能再往ml里存元素,从ml的视角,ml多是ArrayList<Men>、ArrayList<Boy>等等,存入任何Men或者其子类对象都不合适,为了安全起见都不容许存。只能取元素,而且取出的元素只能赋值给Men或者其基类的引用,由于其中元素可能存了任何Men的子类,为了保险起见取出的值用Men或其基类表示。ArrayList<? super Men> ml=new ArrayList<Person>();
,初始化时能够存Person对象,以后能够再存入Men(与其子类,这是默认的),可是再存入Person对象是错误的。从ml视角,等号右边能够是ArrayList<Person>()、ArrayList<Men>()等等,因此最高只能存入Men对象。取出的元素都是Object,由于等号右边能够是ArrayList<Object>()。<? extends T>
和<? super T>
是对等号右边实参数化ArrayList的限制,而不是对ArrayList中可存入元素的描述。由于从引用ml中没法得知其实际指向的是那种参数化的ArrayList实例,因此再往其中添加元素时会采用最谨慎的选择。数组是协变的,也就是Fruit[] fs= new Apple[5];
是合法的,由于Apple是Fruit的子类,则数组也成父子关系,而列表则不适用于该规则。数组的这种关系容易引起错误,如fs[0]= new Banana()
,编译时没错,这在运行时报错。 函数
建立泛型数组是非法的,如new E[]; new List<E>[]; new List<String>[]
。泛型参数在运行时会被擦除,List<String>[]数组可能存入List<Integer>对象,由于运行时二者都是List对象,这显然是错误的,因此泛型不容许用在数组中。 工具
以下代码在编译时不会出错,在运行时出错java.lang.ClassCastException
。测试
ArrayList<String> list=new ArrayList<String>(); for (int i = 0; i < 10; i++) { list.add(""+i); } //该行报错 String[] array= (String[]) list.toArray(); }
缘由很迷,toArray返回的是Object[]数组,可是不能强制转化为String[],明明元素实际类型是String。有的解释说,在运行时只有List的概念,而没有List<String>概念。我感受事情没这么简单。
泛型是在整个类上采用泛型,这样能够在类内部方便的使用泛型参数。泛型方法是更精细的利用参数类型,将泛型参数设定在每一个方法上。
比较下面两个接口,体会其中不一样:
public interface Comparable<T> { public int compareTo(T o); } public interface Comparable2 { public <T> int compareTo2(T o); } public class Apple implements Comparable<Apple>, Comparable2{ @Override public int compareTo(Apple o) { return 0; } @Override public <T> int compareTo2(T o) { //T 能够为任何类,因此Apple能够和任何类比较 return 0; } }
有类:
class Apple implements Comparable<Apple>{ } class RedApple extends Apple{ }
有方法:
public static <T extends Comparable<T>> T get(T t){ return t; }
该方法就采用了递归的类型限制,由于泛型T被限制为 Comparable<T>的子类,而Comparable<T>中又包含了T,这造成一种递归的类型限制。Apple类能够调用该函数,RedApple则会出现错误,以下所示。
RedApple ra=new RedApple(); Apple a= get(ra); //正确 RedApple b=get(ra); //错误
缘由是在调用泛型函数时,会自动进行类型推断,第一个get函数根据左边参数,推断T为Apple,符合条件。在第二个get公式中,推断T为RedApple,不符合get函数的泛型限制条件。
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
其中<T extends Comparable<? super T>>
描述了T实现了Comparable接口或者其基类实现了该接口,经过继承得到Comparable的状况比较常见,这增长了该函数的通用性。参数List<? extends T>
表示List只要存的是T的子类就能够,这是显然合理的,一样加强了该函数的通用性。
一个容器如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<Class<?>, Object> favorites=new HashMap<Class<?>, Object>(); public <T> void putFavorite(Class<T> type, T instance){ if(type==null) throw new NullPointerException("Type is null"); favorites.put(type, instance); } public <T> T getFavorite(Class<T> type){ return type.cast(favorites.get(type)); } }
String.class为Class<String>的实例,Integer.class为Class<Integer>的实例,这二者显然不是同一个类,可是却能够放在同一个Map中。Map中采用了通配符?
,按理Map没法再加入任何元素,可是该通配符并非直接表示Map的类型参数,而是Class<?>。所以Map的键值能够是Class<String>、Class<Integer>等不一样的类型,所以成为一个异构容器。
其中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(){ List<String> list=new ArrayList<String>(); //该方法会抛出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"); } } } }
重载方法是静态的,在编译时就已经选择好,根据参数的表面类型,如Collection<String> c=new ArrayList<String>()
,有两个重载函数,getMax(Collection<?> a)
和getMax(ArrayList<?> a)
,在调用getMax(c)
时,会选择getMax(Collection<?> a)
方法,该选择在编译时就决定好了。当重载方法有多个参数时,状况会变得更复杂,选择结果可能出人意料。
而方法的重写选择时动态的,在运行时根据调用者的实际类型决定哪一个方法被调用。
可变参数可让用户灵活的填入不一样数量的参数,可是该方法本质上是将参数组织成数组,因此每次调用这些方法时都会涉及数组的建立和销毁,开销较大。
除了double和long之外,读写变量是原子性的。可是Java没法保证一个线程的修改对另外一个线程是可见的。
若是一个同步方法在其中调用了一个不禁本身控制的方法,好比客户传入的方法,客户可能在实现方法时申请同步锁,或者启动新线程申请锁,这可能会致使死锁。
java.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(让步)即当前线程将资源归还给调度器,可是并不能保证当前线程下面必定不会被选中。线程的优先级设置也是不能保证按你预期的进行调度。