Generic implementation of the Quick-Union algorithm with Path Compression
In find() and areConnected(), you should be able to use == rather than .equals() since you are comparing references, not contents.



Your find() loop is a bit inefficient:


You do components.get(component) once during the while condition test, and again during path compression.
You do components.put(component, …) during path compression, then immediately lookup components.get(component) to advance the loop.


The remedy is to introduce two temporary variables, which I call parent and grandparent.

I've chosen to write it as a for loop with side-effect assignments, but of course you can choose to use a while loop without side-effect assignments.

private T find(T c) {
    // Lookup with path compression
    for (T parent, grandparent; (parent = components.get(c)) != c; c = grandparent) {
        components.put(c, grandparent = components.get(parent));
    }
    return c;
}




connect() calls areConnected(), which performs redundant work.  I suggest…

public void connect(T leftComponent, T rightComponent) {
    Objects.requireNonNull(leftComponent);
    Objects.requireNonNull(rightComponent);

    T leftComponentTree = find(leftComponent);
    T rightComponentTree = find(rightComponent);

    if (leftComponentTree == rightComponentTree) {
        return;     // Already connected
    }

    …
}