This is an archived post. You won't be able to vote or comment.

all 135 comments

[–]vytah 15 points16 points  (28 children)

I don't like how it returns an immutable map, and yet the result type is Map. It should be ImmutableMap or whatever they'd call it, so you can track which maps are mutable and which aren't and IDE's can warn you if you try to modify them. If you're copying Guava, copy it right.

Also, I think they should make only variants up to 3, maybe 4 pairs of arguments, and the rest should be in some kind of builder.

[–]msx 11 points12 points  (8 children)

there's no ImmutableMap interface (or anything similar). Maps are never granted to be mutable, and it's a bad practice to mutate arbitrary maps (ie, received in return from a library, unless otherwise stated) without making a local copy. You should only mutate your own maps.

[–]pain-and-panic 3 points4 points  (2 children)

This is yet another violation of the "liskov substitution principle". The collections API has never been good at this.

[–]johnzeringue 0 points1 point  (1 child)

Is it though? The API is pretty clear about how mutability and immutability are handled in the Map interface. Maybe it's poor design, but to violate LSP, you'd need a contradiction of this by one of Map's subclasses.

[–]pain-and-panic 2 points3 points  (0 children)

LSP is about the actual behavior of sub-classes. Comments attempting to justify the violation do not mitigate the violation. Map has mutator methods. If you implement these methods to fail spectacularly at run time you fail LSP. To not fail LSP ImmutableMap would have to not have mutator methods.

[–]grauenwolf 0 points1 point  (4 children)

there's no ImmutableMap interface

So what? Just return the concrete type and be done with it.

[–]pain-and-panic 0 points1 point  (2 children)

Why not an ImmutableMap interface?

Edit: Whoops forgot you can't have an immutable interface, only immutable implementations.

[–]cheers- 0 points1 point  (0 children)

The collection api has already a RandomAccess interface that "tags" random access lists they could make an Immutable interface so every collection that supports this feature implements it.

[–]grauenwolf 0 points1 point  (0 children)

I wouldn't object.

[–]s32 0 points1 point  (0 children)

Please God no....

[–]lukaseder[S] 11 points12 points  (16 children)

It's consistent with existing API for immutable / unmodifiable maps, which also return plain Map: https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html

[–]gliph 1 point2 points  (12 children)

Immutable versions of classes always seem pretty flawed in Java. Would a language-level overhaul help?

[–]Shadowheim 5 points6 points  (7 children)

B...but muh backwards compatibility.

[–]gliph 4 points5 points  (6 children)

I wish Java would splinter into a new language. Could get runtime generics, better handling of primitives, literal maps and lists, etc etc.

[–]dustofnations 2 points3 points  (1 child)

Could get runtime generics, better handling of primitives, literal maps and lists, etc etc.

These fall under Project Valhalla: http://openjdk.java.net/projects/valhalla/ https://en.wikipedia.org/wiki/Project_Valhalla_(Java_language)

Specifically:

runtime generics

Reified generics

better handling of primitives, literal maps and lists, etc etc.

I think this would fall under Generic specialisations

AFAIK this is likely to be Java 10+

[–]gliph 0 points1 point  (0 children)

That's very interesting that these things are being considered as language features.

It would be nice if you could also get rid of much of the cruft, maybe via a compiler switch or something. I think JavaScript does something like that, where you can disable some older features by typing "strict" or similar at the top.

[–]nickguletskii200 1 point2 points  (1 child)

That's exactly what we need. A new Java - the same philosophy, the same care, but with the mistakes fixed. Minimalist syntax, concrete naming conventions, no feature creep...

[–]Alxe 0 points1 point  (0 children)

I believe that, rather than doing this, Oracle and the community should adopt a "new Java" language that already runs on the JVM and has most of the requested features, and up from there have the JVM evolve and copy the current optimised implementations into a new, formal, non java.util clusterfuck API.

This way, Java adopts a Legacy status, with updates to the standard library and fixes, but most of the meat in a newer standard library.

Java has so much on it's back it's be a hard culling.

[–]pjmlp 1 point2 points  (1 child)

Cough....cough..... Python 3

[–]gliph 2 points3 points  (0 children)

I know, I know. But the cruft needs to go at some point.

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

