# 改善Java程序的151个建议 36 - 40

改善Java程序的151个建议 36-40

文章目录

36. 使用构造代码块精炼程序

什么叫代码块?

  • 用大括号吧多行代码封装在一起,形成一个独立的数据体,实现特性的算法的代码集合即为代码块。
  • 一般来说代码块是不能单独运行的,必须要有运行主体。在Java中一共有四种类型的代码块。

Java中的代码块类型概览

# 改善Java程序的151个建议 36 - 40

构造代码块的两个特性

  1. 在每个构造函数中都运行
  2. 在构造函数中它会首先运行

构造代码块带来的益处

  • 很好地利用构造代码块的这两个特性不仅可以减少代码量,还可以让程序更容易阅读,特别是当所有的构造函数都要实现逻辑,而且这部分逻辑又很复杂时,这时就可以通过编写多个构造代码块来实现
  • 每个代码块完成不同的业务逻辑(当然了,构造函数尽量简单,这是基本原则),按照业务顺序依次存放,这样在创建实例对象时JVM也就会按照顺序依次执行,实现复杂对象的模块化创建

案例代码

package ad36to40;

/**
 * @author luxiaoyang
 * @create 2021-08-05-9:05
 */
public class Client {

    {
        // 构造代码块
        System.out.println("执行构造代码块1");
    }

    {
        System.out.println("执行构造代码块12");
    }

    public Client() {
        System.out.println("执行无参构造");
    }

    public Client(String _str) {
        System.out.println("执行有参构造");
    }

    public static void main(String[] args) {

        Client client = new Client();

    }
}

// 输出
//执行构造代码块1
//执行构造代码块12
//执行无参构造

37. 构造代码块会想你所想

猜一猜输出几?

package ad36to40;

/**
 * @author luxiaoyang
 * @create 2021-08-05-19:46
 */
public class Client2 {

    public static void main(String[] args) {

        Base base = new Base();
        new Base("");
        new Base(0);
        System.out.println("实例对象数量:" + Base.getNumOfObjects());

    }

}


class Base{

    // 程序计数器
    private static int numOfObjects = 0;

    {
        // 构造代码块,计算产生对象数量
        numOfObjects++;
    }

    public Base() {

    }

    // 有参构造调用无参构造
    public Base  (String _str){
        this();
    }

    // 有参构造不调用其他构造
    public Base(int _i) {

    }

    // 返回在一个JVM中,创建了多少个实例对象
    public static int getNumOfObjects() {
        return numOfObjects;
    }
}

实例对象的数量还是3,程序没有任何问题

上一个建议是说编译器会把构造代码块插入到每一个构造函数中,但是有一个例外的情况没有说明:如果遇到this关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块,对于我们的例子来说,编译器在编译时发现String形参的构造函数调用了无参构造,于是放弃插入构造代码块,所以只执行了一次构造代码块—结果就是如此。

构造代码块的诞生

构造代码块是为了提取构造函数的共同量,减少各个构造函数的代码而产生的,因此,Java就很聪明地认为把代码块插入到没有this方法的构造函数中即可,而调用其他构造函数的则不插入,确保每个构造函数只执行一次构造代码块

还有一点需要说明,读者千万不要以为this是特殊情况,那super也会类似处理了。其实不会,在构造代码块的处理上,super方法没有任何特殊的地方,编译器只是把构造代码块插入到super方法之后执行而已,仅此不同。

38. 使用静态内部类提高封装性

静态内部类的两个优点

  1. 类的封装性
  2. 提高了代码的可读性

案例展示

package ad36to40;

import lombok.Data;

/**
 * @author luxiaoyang
 * @create 2021-08-05-20:55
 */
@Data
public class Person {

    // 姓名
    private String name;
    // 家庭
    private Home home;

    private Person(String _name){
        name = _name;
    }

