《Effective Java》读书笔记

 

引言

第2章 创建和销毁对象

何时以及如何创建对象,何时以及如何避免创建对象,如何确保创建的对象能够被适时地销毁,以及如何管理销毁之前必须进行的所有清楚工作

第1条 考虑用静态工厂方法代替构造器

  • 静态工厂方法与构造器不同的第一大优势在于:它们有名称
  • 静态工厂方法与构造器不同的第二大优势在于:不必再每次调用它们的时候都创建一个新对象
  • 静态工厂方法与构造器不同的第三大优势在于:它们可以返回原返回类型的任何子类型的对象

服务提供者框架包含三个组件:

  • 服务接口:提供者实现
  • 提供者注册API: 系统用来注册实现,让客户端访问
  • 服务访问API:客户端用来获取服务的实例
  • 服务提供者接口(可选): 负责创建其服务实现的实例

示例代码

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模式 安全性和可读性的折衷

不直接生成想要的对象,让客户端利用所有必要的参数调用构造器,得到一个builder对象,然后客户端在builder对象上调用类似于setter方法,来设置每个相关的可选参数,最后客户端调用无参的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使客户端测试十分困难,因为无法替换其模拟实现,除非实现一个充当类型的接口

两种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(){ ... }
}

享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器

第三种Singleton实现方法,编写一个包含单个元素的枚举类型

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语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证其被执行,不应该依赖终结方法来更新重要的持久状态
使用终结方法有一个非常严重的性能损失

对于确实需要终止的方法,应提供一个显示的终止方法,并要求改类的客户端在每个实例不再有用的时候调用这个方法
显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止
终结方法有两种合法用途

  • 当对象所有者忘记调用显式终止方法时,终结方法充当“安全网”
  • 与对象的本地对等体有关。本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象,垃圾回收器无法感知本地对象的存在,当Java对等体被回收时,它不会被回收

“终结方法链”不会被自动执行,需要进行显式调用
另外一种可选方法是终结方法守卫者

第三章 对于所有对象都通用的方法

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值