Search code examples
javadesign-patternshashmaplinkedhashmap

Is HashSet a well-written class?


I'm trying to learn design patterns as good coding practices and I would like to know if a HashSet is considered a well-written class? eg:

To construct a LinkedHashSet we use the following constructor

public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
}

Which calls this one:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

So there is a useless param, is it correct to do that?

Also, I see that LinkedHashSet extends HashSet

LinkedHashSet<E> extends HashSet 

and HashSet references LinkedHashSet in its code, why there is nothing like a compile time recursion there?

// Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

Solution

  • This is severely optinion based.

    And in a lot of situations, the programmers had to make hard choices. And there has been so many teams at work, that it's natural that some of the following problems occur.

    However, here's my opinion on that topic:

    • Most of the JDK code is a mess. Unnecessary code, convoluted code that modern Java JIT compilers easily will do better
    • There's lots of micro optimizations, but in the end a lot of implementations can rewritten to be much faster, cleaner, more compatible.
    • Simple classes like ArrayList are already a mess, and if you write your own implementation, it will be twice as fast.
    • The Input/OuputStream system is a mess, should have had an interface at the very top.
    • A lot of solutions like ThreadLocal are inherently unsafe to use and can cause leaks if not worse problems.
    • There's a lot of repetition of code where there shouldn't be
    • a huge lacking support for default conversions, especially String to something else, should have default methods like s.toInt(int default)-like methods
    • The java.util. functions and FunctionalInterfaces are a mess, with lots of different names that could have been constructed in a far more logical and intuitive way, alsong with all the Collectors and stuff. Could be so much easier.

    On the other hand:

    • The Collections structures (inheritance, Interfaces etc) are over all pretty well implemented, with only very few features missing
    • the java.nio stuff is really good
    • The Exceptions (RuntimeException, Throwable) hierarchies are quite good and provide a stable basis for additional custom classes (that one might prefer to / should use)

    Some aspects that to not target the implementation of classes, but the language specification:

    • how they introduced and integrated Lambdas and Streams (the whole functional show)
    • Generics and Covariance/Contravariance
    • Reflection and Annotations

    All in all, if you give your default Java a little boost with libraries like Project Lombok (adding AOP and stuff), it is awesome. I really love it, and so far no other language could un-favorite Java (even tho I hated Java when I had to learn it)

    So, as others have stated:

    • learn from the code (some techniques are REALLY good)
    • improve upon them (some are obviously BAD)

    And finally to the points you addressed:

    1. The dummy parameter. This is a very rare occurrence, and even then mostly only occurs for quality-of-life: instead of having only one CTOR with 3 arguments, also have another CTOR with 2 arguments
    2. concerning the compile time recursion: Java compiler can be compared to a multi-pass compiler, so the 'recursion' does not play a role. What is a little bad about this implementation - a very theoretical/academic complaint - is that HashSet and LinkedHashSet are now statically bound in BOTH directions. LinkedHashSet -> HashSet would be fine (how else would inheritance be implemented!?), but HashSet -> LinkedHashSet is a bit of a bugger. But, well, academic, because those two classes are in the same package, and not even the new Modules system will rip them apart. So unless you write a packaging tool that discerns on such a low level (like I did), this point has no practical impact.