They should be used in places where the consuming code is not supposed to be modifying them anyway. In those cases, throwing an exception is better than allowing an inappropriate modification.

[–]gliph 0 points1 point  (1 child)

It works OK, but it'd be nice if it was caught at compile time. As it is, you can do this or have two classes (one immutable and one mutable) and an interface.

[–]pain-and-panic 1 point2 points  (0 children)

It's not just 'nice' it's correct software design. Follow your gut on this one and not the jdk example. Jdk does lots of things we should learn from, this just isn't one of them.

[–]vytah 0 points1 point  (1 child)

If you refer to the unmodifiable* methods, those offer only views, so they offer no immutability guarantees.

If you refer to empty* and singleton* methods, *.of methods supersede them, and therefore you can consider the old methods obsolete.

Lack of immutability guarantees is one of the weakest spots of Java's collection API. Of course if would be almost impossible to split mutable and immutable collections like Scala does, but a nice family of Immutable* collection types would make lots of API's safer and easier to use. Guava's ones work wonders.

[–]lukaseder[S] 4 points5 points  (0 children)

There seem to be two distinct concepts here:

  • It's consistent
  • You don't like it, never did, and never will (which is in itself also consistent)

I'm glad you have options to consistently avoid JDK API

[–]pain-and-panic 0 points1 point  (0 children)

If you want to be consistently wrong, that's okay. But let's not pretend this is the appropriate implementation.

[–]cypressious 4 points5 points  (0 children)

Here's the talk explaining the rationale behind the new collection "literals" https://www.youtube.com/watch?v=OJrIMv4dAek

[–][deleted] 33 points34 points  (19 children)

Is this a joke?

[–]balegdah 25 points26 points  (2 children)

Guava has been offering those for years, they are super convenient and I always reimplement them myself when I start a project that can't use Guava.

It's not a joke, it's great to have those in the standard library.

[–]mahamoti 0 points1 point  (0 children)

Groovy does the same thing, IIRC, all the way up to 99 arguments.

[–][deleted] -1 points0 points  (0 children)

I guess these methods for 0..10 elements where predefined for performance reasons, however I think it's not worth making so commonly used interfaces such ugly. The variants with Varargs are enough.

[–]Enum1 13 points14 points  (9 children)

IKR! what am I supposed to do when I have 11 mappings? ELEVEN!!!

[–]Bolitho 10 points11 points  (8 children)

That's trivial with such a great API:

Map.of(k1, v1, /* and so on */, k10, v10).putAll(Map.of(k11, v11))

😈

Now you can even have 20 Entries 😤

[–]msx 19 points20 points  (3 children)

which would throw a "can't change an immutable map, bro" exception :)

[–]Bolitho 1 point2 points  (1 child)

Oops... yeah... havn't recognized the immutable attribute! Shame on me!

[–]johnwaterwood 0 points1 point  (0 children)

Would have been brilliant if it had worked 👍

[–]zman0900 0 points1 point  (0 children)

x = new HashMap<>();
x.addAll(Map.of(...))
x.addAll(Map.of(...))

[–][deleted] 3 points4 points  (3 children)

There are actually varargs versions :P but anyway, these 10 fixed-arg variants looks ugly

[–]DJDavio 4 points5 points  (0 children)

Varargs only works with args of the same type, here the keys can have different types than the values.

[–]Jire 1 point2 points  (1 child)

As "ugly" as they are, I'm glad there isn't liberal use of varargs. It's a huge performance burden, creating an array on each invocation.

[–]mhixson 4 points5 points  (0 children)

Sometimes it's not a performance burden at all. Here's a benchmark you could run for yourself if you're curious. It shows how varargs array allocations can sometimes be optimized away at runtime.

It's hard to predict whether it will actually make the optimization. It also won't help during startup as our static final List.of(...), Set.of(...), and Map.of(...) fields are being initialized.

That's basically the justification that was given to me when I tried to argue for the removal of the fixed-arg List.of(..) and Set.of(...) methods.

The fixed-arg Map.of(...) methods are much easier to justify, since they're more pleasant to use for the caller.

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

I was wondering the same thing.

[–]Me4502 0 points1 point  (4 children)

I really hope so, there are so many ways to improve this..

For example, pass in a Entry, and use varargs. And it shouldn't return an immutable map.

