Java's records, Lombok's data, and Kotlin's data classes
nipafx.dev
nipafx.dev
https://kotlinlang.org/docs/jvm-records.html#declare-records...
Being able to use local records is a useful feature in particular in unit tests.
@Test
void testPoints() {
record Point(int x, int y) {}
...
}If there's a particular language feature records enable that Kotlin doesn't with ordinary data classes in this case, I'd love to see it.
I was also kind of surprised to see the article talking about algebraic data types... table stakes for ADTs is sum types and I have missed them considerably in the Java ecosystem.
Kotlin has something like them with sealed classes, although I'm a newcomer to the Kotlin and Spring Boot ecosystem so I don't see explicitly how I make some simple case like JSON
{"type": "left", "abc": 123}
{"type": "right", "def": "456"}
turn into some structure like: sealed class EitherTest {
data class Left(val abc: Long): EitherTest()
data class Right(val def: String): EitherTest()
}
rather than the hacky version that you have to do in relational databases and Java which don't support such things, enum class EitherType { LEFT, RIGHT }
data class EitherTest(val type: EitherType, val abc: Int?, val def: String?)
Like I’m not saying that there’s no way, I’m sure there’s a way... just that the ecosystem seems so hesitant to embrace sum types that like the above sealed class is widely viewed as a hack and there is no statement about “here is how you actually use sum types for everything in your Spring application with Kotlin.”Was gonna give the choose-your-own-adventure example of why sum types are handy and how you have to kind of hack around them with inheritance when you don't have them but it occurs to me that anyone who has stuck with this comment this far probably already has some familiarity with this?
https://mkyong.com/java/how-do-convert-java-object-to-from-j...
You would just have to have annotations on the enum names to get the lowercase values in your example json.
The reality is that Kotlin can build the very same benefits you're talking about as well, in future versions. All the compiler has to do is to restrict these benefits to data classes which only have 'val' members, do not have hidden state and do not use inheritance. In fact, Kotlin is ALREADY enforcing these constraints on a any data class that you annotate with @JvmRecord.
Now, if we're already talking about imaginary future benefits here, Kotlin is also planning to have (immutable by design) value classes when Project Valhalla is ready (you can already have value classes with a single field in Kotlin 1.5).
[1] To be fully honest, Java records themselves are themselves an imaginary feature for the vast majority of Java users until Java 17 comes out, since very few projects would use a non-LTS version. And even then, you can expect years until libraries can start to use them. Kotlin and Lombok data classes can be used right now.
Scala is a pretty simple, concise and coherent language though. Never understood why people deem it has too many features, I write it professionally and never felt so.
At least comparing it to C, C++, Python. Java may be more simple, but you have tons of features added with metaprogramming via dozens of annotations generating lots and lots of boilerplate, Lombok and Spring are examples of that.
I'll start with one (key)word: `implicit`
I agree with your distaste for metaprogramming though.
They are changing implicits in Scala 3 though with the "given" keyword which is more ergonomic.
Odersky called the approach for Scala 3 "intent over mechanism."
Are you suggesting to use scala without using for comprehensions? Or do you mean you don’t need to write your own?
It’s been a long time since I wrote scala, so may be getting it wrong.
I don't think implicit counts as "too many features" or as something complex. Basically all it does is finding a canonical value in scope for a hole of certain type.
Either way, the local Scala guru there said the Scala community was starting to get over implicits.
At some point it was true, but nowadays the problem is solved because IDEs will show you were an implicit is used and where it comes from. Without that, it was indeed more difficult - but also not more difficult than Java's reflection or DI (where you had and have even less help).
Yeah, but don't you feel the same pain when using Spring's autowiring? Or python's (or C++ template) default arguments? Or dynamic method dispatching in any OO language, where you don't know what method you actually call? Or late bindings?
I don't see how implicits' implicitness is significantly worse than any other kind of implicitness common in other language.
Default params are generally fine since they're hidden on the implementation side but explicit if they vary at the caller site.
And even dynamic dispatch I try to use carefully and sparingly. I just like really explicit programs.
I see. My point was that such things are pretty common in most contemporary languages and I don't see how Scala is special in this regard.
Yes, unlike Go or Java, implicits are part of the language spec and not some metaprogramming magic on top of it, but I'm not sure it's a bad thing.
import io.circe.syntax._
List(1, 2, 3).asJson
Where `asJson` requires an instance of an `Encoder`[1] and this Encoder can be derived with the help of implicits.For you as a normal user, the two common places where you might use implicits are (1) for implicit classes providing syntactic sugar:
// original
def doSomething(a: A): B = ???
val a: A = ???
val b = doSomething(a)
// with implicits
implicit class AImplicits(a: A) {
def doSomething: B = ???
}
val a: A = ???
val b = a.doSomething
and for (2) implicit conversions. These are a foot-gun, so should be used in limited circumstances. At work, we use case classes in data pipelines then convert these to avro classes on save; there's lots of ways to do this, but as an example if you have an `Optional[Int]` and your avro constructor requires a nullable java `Integer` then `JavaConverters` won't save you and you'll need something like: implicit def optIntToInteger(optI: Option[Int]): java.lang.Integer = optI.map(Int.box).orNull
[1] https://circe.github.io/circe/api/io/circe/syntax/package$$E...Java codebases are way scarier than Scala.
But why do you think Kotlin is easier to integrate than Scala? Both work on JVM.
If you want to assert that finding Kotlin developers is easier or that Kotlin is an easier language to learn, sure, that might be the case (I really don't know), but that's not really an integration task.
https://www.scala-lang.org/api/2.13.5/scala/jdk/javaapi/Coll...
One other benefit of that is you maintain object identity. I don't think that Scala's wrappers do that.
But it also has Scala collections. With scala collections you get the full power of the Scala type system, as well as a much richer and full featured collections api. So most scala programmers won't bother with java collections unless they have specific java interop requirements.
The Scala adapters are merely ways of converting java collections to scala collections and vice versa.
If I have to use Java, I put it into a separate sub-project and make sure that I have a nice API to interface between the subprojects. `.asJava` and `.asScala` then only appear at very specific places where the interop happens.
Or an other way of saying it is that your approach of walling off modules as either java-collection or scala-collection is kind of like saying "Java and Scala collections work well together, so long as you don't have to use them together too much. If you minimize how much they need to interoperate, the problem is not so bad."
Yes, that's actually a very good way to rephrase it. That being said; I myself wouldn't limit that to just the collections; Java and Scala on the language level have excellent interop. For Scala-the-ecosystem the story is a bit different.
Java is not the primary target for most of the Scala libraries. Not even a secondary one, I dare say. (I'm not even sure how you would model a Java API for a library - say; Cats - that uses implicits for the heavy lifting.)
That of course stems for the fact that Scala makes use of concepts that have no correspondence in neither Java or Kotlin, so there will necessarily be something lost in translation.
The problem is that those java collections suck in comparison to the Scala collections. So Scala programmers prefer to use Scala collections. Scala programmers would never willfully use java collections if they don't have to, and if they absolutely have to, they have minimal overhead conversions back and forth. The minimal conversion overhead is the price they're willing to pay to use better collections while maintaining java interoperability.
Kotlin:
listOf(1,2,3,4)
.map{i -> i + 1}
.filter { it % 2 == 0 }
.flatMap { listOf(it, it * 2, it * 3) }
Kotlin even has a similar take to Scala's views, which they call sequences: listOf(1,2,3,4)
.asSequence()
.map{i -> i + 1}
.filter { it % 2 == 0 }
.flatMap { listOf(it, it * 2, it * 3) }
.toList()
Is this really so "lacking in capability and grace" compared to Scala? The only think missing is persistent immutable collections, which are implemented in kotlinx.You said:
> You have to litter your whole code with `.asScala` and `.asJava`, the java collections don't work with for comprehensions, etc.
So how does kotlinx achieve zero-cost compatibility with Java collections without having something like `.asKotlin` and `.asJava`? Because otherwise there is no difference between Scala and Kotlin in this regards.
import java.util.List;
public class Foo {
public static void blah(List<Integer> list) {
}
}
Kotlin: import kotlinx.collections.immutable.persistentListOf
fun main(): Unit {
Foo.blah(persistentListOf(1,2,3))
}
This compiles fineThe real difference is that more of the Kotlin ecosystem uses Java's fundamentally mutable collections compared to the Scala ecosystem, and using actually immutable collections in Kotlin is extremely difficult to the point that essentially no-one does it. IMO that's a bad tradeoff in the long term, but you can absolutely take the same approach in Scala if you really want to.
Spring is about half the server side JVM ecosystem. Android represents a good chunk of frontend usage. Kotlin is a first class citizen for both; Scala just isn't. It has its own frameworks of course but they are kind of niche in comparison.
I think it's great that Java is slowly evolving to have features that other languages have. Kotlin supports Java's records as of this week's 1.5.0 release via an annotation. Meaning that if you have Java code that needs to interact with Kotlin code, you can write a data class that from the Java side looks like a record if you put the right annotation on it. It's a compatibility feature that's only relevant if you are planning to use or support Java. Another notable feature that landed with this week's release include sealed interfaces (it already had sealed classes). You can use both with data classes of course.
If you need to do validation, there are very decent frameworks that you should use for both Java and Kotlin. We are using a thing called Konform currently. Very nice.
For non negative numbers, you can use the new unsigned numeric types they added in Kotlin with the last release: https://kotlinlang.org/docs/basic-types.html#unsigned-intege...
> In the case of records, they're treated in a special way by the runtime (e.g. in serialisation).
Scala will use them as underlying implementation (just like it will do with value-types, functions and so on).
In Kotlin data classes, it's already implemented (just called copy) https://kotlinlang.org/docs/data-classes.html#copying
You mean the article author? Yes, it was odd that he mentioned that, but besides that not a bad feature to have (but a bit wordy for my taste, I hope they'll change that).
https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/#w...
I think the author is more saying that a "with" operator would fit into Java record semantics but might not be compatible with Lombok @Data classes or Kotlin data classes (though it looks from your link that Kotlin actually does have something very similar in the "copy" method).
@Value @With
public class Pair {
int left;
int right;
}
final Pair rightIs3 = somePair.withRight(3);In Scala (I don't know Kotlin, but I assume it's similar) you could easily implement a copy() method yourself (and many people do if they need a case-class-like thing but can't use a case class) that behaves identically to the copy() method provided by a case class.
But the semantics of that copy() method require default argument values, and the ability to call functions using named arguments. Java doesn't have either of those, so instead of adding those features (I can understand the former being controversial), I guess the plan is to add entirely new syntax just for records, which IMO is a huge shame.
But it appears the "with" syntax is far from finalized, so it's possible they'll do something better.
The author is referencing possible future work that can build on the current records implementation, and the work Brian Goetz is doing with pattern matching and record deconstruction. Goetz has put together a draft that shows how these could be combined.
https://github.com/openjdk/amber-docs/blob/master/eg-drafts/...
Kotlin will soon be generating records in its back end, thereby gaining all the advantages that it's allegedly not getting today.
The only benefits I've seen in your article are not available right now, and can (and sometimes are) matched by Kotlin and even Lombok.
* Destructuring pattern matching syntax (JEP 405)
Proper pattern matching is obviously one of Kotlin's weak spots, but nothing prevents it from developing this in the future. Just like Java has JEP 405, Kotlin has KT-186[1], although Java might (uncharacteristically) beat Kotlin to this one.
* with blocks
This solution is far away and relies on introducing new syntax to the language. Meanwhile both Kotlin and Lombok already have solutions that give you the same benefits without introducing new syntax.
Lombok has both @With and @ToBuilder, while Kotlin has the copy() method.
* Serialization
As far as I can see, Kotlin Serialization already supports algebraic data types - including Sum Types, not just Product Types!
* Boilerplate reduction
I don't think you were trying to imply otherwise, but just to be clear about it, this is the benefit that both Kotlin and Lombok data classes had from day one.
1. Destructuring - available in Kotlin
2. Copy with change - available in Kotlin
3. Serialization - not sure why Kotlin data class would not be serializable
4. Boilerplate - Kotlin takes care of equals and hashCode
Huge disadvantage that matters to me is that record fields cannot be mutated. It makes the records much less useful.
No. It makes them much more useful.
Well, they have to obey Wirth's law, I guess.
Btw Kotlin allow to make immutable Java records too so clear winner.
“Problem” is with records, that they are only shallowly immutable. record Rect(List<Point> corners) will readily hand out a “pointer” to a mutable list. It can be solved of course by storing only immutable objects.
What parent may have failed to get from grandparent comment is that the latter likely meant it under the hood, transparently to the user. That is, new Point(3,2) != new Point(3,2) but the JVM can make the object reference the same data, because the field itself is final. Thus a copy can be optimized at the JVM level, while still having identity.
Ultimate low-level optimization in Java would be more like packing your structure into arrays of integers - which is something people actually do in Java. Just because you’re using Java doesn’t mean you don’t want your code to run as fast as possible.
Eventually with some more evolution. But not yet.
But GC-wise the JVM is far ahead the game, whatever you see is likely better than the same functionality would be under JS, Python, C#, Go, etc (though the latter two do have value types/structs already so they can at some place manually do the “escape-analysis”. But not every problem requires/can use value types either)
I don't know much about Python and JS, but I do know that they're not using immutable model, everything is mutable in Python and in JS, so I'm not sure what's your point. The only immutable language I'm aware of is Haskell which is not used widely. Just because JVM is faster than Python or V8 does not mean that it's OK to slow it down with immutables.
The major downside is that could ruin cache locality and make passing the instance across a FFI boundary require a copy.
Another optimization would be that the JIT (or even perhaps javac) could notice cases where you make a copy (with one field changed) of a record and then never use the original reference again. If the JIT (or javac) can prove that no other bit of code holds a reference to the original record, it can reuse and mutate the original one instead of making a copy. I don't know if this optimization is or will be implemented, of course.
Either way, I expect the overhead you mention ends up being worth the benefits of immutable data. (That's been my experience using Scala, anyway.)
https://medium.com/@vgonzalo/dont-use-lombok-672418daa819
Autovalue & Immutables
For example, article does not mention lombok @Builder and Kotlin `copy` when talking about boilerplate. Boilerplate is not just about application code, it's also about test code!
We have dozens of entities, and when unit testing them – always having to construct the COMPLETE record with all attributes from scratch is a pain. Nested records things worse. We now have all data classes as @Value+@Builder, and test factories provide consistent builders which the actual test cases can chain, override and use. This is possible in Lombok & Kotlin, but not in Record.
With escape analysis, the compiler can allocate the data on the heap, stack, or even stick it in registers.
https://www.beyondjava.net/escape-analysis-java
https://shipilev.net/jvm/anatomy-quarks/18-scalar-replacemen...
https://www.javaadvent.com/2020/12/seeing-escape-analysis-wo...
The second question to look at is "which JVM are you using?"
Different JVMs may implement this differently. This isn't something that one can say about Java. It is something that one might be able to say about HotSpot, Zulu, or GraalVM.
> Something that Graal can do that C2 cannot, and a key advantage of GraalVM, is partial escape analysis. Instead of determining a binary value of whether an object escapes the compilation unit or not, this can determine on which branches an object escapes it, and move allocation of the object to only those branches where it escapes.
And from https://docs.oracle.com/en/java/javase/11/vm/java-hotspot-vi...
> The Java HotSpot Server Compiler implements the flow-insensitive escape analysis algorithm described in:
> ...
> After escape analysis, the server compiler eliminates the scalar replaceable object allocations and the associated locks from generated code. The server compiler also eliminates locks for objects that do not globally escape. It does not replace a heap allocation with a stack allocation for objects that do not globally escape.
----
So, some JVMs implement, others only do a limited subset of the optimizations available with escape analysis.
I would not say that the answer of "is it used in practice" is "no."
Theoretically it could do hat, but that's just the classic "sufficient smart compiler" strawman [1]
[1] https://wiki.c2.com/?SufficientlySmartCompiler
[2] https://stackoverflow.com/questions/29665748/memory-allocati...
So "does Java allocate a record in an array directly as some structure of values in the array or as a pointer to a record object?" isn't one that can be answered by looking at Java.
It is an interesting question, and I'd be curious to see someone do a deep dive into the internals of GraalVM to show what can be done.
The other part that trickled out in other comments from the person posing the question about the array of records:
> It's a global array of structs, let's say.
and
> No, because my competitors who are attempting to fill the same orders I am attempting to fill are not chasing pointers.
... which, I'd be curious to see how .NET supports an array of struts (that are presumably changing over the lifetime of the array) that is allocated as a global. That sort of use case and the specifics of how it is implemented could make escape analysis give up and you'll see an array on the heap with pointers to records on the heap as they're passed off to different threads (which each have their own stack).
> which, I'd be curious to see how .NET supports an array of struts (that are presumably changing over the lifetime of the array) that is allocated as a global.
Very easy. An array-of-structs (which can still be on the heap mind you) will just be a continuous block of memory. This is totally independent of any locking and synchronization.
For example in a class with 2 32-bit fields, and an array of objects a b object-ref array will look like: [p_ap_b], p being a pointer to [a_0a_1] or [b_0b_1]. A struct-array will look like [a_0a_1b_0b_1].
Regardless, if you care about performance enough (via actual benchmarks) that you know that you really need some data to be guaranteed to be stack-allocated structs, then you probably shouldn't be using Java (or any GC'd language?) in the first place. Records don't change that calculus.
The question of "what is the representation of the object on the heap?" then open.
However, the "this is global" complicates it.
This isn't a question for Java to answer. You would need to dig into the specifics of the particular VM that you are using and how it allocates such a structure along with what optimizations it has available.
I don’t think anybody fully disagrees with that. At least, I haven’t heard people claim int can be removed from the language because a good compiler can produce identical code for Integers.
And yes, that can also apply to instances that do escape. A sufficiently advanced compiler could in some/many cases figure out that an array of Integer can be compiled down to an array of int. However, it’s way easier for a compiler to check a programmer’s claim “we won’t use features of Integer on these ints” than to deduce that code won’t, so a little bit of programmer effort allows for a simpler compiler that can produce faster code.
For me, records and (future) value types are examples of such “little bits of programmer effort”
“Classes cannot extend, implement, or mix in int.”
https://api.dart.dev/stable/2.6.0/dart-core/num-class.html:
“It is a compile-time error for any type other than int or double to attempt to extend or implement num.”
⇒ it seems that, technically, you’re right. int is an object in Dart. At the same time, it’s a restricted type of object.
So restricted that I think it is aan object only in name/at compile time.
I personally never had to do this so I'm not sure if this is a real benefit.
The only answer that makes any sense is this one from Brian Goetz.[1] Namely, that it's a workaround to support mutable fields of otherwise immutable objects.
To be honest, allowing you to override the methods on an immutable, auto-generated class to inject custom accessor logic feels like an immediate retreat from the conceptual goal of providing an immutable record implementation in the first place.
[1] https://stackoverflow.com/questions/66702223/why-do-java-rec...
The best explanation I have is that it is:
- a convention that seemed like a good idea for many people at the time, it even has a name: Javabeans
- it allows for a standard non magic way to add logic to be run when reading or updating fields
- in a time where source control tools and Java refactoring tools where not as developed as they are today it made sense to make getters and setters everywhere since changing from public fields to accessor methods after it were already in use was probably scary for large teams.
...Unless you're Brian Goetz's alt account?
I have a policy of saying whether if is true or false.
That said you should take his word for why it was designed that way and my word as an historical account of why I did that in 2005-2015.
Also, getters and setters don't always modify individual values. Imagine a class where we have:
private val firstName private val lastName
fun returnFullName(): return firstName + lastName
Except I'm talking about Java classes that store data. They don't do any operations on it, they don't have any internal state, they're more like C structs.
> Also, getters and setters don't always modify individual values.
Of course. But one doesn't contradict the other.
They wanted to be able to write framework code that could introspect Java beans and expose them directly in a user interface, amongst other things, allowing users to modify them, create them, delete them and even compose them to create their own applications... they had even imagined there could be marketplaces where you could _buy_ Java beans to add to your program, or even to modify your other beans to give them extra power... getters and setters were part of that - they needed to know how to obtain and change the state of an Object, but how the Object internally handled such state changes were up to the Object itself (what we now call encapsulation)... this was similar to how Smalltalk worked and that was an inspiration for the Java beans specification... all this never really turned out the way they wanted, of course, but have a read of the Java beans spec to get a better idea of what they had in mind if you don't fully understand it yet.
With time, people forgot completely about Java beans, but for whatever reason, getters and setters sticked around to this day. Many Java developers today think Java beans are just classes with a bunch of getters/setters and never probably heard of PropertyChangeListener, VetoableChangeListener and the other parts of the Java beans spec (some of it lives on in Swing).
If you write Java today and just want to expose some data, yeah, just go with public fields if you can't use Java 16 records yet... if you ever need to change how you internally store information, just refactor that to a setter if you really must (it will never happen).
[1] https://www.oracle.com/java/technologies/javase/javabeans-sp...
seriously, I think the reason is so you could override the getter to do something else, like compute a value and return it... but in practice this almost never happens.
items.stream().map(Foo::bar)
vs items.stream().map(i -> i.bar)They are certainly not better, that's just a sad click-bait (the author even admits that).
Sometimes nominal typing is better and sometimes structural typing is better. Forcing people to always use nominal types just ends in a lot of generic or long/meaningless names - one can already see this in Java.
Think about the following type-level (not runtime) representation for a structural type (tuple):
(String -> Integer)
And the following for a structural type with where each "member" has a name: (("name" -> String) -> ("age" -> Integer))
And the following for a named structural type where each "member" has a name: ("User" -> (("name" -> String) -> ("age" -> Integer)))
The last structural type here would be equivalent to a class/record in Java. I would then add syntax that makes working with these special kinds of structural types more easy. So record User (String name, Integer age)
would translate to the structure above.
But one could also do something like that _if they wanted to_: ("User" -> (("field1" -> ("name" -> String)) -> ("field2" -> ("age" -> Integer))))
The closed equivalent would be a Java record where each member is annotated. This could be used to serialize/deserialize it into json.I think that's what I would try :) but I'm not a PL designer.
Is an Email type the same as a String even though they are the same under the hood? I precisely should not accept it without explicitly casting it, otherwise I would have duck typing. But I agree that explicit structural types can be useful at places.
I think tuples are an essential language feature (and it's ridiculous that Java doesn't have them yet), but I think they're often way overused.
Note however that an upcoming version of Java will get first class ergonomic support for manipulating immutable data: https://github.com/openjdk/amber-docs/blob/master/eg-drafts/...
There is also https://github.com/hrldcpr/pcollections
That a member of the JVM ecosystem is leveraging new JVM capabilities isn't ironic, it's totally expected.
Arguably this is more of an issue of JPA than an strict advantage of Kotlin: JPA was designed at a time where the general consensus was to primarily use mutable data, and was heavily influenced not only by existing Java ORM APIs, but by their implementations.
Kotlin itself supports JPA by the use of a compiler plugin, which is a good enough solution, but nevertheless, not one native to the language. Data classes mostly work by accident, but pretty much any documentation you will find points it to _not_ use them with JPA.
But not all of us can use JDK 14, and will continue to use Lombok if we're writing in Java.
Lombok JustWorks™. You forget it's there until you setup a new dev environment and forget to setup the annotation processor in IntelliJ.
Which, realistically, you're virtually never going to do - at least not often enough to justify the boilerplate and especially in the case of things like "records" which were probably auto-generated from a schema (with obligatory getters and setters) anyway. If you did, you'd end up confusing all of your callers who probably wrote client code presuming that what they provided in the setter was going to be exactly what they get back in the getter.
In general I subscribe to the at first make it as simple as possible, when you have two examples of it being too simple it is time to refactor.
Of course when creating libraries it is different. In that case to protect users on the API surface and try to make that stable.
If you can't guarantee you're not going to break someone else's code by changing:
foo.x to foo.getX();
then you should stick with properties. Now, for staying inside a single package, or a single compiled unit, then what you say is reasonable enough. I'd still prefer to write the getters and setters, though, especially in cases where it's free, like Kotlin.
In C# you'd have a point, they have parametric properties and readonly properties. So you'd favor just declaring public properties.
But this is why context matters. And OO design principles also depend on this context.
How many times have you seen that done on the real world?
And how many times have you seen that done, and it not creating a lot of bugs because of the behavior change without interface changes?
Personally, I've seen the first one more than zero times. Not the second. Every single time somebody decided to mess with a setter or a getter, it broke the systems that depended on it, and things would have been much better if they simply changed the interface, so the problems would arrive at compile time.
And yes I see it every day. The collection interfaces in Java have countless swappable implementations. Those are basically getters and setters on a vector.
I also had to change entity storage to columnar for a project. Did that. Never had to change a line of code outside the entities.
I give it 5-10 years before we've tricked everyone into writing OCaml / F#!
Serialization is handled by Java bean getters and setters just fine. I don't really see an advantage.
If the reader is using some third-party software that modifies the original blog design, it's his responsibility to disable it if it doesn't work seamlessly.
Blanks out the content again. Seems rather odd behaviour to me so definitely interested if anyone figures it out, even if this is kind of derailing the discussion.
I'm sorry, you said something?
I'm not sure if you've debugged much, or inherited any large legacy projects, but knowing it is immutable vs "typically it isn't" is a pretty big distinction in that moment.
Guarantees are nice. Especially if objects are going to be passed around every which way from Sunday. It allows you to better reason about what could happen, and where.
Java has taken a while to get there, but I’m glad that they have finally.
Java now has built in support for these things, so no longer will developers rely on third-party solutions.
This is great for the Java world, and for any languages that are being built on top of the JVM by extension.
I would politely disagree with your characterisation of it being just academic, as an engineer I find it incredibly exciting. Admittedly, my bar is pretty low for excitement these days.
Thats the answer. And its a really good one.
The only other languages with the same or better breadth and depth have their own reasons to avoid them like the plague (e.g. C/C++ footguns, difficulty managing large-ish codebases without types)
How could you not see this as an incredible technical advantage in almost any context?
This is the best case scenario, of course. But it's an argument that I see being used in new Java projects.
Basically using Lombok and Vavr give you a very nice lightweight functional experience. Bells and whistles tend to be a distraction from making software that works and scales. I’d way rather focus on the hard problems in computing than how to most idiomatically or elegantly express things in a language.
[0] https://blogs.oracle.com/javamagazine/java-flight-recorder-a...
https://devblogs.microsoft.com/dotnet/introducing-dotnet-mon...
https://devblogs.microsoft.com/dotnet/whats-new-in-dotnet-mo...
My experience overall with the C# ecosystem has been fantastic. We started out on .NET Framework 4.x back in 2014-2015, and today are now talking about moving to .NET 6 and rewriting our native apps to use MAUI as November grows closer. A lot of the same code we had back in 2015 is still running today with zero modifications.
You surely can’t be talking about its type system?
All the customers I have worked with since I became a consultant 4 years ago (and a couple of the best companies I worked with in the decade before) used Java or .Net. More specifically one of them was dotnet core and the rest are JVM.
And I'm happy with that.
Java and Maven means I can focus on the problem and not on all the crazy things people who think they are too clever to use Java do.
I mean I have seen a couple of Abstract Factories in Java, but I haven't seen anyone avoiding putting passwords in the source code by writing things like this:
rot13(<rot13password>)
That is stuff I have found in Python code by the one consultant who was to smart to use Java or .Net.Not sure if I should be mad or just ignore you.
In my personal opinion, C# is a superior language these days now that most of .NET has been ported into dotnet core. However, that's only been the case for a few years and before that, Java was in a league of its own with its excellent library ecosystem and omnipresent JVMs.
Java is a very boring language. Features get added slowly compared to other languages, improvements are generally quite gradual and new concepts only rarely ever get added to the language. This will drive away many hot startups and fresh CS graduates because they want to use the latest technologies in brave new ways and explore the ecosystem.
For general businesses, boring is good. Boring is predictable, understandable and maintainable. Java is not the fastest language to write software in, but it's fast enough not to warrant teaching your staff a new language for. It's missing many features that are standard in other languages, but there's third party libraries to make up for that.
In my mind, languages like Rust and Zig are the Teslas of the software world, exciting, new, full of flaws that need to be ironed out but ready to storm the general market one day soon. Go and Kotlin are the shiny new SUVs, impressive and high quality, but packed with weird design choices and opinions on how to use them. Java and C# are the old van the company has owned for 20 years. Nothing impressive, but reliable, comfortable, predictable, and with quirks that everyone has been learning for years now. Most businesses don't need a Cybertruck, they need to get around, and what businesses already have is good enough for the next while.
It's fast: C fast in most cases. It can use a lot of cores and memory with minimal effort from developers. It has great tuning parameters for things like GC. We hit a cliff at about 150GB heap, tuned it, and now we're at 300GB. It's "good-enough" type safe. Java grows with you.
https://projects.apache.org/projects.html?language#Java
There's a LOT of real interest in Java in many many industries. Saying this like "Most devs I know" just really is more of an observation of your local circle.
Big data, Finance, Government
Just to name three really big industries that are dominated by Java.
meanwhile, stuff i did 2003 still runs on windows out of the box, even in wine/osx. so again: why use java for anything? https://twitter.com/abductee_org/status/711966430133026816 (try it https://www.pouet.net/prod.php?which=11247 )
oh and there is this: https://people.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf so, double-again: why use java for anything?