编写高质量代码:改善Java程序的151个建议(12)

  建议38:使用静态内部类提高封装性

  Java中的嵌套类(NestedClass)分为两种:静态内部类(也叫静态嵌套类,StaticNestedClass)和内部类(InnerClass)。内部类我们介绍过很多了,现在来看看静态内部类。什么是静态内部类呢?是内部类,并且是静态(static修饰)的即为静态内部类。只有在是静态内部类的情况下才能把static修复符放在类前,其他任何时候static都是不能修饰类的。

  静态内部类的形式很好理解,但是为什么需要静态内部类呢?那是因为静态内部类有两个优点:加强了类的封装性和提高了代码的可读性,我们通过一段代码来解释这两个优点,如下所示:

  

    publicclassPerson{ //姓名 privateStringname; //家庭 privateHomehome; //构造函数设置属性值 publicPerson(String_name){ name=_name; } /*home、name的getter/setter方法省略*/ publicstaticclassHome{ //家庭地址 privateStringaddress; //家庭电话 privateStringtel; publicHome(String_address,String_tel){ address=_address; tel=_tel; } /*address、tel的getter/setter方法省略*/ } }

  其中,Person类中定义了一个静态内部类Home,它表示的意思是“人的家庭信息”,由于Home类封装了家庭信息,不用在Person类中再定义homeAddre、homeTel等属性,这就使封装性提高了。同时我们仅仅通过代码就可以分析出Person和Home之间的强关联关系,也就是说语义增强了,可读性提高了。所以在使用时就会非常清楚它要表达的含义:

  

    publicstaticvoidmain(String[]args){ //定义张三这个人 Personp=newPerson(“张三”); //设置张三的家庭信息 p.setHome(newPerson.Home(“上海”,”021″)); }

  定义张三这个人,然后通过Person.Home类设置张三的家庭信息,这是不是就和我们真实世界的情形相同了?先登记人的主要信息,然后登记人员的分类信息。可能你又要问了,这和我们一般定义的类有什么区别呢?又有什么吸引人的地方呢?如下所示:

  提高封装性。从代码位置上来讲,静态内部类放置在外部类内,其代码层意义就是:静态内部类是外部类的子行为或子属性,两者直接保持着一定的关系,比如在我们的例子中,看到Home类就知道它是Person的Home信息。

  提高代码的可读性。相关联的代码放在一起,可读性当然提高了。

  形似内部,神似外部。静态内部类虽然存在于外部类内,而且编译后的类文件名也包含外部类(格式是:外部类+$+内部类),但是它可以脱离外部类存在,也就是说我们仍然可以通过new Home()声明一个Home对象,只是需要导入“Person.Home”而已。

  解释了这么多,读者可能会觉得外部类和静态内部类之间是组合关系(Composition)了,这是错误的,外部类和静态内部类之间有强关联关系,这仅仅表现在“字面”上,而深层次的抽象意义则依赖于类的设计。

  那静态内部类与普通内部类有什么区别呢?问得好,区别如下:

  (1)静态内部类不持有外部类的引用

  在普通内部类中,我们可以直接访问外部类的属性、方法,即使是private类型也可以访问,这是因为内部类持有一个外部类的引用,可以自由访问。而静态内部类,则只可以访问外部类的静态方法和静态属性(如果是private权限也能访问,这是由其代码位置所决定的),其他则不能访问。

  (2)静态内部类不依赖外部类

  普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,也就是说它们会同生同死,一起声明,一起被垃圾回收器回收。而静态内部类是可以独立存在的,即使外部类消亡了,静态内部类还是可以存在的。

  (3)普通内部类不能声明static的方法和变量

  普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是final static修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。

  建议39:使用匿名类的构造函数

  阅读如下代码,看看是否可以编译:

  

    publicstaticvoidmain(String[]args){ Listl1=newArrayList(); Listl2=newArrayList(){}; Listl3=newArrayList(){{}}; System.out.println(l1.getClass()==l2.getClass()); System.out.println(l2.getClass()==l3.getClass()); System.out.println(l1.getClass()==l3.getClass()); }

  注意ArrayList后面的不同点:l1变量后面什么都没有,l2后面有一对{},l3后面有2对嵌套的{},这段程序能不能编译呢?若能编译,那输出是多少呢?

  答案是能编译,输出的是3个false。l1很容易解释,就是声明了ArrayList的实例对象,那l2和l3代表的是什么呢?

  (1)l2=new ArrayList(){}

  l2代表的是一个匿名类的声明和赋值,它定义了一个继承于ArrayList的匿名类,只是没有任何的覆写方法而已,其代码类似于:

  

    //定义一个继承ArrayList的内部类 classSubextendsArrayList{ } //声明和赋值 Listl2=newSub();

  (2) l3=new ArrayList(){{}}

  这个语句就有点怪了,还带了两对大括号,我们分开来解释就会明白了,这也是一个匿名类的定义,它的代码类似于:

  

    //定义一个继承ArrayList的内部类 classSubextendsArrayList{ { //初始化块 } } //声明和赋值 Listl3=newSub();

  看到了吧,就是多了一个初始化块而已,起到构造函数的功能。我们知道一个类肯定有一个构造函数,且构造函数的名称和类名相同,那问题来了:匿名类的构造函数是什么呢?它没有名字呀!很显然,初始化块就是它的构造函数。当然,一个类中的构造函数块可以是多个,也就是说可以出现如下代码:

  Listl3=newArrayList(){{}{}{}{}{}};

  上面的代码是正确无误,没有任何问题的。现在清楚了:匿名函数虽然没有名字,但也是可以有构造函数的,它用构造函数块来代替,那上面的3个输出就很清楚了:虽然父类相同,但是类还是不同的。

  建议40:匿名类的构造函数很特殊

  在上一个建议中我们讲到匿名类虽然没有名字,但可以有一个初始化块来充当构造函数,那这个构造函数是否就和普通的构造函数完全一样呢?我们来看一个例子,设计一个计算器,进行加减乘除运算,代码如下:

  

    //定义一个枚举,限定操作符 enumOps{ADD,SUB} classCalculator{ privateinti,j,result; //无参构造 publicCalculator(){} //有参构造 publicCalculator(int_i,int_j){ i=_i; j=_j; } //设置符号,是加法运算还是减法运算 protectedvoidsetOperator(Ops_op){ result=_op.equals(Ops.ADD)?i+j:i-j; } //取得运算结果 publicintgetResult(){ returnresult; } }

  代码的意图是,通过构造函数输入两个int类型的数字,然后根据设置的操作符(加法还是减法)进行计算,编写一个客户端调用:

  

    publicstaticvoidmain(String[]args){ Calculatorc1=newCalculator(1,2){ { setOperator(Ops.ADD); } }; System.out.println(c1.getResult()); }

  这段匿名类的代码非常清晰:接收两个参数1和2,然后设置一个操作符号,计算其值,结果是3,这毫无疑问,但是这中间隐藏着一个问题:带有参数的匿名类声明时到底是调用的哪一个构造函数呢?我们把这段程序模拟一下:

  

    //加法计算 classAddextendsCalculator{ { setOperator(Ops.ADD); } //覆写父类的构造方法 publicAdd(int_i,int_j){ } }

  匿名类和这个Add类是等价的吗?可能有人会说:上面只是把匿名类增加了一个名字,其他的都没有改动,那肯定是等价的啦!毫无疑问!那好,你再写个客户端调用Add类的方法看看。是不是输出结果为0(为什么是0?这很容易,有参构造没有赋值)。这说明两者不等价,不过,原因何在呢?

  原来是因为匿名类的构造函数特殊处理机制,一般类(也就是具有显式名字的类)的所有构造函数默认都是调用父类的无参构造的,而匿名类因为没有名字,只能由构造代码块代替,也就无所谓的有参和无参构造函数了,它在初始化时直接调用了父类的同参数构造,然后再调用了自己的构造代码块,也就是说上面的匿名类与下面的代码是等价的:

  

    //加法计算 classAddextendsCalculator{ { setOperator(Ops.ADD); } //覆写父类的构造方法 publicAdd(int_i,int_j){ super(_i,_j); } }

  它首先会调用父类有两个参数的构造函数,而不是无参构造,这是匿名类的构造函数与普通类的差别,但是这一点也确实鲜有人细细琢磨,因为它的处理机制符合习惯呀,我传递两个参数,就是希望先调用父类有两个参数的构造,然后再执行我自己的构造函数,而Java的处理机制也正是如此处理的!

  相关链接:

  编写高质量代码:改善Java程序的151个建议(1)

  编写高质量代码:改善Java程序的151个建议(2)

  编写高质量代码:改善Java程序的151个建议(3)

  编写高质量代码:改善Java程序的151个建议(4)

  编写高质量代码:改善Java程序的151个建议(5)

  编写高质量代码:改善Java程序的151个建议(6)

  编写高质量代码:改善Java程序的151个建议(7)

  编写高质量代码:改善Java程序的151个建议(8)

  编写高质量代码:改善Java程序的151个建议(9)

  编写高质量代码:改善Java程序的151个建议(10)

  编写高质量代码:改善Java程序的151个建议(11)

如果困难是堵砖墙,拍拍它说你还不够高。

编写高质量代码:改善Java程序的151个建议(12)

相关文章:

你感兴趣的文章:

标签云: