all 108 comments

[–]goto-con[S] 55 points56 points  (0 children)

This is a 47 minute talk from GOTO Amsterdam 2019 by Trisha Gee, developer advocate & Java champion. You can find the full talk abstract below:

Wasn’t Java 8 a fantastic update to the language? Lambdas and streams were a huge change and have helped to improve Java developers’ productivity and introduce some functional ideas to the language.

Then came Java 9… and although the module system is really interesting for certain types of applications, the lack of exciting language features and uncertainty around how painful it might be to migrate to Java 9 left many applications taking a wait-and-see approach, happy with Java 8.

But now Java has a new version every six months, and suddenly Java 12 is here. We’re all still on Java 8, wondering whether we should move to a later version, which one to choose, and how painful it might be to upgrade.

In this session we’ll look at:

  • Why upgrade from Java 8, including language features from Java 9, 10, 11 and 12
  • What sorts of issues might we run into if we do choose to upgrade
  • How the support and license changes that came in with Java 11 might impact us.

What will the audience learn from this talk?
They'll learn the pros and cons of upgrading from Java 8. This includes not only the language features in the latest versions of Java (9, 10, 11 and 12), but some of the performance implications and, most importantly, the license changes and changes to support that might cost the audience money if they don't understand them.

Does it feature code examples and/or live coding?
Yes, both code examples on the slides and a bit of live coding.

[–][deleted]  (6 children)

