You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Optaplanner跨实体Shadow Variable自定义VariableListener触发空指针异常

Optaplanner跨实体Shadow Variable更新引发NullPointerException问题

在Optaplanner开发中,需要在与真实规划变量所在类不同的@PlanningEntity类中使用Shadow Variable,通过自定义VariableListener更新这些Shadow Variable(例如将真实规划变量的值复制到另一类的Shadow Variable中),但运行求解器时触发了NullPointerException。


复现代码

真实变量类(variable)

@PlanningEntity
public class variable {

    @PlanningId
    private Integer id;
    private Integer x;

    public variable() {
    }

    public variable(Integer id) {
        this.id = id;
    }

    @PlanningVariable(valueRangeProviderRefs = "xRange")
    public Integer getX() {
        return x;
    }

    // getters and setters
    ....
}

Shadow变量类(yvariable)

@PlanningEntity
public class yvariable {

    private int idy;
    private Integer y;

    public yvariable() {
    }

    @CustomShadowVariable(variableListenerClass = yVariableListener.class,
            sources = {@PlanningVariableReference(entityClass = variable.class, variableName = "x")})
    public Integer getY() {
        return y;
    }

    // getters and setters
    ....
}

问题事实类(parameter)

public class parameter {
    private int max;
    private int min;

    public parameter() {
    }

    public parameter(int max, int min) {
        this.max = max;
        this.min = min;
    }

    // getters and setters
    ....
}

规划解决方案类(Planningsolution)

@PlanningSolution
public class Planningsolution {

    @PlanningEntityProperty
    private variable variable;

    @ProblemFactProperty
    private parameter parameter;

    @ProblemFactProperty
    private yvariable yvar;

    @ValueRangeProvider(id="xRange")
    public CountableValueRange<Integer> getxRange(){
        return ValueRangeFactory.createIntValueRange(1, 300);
    }

    @PlanningScore
    private HardSoftScore score;

    public Planningsolution() {
    }

    public Planningsolution(domain.variable variable, domain.parameter parameter, yvariable yvar) {
        this.variable = variable;
        this.parameter = parameter;
        this.yvar=yvar;
    }

    // getters and setters
    ....
}

变量监听器类(yVariableListener)

public class yVariableListener implements VariableListener<Planningsolution, yvariable> {

    @Override
    public void beforeEntityAdded(ScoreDirector<Planningsolution> scoreDirector, yvariable variable) {
        // Do nothing
    }

    @Override
    public void afterEntityAdded(ScoreDirector<Planningsolution> scoreDirector, yvariable variable) {
        // VentesParJour(scoreDirector, variable);
    }

    @Override
    public void beforeVariableChanged(ScoreDirector<Planningsolution> scoreDirector, yvariable variable) {
        // Do nothing
    }

    @Override
    public void afterVariableChanged(ScoreDirector<Planningsolution> scoreDirector, yvariable variable) {
        VentesParJour(scoreDirector, variable);
    }

    @Override
    public void beforeEntityRemoved(ScoreDirector<Planningsolution> scoreDirector, yvariable variable) {
        // Do nothing
    }

    @Override
    public void afterEntityRemoved(ScoreDirector<Planningsolution> scoreDirector, yvariable variable) {
        // Do nothing
    }

    protected void VentesParJour(ScoreDirector<Planningsolution> scoreDirector, yvariable yy) {
        Planningsolution solution = scoreDirector.getWorkingSolution();

        variable xvar= solution.getVariable();

        Integer wtf;

        if(xvar.getX()!=null) {
            wtf=xvar.getX();
            scoreDirector.beforeVariableChanged(yy, "y");
            yy.setY(wtf);
            scoreDirector.afterVariableChanged(yy, "y");
        }
    }
}

约束条件

在x小于max的前提下最大化y


报错信息

