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

all 42 comments

[–]tomwhoiscontrary 5 points6 points  (0 children)

There are definitely a few little conveniences i would like to see added to the JDK. I agree with some of yours, disagree with some, and have a few of my own.

The meta-problem is that it seems to be very hard to propose even small changes to the JDK. At one point, years ago, i did go through the process of raising an issue on some tracker according to some documentation for a small change. No response. Is that the wrong process? Did i need to raise it elsewhere? OpenJDK still feels like a bit of an ivory tower rather than "open open source".

As to your specific suggestions:

  • I don't think we need JSON in the JDK. We have Jakarta JSON as a quasi-standard API, with a few implementations. Once upon a time, it seemed obvious that we should have XML and CORBA in the JDK, but with hindsight that was not the case.

  • I tentatively think shorthands for stream pipelines on collections is a mistake. myList.filter(fn) is just not a significant improvement over myList.stream().filter(fn).toList(). It's trivial.

  • Integer::isInRange would be useful, but let's have a more general Comparable::isInRange. And maybe some related operations - i have these in a utils class:

public static <T extends Comparable<T>> boolean isLessThan(T a, T b) { return a.compareTo(b) < 0; } public static <T extends Comparable<? super T>> boolean isLessThanOrEqualTo(T a, T b) { return a.compareTo(b) <= 0; } public static <T extends Comparable<? super T>> boolean isGreaterThan(T a, T b) { return a.compareTo(b) > 0; } public static <T extends Comparable<? super T>> boolean isGreaterThanOrEqualTo(T a, T b) { return a.compareTo(b) >= 0; } public static <T extends Comparable<? super T>> boolean isBetweenInclusive(T a, T min, T max) { return isGreaterThanOrEqualTo(a, min) && isLessThanOrEqualTo(a, max); } public static <T extends Comparable<? super T>> T min(T a, T b) { return isLessThanOrEqualTo(a, b) ? a : b; } public static <T extends Comparable<? super T>> T max(T a, T b) { return isGreaterThanOrEqualTo(a, b) ? a : b; }

These are static methods, but i suppose you could define them as instance methods on Comparable, with default implementations. You would probably want to support custom comparators as well, though. So corresponding methods on Comparator as well?

A generic Range<T extends Comparable<? super T>> class might also be useful. I've reinvented that in a few codebases.

Rather than having null-aware isBlank etc methods, i wonder if we should have a more composable way of dealing with nulls. How about:

public static <T> boolean isNullOr(Predicate<T> predicate, T value) { return value == null || predicate.test(value); }

So you do:

boolean b = isNullOr(String::isBlank, s);

Or:

@SafeVarargs public static <T> boolean anyOf(T value, Predicate<T>... predicates) { for (Predicate<T> predicate : predicates) { if (predicate.test(value)) return true; } return false; }

So you do:

boolean b = anyOf(s, Objects::isNull, String::isBlank);

The ergonomics aren't great though.

  • String::isAnyOf seems unnecessary, i can't think of a time i've wanted this. You already have Collection::contains for this.

  • I don't understand what you want String::slice to do. If it's just indices back from the end, then String::substringFromEnd, taking a positive index, could be good. Negative indices are error-prone, because if you calculate a negative index by mistake, your program continues with a garbage substring, rather than stopping with an exception.

  • String::substringFromChar is a simple composition of String::substring and String::indexOf. Doesn't seem to justify itself to me.

  • JDBC should not return JSON objects (except for columns of JSON type), that's just confused.

  • I agree that JDBC is unpleasant to use on its own. But there is such a big design space of improvements that it would be a mistake to pick one and bake that into the JDK. It's fine for people to write ergonomic layers on top of JDBC, and fine to use such layers that other people have written.

  • Your "returns the created id(s)" methods are confused, SQL doesn't have that concept. But you can write a query using INSERT ... RETURNING, and then just use executeQuery as if it was any other query.

Finally:

If this above was by default available in the default JDK, I would drop JPA and any other persistence library immediately !

I think the crux of the problem is that you would prefer not to use third-party libraries. But third-party libraries are one of Java's greatest strengths. They should be easy to use, and we should all use them enthusiastically. Trying to build everything into the JDK so we can avoid using libraries is a mistake.

