Java中基于双唯一属性外连接两个对象列表生成新列表
Alright, let's solve this outer join problem where we need to combine two lists (Users and Academics) using both id and name as the unique matching keys. I'll walk through a full outer join solution (includes entries from either list even if there's no match in the other) since that's the most common use case, and also note how to adjust for left/right joins if needed.
Step 1: Define a Combined Result Class
First, we need a class to hold the joined data — this lets us store attributes from both Users and Academics, even when one side has no matching entry.
public class UserAcademic { private Long id; private String name; private String gender; // From Users private String grade; // From Academics private String professional; // From Academics // Constructor, getters, setters, and toString() for usability public UserAcademic(Long id, String name, String gender, String grade, String professional) { this.id = id; this.name = name; this.gender = gender; this.grade = grade; this.professional = professional; } // Add getters and setters here (omitted for brevity) @Override public String toString() { return "UserAcademic{" + "id=" + id + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", grade='" + grade + '\'' + ", professional='" + professional + '\'' + '}'; } }
Step 2: Create a Composite Key for Matching
Since we're matching on two fields (id and name), we need a composite key to use as a lookup in a map. For Java 16+, a Record is perfect (it automatically handles equals() and hashCode()):
// Composite key using Java 16+ Record public record UserKey(Long id, String name) {}
If you're on a pre-Java 16 version, use a custom class with properly overridden equals() and hashCode():
// Composite key for pre-Java 16 import java.util.Objects; public class UserKey { private Long id; private String name; public UserKey(Long id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserKey userKey = (UserKey) o; return Objects.equals(id, userKey.id) && Objects.equals(name, userKey.name); } @Override public int hashCode() { return Objects.hash(id, name); } }
Step 3: Implement the Full Outer Join Logic
We'll use a HashMap to efficiently map composite keys to our combined UserAcademic objects. This gives us O(n + m) time complexity (way better than nested loops):
import java.util.*; import java.util.stream.Collectors; public class OuterJoinExecutor { public static void main(String[] args) { // Your pre-initialized userList List<Users> userList = Arrays.asList( new Users(1L, "Alice", "Female"), new Users(2L, "Bob", "Male"), new Users(3L, "Charlie", "Male") ); // Sample academicsList (replace with your initialized list) List<Academics> academicsList = Arrays.asList( new Academics(1L, "Alice", "A", "Computer Science"), new Academics(2L, "Bob", "B", "Mathematics"), new Academics(4L, "Diana", "A", "Physics") ); // Run the outer join List<UserAcademic> joinedResult = performFullOuterJoin(userList, academicsList); // Print results to verify joinedResult.forEach(System.out::println); } private static List<UserAcademic> performFullOuterJoin(List<Users> userList, List<Academics> academicsList) { Map<UserKey, UserAcademic> joinMap = new HashMap<>(); // First add all Users entries (Academics fields will be null initially) for (Users user : userList) { UserKey key = new UserKey(user.getId(), user.getName()); joinMap.put(key, new UserAcademic(user.getId(), user.getName(), user.getGender(), null, null)); } // Now process Academics: update existing entries or add new ones for (Academics academic : academicsList) { UserKey key = new UserKey(academic.getId(), academic.getName()); if (joinMap.containsKey(key)) { // Update the existing entry with Academics data UserAcademic existingEntry = joinMap.get(key); existingEntry.setGrade(academic.getGrade()); existingEntry.setProfessional(academic.getProfessional()); } else { // Add a new entry (User fields will be null) joinMap.put(key, new UserAcademic(academic.getId(), academic.getName(), null, academic.getGrade(), academic.getProfessional())); } } // Convert map values to a list for the final result return new ArrayList<>(joinMap.values()); } } // Your existing Users class class Users { private Long id; private String name; private String gender; public Users(Long id, String name, String gender) { this.id = id; this.name = name; this.gender = gender; } // Getters (required for the join logic) public Long getId() { return id; } public String getName() { return name; } public String getGender() { return gender; } } // Your Academics class class Academics { private Long id; private String name; private String grade; private String professional; public Academics(Long id, String name, String grade, String professional) { this.id = id; this.name = name; this.grade = grade; this.professional = professional; } // Getters (required for the join logic) public Long getId() { return id; } public String getName() { return name; } public String getGrade() { return grade; } public String getProfessional() { return professional; } }
Key Notes & Variations
- Full Outer Join: The above code includes every entry from both lists. Missing fields will be
null(e.g., Diana will havegender: null, Charlie will havegrade: nullandprofessional: null). - Left Outer Join: If you only want to keep entries from
userList(even without a matchingAcademicsentry), remove theelseblock where we add new entries fromacademicsList. - Right Outer Join: To keep only entries from
academicsList, reverse the order: first add allAcademicsentries to the map, then update with matchingUsersentries.
内容的提问来源于stack exchange,提问作者Jayendran