Exception in thread "main" Exception executing consequence for rule
"maximum" in domain: java.lang.NullPointerException at
org.drools.core.runtime.rule.impl.DefaultConsequenceExceptionHandler.handleException(DefaultConsequenceExceptionHandler.java:39)
at
org.drools.kiesession.agenda.DefaultAgenda.handleException(DefaultAgenda.java:935)
at
org.drools.core.phreak.RuleExecutor.innerFireActivation(RuleExecutor.java:460)
at
org.drools.core.phreak.RuleExecutor.evaluateNetworkAndFire(RuleExecutor.java:101)
at
org.drools.core.concurrent.AbstractRuleEvaluator.internalEvaluateAndFire(AbstractRuleEvaluator.java:33)
at
org.drools.core.concurrent.SequentialRuleEvaluator.evaluateAndFire(SequentialRuleEvaluator.java:43)
at
org.drools.kiesession.agenda.DefaultAgenda.fireLoop(DefaultAgenda.java:748)
at
org.drools.kiesession.agenda.DefaultAgenda.internalFireAllRules(DefaultAgenda.java:695)
at
org.drools.kiesession.agenda.DefaultAgenda.fireAllRules(DefaultAgenda.java:687)
at
org.drools.kiesession.session.StatefulKnowledgeSessionImpl.internalFireAllRules(StatefulKnowledgeSessionImpl.java:1090)
at
org.drools.kiesession.session.StatefulKnowledgeSessionImpl.fireAllRules(StatefulKnowledgeSessionImpl.java:1081)
at
org.drools.kiesession.session.StatefulKnowledgeSessionImpl.fireAllRules(StatefulKnowledgeSessionImpl.java:1073)
at
org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirector.calculateScore(DroolsConstraintStreamScoreDirector.java:70)
at
org.optaplanner.core.impl.solver.recaller.BestSolutionRecaller.solvingStarted(BestSolutionRecaller.java:50)
at
org.optaplanner.core.impl.solver.AbstractSolver.solvingStarted(AbstractSolver.java:65)
at
org.optaplanner.core.impl.solver.DefaultSolver.solvingStarted(DefaultSolver.java:225)
at
org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:192)
at SimpleApp.main(SimpleApp.java:45) Caused by:
java.lang.NullPointerException at
org.optaplanner.constraint.streams.drools.common.UniRuleContext.lambda$newRuleBuilder$c987a6e2$1(UniRuleContext.java:28)
at org.drools.model.functions.Block3$Impl.execute(Block3.java:40) at
org.drools.modelcompiler.consequence.LambdaConsequence.evaluate(LambdaConsequence.java:74)
at
org.drools.core.phreak.RuleExecutor.innerFireActivation(RuleExecutor.java:454)
... 15 more


问题修复方案

  1. 修正Shadow变量类的注解
    yvariable类没有真实的规划变量,不需要标记为@PlanningEntity,移除该注解,保持@ProblemFactProperty在解决方案类中的配置即可。

  2. 调整VariableListener的监听目标
    当前监听器监听的是yvariable的变量变化,但我们需要监听的是variable类中x的变化。修改监听器的泛型和方法实现:

public class yVariableListener implements VariableListener<Planningsolution, variable> {

    @Override
    public void afterVariableChanged(ScoreDirector<Planningsolution> scoreDirector, variable xVar) {
        Planningsolution solution = scoreDirector.getWorkingSolution();
        yvariable yy = solution.getYvar();
        if (xVar.getX() != null) {
            scoreDirector.beforeVariableChanged(yy, "y");
            yy.setY(xVar.getX());
            scoreDirector.afterVariableChanged(yy, "y");
        }
    }

    // 其他空实现方法保留
}
  1. 初始化Shadow变量值
    在求解开始前,确保y变量有初始值,避免约束计算时空指针。可以在监听器的afterEntityAdded方法中添加初始化逻辑:
@Override
public void afterEntityAdded(ScoreDirector<Planningsolution> scoreDirector, variable xVar) {
    Planningsolution solution = scoreDirector.getWorkingSolution();
    yvariable yy = solution.getYvar();
    scoreDirector.beforeVariableChanged(yy, "y");
    yy.setY(xVar.getX() != null ? xVar.getX() : 0);
    scoreDirector.afterVariableChanged(yy, "y");
}
  1. 约束中添加空值判断
    如果约束直接使用y变量,需要添加空值检查,比如在Drools规则中:
rule "maximum"
    when
        $param : parameter()
        $var : variable(x < $param.max)
        $yVar : yvariable(y != null)
    then
        // 最大化y的逻辑
end

内容的提问来源于stack exchange,提问作者yassine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:15:38