以下是一个完整的示例,展示如何将 Person
对象作为 Flink keyBy()
的键:
自定义 Person
类(作为 Key)
import java.util.Date;
import java.util.Objects;
// 推荐实现为不可变对象
public final class Person {
// 关键字段(参与分组的属性)
private final String name;
private final int age;
private final Date birthday;
private final String path;
private final long time;
// 必须有无参构造函数(如果定义为 POJO)
public Person() {
this.name = null;
this.age = 0;
this.birthday = null;
this.path = null;
this.time = 0L;
}
// 全参构造函数(推荐)
public Person(String name, int age, Date birthday, String path, long time) {
this.name = name;
this.age = age;
this.birthday = birthday;
this.path = path;
this.time = time;
}
// ---------------------------
// 必须实现 equals 和 hashCode
// ---------------------------
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
time == person.time &&
Objects.equals(name, person.name) &&
Objects.equals(birthday, person.birthday) &&
Objects.equals(path, person.path);
}
@Override
public int hashCode() {
return Objects.hash(name, age, birthday, path, time);
}
// ---------------------------
// Getter 方法(推荐)
// ---------------------------
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Date getBirthday() {
return birthday;
}
public String getPath() {
return path;
}
public long getTime() {
return time;
}
}
在 Flink 中使用 Person
作为 Key
DataStream<Person> stream = ...; // 输入数据流
// 按 Person 对象分组(所有字段参与分组)
KeyedStream<Person, Person> keyedStream = stream.keyBy(person -> person);
验证 Key 的正确性
public class PersonTest {
public static void main(String[] args) {
Date date = new Date();
Person p1 = new Person("Alice", 30, date, "/data", 1630454400L);
Person p2 = new Person("Alice", 30, date, "/data", 1630454400L);
Person p3 = new Person("Bob", 25, date, "/log", 1630454401L);
System.out.println("p1.equals(p2): " + p1.equals(p2)); // 应输出 true
System.out.println("p1.hashCode() == p2.hashCode(): " + (p1.hashCode() == p2.hashCode())); // 应输出 true
System.out.println("p1.equals(p3): " + p1.equals(p3)); // 应输出 false
}
}
关键点说明
-
不可变性
- 字段声明为
final
,通过构造函数初始化,避免分组后 Key 被修改。
- 字段声明为
-
POJO 要求
- 无参构造函数(Flink 反射机制需要)。
- 所有字段要么是
public
,要么通过getter
访问。
-
equals()
和hashCode()
- 所有参与分组的字段(
name
,age
,birthday
,path
,time
)必须包含在计算中。 - 使用
Objects.equals
和Objects.hash
处理null
值。
- 所有参与分组的字段(
-
实际分组逻辑
- 如果业务上不需要所有字段参与分组(例如仅用
name
和age
),可调整equals
/hashCode
的实现。
- 如果业务上不需要所有字段参与分组(例如仅用
常见问题
-
日期字段的处理
Date
类型在hashCode()
中直接使用Objects.hash
会自动处理其哈希值。如果日期是业务关键字段,需确保其参与计算。 -
性能优化
如果字段较多,可缓存hashCode
值(但需确保对象不可变):private int cachedHashCode; @Override public int hashCode() { if (cachedHashCode == 0) { cachedHashCode = Objects.hash(name, age, birthday, path, time); } return cachedHashCode; }
-
序列化问题
Flink 会自动处理 POJO 的序列化。如果字段包含复杂类型(如自定义对象),需确保它们也是可序列化的。