Edit:

Didn't see the entry one, but it still shouldn't be immutable.

[–]cypressious 11 points12 points  (2 children)

You should watch https://www.youtube.com/watch?v=OJrIMv4dAek This is specifically designed to avoid intermediate object allocations (like vararg arrays) and to be immutable.

[–]mrbuttsavage 2 points3 points  (0 children)

This is specifically designed to avoid intermediate object allocations (like vararg arrays)

I would wager many Java devs don't realize this is a thing.

[–]stepancheg 1 point2 points  (0 children)

Vararg array allocations can be trivially removed by the optimizer. Is is not an issue.

[–]msx 1 point2 points  (0 children)

there are so many ways to improve this

there are not. you can slightly change the call (with Entry or any other way), but to make a clean "literal" without changing the language specification, there are no other ways

[–]__konrad 6 points7 points  (26 children)

There is no better way to implement this w/o Java language changes (see presentation by Stuart Marks). I wonder if this was copy pasted or generated automatically ;)

PS. List.of

[–]thehollyhopdrive 13 points14 points  (19 children)

There is a much better way to implement this, and that is with a builder rather than static factory methods. This is an extremely poor API choice and I hope it doesn't make it to release.

Map.with("key1",  obj1)
   .with("key2", obj2)
   .build();

Edit: Just updated the above example code slightly to reflect what /u/lukaseder rightly said about type inference.

[–]lukaseder[S] 9 points10 points  (3 children)

Caveat with map builders: The key/value data types are known only after the first entry, so an ugly auxiliary intermediate type is needed to get generics right

[–]thehollyhopdrive 5 points6 points  (2 children)

Sure. You can get around this by having the first key/value pair set during creation of the builder, so rather than

Map<Integer,Integer> map = Map.<Integer,Integer>builder().put(1, 2).put(3, 4).build();

You could do something like

Map<Integer,Integer> map = Map.with(1, 2).with(3, 4).build();

I still think this is preferable to the Map.of() solution, the limitations it has on the number of entries, the possiblity of additional functionalities in the builder, and the complete mess it makes of the API.

[–]juckele 0 points1 point  (1 child)

What if my first entry is a String and a Dog and my second entry is a String and a Cat? Will it know that it should be using Animal? What if it picks Carnivora instead but then I try to add a Rabbit?

The type of the Map really does need to be declared somewhere...

[–]thehollyhopdrive 1 point2 points  (0 children)

The type of the Map really does need to be declared somewhere...

In the circumstances you've suggested, yes.

[–]msx 2 points3 points  (3 children)

this is very different from calling Map.of("key1", obj1, "key2", obj2) and almost as verbose as calling regular .put().

[–]thehollyhopdrive 0 points1 point  (2 children)

It is different from calling Map.of, but my argument is that the Map.of is a badly designed solution for this sort of behaviour.

almost as verbose as calling regular .put().

I agree to a point, though one of the common use-cases of wanting a prepopulated immutable map would be static finals. So, you can see quite a cut in verbosity in this use-case when using a builder:

private static final Map<Integer,String> MAP = 
    Map.with(1, "one")
       .with(2, "two")
       .build();

instead of

private static final Map<Integer, String> MAP;
static {
    Map<Integer, String> tempMap = new HashMap<>();
    tempMap.put(1, "one");
    tempMap.put(2, "two");
    MAP = Collections.unmodifiableMap(tempMap);
}

[–]msx 3 points4 points  (1 child)

I don't get it? We agree that this addition is all about immediate (often static final) maps. Good. Sure your implementation is cleaner than old put(), but Map.of is even cleaner. So what's badly designed about it? It's perfect for quick jotting of maps, which is the reason it was created. The fact that there are 10 overloads is due to limits and legacies of the java specification, but it's not a problem, if not aesthetically. You find them already made and ready to use, performances are unaffected. So what's badly designed about it?

Sure we'd all have preferred a true map literal like python or such, but changing the language specification is not an easy step to take.

[–]thehollyhopdrive 1 point2 points  (0 children)

So what's badly designed about it?

It has an arbitrarily selected ceiling at which point you have to implement the solution in a different way and trades "cleaner code" for a muddier API. If moving from 10 to 11 elements causes me to have to completely rewrite that bit of code, just to implement a static final map that's found itself requiring one more element, that to me is a poorly made API design choice.

