栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Effective java 总结1 - 创建和销毁对象

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Effective java 总结1 - 创建和销毁对象

Effective java 总结 - 创建和销毁对象 第1条 用静态工厂方法代替构造器 优势
  • 静态工厂方法有名称

  • 不必每次调用的时候创建一个新的对象

  • 可以返回原返回类型的任何子类型对象

  • 返回对象的类可以根据方法的参数值变化

  • 方法返回对象所属的类,编写包含该静态工厂方法的类时可以不存在(服务提供者框架)

缺点
  • 类如果不含有公有的或者受保护的构造器,就不能被子类化
  • 程序员比较难发现一个类是否有静态工厂方法
案例
  • from – 类型转换方法,只有单个参数,返回相对应的实例

    Date d = Date.from(instant);
    
  • of – 聚合方法,有多个参数,返回该类型的一个实例,并合并

    Set faceCards = EnumSet.of(JACK, QUEEN, KING);
    
  • valueOf – 更繁琐的一种替代方法

    BigInteger prime = BigInteger.valueof(Integer.MAX_VALUE);
    
  • instance - getInstance – 返回的实例通过方法的参数来描述

    StackWalker luke = StackWalker.getInstance(options);
    
  • create - newInstance – 确保每次调用都返回一个新的实例

    Object newArray = Array.newInstance(classObject, arraylen);
    
  • getType – 类似getInstance,是在工厂方法处于不同类中的时候使用,type表示工厂方法返回的对象类型

    FileStore fs = Files.getFileStore(path);
    
  • newType – 类似newInstance,是在工厂方法处于不同类中的时候使用,type表示工厂方法返回的对象类型

     BufferReader br = Files.newBufferReader(path);
    
  • type – getType、newType 的简版

    List litany = Collections.list(legacyLitany);
    

第2条 遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器的局限性:不能很好地扩展到大量的可选参数

解决方案一 : 重叠构造器模式
public class MyChars{
    private final int a;
    private final int b;
    private final int c;
    
    MyChars(int a){
		this(a, 0, 0)
    }
    
	MyChars(int a, int b){
		this(a, b, 0)
    }
    
    MyChars(int a, int b, int c){
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

MyChars ch = new MyChars(1, 3, 5)

缺点:

  • 有多个参数时,客户端代码很难编写,且难以阅读
  • 参数顺序错误可能不会引起编译错误,但是运行即出现错误行为
解决方案二: JavaBean模式
public class MyChars{
    private int a = -1;
    private int b = -1;
    private int c = 0;
    
    MyChars(){}
    
    public void setA(int val){a = val}
    public void setB(int val){b = val}
    public void setC(int val){c = val}
}

MyChars ch = new MyChars();
ch.setA(1);
ch.setB(2);
ch.setC(3);

缺点:

  • 构造过程分散,javaBean可能处于不一致状态
  • 把类做成不可变的可能性不复存在,需要额外确保线程安全的影响
解决方案三: 建造者模式(Builder)
public class MyChars{
	private final int a;
    private final int b;
    private final int c;
    
    public static class Builder{
        private final int a;
    	private final int b;
    	private int c = 0;
        
        public Builder(int a, int b){
            this.a = a;
            this.b = b;
        }
        
        public Builder c(int val){
            c = val;
            return this;
        }
        
        public MyChars build(){
            return new MyChars(this);
        }
    }
    
    MyChars(Builder builder){
        a = builder.a;
        b = builder.b;
        c = builder.c;
    }
}

MyChars ch = new MyChars.Builder(1, 3).c(5).build();

优点:

  • 模拟了具名的可选参数

  • 客户端代码很容易编写, 易于阅读

  • Builder模式也适用类层次结构

  • Builder模式十分灵活,可以利用单个builder构建多个对象

缺点:

  • 为了创建对象,必须先创建构建器
  • 构建器比重叠构造器还要冗长,只有在很多参数的时候使用才合适
public abstract class Pizza{
    public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE};
    public Set toppings;
    
    abstract static class Builder>{
        EnumSet toppings = EnumSet.noneOf(Topping.class); // empty
        
        public T addTopping(Topping topping){
            toppings.add(Object.requireNonNull(topping));
            return self();
        }
    
        protected abstract self(); 
        abstract Pizza build();
    }
}

public class NyPizza extends Pizza{
    public enum Size {SMALL, MIDDLE, LARGE};
    private final Size size;
    
    static class Builder extends Pizza.Builder{
        private final Size size;
        
        public Builder(Size size){
            this.size = size;
        }
        
        @Override protected self(){return this;}
        
        @Override public build(){
            return Nypizza(this);
        }
    }
    
    private NyPizza(Builder builder){
        super(builder);
        size = builder.size;
    }
}

public class Calzone extends Pizza{
    private final boolean sauceInside;
    
    static class Builder extends Pizza.Builder{
        private boolean sauceInside;
        
        public Builder sauceInside(){
            this.sauceInside = True;
            return this;
        }
        
