Optaplanner ConstraintFactory — от, похоже, возвращает неинициализированные сущности

#java #optaplanner

Вопрос:

Я пытаюсь решить простую проблему с доказательством концепции с помощью Optaplanner, и я сталкиваюсь с несоответствием. Документация (и другие вопросы SO), по-видимому, указывает, что ConstraintFactory.from() должны возвращаться только инициализированные объекты планирования. (как здесь)

У меня есть следующий объект планирования (только соответствующий код):

 @PlanningEntity
public class Enrollment {

    @PlanningId
    private UUID id;

    private Student student;
    @PlanningVariable(valueRangeProviderRefs = "possibleLessons", nullable = true)
    private Lesson lesson;

    // No-arg constructor required for OptaPlanner
    public Enrollment() {
    }

    public Enrollment(UUID id, Student student) {
        this.id = id;
        this.student = student;
    }

    public Student getStudent() {
        return student;
    }

    public Lesson getLesson() {
        return lesson;
    }
}
 

и попытался ввести следующее ограничение:

     private Constraint studentsLikePreferredSubjects(ConstraintFactory constraintFactory) {
        return constraintFactory
                .from(Enrollment.class)
                .filter(enrollment -> enrollment.getStudent().getPreferredSubjects().contains(enrollment.getLesson().getSubject()))
                .reward("Student likes subject bonus", HardSoftScore.ONE_SOFT);
    }
 

Этот код НЕ работает, так как я получаю исключение nullpointerexception на getLesson : Cannot invoke "Lesson.getSubject()" because the return value of "Enrollment.getLesson()" is null

Тем не менее, добавление фильтра для отфильтровывания нулевых уроков ДЕЙСТВИТЕЛЬНО работает:

     private Constraint studentsLikePreferredSubjects(ConstraintFactory constraintFactory) {
        return constraintFactory
                .from(Enrollment.class)
                .filter(enrollment -> enrollment.getLesson() != null)
                .filter(enrollment -> enrollment.getStudent().getPreferredSubjects().contains(enrollment.getLesson().getSubject()))
                .reward("Student likes subject bonus", HardSoftScore.ONE_SOFT);
    }
 

Конечно, я мог бы просто использовать этот код, но я не понимаю, зачем это необходимо: я бы ожидал Enrollment null Lesson , что в потоке не будет содержаться s с s, так как я использую from (и нет fromUnfiltered ). Кто-нибудь может объяснить такое поведение?

Ответ №1:

ConstraintFactory.from() возвращает только инициализированные объекты планирования, если nullable = true только (по умолчанию значение nullable равно false, поэтому from() фильтры).

В вашем коде nullable = true (вы выполняете чрезмерно напряженное планирование), поэтому from() возвращает все сущности.

Комментарии:

1. Такое поведение, я думаю, немного удивительно. Если мы когда-нибудь закончим ветвление 9.0, мы, возможно, захотим изменить это поведение .

2. @GeoffreyDeSmet Спасибо! Два быстрых замечания. Во-первых, вы говорите «если только nullable = true (что по умолчанию)», но не является ли значение по умолчанию false? Видишь docs.optaplanner.org/8.11.1.Final/optaplanner-javadoc/org/…

3. @GeoffreyDeSmet Также, для меня ваш ответ противоречит текущим документам, в частности, 4.3.4.2 Переменной планирования, на которой я основывал свой исходный код. Здесь говорится: «Потоки ограничений по умолчанию отфильтровывают объекты планирования с нулевой переменной планирования. Используйте fromUnfiltered (), чтобы избежать такого нежелательного поведения.» непосредственно после введения nullable = true. Это для меня подразумевает from() , что следует также фильтровать, когда значение null является истинным. Конечно, я считаю, что ваш ответ здесь правильный, но тогда, возможно, следует уточнить документы по этому вопросу.

4. Это хорошая обратная связь. Очевидно, что документы могут улучшиться — мы рассмотрим это. (и да, я имел в виду «по умолчанию он равен нулю» — исправляю свой ответ).

5. Мы будем изучать это в issues.redhat.com/browse/PLANNER-2535 .