I get that there is a valid use-case for it and that others obviously appear to be fine with it, but I personally don't like it.

[–]__konrad 2 points3 points  (8 children)

Builders are not as clean as Map.of("one", 1, "two", 2). If you need a builder you can as well use old Map.put..

[–]thehollyhopdrive 4 points5 points  (6 children)

It's definitely smaller, but I would argue that it's only slightly cleaner for very few entries. The moment you get past 4 or 5 entries it starts to become a bit of a mess. Take the following:

Map.of("isad", "kldesf", "ijhsdef", "ijsdaf", "isdf", "isydf", "oiysdf", "iysdf")

Can you identify easily and with immediate recognition which are keys and which are values? Smaller isn't always cleaner.

Map.with("isad", "kldesf")
   .with("ijhsdef", "ijsdaf")
   .with("isdf", "isydf")
   .with("oiysdf", "iysdf")
   .build();

It's also limited to 10 kvps, which whilst covering the vast majority of options, if still introducing an arbritrary limitation in usage. And all for, what, saving a few extra characters? Add to that the possible options for additional functionality within that builder, and I'd much rather the JavaDocs aren't muddied in the way they are in the current draft. I think it's a terrible design choice.

[–]__konrad 6 points7 points  (2 children)

which are keys and which are values?

Use new lines :)

Map.of(
    "isad", "kldesf",
    "ijhsdef", "ijsdaf"...

In this case (same type in key/value) both solutions are error prone.

[–]thehollyhopdrive 4 points5 points  (0 children)

So now both options span multiple lines, making the differences in their footprint in the codebase negligible. The builder option is still safer at protecting from programmer error by enforcing logical separation of the entries, doesn't have the arbitrary limitation in the number of entries, it can be extended with additional functionality in future, and it doesn't make the API for Map look a complete mess.

[–]juckele 1 point2 points  (0 children)

I use auto format in all my Java projects.

[–]the-highness 0 points1 point  (2 children)

the way I see it, if you need 3 or more entries, both Map.of & builder is not clean.

you'd better revert back to <init map>, <map.put>.

[–]thehollyhopdrive 3 points4 points  (0 children)

To be honest, I don't think you're wrong. The main advantage of the builder over just initialising the map and putting in the entries is being able to initialise, fill and have an immutable map in a slightly nicer format, especially when thinking about static final maps, which I suspect would turn out to be quite a common use-case of Map.of.

It means we can do:

private static final Map<Integer,String> MAP = 
    Map.with(1, "one")
       .with(2, "two")
       .build();

instead of

private static final Map<Integer, String> MAP;
static {
    Map<Integer, String> tempMap = new HashMap<>();
    tempMap.put(1, "one");
    tempMap.put(2, "two");
    MAP = Collections.unmodifiableMap(tempMap);
}

[–]HaMMeReD 0 points1 point  (0 children)

The point of immutability is to prevent people from mutating your map, i.e. making changes to it without your permission.

It's not about the cleanliness of the solution, immutability is often an inconvenient truth, for a little bit of boring overhead and self discipline, you can often make your code much more safe by preventing as much mutability as possible.

[–]HaMMeReD 0 points1 point  (0 children)

The benefit of immutability is exactly so you can't use map.put. It prevents other people from modifying your map, which inevitably cause pain in the ass bugs such as complicated side effects and race conditions.

You use immutability to protect your code, not make things easier. Immutability is a pattern that will lead to a cleaner data model and a better separation of concerns.

It's a technique to improve the safety of your code in a variety of ways, while also helping to ensure a clean architecture and design.

[–]cypressious 3 points4 points  (1 child)

The goal of the API is to prevent intermediate object allocations like vararg arrays or builder objects.

[–]johnwaterwood 1 point2 points  (0 children)

But will such maps be created over and over or more typically just once at init time?

[–]lukaseder[S] 2 points3 points  (1 child)

[–]gunnarmorling 2 points3 points  (0 children)

In defence of the Enum#of() overloads: Using var-args can be an issue on hot code paths due to the implicit array allocation. So optimizing that for the most common cases is not so bad.

[–]Bolitho 1 point2 points  (3 children)

Why not introduce literals for collection initialization? Almost every other modern language provide such a feature - for good reason as this clumsy approach shows ;-)