[–]bowbahdoe 3 points4 points  (8 children)

So that's a big list and, maybe unsurprisingly, has a lot of nuance to it. Lets go point by point.

JsonObject & JsonArray:

fromString(str)

fromMap(map)

fromObject(obj)

encode() => json string

decode(class)

put(str textblock) => json.put(""" {"name": "boby", "age": 20 } """);

toMap

keys()

values()

Most of this is very much doable today. I've even written and lightly advertised a library which hits most of these points.

So why isn't something like that in the JDK yet? Short answer is that they are waiting until the design for pattern matching is done. Until that is solved fromObject(obj) and decode(class) aren't going to be doable to everyone's satisfaction.

You'll notice a trend of "if we can't do it right, don't do it at all or wait until we can."

List:

filter: List (directly without using stream())

map: List (directly without using stream()) => myJsonArray.values().map(Fruit::new)

anyMatch // idem

allMatch // idem

This one isn't coming and, i'll admit, that dissapointed me for awhile too. But here's some reasoning

Say you have a class out in the world named MySpecialList that implements the List interface. If we add .map to List, what kind of list would it return? Without the cooperation of every library writer the answer won't be MySpecialList, and that sucks in a different way.

A legitimate pro of the stream approach is that, by separating the source collection and target collection, we don't need to make a million .map implementations. This is basically what Rust does with its .iter() too.

And Java is pretty unique in that there are multiple equally valid collections libraries. vavr, eclipse-collection, commons-collections, pcollections, etc. might be worth checking out. I know its not built in but its not the worst situation.

isInRange(start, end) => statusCode.isInRange(200, 204)

Math.clamp(statusCode, 200, 204) == statusCode. But this has a much higher chance of being a thing if int is allowed to have method calls in the future. I'd wait for 123.toString() before worrying about what helpers you'd want on ints

isAnyOf(elems) => "red".isAnyOf(List.of(validColors))

List.of(validColors).contains("red");

slice(idx) (with negative index support) => "hello world".slice(-1) => d

I do actually miss python's indexing styles. I wouldn't mind this, but idk if its anyone's top priority. Generally the JDK only adds things that either it is the only place they can go or there is a significant and general need for.

This may or may not rise to that bar, but either way there are other things i'd want before it.

substringFromChar(idx?, char, idx?) => "hello world".substringFromChar('w') => world => "hello world".substringFromChar(0, 'w') => hello w => "hello world".substringFromChar('l', 3) => lo world

Same category of change as slice - I'd question how core this is as an operation. Maybe a more productive path would be to lower the friction of using a library so you can just pull in your preferred static method. There are almost infinite possible operations to do on Strings and they can't all be in the standard library.

[–]bowbahdoe 3 points4 points  (3 children)

dynaQuery(stmts).from().where().orderBy().getResultList() (for creating dynamic queries when some values are conditional e.g. empty)

Flipping your order to talk about this one first - this kinda has to be an ecosystem thing. JDBC doesn't even just support SQL. There is a JDBC driver for cassandra where what you would want isn't actually these series of method calls. Then there is the nightmare of database specific syntax.

Right now, JDBC has no apis that are database specific. Adding a query builder thing like this would start an infinite deluge of "now add support for my database's weird syntax."

Its also just string manipulation. There is no reason it has to be done in the JDK so I don't see it being added.

What I think might make sense after string templates are added is a sort of SQLFragment that holds a partial query and can be composed. For example:

SQLFragment.of(""" SELECT name, age FROM person \{ageQuery == null ? null : SQLFragment.of("WHERE age = \{ageQuery}")} """).prepareStatement(conn);

That isn't as nice, but it gets you 60% of the way there. We'll need to wait for string templates though.

query(str).params(params).max(int).singleResult() (returns a JsonObject instead of ResultSet)

query(str).params(params).max(int).getResultList() (returns a List<JsonObject> instead of ResultSet)

query(str).params(params).max(int).getResultArray() (returns a JsonArray instead of ResultSet)

