前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3分钟快速阅读-《Effective Java》(一)

3分钟快速阅读-《Effective Java》(一)

作者头像
cwl_java
发布2019-10-26 20:47:27
3600
发布2019-10-26 20:47:27
举报
文章被收录于专栏:cwl_Javacwl_Java

简介

Effective Java这本书针对的是有经验的程序员,本书共计78个条目,目的是告诉大家如何进行更加有效的编程

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

静态工厂方法的优点

  • 1.1 静态工厂方法有对应方法名称,构造器则没有.所以使用静态工厂方法能够更加直接的表达想要实例化的对象
  • 1.2 静态工厂方法不用每次都创建出一个新的对象
  • 1.3 静态工厂方法可以返回父类对象或者接口对象
  • 1.5 静态工厂方法可以使用泛型来作为返回的参数

静态工厂方法的缺点

  • 1.1 如果不含有公有的或者受保护的构造器,就不能被子类化
  • 1.2 本质上跟其他静态方法没有太大区别
2.遇到多个构造器参数时考虑使用构建器

当一个对象中字段很多,那么需要实例化时如果每种不同的对象都写一个对象的构造器来进行实例化会使得代码十分冗余,如果使用无参构造器,然后使用setter方法来给每个字段设置值,会导致这个对象字段可变且不安全,所以需要考虑使用构建器.构建器代码如下所示

代码语言:javascript
复制
// 工厂实例类代码
public class FactoryDemo {

    private final Long ID;
    private final String adress;

    private final int age;
    private final String name;

    public static class Builder {
        private final int age;
        private final String name;

        private Long ID = 0L;
        private String adress = "";

        public Builder(int age, String name) {
            this.age = age;
            this.name = name;
        }

        public Builder ID(Long ID){
            this.ID = ID;
            return this;
        }

        public Builder adress(String adress){
            this.adress = adress;
            return this;
        }

        public FactoryDemo build(){
            return new FactoryDemo(this);
        }
    }

    private FactoryDemo(Builder builder){
        this.ID = builder.ID;
        this.adress = builder.adress;
        this.age = builder.age;
        this.name = builder.name;
    }
}
代码语言:javascript
复制
// 客户端调用代码
FactoryDemo build = new FactoryDemo.Builder(18, "大爷").ID(35062819920914L).adress("福建漳州").build();
3.用私有构造器或者枚举类型强化Singleton类型
  • JDK1.5前可以有两种方式写单例模式,分别是以下两种
代码语言:javascript
复制
//方式1
public class Elvis {
    public static fianl Elvis INSTANCE = new Elvis();
    private Elvis(){}
}
代码语言:javascript
复制
//方式2
public class Elvis {
    public static fianl Elvis INSTANCE = new Elvis();
    private Elvis(){}
    public static Elvis getInstance(){
        return INSTANCE;
    }
}
  • 推荐使用枚举来进行实例化,可以绝对防止实例化出多个对象
代码语言:javascript
复制
public enum Elvis {
    INSTANCE;
    //业务逻辑方法
    public void run(){
        System.out.println("处理业务逻辑");
    }
}
4.通过私有构造器强化不可实例化的能力

对于工具类而言,是不需要被实例化的,这个时候需要使用私有化构造器的方式来强化它不可被实例化的能力.不要选择不写,这样当该类被调用的时候,编译器会自动生成一个无参构造函数,或者当子类继承它并实例化子类的时候,该类也会被实例化,这样的操作都是不合理的

5.避免创建不必要的对象
  • 5.1 JDK1.5开始出现自动封装,实际开发当中能用基本数据类型就尽量不要使用包装类型,因为包装类型会无端创建出很多对象,代码示例如下所示
代码语言:javascript
复制
public class Test {

    public static void main(String[] args) {

        Long sum = 0L;
        long begin = System.currentTimeMillis();
        for(int i = 0 ;i<=100000;i++){
            sum += 1;
        }
        long end = System.currentTimeMillis();
        System.out.println("Long总耗时:"+(end-begin));//5ms

        long sum1 = 0L;
        long begin1 = System.currentTimeMillis();
        for(int i = 0 ;i<=100000;i++){
            sum1 += 1;
        }
        long end1 = System.currentTimeMillis();
        System.out.println("long总耗时:"+(end1-begin1));//0ms

    }
}
  • 5.2 对于引用类型的初始化尽量不要使用new String(),特别是在for循环当中,这样也会出现过多不必要对象的创建,代码示例如下:
代码语言:javascript
复制
public class Test {

    public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        for(int i = 0 ;i<=10000;i++){
            String str = new String("sb");
        }
        long end = System.currentTimeMillis();
        System.out.println("new总耗时:"+(end - begin));//2ms

        long begin1 = System.currentTimeMillis();
        for(int i = 0 ;i<=10000;i++){
            String str = "sb";
        }
        long end1 = System.currentTimeMillis();
        System.out.println("非new总耗时:"+(end1 - begin1));//0ms
    }
}
  • 5.3 当类A中包含字段类B,当类A进行实例化对象A时,可以考虑使用静态代码块来对关联对象B进行初始化,避免每次调用对象A时都会初始化一次对象B

备注:以上所说的情况仅仅只是针对于需求当中该对象是只需要被初始化一次,后续不需要再进行更改的情况下可以使用,如果是该对象在程序运行过程中需要被修改的,那么则不适用此规则

