
在java应用开发中,我们经常会遇到多个类具有相似的属性和初始化步骤,但具体类型或某些细节有所不同的场景。例如,在一个android应用中,可能有多种ui元素(如 loadelement 和 errorelement),它们都需要初始化一个 viewdatabinding 对象,并设置其生命周期所有者(lifecycleowner)。尽管具体的 binding 类型不同(loadingelementbinding vs errorelementbinding),但初始化流程中的大部分逻辑是共享的。
考虑以下初始代码结构,其中 LoadElement 和 ErrorElement 各自维护并初始化自己的 binding:
public class LoadElement {
    LoadingElementBinding binding;
    public LoadElement(ViewGroup parent) {
        binding = LoadingElementBinding.inflate(
                       LayoutInflater.from(parent.getContext()),
                       parent,
                       false);
        binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
    }
    public void doSomething() {
        // 与 binding 相关的业务逻辑
    }
}
public class ErrorElement {
    ErrorElementBinding binding;
    public ErrorElement(ViewGroup parent) {
        binding = ErrorElementBinding.inflate(
                       LayoutInflater.from(parent.getContext()),
                       parent,
                       false);
        binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
    }
    public void doSomething() {
        // 与 binding 相关的业务逻辑
    }
}这种模式导致了代码重复,难以维护。理想情况下,我们希望能够将这些通用的初始化代码进行抽象和复用。
一种直观的解决方案是引入一个抽象基类 BindingElement,并在其中定义一个抽象方法来创建特定类型的 binding,然后在基类的构造器中调用这个抽象方法。
public abstract class BindingElement <T extends ViewDataBinding>{
    T binding;
    public BindingElement (ViewGroup parent) {
        // 尝试在构造器中调用抽象方法
        binding = createBinding(LayoutInflater.from(parent.getContext()), parent);
        binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
    }
    // 抽象方法,由子类实现具体的 binding 创建逻辑
    abstract T createBinding(LayoutInflater inflater, ViewGroup parent);
    public void doSomething() {
        // 共享的业务逻辑
    }
}
public class LoadElement extends BindingElement<LoadingElementBinding>{
    public LoadElement(ViewGroup parent) {
        super(parent);
    }
    @Override
    LoadingElementBinding createBinding(LayoutInflater inflater, ViewGroup parent){
       return LoadingElementBinding.inflate(inflater, parent, false);
    }
}
// ... 其他子类类似然而,这种做法在Java中存在潜在的风险。在Java中,当一个对象的构造器被调用时,其父类的构造器会首先执行。如果在父类构造器中调用了一个非 final 或 private 的方法(尤其是抽象方法),并且该方法在子类中被重写,那么在子类完全初始化之前,子类重写的方法可能会被调用。这可能导致访问到尚未初始化完成的子类成员变量,从而引发 NullPointerException 或其他不可预测的行为。这种设计模式通常被认为是“构造器中调用虚方法”的反模式。
立即学习“Java免费学习笔记(深入)”;
为了安全且优雅地解决上述问题,我们可以利用Java 8引入的函数式接口(FunctionalInterface)和方法引用(Method Reference)。核心思想是将具体的 binding 创建逻辑封装在一个函数式接口中,并通过构造器参数将其传递给抽象基类。这样,基类在构造时接收的是一个已准备好的“创建器”,而不是主动调用一个尚未完全确定的抽象方法。
首先,我们定义一个函数式接口 BindingCreator,它包含一个抽象方法,用于创建特定类型的 ViewDataBinding 实例。
@FunctionalInterface
public interface BindingCreator<T extends ViewDataBinding>{
    /**
     * 创建一个 ViewDataBinding 实例。
     * @param inflator 用于布局膨胀的 LayoutInflater
     * @param parent 父视图组
     * @param attachToParent 是否将布局附加到父视图组
     * @return 创建的 ViewDataBinding 实例
     */
    T createBinding(LayoutInflater inflator, ViewGroup parent, boolean attachToParent);
}这个接口的签名与 ViewDataBinding 的静态 inflate 方法兼容。
修改 BindingElement 抽象类的构造器,使其接受一个 BindingCreator 实例作为参数。在构造器内部,通过这个 BindingCreator 实例来执行 binding 的创建逻辑。
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.databinding.ViewDataBinding;
public abstract class BindingElement <T extends ViewDataBinding>{
    protected T binding; // 建议设为 protected 以便子类访问
    /**
     * 构造函数,通过 BindingCreator 接收具体的 binding 创建逻辑。
     * @param parent 父视图组
     * @param bindingCreator 用于创建 ViewDataBinding 实例的函数式接口
     */
    public BindingElement(ViewGroup parent, BindingCreator<T> bindingCreator){
        // 使用传入的 bindingCreator 来创建 binding 实例
        binding = bindingCreator.createBinding(
                       LayoutInflater.from(parent.getContext()),
                       parent,
                       false); // 假设 false 是一个通用参数,或可由子类传递
        binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
    }
    public void doSomething() {
        // 共享的业务逻辑,可操作 binding
    }
    // 允许子类或外部访问 binding
    public T getBinding() {
        return binding;
    }
}现在,具体的子类 LoadElement 和 ErrorElement 只需要在其构造器中调用 super(),并传入对应 Binding 类的静态 inflate 方法的方法引用即可。
import android.view.ViewGroup;
import com.example.app.LoadingElementBinding; // 假设这是你的 binding 类
import com.example.app.ErrorElementBinding;   // 假设这是你的 binding 类
public class LoadElement extends BindingElement<LoadingElementBinding>{
    public LoadElement(ViewGroup parent) {
        // 通过方法引用将 LoadingElementBinding::inflate 作为 BindingCreator 传入
        super(parent, LoadingElementBinding::inflate);
    }
    // ... 可以有 LoadElement 特有的方法
}
public class ErrorElement extends BindingElement<ErrorElementBinding>{
    public ErrorElement(ViewGroup parent) {
        // 通过方法引用将 ErrorElementBinding::inflate 作为 BindingCreator 传入
        super(parent, ErrorElementBinding::inflate);
    }
    // ... 可以有 ErrorElement 特有的方法
}通过巧妙地结合Java 8的函数式接口和方法引用,我们能够以一种安全、优雅且高效的方式来组织和重用具有相似初始化逻辑的代码。这种模式不仅解决了传统面向对象设计中“构造器中调用虚方法”的陷阱,还提升了代码的模块化、可读性和可维护性。在处理类似的初始化场景时,优先考虑使用这种函数式编程与面向对象设计相结合的模式,将有助于构建更加健壮和灵活的Java应用程序。
以上就是Java中优雅地组织和重用初始化代码:避免构造器陷阱的函数式接口实践的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号