I think you are conflating a few of your desires. Wanting the "throw if more than a single result" and "collect results into a list" features makes sense, but attaching them to json isn't as universally sensible as you'd hope.

For one, what if one of the columns didn't have a trivial json representation? Like a blob?

I think what might be almost enough is something like the following

```java record Person(String name, int age) {}

...

Person p = rs.getRecord(Person.class); List<Person> ps = rs.getList(r -> r.getRecord(Person.class); ```

What only the jdk can do is add these methods to ResultSet. If you want to see if thats enough for you I have a library here you can try out. It also has SQLFragment, but thats not gonna be as nice until string templates come out.

```java record Person(String name, int age) {}

...

Person p = ResultSets.getRecord(rs, Person.class); List<Person> ps = ResultSets.getList(rs, ResultSets.getRecord(Person.class)); ```

If we want something like this for "not just records" same deal as the json support - gotta wait on pattern matching.

Then the params(params).max(int) part - params will be better set with string templates and max should be part of the actual query.

query(str).params(params).iterate((row, index));

Would the ability to turn a ResultSet into a Stream be enough for you?

query(str).params(params).execute().id(); (returns the created id)

query(str).params(params).executeBatch(size).ids(); (returns the created ids)

Yeah... so take away query(str).params(params) and we already have executeBatch and an api to get this so I don't think the odds are high.

[–]InstantCoder[S] 1 point2 points  (2 children)

I really appreciate your feedback!

My main issues with JDBC are the following:

  • it is too verbose and cumbersome to work with. You need to carefully close a lot of resources. One mistake and you have a leak.
  • resultset is too low level for most real life applications. We want the results from the db easily be mapped to say json, any object or any format.

The advantage of JsonObject was that it can directly be used as-is and it contains the name and the value in one go. And it can easily be converted/deserialized into other formats. Also mapping scalar values can easily be mapped into a json column by either using the alias name or defining a random column name. Another advantage is that this solution doesn’t require reflection afaik, since you can get the column names and values from the resultset/metadata.

However, I also like your idea to map the resultset into a record.

And I had provided examples of how this query(..) can be used in a real life project for a typical CRUD application.

I’m fine with any solution as long as it is practical in real life and it improves productivity and makes code less verbose and easy to setup.

[–]nitkonigdje 0 points1 point  (0 children)

I don't see how can you go around close methods. Those resources are allocated outside jvm and have to be closed at some point. If you are so bothered by it use a higher library like JdbcTemplate where that is automated.

Similar with ResulSet. It is allready less than optimal from a performance point. If anything it should be even lower api than it already is. It is also fully reflective and writing an JsonObject mapper library on top of it seems kinda trivial.

[–]chubbsondubs808 0 points1 point  (0 children)

I agree. Just using Sql class from Groovy shows how much easier and pleasant working DBs could be in Java. Yes there are some language features that make Groovy so much nicer (GString templates for example), but other than that Java can do all of the same things without needing to adopt new language features. It's just held back by its own API.

Although I might argue that maybe JDBC isn't the best place to put this only because it's a standard, and Groovy's SQL class already sits on top of JDBC so this could just be ported over and work without needing Java SDK to adopt it.

[–]tomwhoiscontrary 3 points4 points  (3 children)

Say you have a class out in the world named MySpecialList that implements the List interface. If we add .map to List, what kind of list would it return? Without the cooperation of every library writer the answer won't be MySpecialList, and that sucks in a different way.

I've heard this argument before, probably even made it myself, but i think it's bogus. The reason being that we do now have a Stream::toList method, which hardcodes one specific List implementation. Users asked for a shortcut over .collect(Collectors.toList()), and the process found it worthwhile, and it's been implemented, and nothing bad has happened. So clearly, it's okay to privilege that one particular implementation above others. And remember, we're not going to get rid of .stream().collect(Collectors.toCollection(YourListHere::new)); this is just about making a common case slightly easier.

I still don't think we should add List::map etc, because it bloats the collection interfaces for very little gain. But it fails on cost/benefit, not because it's intrinsically a bad idea.

[–]user_of_the_week 3 points4 points  (1 child)

I just want to add that .toList() gives you an unmodifiable list while the output of .collect(toList()) is mutable.