6.消除过期的对象引用

在JVM当中栈空间弹出的对象引用为过期引用,这一类的过期引用不会再被GC回收,那么这样的过期对象引用越来越多则会导致内存泄漏(Disk paging),严重可能导致OOM,具体代码示例如下

代码语言:javascript
复制
//以下这段代码主要问题在于pop出栈的对象,还对原有的对象在内存中的地址/存在着引用,所以需要对已经出栈的对象做空引用的处理
public class StackDemo {
    private Object[] elements;
    private int size = 0;
    private final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * 功能描述: 
     * 〈初始化栈〉
     *
     * @params : []
     * @return : 
     * @author : cwl
     * @date : 2019/5/5 16:59
     */
    public StackDemo(){
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    /**
     * 功能描述: 
     * 〈压栈〉
     *
     * @params : [o]
     * @return : void
     * @author : cwl
     * @date : 2019/5/5 16:58
     */
    public void push(Object o){
        ensureCapacity();
        elements[size++] = o;
    }
    
    /**
     * 功能描述: 
     * 〈出栈〉
     *
     * @params : []
     * @return : java.lang.Object
     * @author : cwl
     * @date : 2019/5/5 16:59
     */
    public Object pop(){
        if (size == 0){
            throw new EmptyStackException();
        }
        return elements[size--];
    }
    
    /**
     * 功能描述: 
     * 〈自动扩容〉
     *
     * @params : []
     * @return : void
     * @author : cwl
     * @date : 2019/5/5 16:59
     */
    private void ensureCapacity() {
        if(elements.length == size){
            elements = Arrays.copyOf(elements,2*size+1);
        }
    }
}
  • pop方法修改如下
代码语言:javascript
复制
 public Object pop(){
        if (size == 0){
            throw new EmptyStackException();
        }
        elements[size] =null;//将出栈的元素设置为空引用
        return elements[size--];
 }
7.避免使用finally代码块

大多数情况下finally代码块是用来搭配一些资源的打开没有关闭,在这里进行最终的关闭的.但是finally代码块有时候并不会非常及时的去关闭资源,而且会极其消耗JVM的性能,但是如果如果需要搭配FileInputStream/FileOutputStream/Timer/Connection等,自带colse方法时,可以考虑避免文件过多的被打开而导致内存不足,而在finally代码块当中去执行这样的操作

8.覆盖equals时请遵守通用约定
  • 8.1 高质量覆盖equals方法诀窍
    • 使用==操作符检查参数是否为这个对象的引用,是则返回true
    • 使用instanceof操作符检查参数是否为正确类型.如果不是,则返回false
    • 把参数转换成正确的类型
    • 对于该类中的每个关键域,检查参数中的域是否与该对象中的对应的域相匹配
  • 8.2 覆盖equals时必须要做到的三件事
    • 覆盖equals时必须要覆盖hashCode
    • 不要企图让equals方法过于智能.不要把一些过于复杂的等价关系写到equals当中去
    • 不要讲equals声明中的Object对象替换为其他的类型
9.覆盖equals时总要覆盖hashCode

摘自Object规范[JavaSE6]第二条:

  • 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果

结论:如果没有覆盖hashCode方法,那么当使用HashMap,HashSet和HashTable这种键值对结构时比较就会不准确.详情请看以下代码

代码语言:javascript
复制
public class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final String lineNumber;

    public PhoneNumber(int areaCode, int prefix, String lineNumber) {
        rangeCheck(areaCode,999,"areaCode");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = lineNumber;
    }

    private void rangeCheck(int arg, int max, String name) {
        if(arg < 0 || arg > max){
            throw new IllegalArgumentException(name + ":" +arg);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PhoneNumber)) return false;
        PhoneNumber that = (PhoneNumber) o;
        return areaCode == that.areaCode &&
                prefix == that.prefix &&
                lineNumber.equals(that.lineNumber);
    }
}

当你想要尝试搭配map集合使用时,如果没有覆盖hashCode方法,那么当你用相同的对象去get集合Map当中的value值的时候,返回的将会是null,因为他们的hashCode值时不一样的,所以这个时候就需要我们去覆盖hashCode方法,这个方法涉及到hashCode值的计算方式,这里我们不做深究,建议直接调用父类的即可,在PhoneNumber当中添加代码示例1的方法

代码语言:javascript
复制
Map<PhoneNumber,String> numberStringMap = new HashMap<>();
        numberStringMap.put(new PhoneNumber(180,286,"6849"),"Jenny");
代码语言:javascript
复制
//代码示例1:
    @Override
    public int hashCode() {
        return Objects.hash(areaCode, prefix, lineNumber);
    }
10.始终要覆盖toString

这一条的目的非常简单,覆盖toString只是为了让我们更加方便观看而已,Object类的toString是全限定雷鸣+地址值

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 1.考虑用静态工厂方法代替构造器
      • 2.遇到多个构造器参数时考虑使用构建器
        • 3.用私有构造器或者枚举类型强化Singleton类型
          • 4.通过私有构造器强化不可实例化的能力
            • 5.避免创建不必要的对象
              • 6.消除过期的对象引用
                • 7.避免使用finally代码块
                  • 8.覆盖equals时请遵守通用约定
                    • 9.覆盖equals时总要覆盖hashCode
                      • 10.始终要覆盖toString
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档