Optaplanner跨实体Shadow Variable自定义VariableListener触发空指针异常
在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
问题修复方案
修正Shadow变量类的注解
yvariable类没有真实的规划变量,不需要标记为@PlanningEntity,移除该注解,保持@ProblemFactProperty在解决方案类中的配置即可。调整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"); } } // 其他空实现方法保留 }
- 初始化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"); }
- 约束中添加空值判断
如果约束直接使用y变量,需要添加空值检查,比如在Drools规则中:
rule "maximum" when $param : parameter() $var : variable(x < $param.max) $yVar : yvariable(y != null) then // 最大化y的逻辑 end
内容的提问来源于stack exchange,提问作者yassine

