从新开始写JAVA之 第一回

417 阅读4分钟

Java的历程源远流长,代码写作方式五花八门,从今天开始,重新学习 java。每篇3点, 2021年4月20日

对象的创建和销毁

  1. 用静态工厂方法代替构造器
  2. 遇到多个构造器参数时要考虑使用构建器
  3. 用私有构造器或者枚举类型强化Singleton属性

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

对于类而言,为了让客户端获取自身的一个实例,最传统的方法是提供一个公有的构造器,还有一个方法就是静态工厂方法(static factory method),它只是一个返回类的实例的静态方法,类似 Boolean中的一个对象引用 :

public static Boolean valueOf(boolean b) {
   return b? Boolean.TRUE : Boolean.FALSE;
}

注意,静态工厂方法(static factory method)和设计模式中的工厂方法(Factory Method)模式是不同的。

静态工厂方法与构造器不同的第一大优势是,它们都有名称。如果构造器的参数本身没有明确的描述被返回的对象,那么具有适当名称的静态工厂方法会更容易使用,产生的代码更有易读性

静态工厂方法与构造器不同的第二大优势在于,不必再每次调用它们的时候都创建一个新对象.这个可以使不可变的类可以预先构建好实例,或者把构建好的实例缓存起来,进行重复的利用,从而避免重复创建对象,例如Boolean.valueOf(boolean) 方法说明了这个技术.这种方式类似于享元模式,减少了创建对象的代价,可以使性能极大的提升.

静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象,这样在选择返回对象的类时就有了更大的灵活性,例如 API可以返回对象,同时不会使对象的类变成公有的。以这种隐藏方式会让API变得非常简洁。这项技术可以用于基于接口的框架(interface-based framework),因为这种框架中,接口为静态工厂方法提供了自然返回类型。例如 java.util.Collections

public class Collections {
  // Suppresses default constructor, ensuring non-instantiability.
  private Collections() {
  }
 public static <T> List<T> singletonList(T o) {
      return new SingletonList<>(o);
 }
 
 private static class SingletonList<E>
      extends AbstractList<E>
      implements RandomAccess, Serializable {

      private static final long serialVersionUID = 3093736618740652951L;
      .... //省略
  }
  ..... //等等方法
}

说明 : Collections是一个私有的类,不可以被初始化,但是内部的方法 singletonList方法返回了一个 new SimgletonList<>(T) 对象,但是 SimgletonList这个类是私有的

静态工厂方法的第四大优势在于,所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。 例如 :

public static User getUser(String name) {
        return new User().setName(name); // 通过传递不同的参数,就可以返回不同的对象
}

静态工厂的第五大优势在于,方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不参与。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如: JDBC(Java数据库连接API)。服务提供者框架是指这样一个系统: 多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。

缺点 1. 类如果不含公有的或者受保护的构造器,就不能被子类化

public static class B {
        private B(){
        }
        public static B getWhat() {
            return new B();
        }
    }
    public class b extends B {
    }
    new b(); // 错误 应为B并没有提供公有的构造方式,是用静态工厂的方法产出对象B

缺点 2. 我们很难发现它们,对于静态工厂方法来说,因为没有构造器,要想查明一个实例化的类是比较困难的

总的来说,构造器也好,静态工厂方法也好,各有各的好处,理解它们的长处,全面考虑,用到更合适的地方。

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

  1. 以前大多数人的实现
public  class Book {

        private Integer id;
        
        private String name;
        
        private List<Integer>  lists;
        
        public Book(Integer id,String name,List<Integer> lists) {
            this.id = id;
            this.name = name;
            this.lists = lists;
        }
    }

    private void test () {
        Book name = new Book(1, "name", Arrays.asList(1));
    }

过于复杂,对开发来说不是很友好

  1. 现在部分人的实现
public  class Book {

        private Integer id;

        private String name;

        private List<Integer>  lists;

        public void setId(Integer id) {
            this.id = id;
        }
        public void setName(String name) {
            this.name = name;
        }
        
        public void setLists(List<Integer> lists) {
            this.lists = lists;
        }
    }

    private void test () {
        Book book = new Book();
        book.setId(1);
        book.setName("name");
        book.setLists(new ArrayList<>(0));
    }

也还算比较不错,比如有很多个元素,就一直 book.setN

3.链式表达的实现

public  class Book {

        private Integer id;

        private String name;

        private List<Integer>  lists;

        public Book setId(Integer id) {
            this.id = id;
            return this;
        }

        public Book setName(String name) {
            this.name = name;
            return this;
        }

        public Book setLists(List<Integer> lists) {
            this.lists = lists;
            return this;
        }
    }

    private void test () {
        Book book = new Book().setId(1).setLists(new ArrayList<>(0)).setName("name");

    }

使用起来应该清爽了许多了,并且再次看代码,可读性也很高.基于Idea使用lombok注解,更简单

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

Singleton是指仅仅被实例化一次的类。Singleton通常被用来代表一个无状态的对象,或者是本质上唯一的系统组件。使类成为Singleton会使它的客户端测试变得非常困难,因为不可能给Singleton替换模拟实现,除非实现一个充当起类型的接口。

实现Singleton有两种常见的放阿飞。这两种方法都要求构造器一定是私有的,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例。

  • 第一种 :
public class Book {

   private long id;

   public static final Book INSTANCE = new Book().setId(System.currentTimeMillis());

   private Book() {

   }

   public static void main(String[] args) {
       Book b =  Book.INSTANCE;
       Book b1=  Book.INSTANCE;
       Book b2 =  Book.INSTANCE;
       System.out.println(b.hashCode());
       System.out.println(b1.hashCode());
       System.out.println(b2.hashCode());

       System.out.println(b.getId());
       System.out.println(b1.getId());
       System.out.println(b2.getId());
   }
 }

image.png 说明 : 私有构造方法只调用一次,来初始化公共静态 final Elvis.INSTANCE 属性。缺少一个公共的或受保护的构 造方法,保证了全局的唯一性:一旦 Elvis 类被初始化,一个 Elvis 的实例就会存在——不多也不少。客户端所做的 任何事情都不能改变这一点,但需要注意的是:特权客户端可以使用 AccessibleObject.setAccessible 方法,以反射方式调用私有构造方法

  • 第二种
public class Book {

    private long id;

    public static final Book INSTANCE = new Book().setId(System.currentTimeMillis());

    private Book() {

    }

    public static Book getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Book b = Book.getInstance();
        Book b1=  Book.getInstance();
        Book b2 =  Book.getInstance();

        System.out.println(b.hashCode());
        System.out.println(b1.hashCode());
        System.out.println(b2.hashCode());

        System.out.println(b.getId());
        System.out.println(b1.getId());
        System.out.println(b2.getId());
        
    }

image.png 说明: 所有对 getInstance 的调用都返回相同的对象引用,并且不会创建其他的 Book 实例

  • 第三种 基于多线程问题,有种更好的单例模式叫做Initialization Demand Holder (IoDH)的技术
public class Singleton {

    private Singleton(){
        System.out.println(Singleton.class);
    }
    
    private static class HolderClass{
        private final static Singleton s = new Singleton();
    }
    
    public static Singleton getInstance(){
        return HolderClass.s;
    }
   
    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
    }
    
}
  • 第四重 双重锁校验技术,没啥好说的,自行百度吧