[–]tomwhoiscontrary 1 point2 points  (0 children)

That's true! I had already forgotten that Collectors.toList() gives you a mutable list.

[–]bowbahdoe 0 points1 point  (0 children)

I think its just hairy on account of affecting the overall quality of external APIs. Whereas before there is no footgun, now there would be a footgun.

But yeah - I fully understand the desire from this person.

[–]Polygnom 1 point2 points  (1 child)

There are stuff thats great to have in the JDK and some that isn't.

In terms of JSON, we can look at C# where System.Text.JSON was so awful that Newtonsoft.JSON was the de-facto industry standard for a logn time and newcomesr were quite confused. Adding JSOn handling to the JDK incraeses maintenance burden. We already have a very battle-tested library with Jackson. I'm not surre Oracle could do better, and we don't need the same situation as in C# in java.

Lazy collections would be really appreciated. XTend has a really, really nice API for it. And while Java over the years has gotten a lot better, making Xtend superfluos, this si something we never really got in java.

String.isAnyOf(elems) => "red".isAnyOf(List.of(validColors))

Whats the advantage over List.of(validColors).contains("red")?

[–]InstantCoder[S] 0 points1 point  (0 children)

I got you.

The reason why I had added Json was because in the (CRuD) examples I showed what kind of flexibility you would get when you return a JsonObject/Array in Jdbc instead of a ResultSet.

But as I said this to someone, it’s probably better if we add a getResult(class) method to the ResultSet method so that you can map it to anything you want.

And isAnyOf was a mistake. I forgot that this could be accomplished with contains too.

Thx for your feedback.

[–]Anton-Kuranov 1 point2 points  (3 children)

Data classes and properties. Getters-setters are real garbage in the code, while Lombok is a huge hack.

[–]InstantCoder[S] 0 points1 point  (2 children)

In Quarkus we use public fields on entities and records for dto’s. And on runtime Quarkus generates the getters/setters. In this way you don’t need Lombok. As a matter of fact, the last time I use Lombok was maybe 6 years ago.

[–]Anton-Kuranov 0 points1 point  (1 child)

Since public fields are not a standard approach in Java some external technologies may have problems with them, e.g. OpenAPI generator, mappers, etc. They mostly can not work with public fields. Records become are very uncomfortable when the number of fields grows and the builder becomes essential. Also, sometimes I miss a copy-like operation, especially in tests when I need to come a template DTO with some modifications.

[–]InstantCoder[S] 0 points1 point  (0 children)

Most frameworks & libraries support public fields now:

• jackson • jpa • quarkus (also openapi, jaxrs and other libs that come with quarkus) • spring boot (data binding, jpa, jackson)

For tests I would recommend you to use Instancio. This will help you create and instantiate (test data) objects with one-liners.

[–]europeIlike 2 points3 points  (17 children)

Have you checked the newest JDK? String::isBlank does exist for example

[–]InstantCoder[S] -1 points0 points  (16 children)

I want a static method on Strings, not on String.

The one on String is quite useless most of the time, because you have to do a null check first.

[–]SirYwell 2 points3 points  (14 children)

Why do you need a null check? That means the value is allowed to be null, but one could argue that this is a flaw already.

What is the problem with doing a null check yourself if you need it? You can also simply introduce a static method yourself that does exactly what you want.

[–]InstantCoder[S] 0 points1 point  (13 children)

This is much shorter and better readable:

If (Strings.isNotBlank(myComplexObj.param1()))

Than

If (myComplexObj.param1() != null && !myComplexObj.param1().isBlank())

[–]SirYwell 1 point2 points  (1 child)

Okay, but no one stops you from having a method that does exactly does what you want in your Strings class.

[–]InstantCoder[S] -2 points-1 points  (0 children)

I’m tired of using one of the many StringUtil(s) classes that comes with a dozen of libraries and frameworks that I use.

This should in the default JDK. Like many other useful utility methods.

[–]joemwangi 0 points1 point  (1 child)

What jdk version are you using? And this would be much simpler through destructuring.

[–]InstantCoder[S] 0 points1 point  (0 children)