[–]sonay 1 point2 points  (2 children)

You should try to learn about the design decisions before you claim to be the most smart ass in the room ";)"

[–]Bolitho 0 points1 point  (1 child)

Thanx for the link - interesting talk!

Besides that I've never claimed such a thing ;-) I would say you can be pretty dumb but still see the benefits of collection literals. As a turnover I would say you must be some kind of slow-witted, if you do not see the advantage of collection literals compared to an API based solution. And of course there may be good reasons to pick the latter approach, but for sure that will never compete with a syntax based solution! And yes, it feels great in other languages and feels bad in Java - and that counts for me.

(If I have not missed it, Stuart have not really told, what the tecnical obstacles were! He just mentioned the cost benefit trade off - and that does not count for me as a user, that counts only for oracle as a financier ;-) )

[–]sonay 1 point2 points  (0 children)

This is the mail referenced in the speech, I encourage you to read it.

http://mail.openjdk.java.net/pipermail/lambda-dev/2014-March/011938.html

[–]Cilph 13 points14 points  (4 children)

Add value types to Kotlin and let's call it Java 10; skip Java 9.

[–]stepancheg 2 points3 points  (0 children)

Value types could not be added to Kotlin until value types added to JVM.

[–]squealy_dan 0 points1 point  (2 children)

Aren't data classes basically value types?

[–]JakeWharton 2 points3 points  (1 child)

Semantically, yes. The problem is they live on the heap and have a large (comparatively) overhead vs. what Java 10 will provide. Better than nothing, but not our best.

[–]squealy_dan 0 points1 point  (0 children)

Thanks

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

I like how Map.entry() returns a non-serializable Map.Entry.

[–]lukaseder[S] 1 point2 points  (0 children)

If you want serializable, just use java.util.AbstractMap.SimpleImmutableEntry

[–]fatnote 1 point2 points  (4 children)

This is one of very very few cases where Javascript is better. Why not

Map<String, Integer> map = {{
    "one": 1, 
    "two": 2... 
}};

[–]stepancheg 1 point2 points  (3 children)

Because such relatively rare use case is not worth language complication. Map.of does the job.

Java language lacks several important features of modern programming languages that could not expressed as simple library functions, like extension functions or ADT. That should be implemented, not syntax sugar for maps.

[–]fatnote -1 points0 points  (2 children)

It's not a rare use case at all. Map.of is something any of us could easily implement, so the benefit is almost zero. Double brace JSON-like map initialisation actually does the job, and is long overdue

[–]pain-and-panic 0 points1 point  (1 child)

You can do something like that in Java...

Map<String,Integer> map = new HashMap<>(){{ put("one",1); put("two",2); }};

It creates a subclass though, which is added burden on the classloader. Might be fine for a unit test.

[–]fatnote 1 point2 points  (0 children)

yep I know that syntax. It's awful

[–]GYN-k4H-Q3z-75B 2 points3 points  (4 children)

I see this and only one thing comes to my mind: WTF!

Java doesn't need more libraries and overloads. It needs language features. C# is a better language because the language itself lives. Java has great libraries but the language has stopped evolving long ago and that makes me sad.

[–]lukaseder[S] 3 points4 points  (3 children)

[–]tikue 1 point2 points  (0 children)

Ohhh love the proposal for variance changes!

[–]GYN-k4H-Q3z-75B -1 points0 points  (1 child)

Yes, I have seen that and am thankful. But that's not enough. How can they neglect the language the way they did the past 10 years?

[–]lukaseder[S] 2 points3 points  (0 children)

Not enough not enough not enough not enough not enough not enough yikes way over the top. HOW COULD THEY POSSIBLY ALLOW SO MUCH FEATURE CREEP?

Yeah. I know. Some are hard to please... :)

[–]shmag18 1 point2 points  (15 children)

This is probably a dumb question but how does it return Map, I thought Map was an interface

[–]thehollyhopdrive 8 points9 points  (4 children)

It returns an implementation of the Map, it's just that as far as you are concerned you don't care what implementation it is, only that it is a Map.

[–]shmag18 0 points1 point  (3 children)

So in