    @Data
    public static class Home{
        // 家庭地址
        private String address;
        // 家庭电话
        private String tel;
        public Home(String _address,String _tel){
            address = _address;
            tel = _tel;
        }
    }

}

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

  public static void main(String[] args) {
        
        // 定义张三这个人
        Person person = new Person("张三");
        // 设置张三的家庭信息
        person.setHome(new Person.Home("上海","021"));

    }

定义张三这个人,然后通过Person.Home类设置张三的家庭信息,这是不是就和我们真实世界的情形相同了?先登记人的主要信息,然后登记人员的分类信息

使用静态内部类的优势

可能你又要问了,这和我们一般定义的类有什么区别呢?又有什么吸引人的地方呢?如下所示:

  1. 提高封装性。从代码位置上来讲,静态内部类放置在外部类内,其代码层意义就是:静态内部类是外部类的子行为或子属性,两者直接保持着一定的关系,比如在我们的例子中,看到Home类就知道它是Person的Home信息。
  2. 提高代码的可读性相关联的代码放在一起,可读性当然提高了。
  3. 形似内部,神似外部。静态内部类虽然存在于外部类内,而且编译后的类文件名也包含外部类(格式是:外部类+$+内部类),但是它可以脱离外部类存在,也就是说我们仍然可以通过new Home()声明一个Home对象,只是需要导入“Person.Home”而已。

静态内部类与普通内部类区别

  1. 静态内部类不持有外部类的引用
    1. 在普通内部类中,我们可以直接访问外部类的属性、方法,即使是private类型也可以访问,这是因为内部类持有一个外部类的引用,可以*访问。而静态内部类,则只可以访问外部类的静态方法和静态属性(如果是private权限也能访问,这是由其代码位置所决定的),其他则不能访问。
  2. 静态内部类不依赖外部类
    1. 普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,也就是说它们会同生同死,一起声明,一起被垃圾回收器回收。而静态内部类是可以独立存在的,即使外部类消亡了,静态内部类还是可以存在的。
  3. 普通内部类不能声明static的方法和变量
    1. 普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是final static修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。

39. 使用匿名类的构造函数

# 改善Java程序的151个建议 36 - 40

答案是能编译,输出的是3个false

l2=new ArrayList(){}

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

# 改善Java程序的151个建议 36 - 40

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

# 改善Java程序的151个建议 36 - 40

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

40. 匿名类的构造函数很特殊

匿名类虽然没有名字,但可以有一个初始化块来充当构造函数,那这个构造函数是否就和普通的构造函数完全一样呢?

我们来看一个例子,设计一个计算器,进行加减乘除运算,代码如下:

enum Ops {ADD, SUB}

class Calculator {

    private int i, j, result;

    // 无参构造
    public Calculator() {
    }

    // 有参构造
    public Calculator(int _i, int _j) {
        i = _i;
        j = _j;
    }

    //设置符号,是加法运算还是减法运算
    protected void setOperator(Ops _op) {
        result = _op.equals(Ops.ADD) ? i + j : i - j;
    }

    // 取得运算结果
    public int getResult() {
        return result;
    }

    public static void main(String[] args) {

        Calculator calculator = new Calculator(1, 2) {
            {
                setOperator(Ops.ADD);
            }

        };
        System.out.println(calculator.getResult());

    }

}

带有参数的匿名类声明时到底是调用的哪一个构造函数呢?我们把这段程序模拟一下:

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

等价于:

class Add extends Calculator {

    {
        setOperator(Ops.ADD);
    }

    // 覆写父类的构造方法
    public Add(int _i,int _j) {
        super(_i,_j);
    }

    public static void main(String[] args) {

        Add add = new Add(1, 2);
        System.out.println(add.getResult());

    }

}

Add extends Calculator {

{
    setOperator(Ops.ADD);
}

// 覆写父类的构造方法
public Add(int _i,int _j) {
    super(_i,_j);
}

public static void main(String[] args) {

    Add add = new Add(1, 2);
    System.out.println(add.getResult());

}

}


上一篇:Doxygen wizard - Expert - LaTex RTF


下一篇:希尔排序