        @Override protected self(){return this;}
        
        @Override public build(){
            return Calzone(this);
        }
    }
    
    private Calzone(Build build){
        super(build);
        sauceInside = build.sauceInside;
    }
}

NyPizza nyp = new NyPizza.Builder(SMALL).addTopping(HAM).addTopping(MUSHROOM).build();
Calzone cz = new Calzone.Builder().sauceInside().addTopping(PEPPER).build();

第3条 用私有构造器或者枚举类型强化Singleton属性

实现Singleton的三种方法,前两个都需要保持构造器私有

公有静态成员是final域
  • 公有的静态域为final,所以该域总是包含相同的对象引用
  • 简单
public class Elvis{
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
    public void leaveTheBuilding(){}
}
公有的成员是静态工厂方法
  • 灵活性:不改变其API的情况下,改变该类是否为Singleton的想法
  • 可以编写一个泛型Singleton工厂
  • 可以通过方法引用作为提供者, Elvis::insatnce 就是一个Supplier
public class Elvis{
    private static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
    public Elvis getInstance(){
        return INSTANCE;
    }
    public void leaveTheBuilding(){}
}

为了将两种方法实现的Singleton类变成可以序列化的,需要:

  • 声明中加上 implements Serializable
  • 声明所有域都是瞬时的(transient)并提供一个 readResolve方法(保证Singleton)
private Object readResolve(){
    return INSTANCE;
}
声明包含单个元素的枚举类型

实现singleton的最佳方法

  • 功能上与公有域方法相似,更加简洁
  • 无偿提供了序列化机制,绝对防止多次实例化 (复杂序列化or反射攻击)
  • 如果Singleton类必须扩展一个超类,而不是扩展enum时,则不适用
public enum Elvis{
    INSTANCE;
    public void leaveTheBuilding(){}
}

第4条 通过私有构造器强化不可实例化的能力

通过将类做成抽象类来强制该类不可以被实例化是不行的(子类可以被实例化)

不提供构造器系统会默认提供一个公有的无参的缺省构造器

让类包含一个私有构造器,就不能实例化了:

public class UtilityClass{
    private UtilityClass(){
        throw new AssertionError();
    }
}

第5条 优先考虑使用依赖注入来引用资源

依赖注入:创建一个新的实例时,将资源传到构造器中

public class SpellChecker{
    private final Lexicon dictionary;
    public SpellChecker(Lexcion dictionary){ // 依赖注入
        this.dictionary = dictionary;
    }
    public boolean isValid(String word){...}
}

一种有用的变体:将资源工厂传入构造器,工厂是可以被重复调用来创建类型实例的一个对象,接口 Supplier 最适合用于表示工厂

Mosaic create(Supplier tileFactory){...}

总结:

  • 不使用Singleton和静态工具类来实现一个或多个底层资源的类,且该资源的行为会影响到类的行为

  • 使用依赖注入引用资源,提升灵活性、可重用性和可测试性


第6条 避免创建不必要的对象

1、对象不可变,重用单个对象

// 错误写法
String s = new String("adas"); // 将创建不必要的String实例
// 正确写法
String s = "adas"; // 同一台虚拟机中运行的代码,该对象会被重用

2、创建成本较高的对象,建议缓存下来重用

static boolean isRomamNumeral(String s){
    return s.matchs("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[VX]|V?I{0,3})$")
}

问题:matchs 内部为正则表达式创建了一个Pattern实例,只用一次就回收了,重用成本高

解决:将正则表达式编译成一个有限状态机

private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[VX]|V?I{0,3})$");

static boolean isRomamNumeral(String s){
    return ROMAN.matcher(s).matchs();
}

3、适配器情形

一个对象把功能委托给后备对象,并提供一个可以替代的接口,针对某个给定的对象的特定适配器而言,不需要创建多个适配器实例,eg:
Map接口的keySet方法,对于一个给定的Map,每次调用keySet返回的都是同样的Set实例

4、自动装箱可能创建多余对象

private static long sum(){
    Long sum = 0;
    for (long i = 0; i < Integer.MAX_VALUE; i++){
        sum += i;
    }
    return sum;
}

由于sum被定义为Long,程序创建了大概2的31次方个Long实例,因此:

优先使用基本类型而不是装箱类型,避免无意识的自动装箱

注意

  • 维护自己的对象池来避免重复创建对象不是一个好的做法

  • 现代的JVM对小对象的创建回收性能优于维护对象池

  • 数据库连接池这种重型对象,维护对象池是有意义的


第7条 消除过期的对象引用

1、内存泄漏来源1

public class Stack{
    private int size;
    private Object[] elements;
    private static final int DEFAULT_INITIAL_CAPACITY;
    
    public Stack(){
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    public void push(Object e){
        ensureCapacity();
        elements[size++] = e;
    }
    
    public Object pop(){
        if(0 == size) throw new EmptyStackError();
        return elements[--size];  // 内存泄漏
    }
    
    private void ensureCapacity(){
        if(elements.length == size){
            elements = Arrays.copyOf(elements, 2 * size + 1)
        }
    }
}

pop方法调用后,栈维持着这些对象的过期引用(永远不会再被解除的引用),垃圾回收机制不仅不会处理这个对象,而且不会处理被这个对象所引用的其他对象。

解决:

只要类是自己管理内存,就应该警惕内存泄漏问题

public Object pop(){
    if(0 == size) throw new EmptyStackError();
    Object e = elements[--size];
    elements[size] = NULL; // 清除过期引用
    return e;  
}

2、内存泄漏来源2

缓存,将对象引用放入缓存中,容易被遗忘

解决:

  • 缓存之外存在对某个项的键的引用,可以用WeakHashMap代表缓存
  • 使用后台线程清除缓存(ScheduledThreadPoolExecutor)
  • 添加新条目的时候顺便清理,LinkedHashMap类利用removeEldestEntry实现
  • 更加复杂的缓存,使用 java.lang.ref

3、内存泄漏来源3

监听器和其他回调,确保被当做垃圾回收的最佳方法是只保存他们的弱引用,只保存成WeakHashMap中的键

可以借助 Heap 剖析工具发现内存泄漏


第8条 避免使用终结方法和清除方法

终结方法&&清除方法(java 9)

缺点:

  • 不可预测,危险,导致行为不稳定,性能降低,可移植问题
  • 不能保证会被及时执行,且不保证会被执行
  • 不同的JVM中,垃圾回收算法执行方法的时间点都不相同
  • 终结方法有一个严重的安全问题:为终结方法攻击打开了类的大门

结论:

  • 清除和终结方法都是不可预测的,运行缓慢,一般情况下没有必要使用
  • 注重时间的任务不应该由这两方法来完成
  • 永远不依赖终结和清除方法来更新重要的持久状态
  • System.gc 和 System.runFinalizersOnExit增加了方法被执行的机会,但仍旧不保证一定被执行

用途:

  • 充当 “安全网”,在客户端无法正常结束的情况下,迟一点释放资源比永远不释放好
  • 终止非关键的本地资源(清除方法)

解决:

让类实现 AutoCoseable, 利用try-with-resources确保终止。
实现细节:客户端在每个实例不再需要的时候调用close()方法,close()方法必须在一个私有域中记录下“该对象不再有效”。如果方法在对象被停止后调用,则会强制检查这个域,并抛出IllegalStateException异常。

清除方法eg:

public class Room implements AutoCloseable{
    private static final Cleaner cleaner = Cleaner.create();
    
    // 静态的嵌套类, 未引用Room实例, 阻止了Room实例的垃圾回收
    private static class State implements Runable{
        int numJunkPiles;  // 包含一个指向本地对等体的指针
        
        State(int numJunkPiles){
            this.numJunkPiles = numJunkPiles;
        }
        
        @Override public void run(){
            System.out.println("cleaning room");
            numJunkPiles = 0;
        }
    }
    
    private final State state;
    private final Cleaner.Cleanable cleanable;
    
    public Room(int numJunkPiles){
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }
    
    @Override public void close(){
        cleanable.clean();
    }
}
public class Adult{
    public static void main(String[] args){
        try(Room myRoom = new Room(4)){
            System.out.println("Peace out")
        }
    }
}
// Peace out
// cleaning room 
public class Adult{
    public static void main(String[] args){
        Room myRoom = new Room(4);
        System.out.println("Peace out")
    }
}
// Peace out
加上 System.gc() 后可能打印 cleaning room 

第9条 try-with-resources 优于 try-finally

必须通过close()关闭的资源: InputStream、OutputStream、java.sql.Connection …

一般情况

static String firstLineOfFile(String path) throws IOException{
    BufferedReader br = new BufferedReader(new FileReader(Path));
    try {
        return br.readLine();
    }finally{
        br.close();
    }
}

问题: 如果 readLine 和 close 都发生了异常,则第二个异常会覆盖第一个异常,难以查找问题

解决:

static String firstLineOfFile(String path) {
    try (BufferedReader br = new BufferedReader(new FileReader(Path))) {
        return br.readLine();
    }catch (IOException e){
        return "catch exception";
    }
}

复杂情况

static void copy(String src, String dst) throws IOException{
    InputStream in = new InputStream(src);
    try{
        OutputStream out = new FileOutputStream(dst);
        try{
            int n;
        	byte[] buf = new bype[BUFFER_SIZE];
        	while( (n = in.read(buf)) >= 0 )
            	out.write(buf, 0, n);
        }finally{
            out.close();
        }                    
    }finally{
        in.close();
    }
}

改进:

static void copy(String src, String dst) throws IOException{
    try(InputStream in = new InputStream(src);
        OutputStream out = new FileOutputStream(dst)){
        int n;
      	byte[] buf = new bype[BUFFER_SIZE];
      	while( (n = in.read(buf)) >= 0 )
        	out.write(buf, 0, n);
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1022821.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号