zoukankan      html  css  js  c++  java
  • Practical Java

     

    实践1、    参数是以by value方式而非by reference方式传递

    一个普通存在的误解是:java中的参数是以 by value 方式传递。其实不是这样的,参数是以 by value 方式传递的。请看示例:

    class PassByValue {

      public static void modifyPoint(Point pt, int j) {

        pt.setLocation(5,5);                   //1

        j = 15;

        System.out.println("During modifyPoint " + "pt = " + pt +

                           " and j = " + j);

      }

     

      public static void main(String args[]) {

        Point p = new Point(0,0);              //2

        int i = 10;

        System.out.println("Before modifyPoint " + "p = " + p +

                           " and i = " + i);

        modifyPoint(p, i);                     //3

        System.out.println("After modifyPoint " + "p = " + p +

                           " and i = " + i);

      }

    }

    这显示,modifyPoint()改变了 //2 所建立的Point对象,却没有改变 int i。在main()之中,i被赋值为10。由于参数通过by value方式传递,所以modifyPoint()收到i的一个副本,然后它将这个副本必为15并返回。main()内的原值i并没有受到影响。

     

    对于Point对象,其实modifyPoint()是与“Point对象的reference p 的复件”打交道,而不是与“Point 对象的复件”打交道。记住,p是个object reference,并且Javaby value方式传递参数,或者更准确点说,Javaby value方式传递object reference。当pmain()被传入modifyPoint()时,传递的是p(也就是一个reference)的复件。所以modifyPoint()是在与同一个对象打交道,只不过通过别名pt罢了。在进入modifyPoint()之后和执行//1之前,内存中应该是这样的:

    image002

    如果你并不想modifyPoint()改变传进的Point对象,你可以传递一个克隆对象(见6466)或者将Point设计成不可变的(见65)。

    实践2、    对不变的dataobject references使用final

    class FinalTest {

        static final int someInt = 10;

        static final StringBuffer objRef = new StringBuffer("sring");

     

        static void prt() {

           System.out.println("someInt=" + someInt + " - objRef=" + objRef);

        }

     

        public static void main(String[] args) {

           prt();

           //不能重新分配 FinalTest.someInt

           //!!someInt = 20;//1

           objRef.append(" other");//2

           //不能重新分配 FinalTest.objRef

           //!!objRef = new StringBuffer(); //3

           prt();

        }

    }

    输出:

    someInt=10 - objRef=sring

    someInt=10 - objRef=sring other

     

    程序里的//1处理我们应该很清楚了,但//2处又是为什么呢?我们不是已经声明objRef声明成final,为什么还能改变?不,我们确实没有改变objRef的值,我们改变的是objRef所指对象的值,objRef并无变化,仍然指向同一个对象。变量objRef是一个object reference,它指向对象所在的heap位置。而//3处正是我们想的那样,编译出错了,因为你试图改objRef的值,换而言之,它企图令objRe指向其他物体。然而objRef所指对象并不受关键词final的影响,因此所指向的对象本身是可变的。

     

    关键词final只能防止变量值的改变。如果被声明为final的变量是个object reference,那么该reference不能被改变,必须永远指向同一个对象,然而被指向的那个对象是可以随意改变的。

    实践3、    缺省情况下所有non-privatenon-static函数都可以被覆写

    缺省情况下,类中任何non-privatenon-static函数都允许被子类覆写。类的设计者如果希望阻止子类覆写(修改)某个函数,则必须采取明确的动作,也就是将该函数声明为final

     

    关键字finalJava中有多重用途,即可被用于变量(不能修改),也可用于类(不能继承)与方法(不能覆写)。

     

    声明某个类为final,也就暗暗声明了这个类的所有函数都为final。这种做法可以阻止它派生类,从而禁止任何人覆写此类的所有函数。如果这种设计对你而言过于严苛,也可以考虑只将某些方法声明成final,这种方式允许你派生出类,但不允许你覆写你声明成final的方法。另外,finalnon-final方法的性能要高。

    实践4、    arraysArrayList之间慎重选择

    在新建一个数组时,每个元素都将依据其自己类型而被赋予缺省值:boolean-falsechar- 'u0000'byteshortintlong-0floatdouble-0.0object reference-null

     

    数组的容量是固定的,一旦指定了就不可更改,但ArrayList的容量是可变的,它会随着元素的增加自动的增长。数组即可存放基本类型也可存储引用类型,而ArrayList只能存放引用类型元素(虽然1.5可以,但这是借助于自动装箱特性实现的)。

     

    数组比ArrayList拥有更高的性能。

    实践5、    多态(polymorphism)优于instanceof

    //员工

    interface Employee {

        public int salary();//工资计算

    }

    //经理

    class Manager implements Employee {

        private static final int mgrSal = 40000;//工资

     

        public int salary() {

           return mgrSal;

        }

    }

    //程序员

    class Programmer implements Employee {

        private static final int prgSal = 50000;

        private static final int prgBonus = 10000;//奖金

     

        public int salary() {

           return prgSal;

        }

     

        public int bonus() {

           return prgBonus;

        }

    }

    //薪水册

    class Payroll {

        public int calcPayroll(Employee emp) {

           int money = emp.salary();

           if (emp instanceof Programmer)

               //如果是程序员,则计算奖金

               money += ((Programmer) emp).bonus();

           return money;

        }

     

        public static void main(String args[]) {

           Payroll pr = new Payroll();

           Programmer prg = new Programmer();

           Manager mgr = new Manager();

           System.out.println("程序员的薪水 " + pr.calcPayroll(prg));

           System.out.println("经理的薪水 " + pr.calcPayroll(mgr));

        }

    }

    避免使用instanceof,现重构上面的程序。在Employee中可以增加一个bonus()接口,然后员工都实现他,经理没奖金时直接返回0,这样就不用在calcPayroll方法里使用instanceof了。

     

    使用instanceof首先缺乏性能,不够优雅,也不易扩充(如在以后版本中增加另一种Employee,并有另外的福得怎么办?),其次要求程序员写代码去做Java运行期该做的事。而多态则完全可以避免这些问题。

     

    如果这里要为使用instanceof找个理由,那就是Employee不是你设计的,你不能去重构它们。

     

    instanceof操作符很容易被误用。很多场合都应该以多态来替代instanceof。无论何时当你看见instanceof,都请判断是否可以改进设计以消除它。以多态方式改进设计,会产生更合逻辑、更经得起推敲的设计,以及更易维护的代码。

    实践6、    必要时使用instanceof

    除了实践5中面对一个设计不当的class程序库时,你可能无法避免使用instanceof。事实上有更多常见情形,使你除了使用instanceof以外另无选择,如当你必须从一个基础类型向下转型为派生类型时。

     

    将一个类型强转为另一个不相关的类型时,在编译时就会出错。而将一个基本类型强转为派生类型时在运行时可能出错。

     

    使用instanceof可以消除强转型在运行时的错误。

     

    1.5以前,如果我们将不同类型的对象放入到集合中后,在取出时都是一个Object类型,这是instanceof就可以排上用场了,因为它可以消除运行期的错误。

    实践7、    一旦不再需要object reference,就将它设为null

    当不再需要某对象时,你可以将其reference设为null,以协助垃圾回收器取回内存,如果你的程序执行于一个内存受限环境中,这可能很有益处。你可以试着这么做:

    public static void main(String args[]){

           LargeObject lo = new LargeObject();//大对象

           // 使用大对象

           //...

           // 大对象不再需要,置为null

           lo = null;

           //  如果确实紧需内存(但不能保证立刻加收,只是建议)

           System.gc();

           // 后面程序还需要运行很长一段时间...

           //...

    }

     

    为了尽量降低内存乃是,与程序同寿的对象必须尽可能体积小。此外,大块头对象应该尽量“速生速灭”。

     

    检阅代码时,请注意大块头对象,尤其是那些存在于完整(或大部分)程序生命中的对象。你得要仔细研究这此对象的建立与运用,以及它们使用多少内存,如果它们引用了大量内存,请确定是否所有那些在对象生命周期内都真的被需要。也许某些大对象可以解甲归田,从而使其后执行的代码能够更高效地运行。

     

    任何刻候你都可以通过System.gc()要求垃圾回收器起身运行(注,还是只是建议,当控制从方法调用中返回时,虚拟机已经尽最大努力回收了所有丢弃的对象)。如果想将一个对象解除引用,则可以通过调用System.gc()要求垃圾回收器立刻运转,在代码继承执行前先回收被解除引用的那块内存,但你应当仔细都考虑这种做法为你的程序性能带来的潜在影响。

     

    许多垃圾回收算法会在它们运转之前先虚悬其他所有线程,这种做法可以确保一旦垃圾回收器开始运转,就能够拥有heap的完整访问权,可以安全完成任务而不受其他线程的威胁。一旦垃圾回收器完成了任务,便恢复此前被虚悬的所有线程。

     

    因此,通过System.gc()显示调用,要求垃圾回收器起而运行,你得冒“因执行回收工作而带来延迟”的风险,延迟程度取决于JVM所采用的垃圾回收算法。

     

    大多数JVM的垃圾回收器都会足够的运行,因此你实在不必显式地调用它。然而,如果你的代码有些部分期望在继续进行前先释放所有可能的内存,则可以考虑调用System.gc()

    第二章     对象与相等性

    实践8、    区别reference类型与primitive类型

    int i = 5;//基本类型

    Integer j = new Integer(10);//引用类型

    这两个变量都存储在局部变量表(即栈,Stack),它们的操作都在Java操作数堆栈(还是栈)中进行,但二者所表述的意义完全不同。不论是基本类型intobject reference,它们都是static中占据32bits空间,但Integer对象在stack中记录的并不是对象本身,而是对象的reference

    所有Java对象都是通过Object reference来访问的,那是某种形式的指针,该指针指向heap中的某块区域,heap则为对象的生存提供了真实存储场所。当你声明了一个基本类型后,你就为它声明了一份存储空间。前面两行代码可以这样表示:

    image004

     

    如果你使用primitive类型,便免除了“调用new以创建包装对象”的需要,这可节省时间和空间。

     

    下面看看输出结果是否你是预想的:

    class Assign{

      public static void main(String args[]) {

        int a = 1;

        int b = 2;

        Point x = new Point(0,0);

        Point y = new Point(1,1);//1

        System.out.println("a is " + a);

        System.out.println("b is " + b);

        System.out.println("x is " + x);

        System.out.println("y is " + y);

        System.out.println("Performing assignment and " +

                           "setLocation...");

        a = b;

        a++;

        x = y;//2

        x.setLocation(5,5);//3

     

        System.out.println("a is " + a);

        System.out.println("b is " + b);

        System.out.println("x is " + x);

        System.out.println("y is " + y);

      }

    }

    输出结果如下:

    a is 1

    b is 2

    x is java.awt.Point[x=0,y=0]

    y is java.awt.Point[x=1,y=1]

    Performing assignment and setLocation...

    a is 3

    b is 2

    x is java.awt.Point[x=5,y=5]

    y is java.awt.Point[x=5,y=5]

     

    上面代码运行进内存表示如下,经过//1后的情形:

    image006

    经过//2赋值动作后情形:

    image008

    //3调用setLocation()时,函数作用于x所指的对象。由于xy指向同一个对象,故而形成:

    image010

    由于xy指向同一个对象,所有执行于x身上的函数,就好像执行于y身上一样。

     

    弄清楚reference类型和primitive类型之间的差异,以及理解references语义到关重要,否则会导致代码的行为和预想不同。

    实践9、    区别==equals()

    ==”用于基本类型时,比较的就是它们所存储值的大小;如果是用于引用类型,它比较的还是所存储值的大小,不过这时这个值是个特殊的值——它们是对象在heap中的地址,所以当它用于对象时比较的是对象地址,如果被比较的对象指向同一对象,则相等,否则不等。

     

    Integer ia = new Integer(10);

    Float fa = new Float(10.0f);

    System.out.println(ia.equals(fa));//false

    System.out.println(fa.equals(ia));//false

    为什么上面打印的最是false?这不同基本类型的数值彼止可能相等(如果不同类型则会先提升类型后再比),但不同类型的对象则不然。打开类库源就会发现,它们都是先使用instanceof来测试传进被比较对象是否是同一个类型,如果不是则直接返回false。虽然我们可以自己订制一个equals让不现类型的对象也相等,但并不推荐你这么做,这违反equals业界规范。

     

    这里再次说明的是:请使用“==”测试两个基本类型是否完全相同,或测试两个object references是否指向同一个对象;请使用equals()比较两个对象是否一致——基准点是其属性(此处是指对象的实值内容,也就是数据值域field)。我们把“根据属性来比较两个对象是否相等”称为“等值测试”,或称为“语义上的相等测试”。

    实践10、            不要依赖equals()的缺省实现

    如果你设计的类没有重写equals()方法,那么你在使用equals时将会使用Object中的equals默认实现,它们比较的是否指向同一个对象,而不是对象的逻辑值是否相等,源码如下:

    public boolean equals(Object obj) {

        return (this == obj);

    }

     

    String类正确的实现了equals()方法,但StringBuffer类根本就没有重写Object中的equals()方法。如果遇到StringBuffer,则需要将它先转换为String再使用equals相互比较。1.5中的StringBuilderStringBuffer一样。

     

    StringStringBufferStringBuilder都是final类。

     

    equals()使用准则:

    1、  若要比较对象是否相等,其class有责任提供一个正确有equals()

    2、  要“想当然地调用equals()”之前,应该先检查并确保你所使用的class的确实现了equals()

    3、  如果你所使用的class并未实现equals(),请判断java.lang.Object的缺省函数是否可胜任。

    4、  如果无法胜任,就应该在某个外覆类(wrapper class)或subclass中撰写你自己的equals()方法(比如你设计的类中关键域是StringBuffer之类的类型,你得需要继承它重写它的equals方法,但它是final类,所以使用组合的方式设计功能类似的StringBuffer,并提供equals方法;或者不重新设计功能类似StringBuffer的类而是直接在使用StringBuffer的类中提供一个比较方法对StringBuffer进行专门的比较)。

    实践11、            不要依赖equals()的缺省实现

     

  • 相关阅读:
    JSTL和EL
    JSP
    Servlet基础知识
    JSON基础知识
    jQuery基础知识
    ajax基础知识
    索引实战
    反射
    设计模式
    JVM的异常处理
  • 原文地址:https://www.cnblogs.com/jiangzhengjun/p/4259259.html
Copyright © 2011-2022 走看看