引言
创建和销毁对象
何时以及怎样创建对象。何时以及怎样避免创建对象。怎样确保创建的对象能够被适时地销毁,以及怎样管理销毁之前必须进行的全部清楚工作
第1条 考虑用静态工厂方法取代构造器
- 静态工厂方法与构造器不同的第一大优势在于:它们有名称
- 静态工厂方法与构造器不同的第二大优势在于:不必再每次调用它们的时候都创建一个新对象
- 静态工厂方法与构造器不同的第三大优势在于。它们能够返回原返回类型的不论什么子类型的对象
服务提供者框架包括三个组件:
服务接口 提供者实现
提供者注冊API 系统用来注冊实现,让client訪问
服务訪问API client用来获取服务的实例
服务提供者接口(可选) 负责创建其服务实现的实例
演示样例代码
public interface Service
{
public void service();
}
public interface Provider
{
Service newService();
}
public class Services
{
private Services(){}
private static final Map< String, Provider > = new ConcurrentHashMap< String, Provider >();
public static final String DEFAULT_PROVIFER_NAME = "<def>";
public static void registerDefaultProvider( Provider p )
{
registerProvider( DEFAULT_PROVIDER_NAME, p );
}
public static void registerProvider( String name, Provider p )
{
providers.put( name, p );
}
public static Service newInstance()
{
return newInstance( DEFAULT_PROVIDER_NAME );
}
public static Service newInstance( String name )
{
Provider p = providers.get( name );
if ( p == null )
{
throw new IllegalArgumentException( "No provider registered with name : " + name );
}
return p.newServices();
}
}
- 静态工厂方法的第四大优势在于。在创建參数化类型实例的时候。它们使代码变得更加简洁
通过语法糖来简化泛型代码
public static< K, V > HashMap< K, V > newInstance()
{
return new HashMap< K, V >();
}
Map< String, List< String > > map = HashMap.newInstance();
- 静态工厂方法的主要缺点在于,类假设不含公有的或者受保护的构造器。就不能被子类化
- 静态工厂方法的第二个缺点在于,它们与其它的静态方法实际上没有不论什么差别
静态工厂方法惯用名称:
valueOf 返回的实例和其參数具有同样的值,实际上是类型转换方法
of valueOf的替代
getInstance 依据方法的參数返回对应的实例。对于Singleton。无參数,返回唯一的实例
newInstance 和getInstance功能相似,但确保返回的每一个实例是新创建的
getType 和getInstance功能相似。在工厂方法处于不同的类中的时候使用,Type表示工厂方法所返回的对象类型
newType 和newInstance功能相似。在工厂方法处于不同的类中的时候使用
第2条 遇到多个构造器參数时要考虑用构建器
静态工厂和构造器的局限:**不能非常好地扩展到大量的可选參数****一种解决方法** JavaBean模式 调用一个无參构造器来创建对象,然后调用setter方法来设置每一个必要的參数,以及每一个相关的可选參数 潜在问题:状态不一致、阻止了把类做成不可变的可能、 **Builder模式** 安全性和可读性的折衷重载构造器模式可行,可是当有很多參数的时候,client代码会非常难编写。且难以阅读
演示样例代码不直接生成想要的对象,让client利用全部必要的參数调用构造器。得到一个builder对象,然后client在builder对象上调用相似于setter方法,来设置每一个相关的可选參数。最后client调用无參的build方法来生成不可变的对象
public class NutritionFacts
{
private final int seringSize;
private final int servings;
private final int calories;
public static class Builder
{
private final int servingSize;
private final int servings;
private final int calories = 0;
public Builder( int servingSize, int servings )
{
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories( int val )
{
calories = val;
return this;
}
public NutritionFacts build()
{
return new NutritionFacts( this );
}
}
private NutritionFacts( Builder builder )
{
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
}
}
//call code
NutritionFacts cocaCola = new NutritionFacts.Builder( 240, 8 ).calories( 100 ).build();
**builder模式模拟了具名的可选參数**
Java中传统的抽象工厂实现是Class对象,用newInstance方法充当builer方法一部分。但newInstance方法总是企图调用类的无參构造器,而构造器可能不存在,同一时候,该方法还会传播由无參构造器抛出的异常,**`Class.newInstance`破坏了编译时的异常检查**
builer不足:为了创建对象,必须先创建其构建器,可能造成性能问题。比重叠构造器模式更加冗长,仅仅有在非常多參数的时候才使用。
假设类的构造器或者静态工厂中具有多个參数,设计时,Builder模式是个不错的选择
第3条 用私有构造器或者枚举类型强化Singleton属性
使类成为Singleton使client測试十分困难,由于无法替换其模拟实现。除非实现一个充当类型的接口 两种Singleton方法public class Elvis
{
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
//another
public class Elvis
{
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding(){ ... }
}
第三种Singleton实现方法,编写一个包括单个元素的枚举类型享有特权的client能够借助
AccessibleObject.setAccessible
方法,通过反射机制调用私有构造器
public enum Elvis
{
INSTANCE;
public void leaveTheBuilding() { ... }
}
更加简洁,无偿地提供了序列化机制,绝对防止多次实例化。**单元素的枚举类型已经成为实现Singleton的最佳方法**
第4条 通过私有构造器强化不可实例化的能力
企图通过将类做成抽象类来强制该类不可被实例化。是行不通的 一种私有构造器实现public class UtilityClass
{
private UtilityClass()
{
throw new AssertionError();
}
}
第5条 避免创建不必要的对象
一般来说,最好能重用对象而不是在每次须要的时候就创建一个同样功能的新对象String s = new String( "stringtest" );//Donot do this
String s = "stringtest";
对于同一时候提供了静态工厂方法和构造器的不可变类。通常优先使用静态工厂方法,避免创建不必要的对象
public class Person
{
private final Date birthDate;
//Donot do this
public boolean isBabyBoomer()
{
Calendar gmtCal = Calendar.getInstance( TimeZone.getTimeZone("GMT") );
gmtCal.set( 1946, Calendar.JANUARY, 1, 0, 0, 0 );
Date boomStart = gmtCal.getTime();
gmtCal.set( 1965, Calendar.JANUARY, 1, 0, 0, 0 );
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo( boomStart )>=0
&& birthData.compareTo( boomEnd )<0;
}
}
//better implements
public class Person
{
private final Date birthDate;
private static final Date BOOM_START;
private static final Date BOOM_END;
static
{
Calendar gmtCal = Calendar.getInstance( TimeZone.getTimeZone( "GMT" ) );
gmtCal.set( 1946, Calendar.JANUARY, 1, 0, 0, 0 );
BOOM_START = gmtCal.getTime();
gmtCal.set( 1965, Calendar.JANUARY, 1, 0, 0, 0 );
BOOM_END = gmtCal.getTime();
}
//Do this
public boolean isBabyBoomer()
{
return birthDate.compareTo( BOOM_START)>=0
&& birthData.compareTo( BOOM_END)<0;
}
}
**要优先使用基本类型而不是装箱基本类型。要当心无意识的自己主动装箱**
通过维护自己的对象池来避免创建对象并非一种好的做法,除非池中的对象是非常重量级的,真正正确使用对象池的典型对象演示样例就是数据库连接池
一般而言。维护自己的对象池会添加代码的复杂性,添加内存占用。还会损害性能
当应该重用现有对象的时候。不要创建新的对象
当该创建新对象的时候,不要重用现有的对象
在提倡使用保护性拷贝的时候,因重用对象而付出的代价要远远大于因创建反复对象而付出的代价
必要时假设没能实施保护性拷贝,将会导致潜在的错误和安全漏洞。不必要地创建对象则仅仅会影响程序的风格和性能
第6条 消除过期的对象引用
过期引用,指永远也不会再被解除的引用。假设一个对象引用被无意识地保留起来了,那么垃圾回收机制不仅不会处理这个对象,并且也不会处理被这个对象所引用的全部其它对象
内存泄漏的另外一个常见来源是**缓存**:仅仅要在缓存之后存在对某个项的键的引用,该项就有意义,能够用`WeakHashMap`代表缓存;当缓存中的项过期之后,会自己主动被删除清空对象引用是一种例外,而不是一种规范行为
仅仅要类是自己管理内存,应该警惕内存泄漏问题
内存泄漏的第三个常见来源是**监听器和其它回调**:确保回调马上被当做垃圾回收的最佳方法是仅仅保存它们的弱引用仅仅有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时。
WeakHashMap
才实用处
第7条 避免使用终结方法
**终结方法(finalize)一般是不可预測的。也是非常危急的,普通情况下是不必要的** 终结方法的缺点在于不能保证会被及时地执行 Java语言规范不仅不保证终结方法会被及时地执行。并且根本就不保证其被执行,**不应该依赖终结方法来更新重要的持久状态** 使用终结方法有一个非常严重的性能损失 对于确实须要终止的方法。应**提供一个显示的终止方法**,并要求改类的client在每一个实例不再实用的时候调用这种方法 **显式的终止方法通常与try-finally结构结合起来使用。以确保及时终止** 终结方法有两种合法用途- 当对象全部者忘记调用显式终止方法时,终结方法充当“安全网”
- 与对象的本地对等体有关。本地对等体是一个本地对象,普通对象通过本地方法托付给一个本地对象,垃圾回收器无法感知本地对象的存在,当Java对等体被回收时,它不会被回收
“终结方法链”不会被自己主动执行,须要进行显式调用
第二种可选方法是终结方法守卫者
对于全部对象都通用的方法
对equals、hashCode、toString、clone和finalize深入分析
类和接口
关于类和接口的一些指导原则
泛型
通过泛型。告诉编译器每一个集合中接受哪些对象类型。编译器自己主动为插入进行转化。并在编译时告知是否插入了类型错误的对象,使得程序更加安全和清楚
第23条 请不要在新代码中使用原生态类型
泛型类和接口统称为泛型。每种泛型定义一组參数化的类型。每一个泛型都定义一个原生态类型,即不带不论什么实际类型參数的泛型名称
假设不提供类型參数,使用集合类型和其它泛型是合法的。可是不应该这么做。假设使用原生类型。就失掉了泛型在安全性和表述性方面的全部优势
泛型有子类型化的规则,List< String>是原生态类型List的一个子类型,而不是參数化类型List< Object >的子类型。假设使用像List这样的原生态类型,就会失掉类型安全性。可是假设使用像List< Object >这样的參数化类型,则不会
Java提供了一种对于不确定的类型的安全的替代方法,称作无限制的通配符类型:假设要使用泛型,但不确定或者不关心实际的类型參数,就能够使用一个问号取代,能够持有不论什么集合
通配符类型是安全的,原生态类型是不安全的:
能够将不论什么元素放进使用原生态类型的集合中,但不能将不论什么元素( null除外 )放到通配符类型集合中
例外情况:
- 在类文字中必须使用原生态类型
规范不同意使用參数化类型(尽管同意数组类型和基本类型)。如List.class、String[].class和int.class都合法,可是List< String.class >和List< ?
>.class不合法
- 与instanceof操作符有关
泛型信息在执行时被擦除,在參数化类型而非无限制通配符类型上使用instanceof操作符是非法的。用无限制通配符类型取代原生态类型,对instanceof操作符的行为不会产生影响
在这样的情况下。直接用原生态类型就可以,如
if ( 0 instanceof Set )
{
Set< ? > = ( Set< ? > )o;
...
}
注意:一旦确定o为Set。就须要将其转换成通配符类型Set< ? >。而不是转换成远程类型Set
使用原生态类型会在执行时导致异常,因此不要在新代码中使用
第24条 消除非受捡警告
使用泛型编程时。会遇到很多编译器警告:非受检强制转化警告、非受检方法调用警告、非受捡普通数组创建警告以及非受检转换警告
要尽可能地消除每一个非受检警告
假设无法消除警告。同一时候能够证明引起警告的代码是类型安全的。(仅仅有在这样的情况下才)能够用一个@SuppressWarnings( “unchecked” )注解来禁止这条警告
SuppressWarnings注解能够用在不论什么粒度的级别中,从单独的局部变量声明到整个类都能够,应该始终在尽可能小的范围内使用SuppressWarnings注解
每当使用SuppressWarnings( “unchecked” )注解时,都要加入一条凝视。说明为什么这么做是安全的
第25条 列表优先于数组
数组与泛型的不同点
数组是协变的。泛型是不可变的
协变表示假设Sub为Super的子类型。那么数组类型Sub[]就是Super[]的子类型
数组是详细化的,泛型是通过擦除来实现的
数组会在执行时才知道并检查它们的元素类型约束,泛型仅仅在编译时强化它们的类型信息,并在执行时丢弃(擦除) 它们的元素类型信息
数组和泛型不能非常好地混合使用,如创建泛型、參数化类型或者类型參数的数组是非法的,由于泛型数组是非法的。不是类型安全的
从技术角度来说,像E、List< E >和List< String >这样的类型应称作是不可详细化类型,不可详细化类型是指其执行时表示法包括的信息比它编译时表示法包括的信息更少的类型
唯一可详细化參数化类型是无限制的通配符类型,如List< ?
>和Map< ?
>,虽不经常使用,但创建无限制通配类型的数组是合法的
当得到泛型数组创建错误时。最好的解决方法一般是优先使用集合类型List< E >,而不是数组类型E[],损失一些性能或者简洁性,换回更高的类型安全性和互用性
static Object reduce( List list, Function f, Object initVal )
{
synchronized( list )
{
Object result = initVal;
for ( Object o : list )
{
result = f.apply( result, o );
}
return result;
}
}
interface Function
{
Object apply( Object arg1, Object arg2 );
}
不要从同步区域中调用外来方法
static Object reduce( List list, Function f, Object initVal )
{
Object[] snapshot = list.toArray();
Object result = initVal;
for ( E e : snapshot )
{
result = f.apply( result, e );
}
return result;
}
interface Function< T >
{
T apply( T arg1, T arg2 );
}
一种naive尝试
static <E> E reduce( List<E> list, Function<E> f, E initVal )
{
E[] snapshot = list.toArray();
E result = initVal;
for ( E e : snapshot )
{
result = f.apply( result, e );
}
return result;
}
利用泛型改动
static< E > E reduce( List< E > list, Function< E > f, E initVal )
{
List< E > snapshot;
synchronized( list )
{
snapshot = new ArrayList< E >( list );
}
E result = initVal;
for ( E e : snapshot )
{
result = f.apply( result, e );
}
return result;
}
第26条 优先考虑泛型
泛型stack
public class Stack< E >
{
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack()
{
//elements = new E[ DEFAULT_INITIAL_CAPACITY ];
//不能创建不可详细化的类型的数据,两种解决方法
elements = ( E[] )new Object[ DEFAULT_INITIAL_CAPACITY ];
//第二种方法,将elements域的类型从E[]改为Object[]
}
public void push( E e )
{
ensureCapacity();
elements[ size++ ] = e;
}
public E pop()
{
if ( size == 0 )
{
throw new EmptyStackException();
}
E result = elements[ --size ];
elements[ size ] = null;
return result;
}
//...
}
使用泛型比使用须要在client代码中进行转换的类型更加安全。也更加easy
在设计新类型的时候,要确保不要须要这样的转换就能够使用,也就是把类做成泛型的
第27条 优先考虑泛型方法
静态工具方法尤其适合于泛型化。Collections中全部的“算法”方法都泛型化了
利用有限制的通配符类型,能够是方法更加灵活
泛型方法的一个显著特征是,无需明白指定类型參数的值,不像调用泛型构造器的时候是必须指定的
编译器通过检查方法參数的类型来计算类型參数的值,这个过程称作类型推导
在调用泛型构造器的时候,要明白传递类型參数的值可能有点麻烦,类型參数出如今变量声明的两边,代码冗余
Map< String, List< String > > anagrams = new HashMap< String, List< String > >();
能够编写一个泛型静态工厂方法
public static< K, V > HashMap< K, V > newHashMap()
{
return new HashMap< K, V >();
}
Map< String, List< String > > anagrams = newHashMap();
泛型单例工厂:创建不可变但又适合于很多不同类型的对象
public interface UnaryFunction< T >
{
T apply( T arg );
}
private static UnaryFunction< Object > IDENTITY_FUNCTION =
new UnaryFunction< Object >() {
public Object apply( Object arg ) { return arg; }
};
@SuppressWarnings( "unchecked" )
public static< T > UnaryFunction< T > identityFunction()
{
return ( UnaryFunction< T > ) IDENTITY_FUNCTION;
}
public static void main( String[] args )
{
String[] strings = { "jute", "hemp", "nylon" };
UnaryFunction< String > sameString = identityFunction();
for ( String s : strings )
{
System.out.println( sameString.apply( s ) );
}
Number[] numbers = { 1, 2.0, 3L };
UnaryFunction< Number > sameNumber = identityFunction();
for ( Number n : numbers )
{
System.out.println( sameNumber.apply(n) );
}
}
通过某个包括该类型參数本身的表达式来限制类型參数是同意的,这就是递归类型限制
递归类型限制最普遍的用途与Comparable接口有关。定义类型的自然顺序
public interface Comparable< T >
{
int compareTo( T o );
}
//Using a recursive type bound to express mutual comparability
public static< T extends Comparable< T > > T max( List< T > list )
{
Iterator< T > i = list.iterator();
T result = i.next();
while ( i.hasNext() )
{
T t = i.next();
if ( t.compareTo( result ) > 0 )
{
result = t;
}
}
return result;
}
第28条 利用有限制通配符来提升API的灵活性
參数化类型是不可变的,导致泛型间没有子类关系,Java提供了一种特殊的參数化类型,称作有限制的通配符类型来处理逻辑上能够存储的泛型结构
public class Stack< E >
{
public Stack();
public void push( E e );
public E pop();
public boolean isEmpty();
public void pushAll( Iterable< ? extends E > src )
{
for ( E e : src )
{
push( e );
}
}
public void popAll( Collection< ? super E > dst )
{
while ( !isEmpty() )
{
dst.add( pop() );
}
}
}
为了获得最大限度的灵活性,要在表示生产者或消费者的输入參数上使用通配符类型
PECS表示producer-extends, consumer-super
public static< E > Set< E > union( Set< ? extends E > s1, Set< ? extends E > s2 )
Set< Integer > integers = ...;
Set< Double > doubles = ...;
//Set< Number > numbers = union( integers, doubles ); //error
Set< Number > numbers = Union.<Number>union( integers, doubles );
扩展max方法
public static< T extends Comparable< T > > T max( List< T > list )
//modified
public static< T extends Comparable< ? super T > > T max( List< ? extends T > list )
{
//Iterator< T > i = list.iterator(); //error
Iterator< ? extends T > i = list.iterator();
T result = i.next();
while ( i.hasNext() )
{
T t = i.next();
if ( t.compareTo(result) > 0 )
{
result = t;
}
}
return result;
}
List< ScheduleFuture< ? > > scheduleFutures = ...;
public static void swap( List< ? > list, int i, int j )
{
swapHelper( list, i, j );
}
private static< E > void swapHelper( List< E > list, int i, int j )
{
list.set( i, list.set( j, list.get(i) ) );
}
在API中使用通配符类型须要技巧。但使API变得灵活得多。假设编写的是将被广泛使用的类库,一定要是当地利用通配符类型
基本原则:producer-extends, consumer-super
全部的comparable和comparator都是消费者
第29条 优先考虑类型安全的异构容器
异构:不像普通的容器,全部键都是不同类型的
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, type.cast( instance ) );
}
public <T> T getFavorite( Class< T > type )
{
return type.cast( favorites.get( type ) );
}
}
集合API说明泛型的使用方法:限制容器仅仅能由固定数目的类型參数。
能够通过将类型參数放在键上而不是容器上来避开这一限制
对于类型安全的异构容器,能够用Class对象作为键,以这样的方式使用的Class对象称作类型令牌
枚举和注解
方法
怎样处理參数和返回值,怎样设计方法签名。怎样为方法编写文档,在可用性、健壮性和灵活性上有进一步的提升
通用程序设计
讨论局部变量的处理、控制结构、类库的使用、各种数据类型的使用方法,以及反射机制和本地方法的使用方法。并讨论优化和命名惯例
异常
怎样发挥异常的长处,提高程序的可读性、可靠性和可维护性,以及降低使用不当所带来的负面影响,并提供了异常使用的指导原则
并发
序列化
阐述序列化方面的技术,讨论序列化代理模式