如何在 this(...) 或 super(...) 之前 "insert" 编码?

How to "insert" code before this(...) or super(...)?

有什么方法可以在调用super(...)this(...)构造函数之前进行初步计算吗?考虑以下示例:

public class Test {
    private final int n;
    private final int m;
    private final int[] store;

    public Test(int n, int m) {
        /* This is common (most generic) constructor of the class Test.
         It is desirable to invoke it via this(...) call
         from any other constructor of this class
         since it contains some common initialization tasks,
         which are better to concentrate in one place instead
         of scattering them throught the class, due to
         maintainability and security reasons. */
        this.n = n;
        this.m = m;
        store = new int[n];
        // ...
    }

    public Test(Object data) {
        /* This is specific constructor. It depends on some parameters
         which must be processed prior to a call of the Test(int n, int m)
         constructor to get its arguments. The problem is, this processing
         cannot appear here because a call to this(...) must be the first
         statement in a constructor. */
        int a; // Must be calculated via lengthy code
        int b; // Must be calculated via lengthy code
        this(a, b); // <- Compiler error here
        // ... further initialization
    }
}

如何实现参数计算?最简单的解决方案是用静态方法替换特定的构造函数。但是,如果它必须是一个 constructor 而不是别的(例如,它有可能在后代 class 中使用)怎么办?目前我找到的最好的解决方案是引入一个static inner class包含普通构造函数的所有参数,并用它来存储特定构造函数开头的参数:

public class Test {
    private final int n;
    private final int m;
    private final int[] store;

    protected static class ConstrParams {
        int nParam;
        int mParam;

        ConstrParams(int n, int m) {
            nParam = n;
            mParam = m;
        }
    }

    protected Test(ConstrParams params) {
        /* This is the common constructor now.
         It is marked as protected because it is essentially auxiliary,
         as well as the class ConstrParams is. */
        n = params.nParam;
        m = params.mParam;
        store = new int[n];
        // ...
    }

    public Test(int n, int m) {
        // This is public interface to the common constructor.
        this(new ConstrParams(n, m));
    }

    private static ConstrParams makeParams(Object data) {
        /* This is a procedure that inserts lengthy calculations
         before constructor chain invocation. */
        int a = 0; // Calculate a from data
        int b = 0; // Calculate b from data
        return new ConstrParams(a, b);
    }

    public Test(Object data) {
        // Specific constructor. Now compiles successfully.
        this(makeParams(data));
        // ... further initialization
    }
}

有没有更好的解决方法? Test(Object data) 必须调用某些 super(...) 构造函数而不是 this(...) 的情况更糟,因为在这种情况下我们的灵活性较低,而且通常无法更改祖先 class 的代码。

此解决方案适用于 Java 8 及更高版本。我会创建另一个接受 Supplier<Integer>. The method Supplier::get() returns 值的构造函数:

public Test(Supplier<Integer> n, Supplier<Integer> m) {
    this.n = n.get();
    this.m = m.get();
    store = new int[n.get()];
}

可能这样使用:

public Test(Object data) {
    this(() -> {
        int a = data.hashCode();  // expensive calculation
        return a;
    }, () -> {
        int b =  data.hashCode(); // expensive calculation
        return b;
    });
}

这种方法会简化另一个构造函数,只留下一个负责封装的主构造函数:

public Test(int n, int m) {
    this(() -> n, () -> m);
}

您还可以创建辅助方法来计算 ab 并在 this(...) 表达式中调用它们。例如:

public Test(Object data) {
    this(computeA(data), computeB(data));
}

private static int computeA(Object data) {
    ...
}

private static int computeB(Object data) {
    ...
}

这是我发现的一种通用方法。它允许在调用 this(...)super(...) 之前注入任何代码,从而克服 Java 对 this(...)super(...) 作为第一个语句的限制在构造函数中。

public class Test {
    private final int n;
    private final int m;
    private final int[] store;

    public Test(int n, int m) {
        // Primary constructor is unchanged
        this.n = n;
        this.m = m;
        store = new int[n];
        // ...
    }

    private static class ConstrParams {
        private int nParam;
        private int mParam;
        /* This class can also be used by more than one constructor
         or independently, to calculate the parameters and store
         them for other purposes. */
        private ConstrParams(Object data) {
            /* Calculate the parameters and/or do any other operations
             (preprocessing) that you would do in the specific constructor prior
             to calling another constructor. You may even add as many auxiliary
             methods as needed into this class and use them in this constructor. */
            nParam = 1;
            mParam = 2;
        }
    }

    /* Intermediate constructor, the main purpose of which is to extract
     parameters (if any) from a ConstrParams object and pass them to a primary
     or an inherited constructor. If ConstrParams produces no parameters but
     makes some pre-this() or -super() actions, this constructor makes
     insertion of such actions available. */
    private Test(ConstrParams params) {
        this(params.nParam, params.mParam);
        /* You can also call super(...) instead of this(...).
         When calling super(...), primary constructor may even not exist. */
//        super(params.nParam, params.mParam);
        /* As the reference to ConstrParams vanishes upon return to the
         calling constructor, you may want to make some actions connected
         with the params object (post-processing) or store the reference to it
         into this object. If so, here's the right place to do it. Otherwise,
         no further action is generally needed in this constructor. */
    }

    public Test(Object data) {
        // Specific constructor. Now compiles successfully.
        this(new ConstrParams(data));
        // ... further initialization
    }
}

优势包括:

  • 被调用的构造函数代码不受影响。这在使用 super(...) 时特别有用,因为对祖先 class 的更改通常是不可取的或不可能的。使用 this(...) 时,上述方法不会影响任何依赖主构造函数的代码。
  • 它不依赖于被调用的构造函数需要的参数个数。只需添加任意数量的 ConstrParams class 字段,然后在调用主构造函数或继承构造函数之前提取。如果参数是联合计算的(即,将它们的计算拆分为两个或多个独立的方法是不可能的或代价高昂的),则此方法允许这样做。可能存在(并且经常存在)被调用的构造函数不带任何参数的情况,您只需要在 this(...)super(...) 调用之前在依赖构造函数中执行一些操作(这样的一个例子操作正在记录)。此解决方案允许您执行此类操作。
  • 产生参数and/or的辅助ConstrParamsclass使得副作用可以用于额外的目的。如果主 class 的多个二级构造函数需要克服 this(...)/super(...) 调用限制,则可以在其中引入更多的构造函数。
  • 统一应用于 this(...)super(...) 调用。