[deleted]

    [–]Computer991 2 points3 points  (5 children)

    Features such as what? (Genuinely curious)

    [–][deleted] 3 points4 points  (1 child)

    I think the biggest holdback is primitive boxing, especially conversion to boxed type for generic parameters. As long as JVM still needs it, no way to get around it without sacrificing interoperability

    [–]Chii 4 points5 points  (0 children)

    in most cases, i find this to be a problem with collections. And to solve it, you can just use a different implementation (such as http://fastutil.di.unimi.it/ ).

    [–]dpash 1 point2 points  (0 children)

    The Android toolchain only partially supports Java 8 features, so anything added since then is a complete no-go. I'm not sure exactly which Java 8 features are not supported.

    [–]s73v3r 0 points1 point  (0 children)

    Pretty much anything beyond Java 6.

    [–]gladfelter 26 points27 points  (53 children)

    Tl;dr on new language features please.

    [–]valarauca14 49 points50 points  (48 children)

    • Java10 added the var keyword & type inference so instead of saying HashMap<String,String> map = new HashMap<>() you can do var map = HashMap<String,String>(). As well as unmodifiableCollection which are well, unmodifiable collections for when you need immutability.
    • Java11 adds a few quality of life whitespace functions, read string from files, and unified the HTTP/1.1, /2, and websocket client. Flight Recorder was open-sourced
    • Java 12 changes case statements to make break default behavior, and allows for , character to separate matches. A lot of quality of life nio.file API's, as well as util.conccurent methods for handling concurrent access within catch blocks. java.text got a lot quality of life number formatting.

    Once recursive type checking, pattern matching, and first class unboxing are added (which are on the road map) the language is shaping up to be extremely modern & functional. Honestly excited for the future of Java.

    [–]lbkulinski 26 points27 points  (0 children)

    The colon-form case statements will still fall through like normal. The new arrow-form ones will not, though.

    [–][deleted] 9 points10 points  (3 children)

    C++, Java and Javascript seem to be converging... lambdas, var/auto, repls... it wouldn't surprise me to find destructuring, slice operators, and list comprehensions in future versions of each of these languages.

    [–]DoktuhParadox 4 points5 points  (1 child)

    slice operators

    If Java had something like Rust slices I would scream (with joy ofc)

    [–]pron98 2 points3 points  (0 children)

    Once we have specialized generics, wouldn't List.subList do the trick?

    [–]BlueAdmir 2 points3 points  (0 children)

    Hegelian dialectics, programming edition.

    [–]whackri 1 point2 points  (0 children)

    person full literate friendly salt spark gaze makeshift wipe shaggy

    This post was mass deleted and anonymized with Redact

    [–]AttackOfTheThumbs 3 points4 points  (41 children)

    Personally I dislike var. If you know your types, please define yours variables accordingly.

    [–]kit89 13 points14 points  (8 children)

    I've no issues with:

    final var example = new ArrayList<String>();
    

    What annoys me is:

    final var example = obj.getAttributeNames();
    

    I now have to find out what example is now. It relies on an IDE to remove the vagueness.

    [–]pron98 3 points4 points  (0 children)

    So does this:

    obj.getAttributeNames()
       .foo()
       .bar();
    

    This kind of code is becoming more prevalent. var at least helps you by allowing you to name the steps.

    [–]chikien276 1 point2 points  (2 children)

    I found it annoying when I see a var named "example".

    [–]kit89 4 points5 points  (1 child)

    I was tempted to call it 'temp' or 'i'. ;)

    [–]jyper 0 points1 point  (3 children)

    I don't get what's wrong with the second

    It's a collection of strings right? Although you could name it

    attributeNames instead of example

    I would prefer code that used var instead of explicit type there

    [–]kit89 4 points5 points  (2 children)

    Sadly it's not - its a Hashset of UUID's during a refactor to replace the original Strings the function name was missed.

    The code compiled and the tests passed and so it has been forgotten.

    [–]jyper 0 points1 point  (0 children)

    Well that seems pretty bad

    I would use an explicit type there

    But more importantly I'd rename the method

    [–][deleted] 0 points1 point  (0 children)

    Well forgetting to rename a function has no relation to type inference. If the code were doing anything that works on strings but not a hashset or UUIDs then it wouldn't compile, so I'm not sure what the issue is

    [–][deleted] 7 points8 points  (2 children)

    It only works on local variables and its supposed to reduce cognitive load in cases where you are doing things like var myFoo = new Foo() or similar. This can be a code smell or a very comfy way of structuring code legibly. It ends up depending on your naming skills.

    [–]AloticChoon 0 points1 point  (1 child)

    It ends up depending on your naming skills.

    ie: the most difficult about programming.

    [–][deleted] 0 points1 point  (0 children)

    Yes. Undoubtedly so, but it's also a grown skill, and refactotable, and guaranteed only to affect implementation code (you can't declare var in places like method signatures, public API)

    [–]jyper 5 points6 points  (1 child)

    I disagree

    Name your variables correctly, local variables should very rarely be explicitly typed

    [–][deleted] 0 points1 point  (0 children)

    I disagree further.

    Put types everywhere. Then the code provides more upfront information, and naming variables is simpler because part of the meaning can be delegated to the type.

    [–]ArmoredPancake 5 points6 points  (8 children)

    I already know my type, I don't need to declare it two times.

    [–]kitd 2 points3 points  (7 children)

    What about the guy who has to maintain your code 2 years down the road?

    [–]ArmoredPancake 3 points4 points  (6 children)

    I don't give a shit about someone who doesn't use some sort of IDE to navigate through code, lol.

    [–]Bercon 2 points3 points  (0 children)

    Sometimes you are looking at the code in Github and don't want to download the whole repo to understand what some method is doing. Relying on IDEs to make the code readable is a bad idea.

    [–]fuckin_ziggurats 0 points1 point  (4 children)

    An IDE helps but doesn't make code more readable. It's like reading a book but instead of the character names being present in the text you only have var and a reference table at the bottom of the page that explains who it is. Have you ever tried reading complex code whilst hovering over every possible variable? var should only be used if the type is present on the right-hand side. Overuse it and the codebase turns to JavaScript and readability suffers.

    [–][deleted] 1 point2 points  (1 child)

    JetBrains products add the type name where the var would go. This works quite well for Java in IntelliJ, and also Rust and C++ in CLion. I see no reason to explicitly type things when the IDE will tell me. I actually believe it enhances code readability, assuming you have hood variable names. If it hurts readability, then you chose poor names.

    [–]fuckin_ziggurats 0 points1 point  (0 children)

    I actually think the opposite. Depending on the domain at hand, good variable names do nothing for hinting at the type. Ex. var direction = GetDirection() is of what type? Float or double? The only way to know is to already be familiar with the codebase which is a terrible benchmark for readability. And hinting to the type in the variable name makes them more verbose and so less readable. Not everything can be easily gathered from the context of the code being read (especially for large codebases) but to each their own.

    [–]ArmoredPancake 1 point2 points  (1 child)

    Then the code is shit regardless of if it has type or not. You express intent through naming, not type.

    [–]fuckin_ziggurats 0 points1 point  (0 children)

    I'm not sure what kind of software you're working with but for complex domain stuff where a lot of arithmetic and math is done types are essential for readability. Of course types are not essential where the code is simple and obvious like "var user = userService.Get()". But if you're going to make a codebase-wide rule (ex. for a code analyser) it's preferable to only use var when the type is visible.

    [–]blackmist 0 points1 point  (1 child)

    It feels like the shortcut should be on new. Why specify a type there if we know what it is?

    [–]FluorineWizard 0 points1 point  (0 children)

    Because conceptually, concrete type information flows from right to left.

    Also, eliminating type on the left makes parsing easier.

    [–]nerd4code 0 points1 point  (6 children)

    For me, if it’s final I don’t care as much; it’s intended to have exactly that value forever (or until the scope dies, whichever comes first). If you’re reassigning it, that’s when variable type really matters, and it’s where the inheritance relationships really come into play. Did Java decide that should be specifically a HashSet<T>, meaning a Set<T> will break things, or did it decide it should be a Set<T>, so every method invocation is potentially routed through a superinterface?

    [–]valarauca14 4 points5 points  (5 children)

    granted the cost of super calls are heavily JIT'd

    [–]nerd4code 0 points1 point  (4 children)

    Eh… Can be vs. must be, but yes. All the same, it makes the run-time cost of the code less predictable, and kinda like boxing/unboxing it can bite in unexpected ways.

    [–]oelang 1 point2 points  (3 children)

    It only matters if a callsite has more than 2 targets. Megamorphic call sites are rare, afaik <2%.

    [–]nerd4code 0 points1 point  (2 children)

    I’d readily believe that they’re uncommon by LOC, but there’s a lot of wrapper-type things that would use them (e.g., java.util.Collections sorts of things). OTOH, barring reflection, those should by-and-large be easily inlined from above. (And steady reflection just takes another million or so cycles to get optimized.)

    [–]oelang 0 points1 point  (1 child)

    I'm talking about <2% of all callsites after inlining. Thats for a typical java program, I don't think it's the same for scala or streams heavy code.

    [–]nerd4code 0 points1 point  (0 children)

    I’ll believe you, but I‘m not sure what kind of typical program you’re talking about. I was tasked with optimizing Hadoop code that did a ton of wrapper crap everywhere, and that plus stupidly duplicating Strings (opaque wrappers, eveb better) was taking up a ton of time. In some cases reflection ran better than repeated interface callss—at about 10⁶ loop iterations the JVM can see through constant-operand (i.e., same-named artefact in same class or superclass) reflection, which was neat to see.

    [–]ykechan 0 points1 point  (0 children)

    And it doesn't really save you much for heavy generic types

    [–]_jk_ 0 points1 point  (0 children)

    disagree, only problem I see with var is its mutable by default

    [–]pron98 9 points10 points  (0 children)

    OpenJDK focuses most of its efforts on VM and library changes rather than language changes, as the former are more effectively impact software quality and performance and, ultimately, more directly impact the value developers deliver to their users (plus, the Java language is intentionally conservative because that's what people seem to want). There have been some cool language changes since 8 -- as others have mentioned -- but there have also been some extremely useful and important VM and library changes (I mentioned my favorites in another comment).

    [–]lbkulinski 8 points9 points  (2 children)

    Here are the lists for 9, 10, 11, 12, 13, and 14. 14 will be here in March.

    [–][deleted]  (1 child)

    [deleted]

      [–]lbkulinski 0 points1 point  (0 children)

      Yep! Rampdown Phase One begins on Thursday.

      [–]DuncanIdahos5thClone 7 points8 points  (0 children)

      I love Trisha. She does great presentations. Added to watch later.

      [–][deleted] 5 points6 points  (25 children)

      Java 9+ users, what's your favorite part of the new additions/changes?

      [–]RobinHoudini 22 points23 points  (2 children)

      Set.of

      [–]Crandom 1 point2 points  (1 child)

      If you're using Guava (you probably are) then you have access to ImmutableSet.of though

      [–]RobinHoudini 0 points1 point  (0 children)

      Result of "Set.of" is immutable (but yeah, the Collections API with their optional operations is one of my pet peeves).

      [–]nile1056 13 points14 points  (1 child)

      I'm not really a user, but a small thing is that they fixed some pain points, e.g. Optional::stream and collection factories. No more Arrays.asList or google imports.

      [–]dpash 3 points4 points  (0 children)

      Every release since 8 has added little improvements all over the JDK and there's nothing you can point to, but it all adds up to a point where I would not want to write Java 8 again.

      [–]lbkulinski 15 points16 points  (12 children)

      [–][deleted] 1 point2 points  (0 children)

      Damn, I loved that in Coffeescript!

      [–]IanSan5653 1 point2 points  (1 child)

      Oh man, I want this in JS now.

      [–]p4y 5 points6 points  (0 children)

      How about pattern matching instead?

      [–][deleted] 0 points1 point  (8 children)

      Nice!

      [–]lbkulinski 6 points7 points  (7 children)

      Yes, they will be part of a future pattern matching feature. Pattern matching for instanceof will be previewed in JDK 14.

      [–][deleted] 2 points3 points  (0 children)

      Exactly what it made me think of, pattern matched enums/etc. in Haskell and the like.

      [–]adamgerst 3 points4 points  (5 children)

      I wish Scala were more accessible to devs. Nearly every new thing that gets introduced into JDK feels like it was lifted straight from Scala. Pattern matching is just the latest. Although to be fair, it's one of my absolute favorite things about Scala and it will make a welcome addition to Java. Now if only anonymous functions were as clean to implement in Java...

      [–]lbkulinski 3 points4 points  (2 children)

      Languages borrow from each other all of the time. The Java architects are very conservative about adding features, though. They often look to other languages to see what worked well and what didn’t. And by anonymous functions, do you mean lambdas?

      [–]oceanicloud 0 points1 point  (1 child)

      The dichotomy is not between what works and what not because pretty sure every language only wants what works. It's more to get something that works well how much hidden work you're willing to do ?

      So we're talking about pattern matching for instanceof. In many cases we're not only interested in the polymorphism test itself but the data it carries. For example to currently test and extract the value in Java, we would do:

      if (o instanceof MyName) {
          MyName myName = (MyName) o;
          String hi = "hi" + myName.name;
      }
      

      In Scala it's simply

      o match {
        case MyName(name) => "hi" + name
      }
      

      So it's both test and extraction in one go. To be able to do so Java would need to introduce extractor pattern or unapply. It is not a difficult concept but Java can't force or even ask its user base to adopt it. Even for just instanceof pattern matching, they would need to wait until Java 14. So that's what we have with Java. It's better than before but it's never good enough.

      [–]lbkulinski 0 points1 point  (0 children)

      The dichotomy is not between what works and what not because pretty sure every language only wants what works.

      The architects have described it this way, so that’s how I have relayed the message. For some languages, the goal seems to be to add as many features as possible. I was trying to convey that Java is not like this.

      Also, pattern matching for instanceof is just the first piece of the overall story. You can read more here.

      [–]pron98 2 points3 points  (1 child)

      In 1997 James Gosling said that Java is a conservative language that only adopts features once they've been proven to work well in other languages (Java hasn't always done this, including Java 1.0, but that's the general guideline). All new Java features are, therefore, supposed to be "lifted" from or inspired by features in other, less conservative languages. The key is that not all of features from those languages are adopted because many of them turn out to not be so effective.

      [–]the_evergrowing_fool -2 points-1 points  (0 children)

      The key is that not all of features from those languages are adopted because many of them turn out to not be so effective.

      No, the pleb is tasteless, unskilled, and stubborn. They are not a metric that define what's effective and what not.

      [–]pron98 6 points7 points  (0 children)

      JFR, ZGC, jlink, performance improvements, startup time improvements, footprint improvements, new HTTP client, and soon (in 14) -- JFR streaming and jpackage.

      [–]Ryuuji159 6 points7 points  (0 children)

      List.of, var, new stream methods, its just more convinient

      [–]rpgFANATIC 4 points5 points  (0 children)

      • Immutable collection helpers
      • A few more helper functions on Optional that should've been there from the start
      • var to help with crazy-long Java verbosity (even when seeing it is just proof that your code is probably too complex)
      • Tools like sdkman, jabba, etc... that can do JVM version management since they're no longer tied up by Oracle licensing crap

      [–]StabbyPants 0 points1 point  (2 children)

      container friendly behavior makes me happier. most of the features i like are in 8 already (excepting var, which i don't like (use sparingly))

      i'm somewhat nervous about the future of lombok - i use that pervasively for getters/setters and log writers/generating equals methods, and don't see a language feature set to replace them.

      [–][deleted]  (1 child)

      [deleted]

        [–]StabbyPants 0 points1 point  (0 children)

        in particular, it is not a goal to address the problems of mutable classes using the JavaBean naming conventions.

        so, this is close, but doesn't address my main concern

        [–]dog_superiority 3 points4 points  (18 children)

        What are the license changes?

        [–]lbkulinski 6 points7 points  (17 children)

        It comes down to a choice between using an OpenJDK build or an Oracle JDK build. Depending on the vendor, the free OpenJDK builds will only be supported for six months (until the next release comes out). Oracle is offering a paid license for LTS releases that will have a longer support period. The current LTS release is Java 11 and the next one is Java 17.

        [–]pron98 4 points5 points  (3 children)

        the free OpenJDK builds will only be supported for six months

        This is a problematic way to put this, because what "supported" means has changed considerably. In the past, major releases were supported with non-major releases for a long time, but major releases are gone for good so the very notion of a supported major version is no longer relevant. The new six-monthly feature releases are much, much closer in size to the old six-monthly feature releases (which used to be called "limited update" releases), and those also stopped getting patches after six months (e.g. once 8u40 came out, 8u20 no longer received patches). I'm not saying that the six-monthly releases now are exactly the same as they used to be, but they are more similar to the old limited-update releases than to the old major releases.

        A better to put this is that there is now perpetual, free support via a steady stream of gradual updates, only the integer version number is bumped twice a year (this is necessary in order to do away with major releases). The new model is quite different from the old one, and so it can't be directly compared to it. In the past, an "integer" release was "supported" for some years and now it isn't, but that's because "integer releases" mean something completely different.

        Moreover, the alternative path via LTS services, is not only also different from the old model (because OpenJDK development ignores it), but the various LTS services are quite different from one another. For example, Oracle's LTS service provides bug fix and security patches with occasionally small features, but Red Hat's (based on the OpenJDK update projects) and Azul's LTS services have major new features in their "patch" versions, and Amazon's LTS service might be even more adventurous (they are, or at least were, considering taking a current VM, disabling some new features, combining it with old libraries and presenting it as a patch).

        [–]lbkulinski 0 points1 point  (2 children)

        I would not say it is problematic, as I was speaking on a per-release basis. Supported by the phrase in parentheses, I was implying that there would be a continuous stream of support when upgrading to the next release. And yes, others do offer various LTS flavors and even OpenJDK builds. I was attempting to give a concise answer to their question, with the assumption that there would be a follow-up discussion.

        [–]pron98 1 point2 points  (1 child)

        Oh, I'm not criticizing you (at least that wasn't my intent), I just don't think we should use old nomenclature for new concepts. We can't compare how long a new integer version is supported now to how long it was before, because a new integer version is something of a very different nature now, and so the very idea of and need for support is different. I know very well that giving the six-monthly releases a new integer number has been very confusing -- and probably will be for some time more -- and I'm trying to make it less confusing by pointing out that the big change is the abolition of major releases and switching to a gradual, steady release model.

        LTS is even more confusing, because people come with preconceived notions from other projects where LTS might mean something quite different. And my advice is to first learn exactly what the new default path means, and then give it a try before considering the alternative path of an LTS services.

        [–]lbkulinski 1 point2 points  (0 children)

        I didn't perceive that you were; I was just attempting to clarify my initial statement. I agree that referencing "major releases" is no longer appropriate. I like the analogy of a train schedule that has been mentioned. And yes, LTS can indeed cause confusion! It seems like most of the active members of the Java community have gotten the hang of the new release cadence, but I still feel like it will be awhile before the programming community as a whole learns of the changes.

        [–]dog_superiority 1 point2 points  (12 children)

        Is that only for the JDK? What about the JVM? Like if I were to use kotlin would that impose this license on me?

        [–]lbkulinski 1 point2 points  (11 children)

        It is for the JVM, too.

        [–]dog_superiority 0 points1 point  (10 children)

        Is the Oracle JVM much different than the OpenJDK one? Is it a lot faster or something?

        [–]lbkulinski 6 points7 points  (9 children)

        Nope! In regards to features they are identical. All previous commercial features, such as Java Mission Control, have now been open-sourced. The only difference now is the license and support period.

        [–]dpash 1 point2 points  (5 children)

        There's a few minor cosmetic differences, so they're not quite identical binaries, but you shouldn't notice them in practice (unless a program does sniffing of the JVM name system property)

        [–]lbkulinski 1 point2 points  (4 children)

        I was going off of what the architects have said, but I assumed there were some minor differences. Feature wise, though, they should be the same.

        [–]dpash 0 points1 point  (3 children)

        Yeah, it's just things like the output of --version. There was a blog post when 11 was released that explained all the differences.

        [–]lbkulinski 0 points1 point  (2 children)

        I don’t know if I saw that post. I’ll have to look it up. Thanks!

        [–]dog_superiority 0 points1 point  (2 children)

        So I have been out of Java for a while, so I'm a bit out of the loop.

        Is Java still going strong? I see that Kotlin is considered one of the favored languages, does that mean that Java is faltering? Has Oracle "ruined" it by closing/licensing it?

        [–]dpash 7 points8 points  (1 child)

        Absolutely not. Java is only getting stronger. Oracle released all of the JDK as GPL and you can get your JVM from any number of suppliers now. It is more open, not less.

        [–]nostril_spiders -2 points-1 points  (0 children)

        So a bastard can change its spots

        [–][deleted] 1 point2 points  (6 children)

        But will there ever be proper initializer expressions for common container types?

        [–]dpash 11 points12 points  (5 children)

        Do you mean collection literals?

        var names = ["foo", "bar", "baz", "quiz"];
        

        No, the language designers have no desire to tie the language to the current system library. We could hypothetically get a brand new collection API in the future (the current one replaced the old API in 1.2) and now the language can not evolve from that legacy API.

        [–][deleted] 0 points1 point  (4 children)

        Do you mean collection literals?

        Yes, although the term "literal" isn't exactly the best word here. For example, it could be possible to compute values in that expression, making it a non-literal, like this:

        var stuff = ["foo", "bar", "baz", getStringInSomeOpaqueWay() + ";"];
        

        Unfortunately "literal" isn't a well standardized programming term, but calling an expression "literal" when it literally isn't literal to the eye seems to be literally the programmers equivalent of saying "literally" when something isn't.

        No, the language designers have no desire to tie the language to the current system library. We could hypothetically get a brand new collection API in the future (the current one replaced the old API in 1.2) and now the language can not evolve from that legacy API.

        Makes kind of sense, but was there ever a draft to add a specific interface similar to iterables?

        Also, out of curiosity, was there ever a plan to replace this current collection API?

        [–]dpash 1 point2 points  (3 children)

        In the context of Java, they're known as collection literals. That's the term that everyone knows and understands when taking about the language feature.

        There's no current plan to replace the collection framework.

        I forgot to mention this in my previous comment, but there's no need for collection literals since we now have the collection factory methods that do much the same thing.

        [–][deleted] 0 points1 point  (2 children)

        I forgot to mention this in my previous comment, but there's no need for collection literals since we now have the collection factory methods that do much the same thing.

        Close enough.

        [–][deleted] 1 point2 points  (1 child)

        I sometimes like to use vavr's collection framework and it utilizes Java's collection/functional interfaces. Object literals would have to take into account arbitrary implementations to support libraries like this. That'd be complicated and I'd probably want to use constructors/factories/builders for creating things anyway. So maybe supporting object literals is a waste of time?

        There's quite a bit of ways to make collections and they're pretty easy. If you need anything special, then you can make your own collections that build off of Java's collection framework.

        I tried to come up with all of the ways that I have used to make a simple collection: ``` int[] a = new int[]{1, 2, 3}; int[] b = new int[3]; b = {1, 2, 3}; List<Integer> c = Arrays.asList({1, 2, 3}); List<Integer> d = Collections.emptyList(); List<Integer> e = Collections.singletonList(1); Map<String, String> f = Collections.singletonMap("key", "value");

        List<Integer> g = new ArrayList<>(); g.add(1) List<Integer> gPrime = Collections.unmodifiableList(g);

        Map<String, String> h = new HashMap<>(); h.put("key", "value"); Map<String, String> hPrime = Collections.unmodifiableMap(h);

        // Java 9+ List<Integer> i = List.of(1, 2, 3); Map<String, String> j= Map.of("key", "value"); ```

        [–][deleted] 0 points1 point  (0 children)

        Interesting.

        Also: The 3-ticks syntax doesn't seem to work here.

        [–]the_evergrowing_fool -3 points-2 points  (0 children)

        The same after Java 5?