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

OptaPlanner中Joiners无法正常工作问题求助

OptaPlanner中Joiners关联失效问题排查

我是OptaPlanner新手,正尝试修改员工排班示例。使用Joiners关联Shift与Availability类时,约束无法生效——员工仍被安排到其不可用的班次;但改用Filter手动对比员工ID的方式可以正常工作。我知道Filter方式效率更低,希望找出Joiners的使用错误。

失效的Joiners版本约束函数

Constraint unavailableEmployee (ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Shift.class)
            .join(Availability.class,
                Joiners.equal(Shift::getEmployee,Availability::getEmployee))
            .filter((shift,availability) -> availability.getAvailabilityType() == AvailabilityType.UNAVAILABLE)
            .penalize(HardMediumSoftScore.ONE_HARD)
            .asConstraint("Unavailable employee");
}

可行的Filter版本约束函数

Constraint unavailableEmployee(ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Shift.class)
            .join(Availability.class)
            .filter((shift, availability) -> (shift.getEmployee().getEmployeeId().equals(availability.getEmployee().getEmployeeId()) && availability.getAvailabilityType() == AvailabilityType.UNAVAILABLE))
            .penalize(HardMediumSoftScore.ONE_HARD)
            .asConstraint("Unavailable employee");
}

关联的领域类代码

Shift类

package com.guidematcher.domain;

import lombok.Getter;
import lombok.Setter;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.variable.PlanningVariable;

import java.time.LocalDateTime;
import java.util.UUID;

@PlanningEntity
public class Shift {
    @Getter
    @Setter
    @PlanningId
    private String shiftId;

    @Getter
    @Setter
    private LocalDateTime shiftStart;

    @Getter
    @Setter
    private LocalDateTime shiftEnd;

    @Getter
    @Setter
    private EmployeeType requiredEmployeeType;

    @Getter
    @Setter
    private String requiredEmployeeTypeDesc;

    @Getter
    @Setter
    @PlanningVariable
    private Employee employee;

    public Shift() {}

    public Shift (LocalDateTime shiftStart,LocalDateTime shiftEnd, String requiredEmployeeTypeDesc) {
        this(UUID.randomUUID().toString(),shiftStart,shiftEnd,requiredEmployeeTypeDesc,null);
    }

    public Shift (String shiftId,LocalDateTime shiftStart,LocalDateTime shiftEnd,String requiredEmployeeTypeDesc, Employee employee) {
        this.shiftId = shiftId;
        this.shiftStart = shiftStart;
        this.shiftEnd = shiftEnd;
        this.setRequiredEmployeeTypeDesc(requiredEmployeeTypeDesc);
        this.employee = employee;
    }

    public void setRequiredEmployeeTypeDesc(String requiredEmployeeTypeDesc) {
        this.requiredEmployeeTypeDesc = requiredEmployeeTypeDesc;
        this.requiredEmployeeType = EmployeeType.getEmployeeTypeByDesc(requiredEmployeeTypeDesc);
    }

    public String toString() {
        return this.getShiftId() + "-" + this.getRequiredEmployeeType().getEmployeeTypeDesc() + " " + this.getShiftStart();
    }
}

Availability类

package com.guidematcher.domain;

import lombok.Getter;
import lombok.Setter;
import org.optaplanner.core.api.domain.lookup.PlanningId;

import java.time.LocalDate;
import java.util.UUID;

public class Availability {
    @Getter
    @Setter
    @PlanningId
    private String availabilityId;
    @Getter
    @Setter
    LocalDate availabilityDate;

    @Getter
    @Setter
    AvailabilityType availabilityType;

    @Getter
    @Setter
    Employee employee;

    public Availability() {}

    public Availability(String availabilityId, Employee employee, LocalDate availabilityDate, AvailabilityType availabilityType ) {
        this.availabilityId = UUID.randomUUID().toString();
        this.employee = employee;
        this.availabilityDate = availabilityDate;
        this.availabilityType = availabilityType;
    }
}

问题原因与解决方案

问题核心在于Employee类未正确重写equals()和hashCode()方法:

  • Joiners.equal()依赖对象的equals比较逻辑,默认的Object.equals()仅比较对象引用地址;
  • Filter方式是基于employeeId的值比较,能匹配同一个员工的不同实例;但Joiners用Employee对象比较时,只有当两个对象是内存中的同一个引用才会匹配,实际场景中Shift和Availability关联的Employee可能是不同实例但ID相同,导致关联失效。

解决方案:给Employee类重写equals()和hashCode(),基于employeeId实现值比较:

import java.util.Objects;

// 在Employee类中添加以下方法
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Employee employee = (Employee) o;
    return Objects.equals(getEmployeeId(), employee.getEmployeeId());
}

@Override
public int hashCode() {
    return Objects.hash(getEmployeeId());
}

修改后,Joiners.equal(Shift::getEmployee, Availability::getEmployee)就能正确匹配同一个员工的Shift和Availability记录,约束会正常生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 14:27:17