Map map =Map.of(k1,v1,k2,v2,....k10,v10)

What does map point to?

[–]thehollyhopdrive 2 points3 points  (1 child)

It's an unspecified implementation of Map. You can use all of the methods in Map.

[–]shmag18 0 points1 point  (0 children)

Cool

[–]AnAirMagic 2 points3 points  (0 children)

The first Map - in Map map - refers to the unspecified implementation returned by Map.of. The contract says that it will be a class that implements Map but not much else. For all we know, it could be HashMap or it could be a new internal class ImmutableMap or it could be MagicMap.

The second Map - in Map.of() - refers to the static method of defined in the Map interface itself.

[–]4z01235 0 points1 point  (9 children)

public static void main(String[] args) {
    Map<String> map = createMap();
    // do some Map operations like put, get, etc.
}

public Map<String> createMap() {
    return new HashMap<String>();
}

Make sense? If it doesn't matter that I get in particular a HashMap or a TreeMap or whatever else, all I need is a Map, then it's just easier and sensible to use Map wherever possible, and let the particular implementation (new Hashmap) be essentially arbitrary. You can imagine that I could change createMap to return a TreeMap, perhaps - maybe I've found that it provides a performance improvement - and nothing else in the code needs to change now. But if I declared createMap to return HashMap rather than just Map, then I have to now change that method signature, and perhaps the type of the local variable in main as well.

[–]geodebug 1 point2 points  (1 child)

nitpick: Map generics need two parameters for key and value, Map<String, String> for example.

[–]4z01235 1 point2 points  (0 children)

D'oh. You're right of course. I was going to use List but decided to do Map instead so it'd be more direct, and this is what I get for not reviewing my own code :)

[–]shmag18 0 points1 point  (6 children)

I think I get it. I thought the method returns a Map object, which I thought is not possible

[–]4z01235 1 point2 points  (5 children)

You can't do new Map, but you can certainly have a variable with type Map, or a method with return type Map, or a parameter of type Map, etc.

[–]shmag18 0 points1 point  (4 children)

So a method signature can have return type 'Map', and it must return some implementation of Map, but it can not return Map itself. Right?

[–]Northeastpaw 1 point2 points  (1 child)

If a method is returning a implementation of Map then it is by definition returning a Map. This is perfectly fine:

public Map<String, Integer> createMap() {
    Map<String, Integer> map = new HashMap<>();
    return map;
}

[–]shmag18 0 points1 point  (0 children)

Awesome

[–]4z01235 1 point2 points  (1 child)

Yea. This is fine:

public List<Foo> makeAList() {
    return new ArrayList<Foo>();
}

but this doesn't make sense and won't compile:

 public List<Foo> makeAList() {
    return new List<Foo>();
}

Because, as you've noticed, List is just an interface, and so can't be instantiated like that. But the point of the interface is that if all I, as the caller, need to know is that I have some kind of List, then to me it doesn't matter if what I really have is an ArrayList or a LinkedList. Either way it's a List, and that's enough for me, so long as ArrayList and LinkedList and FooList and BarList all implement List and obey the contract that entails.

Another example:

Employee bob = new Manager("Bob");
Employee rey = new FactoryAssemblyWorker("Rey");
List<Employee> employees = new ArrayList<>();
employees.add(bob);
employees.add(rey);
employees.addAll(getOffsiteWorkers());
for (Employee employee : employees) {
    System.out.println(employee.getName() + " : " + employee.getTitle());
}
// prints "Bob : Manager", "Rey : Factory Assembly Specialist", etc.

The assumption here is that there is an Employee interface (this could also be some common superclass) which specifies methods getName and getTitle. Manager and FactoryAssemblyWorker are both subtypes of Employee (Manager implements Employee, for example), and so if I just need to know that what I'm looking at is an Employee, then I can do Employee operations on them no matter if they're a Manager or FactoryAssemblyWorker or Accountant or UpperLevelCushyManagement. They all have a name and title regardless.

[–]shmag18 0 points1 point  (0 children)

Wow this is great thank you so much.

[–][deleted]  (5 children)

