• Java


    通常,我们更喜欢重用一个对象而不是重新创建一个。
    如果对象是不可变的,它就始终可以被重用。


    下面是一个反面例子:

    String s = new String("stringette");

    该语句每次执行时都创建一个新的实例。
    String构造器中的参数"stringette"本身是一个实例,功能方面等同于那些通过构造器创建的对象。
    如果这种语句放到循环里,效果会变得更糟。

    于是我们只需要:

    String s = "stringette";

    这样就永远是同一个string实例,并且可以保证同一个VM中会重用相同字符串字面量的对象。
    (the object will be reused by any other code running in the same vitual machine that happens to contain the same string literal.)

    如果类中同时提供了静态工厂方法和构造器,客户端通常可以使用静态工厂方法来防止创建不必要的对象。
    比如java.lang.Boolean:

    public static final Boolean TRUE = new Boolean(true);
       
    public static final Boolean FALSE = new Boolean(false);
        
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    而在构造器的javadoc中也做了相应的说明:

        /**
         * Allocates a {@code Boolean} object representing the
         * {@code value} argument.
         *
         * <p><b>Note: It is rarely appropriate to use this constructor.
         * Unless a <i>new</i> instance is required, the static factory
         * {@link #valueOf(boolean)} is generally a better choice. It is
         * likely to yield significantly better space and time performance.</b>
         *
         * @param   value   the value of the {@code Boolean}.
         */
        public Boolean(boolean value) {
            this.value = value;
        }

    除了重用不可变的对象,我们也可以重用那些不会再有变化的可变对象。
    (Also use mutable object if you know they won't be modified.)

    下面是一个反面例子,检查某人是否出生于生育高峰期(1946~1964):

    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimeZone;
     
    public class Person {
        private final Date birthDate;
     
        public Person(Date birthDate) {
            // Defensive copy - see Item 39
            this.birthDate = new Date(birthDate.getTime());
        }
     
        // Other fields, methods omitted
     
        // DON'T DO THIS!
        public boolean isBabyBoomer() {
            // Unnecessary allocation of expensive object
            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
                    && birthDate.compareTo(boomEnd) < 0;
        }
    }

    一次Calendar.getInstance和两次getTime,这些都会创建新的实例:

        public static Calendar getInstance(TimeZone zone,Locale aLocale){
            return createCalendar(zone, aLocale);
        }
      
        private static Calendar createCalendar(TimeZone zone,Locale aLocale){
            Calendar cal = null;
     
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype == null) {
                // Calendar type is not specified.
                // If the specified locale is a Thai locale,
                // returns a BuddhistCalendar instance.
                if ("th".equals(aLocale.getLanguage())
                        && ("TH".equals(aLocale.getCountry()))) {
                    cal = new BuddhistCalendar(zone, aLocale);
                } else {
                    cal = new GregorianCalendar(zone, aLocale);
                }
            } else if (caltype.equals("japanese")) {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else if (caltype.equals("buddhist")) {
                cal = new BuddhistCalendar(zone, aLocale);
            } else {
                // Unsupported calendar type.
                // Use Gregorian calendar as a fallback.
                cal = new GregorianCalendar(zone, aLocale);
            }
     
            return cal;
        }
          
        public final Date getTime() {
            return new Date(getTimeInMillis());
        }

    于是我们做一下改进,将对象声明为private static final,并在静态初始化块中把需要的实例都准备好:

    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimeZone;
     
    class Person {
        private final Date birthDate;
     
        public Person(Date birthDate) {
            // Defensive copy - see Item 39
            this.birthDate = new Date(birthDate.getTime());
        }
     
        // Other fields, methods
     
        /**
         * The starting and ending dates of the baby boom.
         */
        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();
        }
     
        public boolean isBabyBoomer() {
            return birthDate.compareTo(BOOM_START) >= 0
                    && birthDate.compareTo(BOOM_END) < 0;
        }
    }

    这样不仅提高了效率,而且更加明确。

    但是,我们还可以再改进一些。
    如果上面的例子中的isBabyBoomer没有被调用过,那我们在静态初始化块中创建的实例就白白浪费了(可能例子中的对象太少了,多了就爽了)。
    对于这个问题,我们可以用延迟初始化(lazily initializing)来解决,也就是在isBabyBoomer被第一次调用时进行初始化。
    但是作者并不建议这样做,因为这可能只会让方法实现变得更复杂,而没有显著提高效率。

    上面的例子中我们把重用的对象在实例化以后不会再有变化,那么能不能重用可变对象?
    比如Map.keySet方法返回的是同一个Set实例,这个Set实例的状态依赖于其backing Object,即Map实例。
    当Map发生变化时Set也跟着变化就可以了。
    虽然每次调用时重新创建一个Set实例也是可行的,但实在没必要。

    另外,autoboxing这个特性也为用户提供了创建不必要的对象的新方法。
    试试基本类型和封装类型混用,并让他们超出封装类型的缓存范围。
    比如这样做:

    public class Sum {
        // Hideously slow program! Can you spot the object creation?
        public static void main(String[] args) {
            Long sum = 0L;
            for (long i = 0; i < Integer.MAX_VALUE; i++) {
                sum += i;
            }
            System.out.println(sum);
        }
    }

    只是把sum的类型声明为Long,造成了巨大的浪费。
    (prefer primitives to boxed primitives,and watch out for unintentional autoboxing.)

  • 相关阅读:
    Js中的正则表达式
    js内存泄露的几种情况
    JavaScript的setTimeout与setInterval执行时机
    IE模拟addDOMLoadEvent和jQuery的ready实现
    谈谈JavaScript的异步实现
    在iOS7中修改状态栏字体的颜色
    IOS 疯狂基础之 页面间跳转
    ATL2.1版的CString分析
    翻译: 如何改变MFC应用程序主窗口的类名
    VC5.0中的ATL的一个有趣的bug
  • 原文地址:https://www.cnblogs.com/kavlez/p/4074629.html
Copyright © 2020-2023  润新知