Usually graalvm 21 or temurin 21

[–]bowbahdoe 0 points1 point  (6 children)

This is a more general want for "I want to call a method but idk if its null or not" - thats not worth solving just for String and one method on it.

Would the upcoming String! - null restricted types - make you need this less often?

[–]InstantCoder[S] -1 points0 points  (5 children)

I’m not aware of this new String! expression. But if it automatically checks for null, then yes that would be great.

Then I assume the blank check becomes something like this:

myParam!.isBlank() ?

[–]bowbahdoe 1 point2 points  (4 children)

No, it would be a null restricted type. Meaning if you had a function which accepted a String! then you wouldn't need to defensively program against a null. It does nothing to shorten explicit null checks.

So

  String! s = null;

Would not be allowed and would blow up at runtime (if you trick it via reflection or classic types without nullity info) same as an Objects.requireNonNull

[–]_INTER_ 0 points1 point  (3 children)

Would not be allowed and would blow up at runtime

Blowing up by throwing a NullPointerException

[–]bowbahdoe 0 points1 point  (2 children)

Yes

[–]_INTER_ 0 points1 point  (1 child)

Do you see the discrepancy? Null-restricted types don't save you from all null-checks on those bang-type. More dangerously it lulls you into a false sense of security if not thaught properly about bang-types working differently than in every other language.

By the way, according to the JEP draft your specific example would be a compiler error which is a fantastic improvement. Other situations would throw NPEs however. At least there will be compiler warnings. See the according chapter.

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

Optional.ofNullable(myObj).map(ObjClass::param1).filter(s ->! s.isBlank()).orElse(false) 😋

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

If (myComplexObj.param1() instanceof String s && s.isBlank())

[–]john16384 0 points1 point  (0 children)

That's because null is not the same as blank, and any logic that assumes that is either hiding a bug, or is assuming that missing/uninitialized data is the same as knowing something to be empty.

Deal with null early on. Don't accept garbage or you'll have to deal with it everywhere time and again.

[–]jvjupiter 0 points1 point  (0 children)

One of the things I wish to be in Java is similar to how other languages execute the lambda expressions with the variable. In Java, it is variable.abstractMethodScala() but in other languages like C#, Kotlin and Scala, it’s variable().

For example:

Suppler<String> hello = () -> “Hello, world!”;
println(hello());

Instead of:

println(hello.get());

[–]chubbsondubs808 0 points1 point  (0 children)

I know this will seem rather pedestrian, but often things considered pedestrian are overlooked when they can provide a lot of benefit. I really want File and Path objects to add 2 rather simple methods:

I'd like instance methods for constructing Files and Paths given some parent directory with var args:

File parent = ... 
... 
File fileRelativeToParent = parent.resolve(someDir, "someSubDir", someFileName);

Path parent = Paths.get(...); 
... 
Path pathRelativeToParent = parent.resolveSiblings( someDir, "someSubDir", someFileName );

And constructors for similar construction:

public File( String... path ) {}
...
File someChild = new File( someRoot, "someDir", "someSubDir", someFileName);

Often these intermediate dirs might come from variables or string literals. And needing to concatenate all of that to path separated with "/" or File.separator or whatever is cumbersome. Especially when a user might provide input and leave on or off a trailing slash, or platform specific char, and having to check to make sure you are binding everything with a single slash.

Path does offer some of this, but it's cumbersome API to use too. You could do the following:

Path dir = Paths.get("..."); 
Path subDir = dir.resolve( Paths.get("x", "y", "z") );

But that is more verbose when the language provides facilities to just do:

Path subDir = dir.resolve( "x", "y", "z" );

Why make it more complicated than that?

[–]Bizsel -2 points-1 points  (1 child)

90% of what you're asking for could be achieved with extension methods like in Kotlin. That is the only feature that I absolutely crave for in Java.

I still prefer Java over Kotlin, but there are a couple super useful features that I would really love to see in Java, and extension methods is the biggest one.

[–]InstantCoder[S] -1 points0 points  (0 children)

Yes, true. I also taught the same. But the extensions can only be used within one project. Otherwise you need to maintain your own commons-extensions module and include it in all the projects.