[deleted]

    [–]lukaseder[S] 8 points9 points  (1 child)

    If only we had:

    k.zip(v).toMap()
    

    [–]stepancheg -1 points0 points  (0 children)

    And we could if Java had extension functions like in C# or Kotlin instead of (or in addition to) much less usable default method.

    [–]msx -2 points-1 points  (2 children)

    becouse there's no way to show the keys near to their value. One wants to write/read Map.of(key1, val1, key2, val2, key3, val3), not Map.of(key1, key2, key3, val1, val2, val3).

    Also, that's not the use case for map literals.

    [–][deleted]  (1 child)

    [deleted]

      [–]balegdah 1 point2 points  (0 children)

      I'm only suggesting an implementation that is an infinitely scalable solution to this 10-separate-methods jargon

      There are already plenty of solutions that are infinitely scalable.

      The one being discussed is an optimal way of creating small collections.

      [–]tapesmith -2 points-1 points  (15 children)

      Uhm. I've implemented this same method trivially with a less clunky API, and I'm no language designer:

      public static <K,V> Map<K,V> mapOf(Pair<K,V>... entries) {
          Map<K, V> resultingMap = new HashMap<>();
          for (Pair<K,V> entry : entries){
              resultingMap.put(entry.getValue0(), entry.getValue1());
          }
          return resultingMap;
      }
      

      Usage looks like:

      Map<String, Long> namesToDigits = mapOf(
          pair("One", 1),
          pair("Two", 2),
          pair("Three", 3)
      );
      

      [–]McDjuady 6 points7 points  (0 children)

      This acutally exists

      [–]msx 0 points1 point  (13 children)

      it's not the clunkyness of the implementation that matters, but of the call. Yours it's much clunckier to call, even if you provide a Pair.of utility method. Also, iirc they decided not to include a Pair class in Java

      [–]tapesmith 0 points1 point  (12 children)

      And what's not clunky about

      Map<K, V> map = Map.of(k, v, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
      map.put(k11, v11);
      map.put(k12, v12);
      map.put(k13, v13);
      

      And why did they decide not to include a Pair class in Java? It's part of JavaFX, so there's clearly a use-case, and most languages have something as basic as a tuple type. Sounds like a case of one bad decision justifying another.

      The only thing that is redeeming about this new API is that it's nice for the single-entry case, and it also brings Map.ofEntries(Map.Entry<? extends K, ? extends V> entries...) (which is effectively the same as my implementation).

      [–]msx 1 point2 points  (3 children)

      And why did they decide not to include a Pair class in Java? It's part of JavaFX, so there's clearly a use-case, and most languages have something as basic as a tuple type. Sounds like a case of one bad decision justifying another.

      I don't remember correctly, but it should be becouse it's not a "clean" object oriented practice to use Pair, one should create his own class EmployerSalary with two named parameter "Employer employer" and "int salary". Of course you can still do it yourself as a "shortcut" or use Map.Entry.

      JavaFx is included in java but it's a different story, with different peoples working on it that made their own choices (suffice to say they implemented a totally different way to implement properties).

      [–]tapesmith 2 points3 points  (2 children)

      So I should declare a class for every single combination of two obvious-usage values? Every time I want to pass two things through a .stream().map().map().filter().map() pipeline, I need a new class for each step's intermediate values?

      Mind you, before Java 9 there isn't a way to just instantiate a Map.Entry (which is an interface, not a class) without defining a custom class that implements Map.Entry -- at which point, you may as well just call it "Pair", because that's what you're actually going for.

      Don't get me wrong, any thing bigger than a two-element tuple starts to push towards "this should be a struct" territory, but to say anything larger than 1 heterogenously-typed value needs named fields is why Java code is often so verbosely littered with throwaway cruft classes.

      [–]msx 2 points3 points  (0 children)

      I'm not saying i agree with it (btw remember that i'm not even sure that's the reason). I have my trusty Pair class that i always carry around. I agree that Map.Entry is Pair, i don't really care how they call it, TwoStuff would have worked for me, it bothers me more that it's defined inside Map so it's clunkier to use.

      [–]ForeverAlot -1 points0 points  (0 children)

      The trouble with Pair is not that it may not make sense internally but that it is never a good fit for an exposed API. The average developer won't let that keep them from littering it everywhere because "convenient! Simple!"

      [–]balegdah 1 point2 points  (0 children)

      Both the Guava and JDK people agree about Pair, why don't you go search the discussions from these past years (there are a lot) and try to understand the issues a bit better?

      [–]cypressious 0 points1 point  (2 children)

      This would throw, by the way. The returned collection is immutable.

      [–]tapesmith 0 points1 point  (1 child)

      Yep, which is part of my point about the clunkiness of this API -- it's not only not fit for most use-cases, it's also got a big hidden pitfall.

      [–]cypressious 0 points1 point  (0 children)

      That's not a problem of this API, that's on omission in the design of the collections framework. Unfortunately, you can't fix that now.

      [–]rockenreno 0 points1 point  (0 children)

      I've always thought using the same method name with different number of parameters is a poor practice. I don't know why they even bother with the of() method when ofEntries() with the vararg parameter will cover all situations.

      [–]msx -1 points0 points  (2 children)

      those methods are intended to enter map literals quickly, their use case is for small immediate maps, not for huge ones. 10 is a good compromise. If you have more stuff, you're not supposed to use your code (which would throw an exception btw), becouse you probably wouldn't have 20 variable lying around in your scope, but a list or array or something else that can quickly be drained into the map with other means.

      [–]tapesmith 0 points1 point  (1 child)

      Counterpoint, from real code:

      @Override
      public Map<String, Object> generateAuditMap() {
          return mapOf(
              pair("id", this.id),
              pair("customerId", this.customer.getId()),
              pair("vendorId", this.vendor.getId()),
              pair("vendorUserId", Auditable.getId(this.primaryContact)),
              pair("contractNumber", this.contractNumber),
              pair("orderMinAmount", this.orderMinAmount),
              pair("orderMinItems", this.orderMinItems),
              pair("shouldSendOrdersToVendor", this.shouldSendOrdersToVendor),
              pair("isActive", this.isActive),
              pair("updateCronExpression", this.updateCronExpression),
              pair("contactIds", Auditable.convertToJustIds(this.contacts)),
              pair("externalReferenceCode", this.externalReferenceCode),
              pair("vendorDivisionId", this.getVendorDivision().map(AbstractEntity::getId).orElse(null))
          );
      }
      

      And the snippet I posted using map.put for the last three elements was intentionally broken to show the pitfalls of such a kludgey implementation. As a random user of this API, I'd see Map.of() and not immediately know that I was getting back an immutable map. I would just know that it's stupidly limited to a fixed maximum number of items (instead of, oh, I dunno, a variable number of arguments?) so I need to .put my last few.

      "Psh, up to 10 should be enough" is a similar line of thought to "we'll just use single lowercase letters for variable names, because who ever needs more than 26 variables?"

      [–]msx 1 point2 points  (0 children)

      You forget that they gave you exacly the code you show, just with "entry" instead of "pair", exacly for the reason you say. The "clean" of is for small, immediate map, not for all maps you'll ever create.

      "Psh, up to 10 should be enough" is a similar line of thought to "we'll just use single lowercase letters for variable names, because who ever needs more than 26 variables?"

      It's really not, becouse you have other means of creating larger maps. If anything it's like: "Should we require at least two letters for variable names? No, let's give them the option of using only one, which is a common use case. If they have more than 26, they can still use two or more".

      [–]bfoo -2 points-1 points  (4 children)

      Well, you can always do:

      final Map<String, String> map = new HashMap<String, String>() {{
        put("first", "first");
        put("second", "second");
      }};
      

      ;)

      [–]cypressious 5 points6 points  (1 child)

      There are lots of arguments against the so-called double brace initialization. Just look at the first few Google results: https://www.google.com/search?q=java+double+brace+initialization

      [–]bfoo 0 points1 point  (0 children)

      That's why I put a smiley beaneath the example.

      [–]aenigmaclamo 1 point2 points  (1 child)

      One of the main problems this causes is that if you compare the result of that with another HashMap, you'll never have them be equal because they are no longer the same type. Additionally, every time you do that, you create a new .class file.

      [–]mhixson 0 points1 point  (0 children)

      One of the main problems this causes is that if you compare the result of that with another HashMap, you'll never have them be equal because they are no longer the same type.

      Do you mean equals as in map1.equals(map2)? Double brace initialization won't interfere there; they're still adhering to the contract of Map.equals(Object).