Search code examples
javagenericsvar

Should you use var on generic types in Java?


My question is a code style question. So there's obviously not a definitive "right" answer to it.

var foo = new Foo<String>();

or

Foo<String> foo = new Foo<>();

Both versions are equivalent valid Java code assuming the generic class Foo exists. I couldn't find anything like guidelines regarding var and generic types.


Solution

  • Here's how you deal with style advice:

    First, never just blindly follow advice. Virtually no programming advice is a rule that has zero exceptions and can be blindly applied regardless of circumstance. They are rules of thumb - things that are usually but not always true.

    Thus, if you don't understand the actual reasons behind a rule, it is hopeless - you will be cargo culting your way through your programming career, and that's no way to live. You'll be rigorously applying rules in cases where they are flat out the wrong thing to do!

    Thus, step 1: Figure out why some pattern, style, or other thing you are doing in your code is being advised against. You want specific reasons, preferably with examples.

    Then, measure these specific reasons against the best examples your experience (limited as it might be) can come up with and take stock. Do the arguments make some sense to you, or do they seem wildly irrelevant or out of proportion vs the upside you gain?

    If that is the case, then ignore that advice, it was silly, or there is no hope you can properly apply it right now.

    That's because there are only 3 options:

    • The naysayers are koolaid drinking loonies that are completely wrong. Ignoring them is the correct course of action. This is unlikely, but it happens in programming land; zealotry is unfortunately somewhat common.

    • They were thinking of different usages that haven't even entered your mind yet, or their code style is so significantly different than yours, for them var is really bad but it simply doesn't apply to you. You heard "You should not use var" but that's not what they actually meant. They meant: "You should not use var in [cases that do not apply to you]". Their advice should be ignored: It doesn't apply to you.

    • They have discovered some hidden truth that applies to you and is a fair reason not to do the thing you are doing. However, this line of reasoning is so foreign to you, you are epically misjudging the situation. You can't even begin to follow it. This is possible, experienced programmers are a heck of a thing to deal with, and programming is complicated enough for this to be a realistic option. But, that also means you stand no chance at all of applying common sense to expand the rule of thumb into actually applying it properly. You are doomed to make the very mistake they are warning you about, whether you apply their rule of thumb or not.

    You know what mistakes are? They are the thing that is required for a noob programmer to turn into an experienced programmer. So don't sweat it. Ignore their advice as you can't apply it. If truly it is this case, you will, eventually, swear under your breath, and get that sudden rush of realization: They were right. But now you are enlightened, and on your way to a more experienced programmer. I have yet to figure out a better way to turn new coders into experienced ones; some mistakes are required along the way, and this will perhaps be one of them. Celebrate it.

    As an example, let's go through it for var.

    First, we ignore "Just do not use it" style "advice". Without reasons we can't apply common sense which makes it useless. We focus on those who bring falsifiables to the table and focus on their reasoning.

    In my experience, the two primary named reasons are thus, given var foo = something;:

    • It is less readable - you simply can't tell what foo might be.
    • It hides details, for example, if you replace List<String> foo = new ArrayList<>(); with var foo = new ArrayList<String>();, then you 'lost' the idea that you don't care about foo's implementation, you simply care that it is a List.

    Fortunately, in the vast majority of code snippet cases neither point is relevant. At least, when I try to apply these rules to my code style. Your mileage may vary, of course. To wit:

    It hides types

    Peruse any java project and doubtlessly you'll find many, many instances of 'chained methods'. Something as simple as:

    someObj.getClass().getName()
    
    someStr.toLowerCase().replace("_", "");
    

    These are just as guilty of the above problems! They hide the type of what the first method invocation returns. After all, how could one possibly know that someObj.getClass() is of type java.lang.Class<?whoknows?>, and .getName() is invoked on that (fortunately, also obviating the issue that we don't know what the generics are there. getName() doesn't care at all about them, so they aren't relevant here). Similarly, what does someStr.toLowerCase() return? What kind of thing are we invoking replace on? A total mystery!

    Except of course it isn't a mystery, even quite junior programmers, heck, programmers with some experience in other languages but with virtually zero experience in java will doubtlessly be utterly unfazed by the above two snippets. getClass() returns an instance of Class? Gosh, what a surprise. .toLowerCase() returns a String? You don't say! Let me make some notes!

    Clearly then, anybody advocating these rules is a hypocrite, or at least oversimplifying considerably. Unless they actually banned any method chaining of any kind in their code base before var's introduction, that is. I know a few var dislikers. None of them dislike method chaining. I have yet to hear a good reason for their evident doublethink on these concepts.

    But it also highlights a crucial aspect of var. Method chaining isn't always right. For the same reason, neither is var. If you write this:

    // This is an entirely hypothetical method!
    foo.pingPeer(message, sender).getName()
    

    Then perhaps that's not such a good idea. What does pingPeer return? I have no idea. Unless casual familiarity with whatever this is an API for would make the answer obvious, then the above is probably not a good idea. Neither pingPeer nor getName lends itself to knowing what the heck is going on here. The method chaining is hiding important information. This code is bad code style for that reason - it is less legible than the obvious replacement:

    MessageReply reply = foo.pingPeer(message, sender);
    reply.getName();
    

    Yes, it is longer, and longer code is usually worse, but not always. This is one of those cases.

    (I have no idea how a reply has a name either. As I said, hypothetical API I just made up. The fact that replies having names is surprising is part of the point: All the more reason to make sure anybody perusing this code is clear about what thing you're getting the name of).

    Thus, the rule: Use var only if the type that is now hidden is trivially determinable from context. Good hints that this is the case is if the type name is trivially derivable from a variable name or method, e.g:

    // eligible for replacing with var
    List<Student> x = course.getEnrolledStudents();
    List<Student> students = course.getEnrolled();
    
    // Not so clear cut, use careful judgement
    List<Student> x = course.getEnrolled();
    

    Type specificity loss

    The same method chaining argument applies here to a lesser extent as well. The solution to this dilemma is similar too: If it feels like omitting this information impacts the readability of a casual glance at the method, then don't use var.

    A trivial way to know it is highly unlikely to matter is if you never reassign the variable. If you write:

    public void List<String> foo() {
      var out = new ArrayList<String>();
      // code that interacts with "out",
      // but never overwrites it, i.e. no out = ... anywhere..
      return out;
    }
    

    Then you might want to mark out as final, but in general it doesn't really feel like the lack of representing 'actual list impl does not really matter' is particularly relevant in understanding what this code does, and a reader of this code is unlikely to be completely sent on a wild goose chase by erroneously thinking: "Huh! The original author must clearly care a lot about the list being, specifically, an arraylist! I'm going to read this entire method looking for the reason for that instead of focussing on what it is doing!".

    I'd use var here, with no hesitation at all.

    A third reason not known to me

    If I program long enough and on sufficient different kinds of projects, perhaps one fine day I will realize usage of var caused me a ton of pain, and I now know this third reason. I will never forget it, in fact. I will swear under my breath, but the moment will pass, as I'm also celebrating that I just became a better programmer. I'll refactor the code and write the time this takes off as unknown tech debt that had to be solved.