Create a complete binary tree
There are two major issues I see with your implementation, one functional, and one performance.

You have implemented the equals() method and, additionally, you have implemented the hashCode() method too. It is good that you are implementing them both, but, they are broken.

The hashCode() method only calculates the hashCode of the root node (if any), and it does not calculate it off the value in the node, but off the node itself.


@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((root == null) ? 0 : root.hashCode());
    return result;
}



You do not override the hashCode of TreeNode, so the result is just a somewhat unique number, and thus, even if you have two trees with the same value at the root, they will have different hashCode values. If you override equals, you have to override hashCode, but, if you override hashCode, do it properly!

The Performance issue relates to the core addItem method. Your method is an \$O(n)\$ operation, scanning almost the entire tree, to get to the last item added.

A better algorithm would be to maintain a stack of the path to the most recently added node's parent. This would be maintained at the same time as the root node.... consider:

Deque&lt;TreeNode&lt;T&gt;&gt; insertPoint = new LinkedList&lt;&gt;();
TreeNode root = null;


Then, when you add an item:

/**
 * Adds item, such the tree is a complete binary tree.
 * 
 * @param item the item to be added.
 */
public void addItem(T item) {

    if (root == null) {
        root = new TreeNode&lt;T&gt;(item);
        return;
    }

    if (!insertedAt.isEmpty() &amp;&amp; insertedAt.peek().right != null) {
        TreeNode&lt;T&gt; kid = insertedAt.pop();
        while (!insertedAt.isEmpty() &amp;&amp; kid == insertedAt.peek().right) {
            kid = insertedAt.pop();
        }
        if (!insertedAt.isEmpty()) {
            kid = insertedAt.peek();
            kid = kid.right;
            insertedAt.push(kid);
            while (kid.left != null) {
                kid = kid.left;
                insertedAt.push(kid);
            }
        }
    }

    if (insertedAt.isEmpty()) {
        // filled the leaf row, go down the left edge again.
        TreeNode&lt;T&gt; node = root;
        while (node != null) {
            insertedAt.push(node);
            node = node.left;
        }
    }

    TreeNode&lt;T&gt; node = insertedAt.peek();
    TreeNode&lt;T&gt; toadd = new TreeNode&lt;&gt;(item);
    if (node.left == null) {
        node.left = toadd;
    } else {
        node.right = toadd;
    }

}


What the above code does, is keep the most recently added-to node at the top of the stack. Then, if the node can still be added to, it fills it.

If the node is now full, it goes back up the tree, looking for the next insertion point, which may nto exist on this level. If it does not then it goes all the way down the left edge and starts a new level....

This means that, for many inserts (half of them?), the performance is \$O(1)\$ and for the worst case, when you have just completed a level of the tree, and you have to start over again at the left, it requires one walk up the right edge, and back down the left edge, in an \$O(\log{n})\$ operation.

So, this brings the process from a \$O(n)\$ operation with \$O(\log{n})\$ space complexity, to a worst-case \$O(\log{n})\$ operation with \$O(\log{n})\$ space as well.