精华内容
下载资源
问答
  • 学习Java的同学注意了!!... 学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:183993990 我们一起学Java! 一、考虑静态工厂方法代替构造器: ...

    学习Java的同学注意了!!! 
    学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:183993990  我们一起学Java!


    一、考虑用静态工厂方法代替构造器:
        
          构造器是创建一个对象实例最基本也最通用的方法,大部分开发者在使用某个class的时候,首先需要考虑的就是如何构造和初始化一个对象示例,而构造的方式首先考虑到的就是通过构造函数来完成,因此在看javadoc中的文档时首先关注的函数也是构造器。然而在有些时候构造器并非我们唯一的选择,通过反射也是可以轻松达到的。我们这里主要提到的方式是通过静态类工厂的方式来创建class的实例,如:

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

          静态工厂方法和构造器不同有以下主要优势:
          1.    有意义的名称。
          在框架设计中,针对某些工具类通常会考虑dummy对象或者空对象以辨别该对象是否已经被初始化,如我曾在我的C++基础库中实现了String类型,见如下代码:

    复制代码
     1     void showExample() {
     2         String strEmpty = String::empty();
     3         String strEmpty2 = "";
     4         String strData = String::prellocate(1024);
     5         if (strEmpty.isEmpty()) {
     6             //TODO: do something
     7         }
     8     }
     9     static String String::emptyString;
    10     String& String::empty() {
    11         return emptyString;
    12     }
    13      
    14     bool String::isEmpty() {
    15         if (this->_internal == &emptyString->_internal)
    16             return true;
    17         //TODO: do other justice to verify whether it is empty.
    18     }
    复制代码

          在上面的代码中,提供了两个静态工厂方法empty和preallocate用于分别创建一个空对象和一个带有指定分配空间的String对象。从使用方式来看,这些静态方法确实提供了有意义的名称,使用者很容易就可以判断出它们的作用和应用场景,而不必在一组重载的构造器中去搜寻每一个构造函数及其参数列表,以找出适合当前场景的构造函数。从效率方面来讲,由于提供了唯一的静态空对象,当判读对象实例是否为空时(isEmpty),直接使用预制静态空对象(emptyString)的地址与当前对象进行比较,如果是同一地址,即可确认当前实例为空对象了。对于preallocate函数,顾名思义,该函数预分配了指定大小的内存空间,后面在使用该String实例时,不必担心赋值或追加的字符过多而导致频繁的realloc等操作。    
          2.    不必在每次调用它们的时候创建一个新的对象。
          还是基于上面的代码实例,由于所有的空对象都共享同一个静态空对象,这样也节省了更多的内存开销,如果是strEmpty2方式构造出的空对象,在执行比较等操作时会带来更多的效率开销。事实上,Java在String对象的实现中,使用了常量资源池也是基于了同样的优化策略。该优势同样适用于单实例模式。  
          3.    可以返回原返回类型的任何子类型。
          在Java Collections Framework的集合接口中,提供了大量的静态方法返回集合接口类型的实现类型,如Collections.subList()、Collections.unmodifiableList()等。返回的接口是明确的,然而针对具体的实现类,函数的使用者并不也无需知晓。这样不仅极大的减少了导出类的数量,而且在今后如果发现某个子类的实现效率较低或者发现更好的数据结构和算法来替换当前实现子类时,对于集合接口的使用者来说,不会带来任何的影响。本书在例子中提到EnumSet是通过静态工厂方法返回对象实例的,没有提供任何构造函数,其内部在返回实现类时做了一个优化,即如果枚举的数量小于64,该工厂方法将返回一个经过特殊优化的实现类实例(RegularEnumSet),其内部使用long(64bits在Java中) 中的不同位来表示不同的枚举值。如果枚举的数量大于64,将使用long的数组作为底层支撑。然而这些内部实现类的优化对于使用者来说是透明的。 
          4.    在创建参数化类型实例的时候,它们使代码变得更加简洁。
          Map<String,String> m = new HashMap<String,String>();
          由于Java在构造函数的调用中无法进行类型的推演,因此也就无法通过构造器的参数类型来实例化指定类型参数的实例化对象。然而通过静态工厂方法则可以利用参数类型推演的优势,避免了类型参数在一次声明中被多次重写所带来的烦忧,见如下代码:
          public static <K,V> HashMap<K,V> newInstance() {
              return new HashMap<K,V>();
          }
          Map<String,String> m = MyHashMap.newInstance();

        
    二、遇到多个构造参数时要考虑用构建器(Builder模式):
        
          如果一个class在构造初始化的时候存在非常多的参数,将会导致构造函数或者静态工厂函数带有大量的、类型相同的函数参数,特别是当一部分参数只是可选参数的时候,class的使用者不得不为这些可选参数也传入缺省值,有的时候会发现使用者传入的缺省值可能是有意义的,而并非class内部实现所认可的缺省值,比如某个整型可选参数,通常使用者会传入0,然后class内部的实现恰恰认为0是一种重要的状态,而该状态并不是该调用者关心的,但是该状态却间接导致其他状态的改变,因而带来了一些潜在的状态不一致问题。与此同时,过多的函数参数也给使用者的学习和使用带来很多不必要的麻烦,我相信任何使用者都希望看到class的接口是简单易用、函数功能清晰可见的。在Effective C++中针对接口的设计有这样的一句话:"接口要完满而最小化"。针对该类问题通常会考虑的方法是将所有的参数归结到一个JavaBean对象中,实例化这个Bean对象,然后再将实例化的结果传给这个class的构造函数,这种方法仍然没有避免缺省值的问题。该条目推荐了Builder模式来创建这个带有很多可选参数的实例对象。

    复制代码
     1     class NutritionFacts {
     2         private final int servingSize;
     3         private final int servings;
     4         private final int calories;
     5         private final int fat;
     6         private final int sodium;
     7         private final int carbohydrate;
     8         public static class Builder {
     9             //对象的必选参数
    10             private final int servingSize;
    11             private final int servings;
    12             //对象的可选参数的缺省值初始化
    13             private int calories = 0;
    14             private int fat = 0;
    15             private int carbohydrate = 0;
    16             private int sodium = 0;
    17             //只用少数的必选参数作为构造器的函数参数
    18             public Builder(int servingSize,int servings) {
    19                 this.servingSize = servingSize;
    20                 this.servings = servings;
    21             }
    22             public Builder calories(int val) {
    23                 calories = val;
    24                 return this;
    25             }
    26             public Builder fat(int val) {
    27                 fat = val;
    28                 return this;
    29             }
    30             public Builder carbohydrate(int val) {
    31                 carbohydrate = val;
    32                 return this;
    33             }
    34             public Builder sodium(int val) {
    35                 sodium = val;
    36                 return this;
    37             }
    38             public NutritionFacts build() {
    39                 return new NutritionFacts(this);
    40             }
    41         }
    42         private NutritionFacts(Builder builder) {
    43             servingSize = builder.servingSize;
    44             servings = builder.servings;
    45             calories = builder.calories;
    46             fat = builder.fat;
    47             sodium = builder.sodium;
    48             carbohydrate = builder.carbohydrate;
    49         }
    50     }
    51     //使用方式
    52     public static void main(String[] args) {
    53         NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100)
    54             .sodium(35).carbohydrate(27).build();
    55         System.out.println(cocaCola);
    56     }
    复制代码

          对于Builder方式,可选参数的缺省值问题也将不再困扰着所有的使用者。这种方式还带来了一个间接的好处是,不可变对象的初始化以及参数合法性的验证等工作在构造函数中原子性的完成了。

    三、用私有构造器或者枚举类型强化Singleton属性:

          对于单实例模式,相信很多开发者并不陌生,然而如何更好更安全的创建单实例对象还是需要一些推敲和斟酌的,在Java中主要的创建方式有以下三种,我们分别作出解释和适当的比较。
          1.    将构造函数私有化,直接通过静态公有的final域字段获取单实例对象:

    1     public class Elvis {
    2         public static final Elvis INSTANCE = new Elvis();
    3         private Elivs() { ... }
    4         public void leaveTheBuilding() { ... }
    5     }

          这样的方式主要优势在于简洁高效,使用者很快就能判定当前类为单实例类,在调用时直接操作Elivs.INSTANCE即可,由于没有函数的调用,因此效率也非常高效。然而事物是具有一定的双面性的,这种设计方式在一个方向上走的过于极端了,因此他的缺点也会是非常明显的。如果今后Elvis的使用代码被迁移到多线程的应用环境下了,系统希望能够做到每个线程使用同一个Elvis实例,不同线程之间则使用不同的对象实例。那么这种创建方式将无法实现该需求,因此需要修改接口以及接口的调用者代码,这样就带来了更高的修改成本。
          2.    通过公有域成员的方式返回单实例对象:

    1     public class Elvis {
    2         public static final Elvis INSTANCE = new Elvis();
    3         private Elivs() { ... }
    4         public static Elvis getInstance() { return INSTANCE; }
    5         public void leaveTheBuilding() { ... }
    6     }

          这种方法很好的弥补了第一种方式的缺陷,如果今后需要适应多线程环境的对象创建逻辑,仅需要修改Elvis的getInstance()方法内部即可,对用调用者而言则是不变的,这样便极大的缩小了影响的范围。至于效率问题,现今的JVM针对该种函数都做了很好的内联优化,因此不会产生因函数频繁调用而带来的开销。
          3.    使用枚举的方式(Java SE5):

    1     public enum Elvis {
    2         INSTANCE;
    3         public void leaveTheBuilding() { ... }
    4     }

          就目前而言,这种方法在功能上和公有域方式相近,但是他更加简洁更加清晰,扩展性更强也更加安全。
          我在设计自己的表达式解析器时,曾将所有的操作符设计为enum中不同的枚举元素,同时提供了带有参数的构造函数,传入他们的优先级、操作符名称等信息。

    四、通过私有构造器强化不可实例化的能力:

          对于有些工具类如java.lang.Math、java.util.Arrays等,其中只是包含了静态方法和静态域字段,因此对这样的class实例化就显得没有任何意义了。然而在实际的使用中,如果不加任何特殊的处理,这样的classes是可以像其他classes一样被实例化的。这里介绍了一种方式,既将缺省构造函数设置为private,这样类的外部将无法实例化该类,与此同时,在这个私有的构造函数的实现中直接抛出异常,从而也避免了类的内部方法调用该构造函数。

    1     public class UtilityClass {
    2         //Suppress default constructor for noninstantiability.
    3         private UtilityClass() {
    4             throw new AssertionError();
    5         }
    6     }

          这样定义之后,该类将不会再被外部实例化了,否则会产生编译错误。然而这样的定义带来的最直接的负面影响是该类将不能再被子类化。
        
    五、避免创建不必要的对象:

          试比较以下两行代码在被多次反复执行时的效率差异:
          String s = new String("stringette");
          String s = "stringette";

          由于String被实现为不可变对象,JVM底层将其实现为常量池,既所有值等于"stringette" 的String对象实例共享同一对象地址,而且还可以保证,对于所有在同一JVM中运行的代码,只要他们包含相同的字符串字面常量,该对象就会被重用。
        
          我们继续比较下面的例子,并测试他们在运行时的效率差异:
          Boolean b = Boolean.valueOf("true");
          Boolean b = new Boolean("true");
          前者通过静态工厂方法保证了每次返回的对象,如果他们都是true或false,那么他们将返回相同的对象。换句话说,valueOf将只会返回Boolean.TRUE或Boolean.FALSE两个静态域字段之一。而后面的Boolean构造方式,每次都会构造出一个新的Boolean实例对象。这样在多次调用后,第一种静态工厂方法将会避免大量不必要的Boolean对象被创建,从而提高了程序的运行效率,也降低了垃圾回收的负担。  
          继续比较下面的代码:

    复制代码
     1     public class Person {
     2         private final Date birthDate;
     3         //判断该婴儿是否是在生育高峰期出生的。
     4         public boolean isBabyBoomer {
     5             Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
     6             c.set(1946,Calendar.JANUARY,1,0,0,0);
     7             Date dstart = c.getTime();
     8             c.set(1965,Calendar.JANUARY,1,0,0,0);
     9             Date dend = c.getTime();
    10             return birthDate.compareTo(dstart) >= 0 && birthDate.compareTo(dend) < 0;
    11         }
    12     }
    13     
    14     public class Person {
    15         private static final Date BOOM_START;
    16         private static final Date BOOM_END;
    17         
    18         static {
    19             Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    20             c.set(1946,Calendar.JANUARY,1,0,0,0);
    21             BOOM_START = c.getTime();
    22             c.set(1965,Calendar.JANUARY,1,0,0,0);
    23             BOOM_END = c.getTime();
    24         }
    25         public boolean isBabyBoomer() {
    26             return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
    27         }
    28     }
    复制代码

          改进后的Person类只是在初始化的时候创建Calender、TimeZone和Date实例一次,而不是在每次调用isBabyBoomer方法时都创建一次他们。如果该方法会被频繁调用,效率的提升将会极为显著。
          集合框架中的Map接口提供keySet方法,该方法每次都将返回底层原始Map对象键数据的视图,而并不会为该操作创建一个Set对象并填充底层Map所有键的对象拷贝。因此当多次调用该方法并返回不同的Set对象实例时,事实上他们底层指向的将是同一段数据的引用。
          在该条目中还提到了自动装箱行为给程序运行带来的性能冲击,如果可以通过原始类型完成的操作应该尽量避免使用装箱类型以及他们之间的交互使用。见下例:

    复制代码
    1     public static void main(String[] args) {
    2         Long sum = 0L;
    3         for (long i = 0; i < Integer.MAX_VALUE; ++i) {
    4             sum += i;
    5         }
    6         System.out.println(sum);
    7     }
    复制代码

          本例中由于错把long sum定义成Long sum,其效率降低了近10倍,这其中的主要原因便是该错误导致了2的31次方个临时Long对象被创建了。

    六、消除过期的对象引用:

          尽管Java不像C/C++那样需要手工管理内存资源,而是通过更为方便、更为智能的垃圾回收机制来帮助开发者清理过期的资源。即便如此,内存泄露问题仍然会发生在你的程序中,只是和C/C++相比,Java中内存泄露更加隐匿,更加难以发现,见如下代码:

    复制代码
     1     public class Stack {
     2         private Object[] elements;
     3         private int size = 0;
     4         private static final int DEFAULT_INITIAL_CAPACITY = 16;
     5         public Stack() {
     6             elements = new Object[DEFAULT_INITIAL_CAPACITY];
     7         }
     8         public void push(Object e) {
     9             ensureCapacity();
    10             elements[size++] = e;
    11         }
    12         public Object pop() {
    13             if (size == 0) 
    14                 throw new EmptyStackException();
    15             return elements[--size];
    16         }
    17         private void ensureCapacity() {
    18             if (elements.length == size)
    19                 elements = Arrays.copys(elements,2*size+1);
    20         }
    21     }
    复制代码

          以上示例代码,在正常的使用中不会产生任何逻辑问题,然而随着程序运行时间不断加长,内存泄露造成的副作用将会慢慢的显现出来,如磁盘页交换、OutOfMemoryError等。那么内存泄露隐藏在程序中的什么地方呢?当我们调用pop方法是,该方法将返回当前栈顶的elements,同时将该栈的活动区间(size)减一,然而此时被弹出的Object仍然保持至少两处引用,一个是返回的对象,另一个则是该返回对象在elements数组中原有栈顶位置的引用。这样即便外部对象在使用之后不再引用该Object,那么它仍然不会被垃圾收集器释放,久而久之导致了更多类似对象的内存泄露。修改方式如下:

    复制代码
    1     public Object pop() {
    2         if (size == 0) 
    3             throw new EmptyStackException();
    4         Object result = elements[--size];
    5         elements[size] = null; //手工将数组中的该对象置空
    6         return result;
    7     }
    复制代码

          由于现有的Java垃圾收集器已经足够只能和强大,因此没有必要对所有不在需要的对象执行obj = null的显示置空操作,这样反而会给程序代码的阅读带来不必要的麻烦,该条目只是推荐在以下3中情形下需要考虑资源手工处理问题:
          1)    类是自己管理内存,如例子中的Stack类。
          2)    使用对象缓存机制时,需要考虑被从缓存中换出的对象,或是长期不会被访问到的对象。
          3)    事件监听器和相关回调。用户经常会在需要时显示的注册,然而却经常会忘记在不用的时候注销这些回调接口实现类。
        
    七、避免使用终结方法:

          任何事情都存在其一定的双面性或者多面性,对于C++的开发者,内存资源是需要手工分配和释放的,而对于Java和C#这种资源托管的开发语言,更多的工作可以交给虚拟机的垃圾回收器来完成,由此C++程序得到了运行效率,却失去了安全。在Java的实际开发中,并非所有的资源都是可以被垃圾回收器自动释放的,如FileInputStream、Graphic2D等class中使用的底层操作系统资源句柄,并不会随着对象实例被GC回收而被释放,然而这些资源对于整个操作系统而言,都是非常重要的稀缺资源,更多的资源句柄泄露将会导致整个操作系统及其运行的各种服务程序的运行效率直线下降。那么如何保证系统资源不会被泄露了?在C++中,由于其资源完全交由开发者自行管理,因此在决定资源何时释放的问题上有着很优雅的支持,C++中的析构函数可以说是完成这一工作的天然候选者。任何在栈上声明的C++对象,当栈退出或者当前对象离开其作用域时,该对象实例的析构函数都会被自动调用,因此当函数中有任何异常(Exception)发生时,在栈被销毁之前,所有栈对象的析构函数均会被自动调用。然而对于Java的开发者而言,从语言自身视角来看,Java本身并未提供析构函数这样的机制,当然这也是和其资源被JVM托管有一定关系的。
          在Java中完成这样的工作主要是依靠try-finally机制来协助完成的。然而Java中还提供了另外一种被称为finalizer的机制,使用者仅仅需要重载Object对象提供的finalize方法,这样当JVM的在进行垃圾回收时,就可以自动调用该方法。但是由于对象何时被垃圾收集的不确定性,以及finalizer给GC带来的性能上的影响,因此并不推荐使用者依靠该方法来达到关键资源释放的目的。比如,有数千个图形句柄都在等待被终结和回收,可惜的是执行终结方法的线程优先级要低于普通的工作者线程,这样就会有大量的图形句柄资源停留在finalizer的队列中而不能被及时的释放,最终导致了系统运行效率的下降,甚至还会引发JVM报出OutOfMemoryError的错误。
          Java的语言规范中并没有保证该方法会被及时的执行,甚至都没有保证一定会被执行。即便开发者在code中手工调用了System.gc和System.runFinalization这两个方法,这仅仅是提高了finalizer被执行的几率而已。还有一点需要注意的是,被重载的finalize()方法中如果抛出异常,其栈帧轨迹是不会被打印出来的。在Java中被推荐的资源释放方法为,提供显式的具有良好命名的接口方法,如FileInputStream.close()和Graphic2D.dispose()等。然后使用者在finally区块中调用该方法,见如下代码:

    复制代码
    1     public void test() {
    2         FileInputStream fin = null;
    3         try {
    4             fin = new FileInputStream(filename);
    5             //do something.
    6         } finally {
    7             fin.close();
    8         }
    9     }
    复制代码

          那么在实际的开发中,利用finalizer又能给我们带来什么样的帮助呢?见下例:

    复制代码
     1     public class FinalizeTest {
     2         //@Override
     3         protected void finalize() throws Throwable {
     4             try {
     5                 //在调试过程中通过该方法,打印对象在被收集前的各种状态,
     6 //如判断是否仍有资源未被释放,或者是否有状态不一致的现象存在。
     7 //推荐将该finalize方法设计成仅在debug状态下可用,而在release
     8 //下该方法并不存在,以避免其对运行时效率的影响。
     9                 System.out.println("The current status: " + _myStatus);
    10             } finally {
    11                 //在finally中对超类finalize方法的调用是必须的,这样可以保证整个class继承
    12 //体系中的finalize链都被执行。
    13                 super.finalize(); 
    14             }
    15         }
    16     }
    复制代码

    学习Java的同学注意了!!! 
    学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:183993990  我们一起学Java!

    转载于:https://my.oschina.net/abcijkxyz/blog/851400

    展开全文
  • 创建和销毁对象(JAVA)

    2011-02-22 11:09:20
    总叙述:什么时候、如何创建对象什么时候、如何避免创建对象;如何保证对象能够适时地销毁对象销毁之前如何管理各种清理工作。 item1:考虑静态工厂方法代替构造函数 类可以提供一个公有的静态工厂方法,...

    总叙述:什么时候、如何创建对象;什么时候、如何避免创建对象;如何保证对象能够适时地销毁;对象被销毁之前如何管理各种清理工作。

    item1:考虑用静态工厂方法代替构造函数

    类可以提供一个公有的静态工厂方法,实际上只是一个简单的静态方法,它返回的是类的一个实例。
    例如:Boolean类的简单例子<他把一个boolean原语值转换为一个Boolean对象引用。
    public static Boolean valueOf(boolean b){
        return (b ? Boolean.TRUE : Boolean.FALSE);
    }
    类可以为他的客户提供一些静态工厂的方法,来替代构造函数,或者同时也提供一些构造函数。用静态工厂方法来代替公有的构造函数,就有好处也有不足之处。


    静态工厂方法的一个好处是,与构造函数不同,静态工厂方法具有名字。如果一个构造函数的参数没有确切地描述被返回的对象,那么选用适当名字的静态工厂方法可以使一个类更易于使用,并且相应的客户代码更易于阅读。一个类只能有一个原型相同的构造函数。那么我们怎样绕开这个限制呢,他可以提供两个共造函数,它们的参数列表只是在参数类型的顺序上有所不同。这并不是一个好主意,面对这样的API,用户永远也记不住该用哪个构造函数,如果常常会调用到错误的构造函数上。并且,读到使用这样的构造函数的代码时往往会不知所云,除非去查看该类的文档。
    因为静态工厂方法有自己的名字,所以它们没有构造函数那样的限制,对于给定的原型特征可以有不止一个静态工厂方法。如果一个类看起来需要多个构造函数,并且它们的原型特征相同,那么你应该考虑用静态工厂方法来代替其中一个或多个构造函数,并且慎重选择他们的名字以便明显地标出他们的不同。

     


    静态工厂方法的第二个好处是,与构造函数不同,它们每次被调用的时候,不要求非得创建一个新的对象。这使得一些非可变类可以使用一个预先构造好的实例,或者把已经构造好的实例缓存起来,以后再把这些实例分发给用户,从而避免创建不必要的重复对象。Boolean.valueOf(boolean)方法说明了这项技术:它从来不创建对象。如果一个程序要频繁地创建相同的对象,并且创建对象的代价很昂贵,则这项技术可以极大地提高性能。
    静态工厂方法可以为重复的调用返回同一个对象,这也可以被用来控制“在某一时刻哪些实例应该存在”。这样做有两个理由:第一,他使得一个类可以保证是一个 singleton。第二,他使非可变类可以保证“不会有两个相等的实例存在”,即当且仅当a==b的时候才有a.equals(b)为true。如果 一个类保证了这一点,那么它的客户就可以用==操作符来代替equals(Object)方法,其结果是实质性的性能提高。


    静态工厂方法的第三个好处是,与构造函数不同,它们可以返回一个原返回类型的子类型的对象。使用这样的静态工厂方法,可以强迫客户通过接口来引用被返回的对象,而不是通过实现类来引用被返回的对象,这是一个很好的习惯。


    公有的静态工厂方法所返回的对象的类不仅可以是非公有的,而且该类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值,只要是已声明的返回类型的子类型,都是允许的,而且,为了增强软件的可维护性,返回对象的类型也可以随着不同的发行版本而不同。

    静态工厂方法返回 的对象所属的类,在编写包含该静态工厂方法的类时可以并不存在。


    静态工厂方法的主要缺点是,类如果不含公有的或者受保护的构造函数,就不能被子类化。

    静态工厂方法的第二个缺点是,它们与其他的静态方法没有任何区别。

    总的来说,静态工厂方法和公有的构造函数方法都有他们各自的用途,我们需要理解他们各自的长处。如果你正在权衡这两种选择,又没有其他因素强烈地影响你的选择,那么你最好还是简单地使用构造函数,毕竟他是语言提供的规范。

     

    item2.使用私有构造函数强化singleton属性

    singleton是这样的类,它只能实例化一次。singleton通常被用来代表那些本质上具有惟一性的系统组件,比如视频显示或者文件系统。

    实现singleton有两种方法,这两种方法都要把构造函数保持为私有的,并且提供一个静态成员,以便允许客户能够访问该类惟一的实例:在第一种方法中,公有静态成员是一个final域:

    public class Elvis{
        public static final Elvis INSTANCE =new Elvis();
        private Elvis(){
        }
    }

    私有构造函数仅被调用一次,用来实例化公有静态final域Elvis.INSTANCE。由于缺少公有的或者受保护的构造函数,所以保证了 Elvis的全局惟一性。一旦Elvis类被实例化之后,只有一个Elvis实例存在--不多也不少。客户的任何行为都不会改变这一点。


    第二种方法提供了一个私有的静态工厂方法,而不是公有的静态final域:
    public class Elvis{
       private static final Elvis INSTANCE =new Elvis();
       private Elvis(){}
       public static Elvis getInstance(){
           return INSTANCE;
       }
    }
    所有对于静态方法Elvis.getInstance的调用,都会返回同一个对象的引用,所以,不会有别的Elvis实例被创建。

    第一种方法主要好处是:组成类的成员的声明很清楚地表明了这个类是一个singleton。公有的静态域是final的,所以该域将总是包含相同的对象引用。第一种方法可能在性能上稍微领先,但是在第二种方法中,一个优秀的JVM实现应该能够通过将静态工厂方法的调用内联化,来消除这种差别。

    第二种方法的主要好处在于,他提供了灵活性:在不改变API的前提下,允许我们改变想法,把该类作成singleton,或者不做singleton.sigleton的静态工厂方法返回该类的惟一实例,但是,他也很容易被修改,比如说,为每个调用该方法的线程返回一个惟一的实例。

    总而言之,如果你确信该类将永远是一个singleton,那么使用第一种方法是由意义的。如果你想保留一点余地,那么请使用第二种方法。

     

    item3: 通过私有构造函数强化不可实例化的能力

    由于只有当一个类不包含显式的构造函数的时候,编译器才会生成默认构造函数,所以,我们只要让这个类包含单个显式的私有构造函数,则它就不可被实例化了。

    public class UtilityClass{
        private UtilityClass(){}
    }

    因为显式构造函数是私有的,所以在该类的外部它是不可被访问的。假设该构造函数不会被类自身从内部调用,就能保证该类永远不会被实例化。
    这种习惯用法也有副作用,它使得一个类不能被子类化。所有的构造函数都必须要调用一个可访问的超类构造函数,无论显式或隐式地调用,在这种情况下,子类就没有可访问的构造函数来调用了。


    item4:避免创建重复的对象
    重复使用同一个对象,而不是每次需要的时候就创建一个功能上等价的新对象,通常前者更为合适。
    例如:
    String s=new String("silly");
    该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作没有一个是真正必需的.传递给String构造函数的实参 ("silly")本身就是一个String实例,功能上等同于所有的构造函数创建的对象。如果这种用法是在一个循环中,或者是在一个被频繁调用的方法中,那么成千上万不必要的String实例会被创建出来。

    改进版本:String s="no longer silly";

    这个版本只使用一个String实例,而不是每次执行的时候创建一个新的实例。而且,它可以保证,对于所有在同一个虚拟机中运行的代码,只要它们包含相同的字符串常量,则该对象就会被重用。

    对于提供了静态工厂方法和构造函数的非可变类,你通常可以利用静态工厂方法而不是构造函数,以避免创建重复的对象。例如,静态工厂方法 Boolean.valueOf(String)几乎总是优先于构造函数Boolean(String)。构造函数在每次调用的时候都会创建一个新的对象,而静态工厂方法从来不要求这样做。

    item5:消除过期的对象引用


    对于一种具有垃圾回收功能的语言,我们工作可以很容易,因为当我们用完了对象之后,它们会被自动回收,但就认为自己不再需要考虑内存管理的事情了,实际上这是不正确的。
    例如:
    public class Stack{
       private Object[] elements;
       private int size=0;
       
       public Stack(int initialCapacity){
           this.elements=new Object[initialCapacity];
       }
      
       public void push(Object e){
            ensureCapacity();
            elements[size++]=e;
       }

       public Object pop(){
           if(size==0) throw new EmptyStackException();
           return elements[--size];
       }

       private void ensureCapacity(){
           if(elements.length==size){
               Object[] oldElements=elements;
               elements=new Object[2*elements.length + 1];
               System.arraycopy(oldElements,0,elements,0,size);
           }
       }
    }

    分析:这个程序没有很显然的错误。无论你如何测试,它都会成功地通过你的每一项测试,但是,这个程序中潜伏着一个问题。不严格地讲,这个程序有一个 “内存泄露”,随着垃圾回收器活动的增加,或者由于不断增加的内存占用,程序性能的降低会逐渐表现出来。那么,程序中哪里会发生内存泄露呢?如果一个栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的客户程序不再应用这些对象,它们也不会被回收。这是因为,栈内部维护着这些对象的过期引用。所谓过期引用,是指永远也不会被解除的引用。在本例中,凡是在elements数组的“活动区域”之外的引用都是过期的,elements的活动区域是指下标小于size的那一部分。

    在支持垃圾回收的语言中,内存泄露是很隐蔽的,如果一个对象引用被无意识地保留起来了,那么,垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象引用的所有其他对象。即使只有少量的几个对象引用被无意识地保留下来,也会有很多的对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。

    要想修复这一类问题也很简单:一旦对象引用已经过期,只需清空这些引用即可。在上述例子的Stack类中,只要一个单元被弹出栈,指向它的引用就过期了。pop方法的修订版本如下所示:
    public Object pop(){  
         if(size==0) throw new EmptyStackException();
         Object result=elements[--size];
         elements[size]=null;//Eliminate obsolete reference
         return result;
    }

    清空过期引用的另一个好处是,如果它们在以后又被错误地解除引用、则程序会立即抛出NullPointerException异常,而不是悄悄地错误运行下去。尽可能早地检测出程序中的错误总是有益的。


    对于一个对象的引用,一旦程序不再使用到它,就把他清空。这样做既没必要,也不是我们所期望的,因为这样做会把程序代码弄得很乱,并且可以想象还会降低程序的性能。“清空对象引用”这样的操作应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是重用一个本来已经包含对象引用的变量,或者让这个变量结束其生命周期。如果你是在最紧凑的作用域范围内定义每一个变量,则这种情形就会自然而然地发生。应该注意到,在目前的JVM实现平台上,紧紧退出定义变量的代码是不够的,要想使引用消失,必须退出包含该变量的方法。

    那么,何时应该清空一个引用呢?Stack类的哪个方面特性使得它遭受了内存泄露的影响?简而言之,问题在于,Stack类自己管理内存。存储池包含了elements数组的元素。数组的活动区域中的元素是已分配的,而数组其余部分的元素是自由的。但是垃圾回收器并不知道这一点;对于垃圾回收器而言,elements数组中的所有对象引用都同等有效,只有程序员知道数组的非活动区域是不重要的。程序员可以把这个情况告知垃圾回收器,做法很简单:一旦数组元素变成了非活动区域的一部分,程序员就手工清空这些元素。


    一般而言,只要一个类自己管理它的内存,程序员就应该警惕内存泄露问题,一旦一个元素被释放掉,则该元素中包含的任何对象引用应该被清空。

    内存泄露的另一个常见来源是缓存。一旦你把一个对象引用放到一个缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。对于这个问题,有两种可能的解决方案。如果你正巧要实现这样的缓存:只要在缓存之外存在对某个条目的键的引用,该条目就有意义,那么你可以使用 WeakHashMap来代表缓存;当缓存中的条目过期之后,它们会自动被删除。而更为常见的情形是,“被缓存的条目是否有意义”的周期并不很容易确定,其中的条目会在运行的过程中变得越来越没有价值。在这样的情况下,缓存应该时不时地清除掉无用的条目。这项清除工作可以由一个后台线程来完成,或者也可以在加入新条目的时候做清理工作。


    item6:避免使用终结函数


    终结函数通常是不可预测的,常常也是很危险的,一般情况下是不必要的。使用终结函数会导致不稳定的行为、更差的性能,以及带来移植性问题。当然终结函数也有可用之处,经验告诉我们:应该避免使用终结函数。

    时间关键的任务不应该由终结函数来完成。因为从一个对象变得不可达到开始,到它的终结函数被执行,这段时间的长度是任意的、不确定的。

    我们不应该依赖一个终结函数来更新关键性的永久状态。

     


    //考虑用静态工厂方法代替构造函数
    import java.util.*;
    // Provider framework sketch
    public abstract class Foo {
         // Maps String key to corresponding Class object
         private static Map implementations = null;

         // Initializes implementations map the first time it's called
         private static synchronized void initMapIfNecessary() {
             if (implementations == null) {
                 implementations = new HashMap();
                 // Load implementation class names and keys from
                 // Properties file, translate names into Class
                 // objects using Class.forName and store mappings.
                 // ...
             }
         }
         public static Foo getInstance(String key) {
             initMapIfNecessary();
             Class c = (Class) implementations.get(key);
             if (c == null)
                 return new DefaultFoo();
             try {
                 return (Foo) c.newInstance();
             } catch (Exception e) {
                 return new DefaultFoo();
             }
         }
         public static void main(String[] args) {
             System.out.println(getInstance("NonexistentFoo"));
         }
    }
    class DefaultFoo extends Foo {
    }


    //使用私有构造函数来强化singleton属性的两种方法
    public class Elvis {
         public static final Elvis INSTANCE = new Elvis();
         private Elvis() {        
         }    

         public static void main(String[] args) {
             System.out.println(Elvis.INSTANCE);
         }
    }

     

    public class Elvis {
         private static final Elvis INSTANCE = new Elvis();

         private Elvis() {
         }

         public static Elvis getInstance() {
             return INSTANCE;
         }
         public static void main(String[] args) {
             System.out.println(Elvis.INSTANCE);
         }
    }


    //为了让一个singleton类变成可序列化的,仅在声明中加上"implements Serializsble"是不够的,为了维护singleton性,必须也要

    提供一个readResolve方法。否则的话,一个序列化的实例在每次反序列化的时候,都会导致创建一个新的实例。
    import java.io.*;
    public class Elvis {
         public static final Elvis INSTANCE = new Elvis();
         private Elvis() {
             // ...
         }
         // ...   // Remainder omitted
         // readResolve method to preserve singleton property
         private Object readResolve() throws ObjectStreamException {
             /*
              * Return the one true Elvis and let the garbage collector
              * take care of the Elvis impersonator.
              */
             return INSTANCE;
         }
         public static void main(String[] args) {
             System.out.println(Elvis.INSTANCE);
         }
    }

     

    //通过私有强化不可实例化的能力
    public class UtilityClass {

         // Suppress default constructor for noninstantiability
         private UtilityClass() {
             // This constructor will never be invoked
         }

         // ...   // Remainder omitted
    }

     

    //消除过期的对象引用
    import java.util.*;
    // Can you spot the "memory leak"?
    public class Stack {
         private Object[] elements;
         private int size = 0;

         public Stack(int initialCapacity) {
             this.elements = new Object[initialCapacity];
         }

         public void push(Object e) {
             ensureCapacity();
             elements[size++] = e;
         }

         public Object pop() {
             if (size==0)
                 throw new EmptyStackException();
             Object result = elements[--size];
             elements[size] = null; // Eliminate obsolete reference
             return result;
         }

         /**
          * Ensure space for at least one more element, roughly
          * doubling the capacity each time the array needs to grow.
          */
         private void ensureCapacity() {
             if (elements.length == size) {
                 Object[] oldElements = elements;
                 elements = new Object[2 * elements.length + 1];
                 System.arraycopy(oldElements, 0, elements, 0, size);
             }
         }

         public static void main(String[] args) {
             Stack s = new Stack(10);
             for (int i=0; i<args.length; i++)
                 s.push(args[i]);
             for (int i=0; i<args.length; i++)
                 System.out.println(s.pop());
         }
    }

    展开全文
  • 什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化。 比如说 public class StaticFactory { private String name; private Integer age; public StaticFactory(){ System.out.print...

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

    什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化。
    比如说

    public class StaticFactory {
        
        private String name;
        private Integer age;
        
        public StaticFactory(){
            System.out.println("hello");
        }
        
        public static StaticFactory initAndGetObject(String name,int age){
            StaticFactory sf = new StaticFactory();
            sf.setAge(age);
            sf.setName(name);
            return sf;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }   
    
    }

    用这种方法确实可以代替构造器。
    静态工厂方法相比于构造器的优点之一在于:它们有名称。比如我可以随便起名称getObject,getInstance,getStaticFactoryObject,而如果使用构造器的话,就只能是StaticFactory()或者带不同参数的构造器。

    优点之二在于,可以不用每次调用的时候都创建一个新对象,这里就使用到了单例。

    转载于:https://www.cnblogs.com/fonxian/p/5866997.html

    展开全文
  • 创建和销毁对象

    2020-07-27 08:50:00
    第二章:创建和销毁对象 1,静态方法类替代构造器 为什么要使用静态方法而不是public构造器: 优点: 1,静态方法是有名字的,构造器只能根据参数来决定返回实例的类型.但是通过静态工厂方法就可以避开这个过程. 2,可以...

    本文是Java 四大教科书之一的Effective Java的中文第三版的笔记整理

    第二章:创建和销毁对象

    1,用静态方法类替代构造器

    为什么要使用静态方法而不是public构造器:
    优点:
    1,静态方法是有名字的,构造器只能根据参数来决定返回实例的类型.但是通过静态工厂方法就可以避开这个过程.
    2,可以复用对象,不用每次都创建一个新的实例,而这个实例可以是提前构造好的,从而实现实例受控类.
    3,返回原返回类型的任何子类的对象.
    4,返回对象的类可以随着每次调用而发生变化,取决于参数值.
    5,方法返回的对象的所属类,在编写包含该静态工厂方法的类时可以不存在

    缺点:
    1,类如果不包含公有的或者protected的构造器,就不能被子类化.
    2,程序员可能无法发现他们

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

    因为静态工厂和构造器都有个共同的局限性,不能扩展到大量的可选参数.
    一个替代的方法是用的javaBeans模式,先调用一个无参构造器来构造对象,然后使用setter方法来设置必要的参数.
    但是这个办法也会有线程安全的问题,如果强行冻结的话就很笨拙.

    实际生产过程中是用的建造者模式来处理,使用lombok的builder方法,先使得客户端利用所有必要的参数调用构造器,得到一个builder对象,再在builder对象上调用build方法来生成不可变的对象.
    builder一般是它构建的类的静态成员类.
    优点是流式编程,而且模拟了具名的可选参数.

    一个大优点是便于扩展参数,对于之前的代码,可以保持原有的builder不变.

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

    首先要注意私有方法还是可以被反射给调用,所以要在构造器中添加二次创建就报异常的机制.
    有两种常见方法:
    1.public静态成员,final域
    aPEKMQ.png

    2,public获取方法,final成员
    aPEMrj.png

    方法2有3个好处:
    1,便于修改,保持api不变.
    2,简单,可以返回一个泛型Singleton工厂
    3,可以通过方法引用来提供实例.

    注意还要声明所有实例域都是transient的,并且提供一个readResolve方法.

    3,声明一个包含单个元素的枚举类型
    aPEeG8.png
    这是最好的方法.

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

    对于一些只包含静态方法的类,实例化是毫无意义的,但是如果不包含构造器,系统会提供一个默认的无参构造器.所以需要给一个私有的构造器,这样就可以完全避免被实例化了.
    私有构造器可以直接抛出一个异常,以防在内部被错误调用.
    这样的一个问题就是该类不可被子类化,因为无法调用超类的构造器.

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

    比如使用spring这种方法,定义依赖并且把依赖注入,从而进行使用.

    6.避免创建不必要的对象

    多使用静态工厂方法避免使用构造器
    比如创建string的时候,避免调用构造器,而是直接赋值.
    以及使用基本变量类型的时候,尽可能避免自动装箱.
    或者使用正则表达式的时候,先显式定义再进行调用.

    7.消除过期的对象引用

    过期引用: 永远不会再被解除的引用
    往往发生在自己管理内存的类上

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

    能不用就不用.
    记得每次使用完资源都close一下;

    9.try-with-resources 优先于try-finally

    实际使用就是在try的括号里打开资源,并且不需要使用finally这个方法来关闭资源了,从而避免异常覆盖.

    aPEQqs.png

    展开全文
  • 最近在研读Effective JAVA这一本书,这本书可以说是开发阶段中对平时开发的设计提供了一个比较好的规范思想,避免设计时产生不必要的资源浪费,提高软件性能的一方面,废话不多话,让我们一步步进入开发中常见的做法...
  • 1.什么是构造器? 构造器与类同名,在新建一个类的对象时,构造器会运行,以便将实例初始化为所希望的状态。 每个类都会有一个默认的无参的构造器。也可以自己手动写一个构造器。 如,在People类中写一个构造器: ...
  • 参考资料:《Effective Java》、...1.什么是枚举? 有时候一个变量的取值只在一个有限的集合内,比如衣服的大小,就可以用到枚举。 //自定义枚举类型 enum Size {S,M,L,X}; //声明Size类型的变量 Size size=Size...
  • 对象的创建或者销毁,需要耗费较多的系统资源; 对象的创建或者销毁,耗时长,需要频繁的操作和较长时间的等待; 对象创建后,通过一些状态重置,可被反复利用。 HikariCP为什么快? 他是fastlist代替arraylist...
  • 1.创建和销毁对象_EJ

    2018-08-03 16:17:00
    该书介绍了java编程中70多种极具实用价值的经验规则,揭示了该做什么,不该做什么才能产生清晰、健壮和高效的代码。 第1条: 考虑静态工厂方法代替构造器 对于类而言,为了让客户端获得它自身的实例,最常用的...
  • java对象

    2019-06-17 00:04:58
    那么对象池有什么用呢,当然是有用的,假设一个场景,在消息处理的时候,比如聊天软件中每个消息都不停的创建和销毁那么这样一来就会给GC造成很大的压力,此时此刻对象池就可以派上用场了,其实在这里我们可以联想到...
  • 我们都知道,Java是一门面向对象的编程语言,每个对象都是独一无二的(独立的内存空间),都有自己的生命周期,一个对象从创建到销毁,最后被垃圾回收机制回收,这一过程看似简单,实则错综复杂,比如对象是如何被创建...
  • Spring框架管理Bean对象什么优势吗?(面试) Spring框架可以为由它管理的对象(Bean) 提供懒加载策略(对象暂时不到,则无需加载和实例化), Scope作用域(例如singleton-频繁用时可以考虑内存中只有一份, ...
  • 堆栈中的数据完就销毁,访问速度快,因为基本数据类型使用得太频繁了,这所以种方法来提高性能。 但有些地方必须用到对象,基本数据类型不是对象,怎么办呢?Java针对每种基本数据类型提供了包装类,即Boole...
  • 一切皆对象 引用操纵对象 1 创建引用 必须由你创建所有对象 1 new 给我一个新对象 2 对象存储到什么地方 21 程序计数器Program Counter Register 22 Java 虚拟机栈VM Stack ...永远不需要销毁对象 1 对象的作用
  • Java什么 finalize() 方法?

    千次阅读 2019-07-23 18:56:43
    引言 接上篇文章Object源码分析,我们知道了Java 从 1.0 开始就有了 ...一般情况下,在垃圾回收期间,一个无法触及的对象会立即被销毁。不过,覆盖了 finalize() 方法的对象会被移动到一个队列里,一个独立的线程...
  • 什么是单例模式?单例模式是指,采用某种手段或者方法,保证整个软件系统中只能存在一个对象的实例,并且该实例只提供创建该对象的方法。使用情况及优缺点使用情况:当您想控制实例数目,节省系统资源的时候,和一个...
  • 线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候就从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销 线程池带来的好处 降低资源...
  • 2.1 引用操纵对象2.2 必须由你创建所有对象 一旦创建了一个引用,就希望它能与一个新的对象相...2.2.1 存储在什么地方2.3 永远不需要销毁对象2.4 创建新的数据类型:类2.5 方法,参数和返回值2.6 构建一个Java程序
  • 感悟 笔记 2.1引用操纵对象 不好理解,但是却是核心,需要反复理解 ...2.3永远不需要销毁对象 1)作用域 2)对象的作用域 2.4创建新的数据类型:类 1)字段和方法 2.5方法、参数和返回值 1)参数列表 2...
  • 2.1 引用操作对象  1.对象和引用联系起来,就可以通过引用来操作对象;  2.引用亦可单独存在,即没有和任何对象联系起来;对没有和对象联系起来的引用操作,会报错;  2.2 必须由你创建所有对象  1.存储...
  • 设计者的初衷是什么 ,为什么不能都是静态方法 或者都是非静态方法 ,为什么...非静态方法所在对象,你什么时候想要就实例化这个对象,实例化后才放入内存中,待不用了之后,JAVA 的GC机制会回收不用的内存,而你的静.
  • finalize是一个Object的方法,基本用法:Class A{ void finalize(){ //do sth... super.finalize(); } }Java不同于c++的一点是:c++中销毁内存的对象...java销毁对象的方式是垃圾回收,但是即使你调用了...
  • 倒计时10分钟时执行一个DB操作+推送+邮件+任务数据的销毁。有以下几个方案: 方案1:任务生成时记录开始时间,使用定时器或Timer控件的时候,1s执行一次,内部执行方法使用线程池异步执行,判断间隔时间,更改操作。...
  • 从今天开始计划半个月的时间,写十篇读书笔记来记录阅读这本Java领域号称经典中的经典书籍-Effective Java(高效Java)过程中自己所思所想,以备以后查阅,同时分享出去也希望可以帮助到其他人,废话不多说,现在...
  • 避免频繁的创建销毁对象,可以提高性能。避免对共享资源的多重占用。可以全局访问。 2、确保一个类只有一个实例,自行实例化并向系统提供这个实例 单例需要注意的问题 1、线程安全问题 2、资源使用问题 ...
  • 什么Hibernate创建的对象不被销毁呢? 如图(1)先hibernate查询,创建了36(TMain.java)个对象 然后再jdbc查询如图(2)创建了37(TMain2.java)个对象 系统设置会话过期时间为2分钟 会话过期后如图(3...
  • java线程池

    2020-11-18 23:37:32
    创建和销毁对象都是比较耗费时间的,使用线程就可以很好的避免频繁创建和销毁 可以在哪些地方 线程池,数据库连接池 从JDK1.5起,提供了内置线程池 使用线程池的好处 提高响应速度(减少了创建新线程的时间) ...
  • 书的第一章是创建和销毁对象,接下来的几篇也都是围绕这个展开。 本篇对应书中的第一条:静态工厂方法代替构造器。 什么是静态工厂方法 先看一个例子,Boolean类中有如下构造器 public Boolean(boolean value) { ...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 194
精华内容 77
关键字:

java销毁对象用什么

java 订阅