You mentioned Go, so I'd like to add that Project Loom is the OpenJDK initiative to add green threads/coroutines to the JVM which I'd love. The tricky thing with "data classes" are that everyone wants something similar but different between the various terms: records, data classes, value types.
Personally, what I want from this is essentially Lombock-style all-args constructors, equals, hashcode, and toString. Struct-like like embeddability and flat object-headerless arrays are cool but honestly more niche. I don't mind sucking it up and using unsafe.
I don't mind what type of data classes they come up with. I just want to not have to write stupidly long constructors, multiple sets or chain-able objects when I want to populate some fields.
Green threads are nice because they let me separate logical execution from operating system resources. Unlike Go, Java will expose the ability to use a custom scheduler, so that should be nice!
My personal pet usecase is actually the opposite. I want to isolate 5-10 "low priority" tasks to a single kernel thread to isolate how much damage misbehaving code can do.
Yes, you can change the thread stack size, but that's a global fixed size for all threads, so you can't make it to small, or you'll risk stack overflows.
Meanwhile, Go is using dynamically growing stacks, starting at 2k.
[1] https://cr.openjdk.java.net/~briangoetz/amber/datum.html
I started learning Perl in 2007; after it was no longer popular, but also around the time that the principles/practices that became 'Modern Perl' were becoming popular in the community.
I think there is a difference between the two though. Java was always a language which required a lot of ceremony and typing to get anything done at all. Perl was always focused on expressivity. Modern Perl was a case of learning from past mistakes and consciously avoiding some of the more egregious code the language easily afforded. Modern Java is more a case of Java playing catch-up to the more expressive languages out there.
One is applying constraints on your coding style, the other is trying to lift constraints. Both are trying to reach some middle point.
But! I agree that no company writes purely 'Modern Perl' - you will have to deal with some terrifying code at some point.
So. I agree with the concept of the 'Modern X' fallacy, but I would say that dealing with non-modern code is pretty normal, and people who are OK putting up with the pre-modern code may be some of your best employees. I suffer from rewrite-it-all syndrome myself sometimes, but it's something I try to avoid. Code that frustrates me, isn't necessarily bad.
(If it helps contextually, I'm - at heart - a Clojure programmer.)
It's true that post 8 Java becomes palatable. And someone on reddit just showed me this piece of stats:
https://www.jetbrains.com/lp/devecosystem-2019/java/
83% on java 8, java 11 nicely going up
I pity those forced to work with old techniques
I’d much rather have explicit boilerplate code (preferably in library form as much as possible) than complex, invisible magic.
The latter is “easy”, but the former is “simple”. In most cases, “simple” is more important.
haskell has magic, but at least its concise..
I think it's a fundamentally different mentality as far as the code. The codebase is so obfuscated that it might as well be binary data rather than plaintext files. You need an IDE to manipulate it efficiently.
http://jeffacubed.com/the-boilermaker-story-or-knowing-where...
Does not spending time on boilerplate improve the quality of software you produce? I find that it doesn't.
java modules broke a lot of software that were using sun classes or did classloading magic.
I was on one of such application, it's not a lot of effort to migrate forward, but many of the issues are runtime only so unless you have a good test suite, a strong incentive and no closed source library that use some such classes you can't really move forward with it.
anyway, in java 7 was already possible to write "unbloated" code, especially with the servlet 3.0+ spec, but spring and osgi have a lot of inertia in the enterprise community, meaning most people were stuck programming in xml, one of the most unpleasant experience ever.
ps: also how many shops are doing 'migration off of spring' ?
Meh.. in my opinion a lot of this is just tropes. Spring is a fine environment to work in, although you really don't have to use any XML with modern Spring. But you can if you prefer to for some reason.
All the annotation driven "black magic" can be mildly annoying at times, especially before you understand what's going on under the hood... but considering the amount of boiler-plate crap it saves you from dealing with, I'll happily accept that tradeoff.
For my money, if I was starting a new backend service today, I'd absolutely reach for Spring Boot as my starting point. Of course, I may be biased given that I've been writing Java code for 20 years, and I remember what a breath of fresh air Spring was compared to the old EJB 2.x era "J2EE" stuff.
could have had worse you started just around the xdoclet, that was peak madness
is it allowed to produce libs in jars from non java languages ? Even though I don't think clojure/kotlin/else support jre5 out of the box anymore.
So I have to build this as a special type of jar and then set up this other, rather opaque, set of stuff and deal with all sorts of nonsense ...
Now?
get("/hello", (a,b) -> "Hello World");
And we're done...The applications certainly got a lot more complex than that, but we never found ourselves in need of a magic annotation that you can't step through with your debugger.
This is all about simplicity. It's pretty clear how a get() function on the router which takes a route and a callback works, but it requires some work to understand how Spring magically scans all your classes looking for annotations and then builds parser based on that. It's quite unclear where all of the routes are coming from (since they can come from a class just about anywhere). You suddenly need special tools. And the worst thing that suffers is transparency. Ctrl-clicking/Cmd-clicking on an annotation in your favourite IDE never tells you what it does.
In comparison, a second-order function is a vastly simpler and more grkoable beast.
The point was just to show that the http server aspect of it is trivial to set up.
You can hook in whatever you want from there.
object MinimalApplication extends cask.MainRoutes{
@cask.get("/")
def hello() = "Hello World!"
initialize()
}
API design shamelessly stolen from Python's Flask framework @GetMapping("/hello") fun hello() = "Hello, World!" import static spark.Spark.get;
public class HelloWorld {
public static void main(String[] args) {
get("/hello", (req, res) -> "Hello World");
}
}
[1] http://sparkjava.com/ @SpringBootApplication
@RestController
class DemoApplication{
@GetMapping("/hello") fun hello() = "Hello, World!"
}
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
And this taught me that the best sinatra clone in java isn't really worth it in terms of lines of code saved (we save what? 3 lines of code? 6 with the imports?) when compared to a full-blown web framework like spring boot.Would you care to do another one where we return a json response? I'd bet you will need a json mapper class for complex objects whereas I will return a dataclass instance and it will just work.
fun main(args: Array<String>) = run(*args) {
get { "Hello Kotlin" }
}
It's always easy to make a framework lightweight. But there is no argument that Spring requires a lot of tedious boilerplate in Java (some of which can be avoided with Kotlin), and annotations which are hard to understand.Lightweight frameworks don't really tell you "We do everything that Spring does in less lines", since at one point you'll ask them to make avocado toasts or something else that spring-bakery has a plugin for.
The real power of lightweight frameworks is their simplicity. This is not for everyone (that's why we've got Spring and Rails), but if everybody was fine with the tradeoffs that Spring makes, you'll see the same kind of frameworks ruling the Node.js and Go landscape.
The annotations I used were "this is a spring boot application" on top of a class and another which says "this is a rest controller" to start your hello world application and we only saved a few lines and introduced additional hidden complexity with sparkjava because we still don't have a way to handle json whereas it comes free with spring boot.
You've got another one that says "this is an injectable service" and another which says "this is a db entity" and with the "this is a configuration" you've got 99% of spring boot applications covered. Not sure what is so hard about the annotations. Maybe it has to do with "knowing more than just java/kotlin syntax" that puts people off?
Your Spring example immediately turns me off because it uses a bunch of annotations that could do pretty much anything (and I'd have to learn). Further, it uses kotlin which is not java - it's a valid choice but it's a more terse language in general (so not necessarily valid to compare against java examples).
Spark is straightforward and easy to read, it does one thing and it does it well, and it doesn't demand that I spend a lot of time thinking about anything other than what I'm trying to achieve. I have a lot of time for that.
For the record, with kotlin and skipping imports (of which there would only be one anyway) -
fun main(args: Array<String>) {
get("/hello") { req, res -> "Hello World" }
}
I do indeed find this simpler than your proposal upthread.This behavior which looks helpful at first glance, is very confusing. If your not proficient with Spring or spend hours reading the documentation you'll never understand that: 1. Your object lifetime is tied to the request. 2. The instance you see at the debugger is actually a subclass that redirects all call to the actual scoped object instance depending on a thread local variable (I guess?) 3. The proxy instance is either slow (JDK Proxy) or could cause you grief when you upgrade to the next version of Java (CGLIB). It's not clear which is which without some investigating.
I rather prefer to go with a lightweight framework + a sane DI framework and do something more explicit, e.g. create a RequestProvider<T> interface:
interface RequestProvider<T> {
get(request: Request): T
}
class MyController() {
val fooProvider: RequestProvider<Foo> by inject(name = "foo")
fun myHandler(request: Request) {
val requestScopedFoo = fooProvider.get(request)
}
}
This approach is more explicit, but I prefer it.How exactly does Guice differ in request- or session-scoped injections that makes it that much better?
I have no need for one of those, I'm good building some fairly simple microservices against spark without 'lazy client initialization' or any framework features.
That's kinda the point, it's a simpler toolset for a single purpose. You may have requirements that are made simpler for you by knowing the arcane ins and outs of a feature-rich framework.
Not everyone does, and bringing in said framework to achieve something simple in the name of "Maybe, in future, I may need this" is a prime route to an overcomplex, heavyweight mess. IMHO.
Maybe you've avoided Spring-related hell, but all the java devs I know who survived that era tend to groan whenever it's mentioned.
I wrote a lot of Java, but it was a long time ago. Could you share a bit of code representative of the modern Java 11 style you mentioned (var keyword, lambdas, streams, etc.)?
public static void assertIsSuperset(Collection<Object> superSet, Collection<Object> actualSet) {
final List<Object> missing;
missing = actualSet.stream().filter(x -> !superSet.contains(x)).collect(Collectors.toList());
if (missing.size() != 0) {
/* Imagine some more verbose exception creation here, which is why we caught it in a list*/
}
}
The lambda is in the creation of 'missing': filter(x -> !superSet.contains(x))
A lot collections in Java 8 brought in a stream method which has some functional esqe methods. Filter is one of them, and if you look at filter's method signature: public interface Stream<T> extends BaseStream<T, Stream<T>> {
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
[SNIP!]
}
Predicate is an interface that has one method: 'public boolean test(T t);'. The lambda that I created conforms to that single method; accepting the generic object (x) and returning the boolean tested by '!superSet.contain(x)' The other way I've seen them used is to implement an anonymous version of an interface. public static void assertIsSuperset(Collection<Object> superSet, Collection<Object> actualSet) {
var missing = actualSet.stream().filter(x -> !superSet.contains(x)).collect(Collectors.toList());
if (missing.size() != 0) {
/* Imagine some more verbose exception creation here, which is why we caught it in a list*/
}
} func isSuperset<Type: Equatable>(superSet: Set<Type>, actualSet: Set<Type>) -> Bool {
return actualSet.contains(where: { !superSet.contains($0) })
}
What is all the noise about "collect"? Why allocate a while new list? What's with the "final"? And why do I have to type out obvious types in 2019? return superSet.containsAll(actualSet);
But a straight up boolean doesn't tell me what is missing, only that something is missing. I snipped out the bit about creating a more verbose exception creation because it wasn't really needed for the example:if (missing.size() != 0) { final StringBuilder builder; builder = new StringBuilder(); builder.append("Missing item found: [ "); builder.append(String.join(",", missing.stream().map(x -> x.toString())).collect(Collectors.toList()))); builder.append(" ]"); throw new IllegalStateException(builder.toString()); }
Keep in mind that the objects that this method was written for actually implement human readable toString methods.
> What's with the "final"?
Habit that I forced on myself. But it's not a bad habit to get into. In theory methods are supposed to be short and readable but reality often ends up being that in the fury of writing under looming deadlines you can have horrifically long methods that do many things. And I've seen more then a few times someone reusing variable names badly. Using final when possible is just a way for someone to be able to look at the code, see the variable name and be able to know that what it says is what it is.
>And why do I have to type out obvious types in 2019
Because types aren't always obvious. That's a list, meaning that you can have duplicated items and that order matters. What if my 'list' was actually a set, in which order was not guaranteed? What about if you're trying to integrate this with multiples teams scattered across the world that don't speak English on a code base that's about 500,000 lines of code? Or when you walk away for 2 or 3 years and then have to come back to a method that's not as small and trivial as the given example? What about the next poor sap that has it dumped in his lap?
I know it seems stupid in the short run, and if you've got something small or something that probably wont' be relevant in a year or two, and I'd agree with you if that's the case. But but if you've got a code base that needs to be communicated to other people, then typing is a good way of handling that communication without having to write extra documentation.
Linux support is still WIP, Windows nowhere to be seen, and everything else is not supported at all.
actualSet.stream().anyMatch(x -> !superSet.contains(x));
I have a Map of Lists of Foo items (foos), and I want to transform each item in the sublists to a Bar, filter out bad Bars, transform to Baz and keep the results as a new list of Bazs
List<Baz> barList = foos.values()
.stream()
.flatMap(List::stream)
.map(Foo::transformToBar)
.filter(Bar::isBad)
.map(Baz::transformToBaz)
.collect(Collectors.toList());
The syntax is a little clunky at times... but once you get used to it it's quite expressive.Java's problem is that for many years it used to be the slowest car around. Maybe this was one of the things that helped Java become the undisputed ruler Enterprise Inc. but now it's got a lot to catch up with, and even though the pace has picked up since Java 9, it's far behind its competitors.
Everything you mention (var, lambdas, streams) and everything you might mention when Java 13 comes out (switch expressions, text blocks) is just too little, too late.
Yeah, var is a lifesaver if you have to use Java, but compare the limited type inference it brings to the table with almost any other competing language (Kotlin, C#, Scala or Swift) which at the very least can do a return value inference.
Likewise, streams are long due, but the syntax is horrible and the repertoire of built-in operations is meager, and since Java does not support extension functions like the rest of the languages mentioned here you can't extend them gracefully.
If you take one of the examples given here:
var grades = school.students()
.stream()
.flatMap(student -> student.grades()
.stream()
.map(grade -> new NamedGrade(student.name(), student.grades())
.filter(x -> x.grade() > 60)
.collect(Collectors.toList());
Kotlin goes the same route, but makes it little more natural to use: val grades = school.students
.flatMap { (name, grades) -> grades.map {
NamedGrade(name, it)
}}
.filter { it.grade() > 60 }
.toList()
Besides simply more natural syntax, get the special 'it' variable, destructuring, more operations andNow take C#, a language had something "Streams" since version 3.0 (released 7 years before Java 8):
var result = students
.SelectMany(student => student.Grades.Select(grade => new { Name = student.Name, Grade = grade }))
.Where(record => record.Grade > 60);
Where you get anonymous records - you don't need to define a new type anymore.Or with LINQ:
var result = from student in students select
from grade in student.Grades
where grade > 60
select new { Name = student.Name, Grade = grade };
You get a highly readable built-in query language as well.And neither Java nor C# have anything to brag about Streams or LINQ.
I could already writen those code examples in Lisp, Scheme, Caml Light or Smalltalk.
What we have is mainstream computing catching up, or as Erik Meyer has stated, selling Haskell to VB programmers.
Who started using Java way before Java 8, used to experience serious issues about memory usage and performances in the JVM.
These memories struggle to leave.
I built my profitable, bootstrapped B2B SaaS using sparkjava.
You can find sparkjava here: http://sparkjava.com/
Where Spring shines is enterprise. When you have 600k concurrent users it's just something I can trust. Other solutions can work too, but we value the stability.
I agree with modern Java improvements for the most part, especially if you are including Kotlin in that ecosystem - I really enjoy writing Kotlin.
However, I've professionally switched to Go and really wouldn't want to go back to Java much. I already think Go is a bit bloated, and I still feel like Java (even w/ the latest modern features) still gets incredibly bloated.
Could you clarify this? I last worked on Java in 2016 but even at the time, we had eclipse settings that had Syntax highlighters. Am I missing something?
You can use Pygments in Java using Jython but its performance is questionable and can be incredibly slow when highlighting lisp code for example.
Its a very edge case for most people, but seeing as I am working on a searchcode.com upgrade I need a fast highlighter. Currently I am spawning a background Go process using Chroma to take care of this for me, but if there was a decent Java library I would use it. None exist I am aware of that are even slightly up-to date.
gaming industry is huge, but you can't with java because autoboxing everywhere, no value type, and no specialized generics
same for serverless, you can't because you have to ship heavy runtime
same for machinelearning, you can't do math because lack of value type
etc etc etc
Java tried to fix all problems, but they forgot the essential stuff, performance and efficiency
Outside Android, we can enjoy modern Java, which makes Kotlin just yet another language capable of targeting the JVM.
Pre Java 8 is just fine, I don't think it's materially changed, I think what has changed is perception i.e. the realization that all that Enterprisy cruft 'does not equal' Java - nor do wee need FactoryFactor patterns.
My wishlist: - Stream syntax was nice, but a little clumsy - In addition to data objects, some kind of 'un-typed' object (better than a Map) for dealing with json-ish type data, right in the syntax. - First class functions instead of lambdas - Async notation as a better way to express threads, or even combining within the same thread - Union types
For compilation, you should check out Graalvm, which has some neat new features there.
Once I show them Java 11 with var keyword, lambda's and streams they start appreciating how modern the language has become. Then you throw in frameworks like javalin or sparkjava and suddenly they are not as hostile as they were before.
Honestly the only things I would really like out of Java these days are data classes and possibly native runtime compilation out of the box. Containers solve the latter issue, but id love to just build a binary and run it.
Do that and its on the same level as Go in many ways which nailed the tooling and deployment model, even if the language has issues. Java already has the tooling.
I suppose a decent syntax highlighter library like pygments or chroma would be nice too.