8 ms·
Kotlin Features I miss most in Java
- arikrak 9y agoIt seems like Kotlin is similar to Dart in many ways. New languages like them are able to be more concise while Java is stuck staying compatible with its past.
- thenewwazoo 9y agoThis article wonderfully communicates the same feelings I have after writing Rust, and having to write C/C++ for my day job. I regularly encounter situations where I could do it clearer, safer, or clearer and safer in Rust, but instead I just return a null pointer.
- Asooka 9y agoI have taken to, where possible, using something like this: template <class T> struct Optional { T value; bool hasValue; Optional():empty(false) {} Optional(const T& value):value(value), empty(true) {} // Also T move ctor, copy ctor, etc. }; And I try to make sure that all my structs' "empty" state can be set by memset(0). Pointers are a bit tricky, especially strings, but for them I kind of like this: class MyString { MyBuffer *buf; // Pointer to refcounted shared storage, i.e. CoW string public: const char *cStr() { if (!buf || !buf->chars) { return ""; } else { return buf->chars; } } }; In short, 1. Try to design structs such that they can be memset(0). 2. Try not to return NULL pointers, if possible. Maybe keep static empty instances to return instead of NULL pointers. Of course, the overhead of that approach is not always acceptable, but when it is, I find that it significantly simplifies the code that uses such classes.
- sbmassey 9y agoYou could use boost::optional. Or std::optional, come c++17.
- thenewwazoo 9y agoUnfortunately for me, all of my C/C++ is in the embedded space (by that I mean, I am thrilled to do embedded programming. but it's C), so a lot of the nicer features of C++ either aren't available to me (unsupported by the compiler), or are too expensive to use (dynamic dispatch, exceptions). Your Option implementation is definitely interesting; I may crib it some day. Half my consternation comes from an itchy feeling of "why am I inventing this, when Rust already does it!" that perpetually nags at me. It's entirely a hangup that I make myself get over, but it's still there.
- lanna 9y agoSomeone should write "Scala features I miss most in Kotlin"
- xaduha 9y agoNo, that's not how it works. No one is forced to write Kotlin I don't think. Plenty of people have to use Java. The fact that Kotlin is a simpler language than Scala is a feature, not a bug.
- wtetzner 9y agoIs Kotlin actually simpler than Scala? It seems to be less powerful in a lot of ways, but on the other hand, it looks like a lot of Kotlin's features are special-case things, whereas Scala's features are largely orthogonal and composable. I haven't really used Kotlin much, so I may be wrong, but that's the impression I get.
- emodendroket 9y agoIt's simpler for a compiler at least
- andybak 9y ago> Is Kotlin actually simpler than Scala? Unless Scala has radically been pared back in the couple of years since I read about it or I was seriously misinformed at the time, then I'd say it probably a yes. Scala has the reputation for being at the complex end of the language spectrum. Is that undeserved?
- necrobrit 9y agoIt's both... simultaneously. The "feature set" really is smaller than Java or Kotlin (especially if you count in terms of "keywords as features" and ignore things included for Java compatibility). The flexibility though can be genuinely overwhelming (the name, scala[bility], refers to scalability of the syntax, not performance). Both in terms of the syntax flexibility , e.g. where curly braces are optional vs not or different ways of writing lambdas, and powerful/flexible features like implicit. However there are very rarely special cases to these, it's all very consistent once you know the rules. Even the infamous underscore is "logically" consistent in it's use, it's just a placeholder in both contexts you typically see it used (context of variables and context of types). Even my go-to example of a special case in scala, vararg unpacking: `foo(myArgs: _*)` Despite being a special case actually reads kind of consistently.. it reads like a type ascription[1] asking for myArgs to be treated as a varargs of whattever type. So consistent with other "type context" usages of underscore despite otherwise being weird. [1] https://docs.scala-lang.org/style/types.html#ascription https://docs.scala-lang.org/style/types.html#ascription
- myroon5 9y agoI wish comparisons to Java would use Lombok. I'm never going to actually write something that verbose in Java.
- mahmoudimus 9y agoPitfalls of Lombok aside, I agree that it is indispensable to reduce the amount of boilerplate in Java. That being said, it is largely a hack but most of the IDEs have Lombok support so it's not _as_ bad.
- al452 9y agoAutoValue beats Lombok hands down. Not least because it is pure Java so no extra IDE support is needed.
- minus7 9y agoLombok addresses one pain point of Java (constructors/getters/setters), Kotlin addresses more. Lombok also does a bit of magic, so should be used with care. E.g. @AllArgsConstructor is a dangerous one if you change the order of fields, especially if you don't have IDE support for Lombok.
- depsypher 9y agoNot sure where the danger is with @AllArgsConstructor. If you change the order of fields, it's the same as if you changed the order of the constructor parameters. You'll get compile errors and be forced to fix them (IDE or not). Seems as safe as anything to me.
- joneholland 9y agoThat assumes your constructor parameters are all unique types.
- mseebach 9y agoI would consider that a bit of a design smell, but yes. I think the danger point has more to do with training new converts: it is very unusual in any language that changing the order of class internals can change its external behavior.
- minus7 9y agoOh Vert.x, I despise it. We jumped on that in 2014 (Vert.x 2) and it's still haunting us. Half-broken build with an outdated Gradle plugin (should've stuck with the official build system), doesn't integrate with the major part of the Java ecosystem due to being callback-based (exceptions can hardly be used, nor can anything that blocks on IO) and tooling for managing tasks doesn't exist either (e.g. running a couple of IO-tasks in order requires you to nest the callbacks if you don't write your own tooling). I assume Vert.x 3 is doing a better job there, but the fundamental problem remains: callbacks don't work well with exceptions. Maybe Kotlin helps wrapping this nicer, I don't know. All I know is that I'll be more careful when selecting a framework for productive use now. And maybe that I won't be using Vert.x again.
- zbentley 9y agoAs a Node.js dev: don't hold your breath waiting for better exception support in callback-based asynchronous code. Node didn't really have a procedural legacy; it was always async (sorta), and exceptions are awful to try to reason about with callbacks. Then we got Promises, which made it worse. Now we have async/await, which helps restore typical exception behavior a bit . . . until you have to integrate with any code written in a callback- or promise-oriented style (anything older than 15 months, almost all of the standard library). In general, I think without language-level support for continuations (callbacks, coroutines, promises, async-"colored" functions, whatever), projects like Vert.x are going to cause a lot of friction with regards to exceptions. Having a large synchronous legacy to contend with doesn't help either.
- thenewwazoo 9y agoI don't mean this to be flippant: there's a Javascript standard library?
- vbezhenar 9y agoHonestly I don't understand point of data classes. Why was it introduced into language? The only time when I'm implementing equals/hashCode is when I'm going to use this class as a hash key and it's quite a rare situation. Why introduce a keyword just for that? Am I missing something? I'm using Kotlin and I'm just using classes without data modifier, they work just fine.
- on_and_off 9y ago1- automatic equals & hashcode is awesome. With data classes, you no longer need AutoValue. 2- it also adds toString and destructuring functions. for the data layer, 'data' classes are awesome IMO.
- tomjen3 9y agoIt may just be me, but I find that sooner or later I need to debug or print something to the console and having a reasonably to string method already implemented means I don't have to be annoyed I forgot it and go back.
- cle 9y agoIt's really easy to throw a data obj into a map/set and forget to check that it correctly implements equals+hashCode. Or add a field to a data class and forget to update equals/hashCode. Or log a list/set/map without realizing that the toString is just going to print some useless hash code. Data classes handle all of that for you, automatically.
- wtetzner 9y agoOne important reason for equals/hashCode on data objects is that you can put them into sets, and have them uniqued correctly. Also, do you really never need to call .equals on data objects?
- krzyk 9y agoequals/hashCode is useful in tests also when you want the check that the returned object is what you expect, instead of doing field by field comparison: Foo foo = bar.foo(); Assertions.assertThat(foo).equalTo(new Foo(1, 2, 3)); instead of: Foo foo = bar.foo(); Assertions.assertThat(foo.getFirst()).equalTo(1); Assertions.assertThat(foo.getSecond()).equalTo(2); Assertions.assertThat(foo.getThird()).equalTo(3);
- Aardwolf 9y agoC++ features I miss in Java: * unsigned ints. Even Java's 8 bit byte is signed??! * operator overloading, mostly for math like matrices, vectors, complex numbers * containers of primitives * ability to write a "swap" function (pass by reference) * compiler that turns readable code into good assembly (no verbose code needed due to simpler code introducing stray stringstream objects and things like that, ...) * destructors that reliably do something at end of scope * no need to write typename twice to create an object (altho C++ std lib on the other hand requires writing container name twice for begin and end for sort and other algos and has horrificly inconvenient number to string conversions so that part I don't miss) * ...
- minus7 9y ago> * operator overloading, mostly for math like matrices, vectors, complex numbers Kotlin has infix functions which help there without treading the dangerous territory of ambiguousness
- peoplewindow 9y agoKotlin also has operator overloading.
- tomjen3 9y agoI would agree with plenty of those, but they are mostly not needed for the work Kotlin targets (ie Android and Web dev).
- peoplewindow 9y agoSigned-only ints is really painful when working with binary formats, for sure. But the rules around unsigned vs signed ints in C/C++ are pretty painful. -fwrapv shouldn't exist. There is a library (GNU Trove) that provides specialised containers for primitives. It'd be nice if the language had them built in, but they're only quite rarely needed it seems. Normally there's some wrapper type involved. Java has scoped 'destruction' (called try-with-resources). An equivalent for 'auto' is coming to Java, though Kotlin has it already.
- kaputsmack 9y agoKotlin is an irrelevant language with no future.
- morecoffee 9y agoJava is commonly criticized for being too verbose, but I never understood why. I agree it is verbose, but verbosity isn't really that bad of a problem. Programs aren't harder to write, they aren't more bug prone, and they aren't harder to understand. On the contrary, high verbosity means less information per token, making it easier in my opinion to read. A good example is the anonymous class. They are portly and wasteful of vertical space, often costing 5 lines when one would due. But, the amount thinking per line is greatly reduced. In order to visually afford anonymous classes, logic must be simplified or structured, commonly by splitting up a larger function in order to fit it on a single screen. An arguable misfeature of the language has a convenient side effect of forcing best practices.
- yters 9y agoI agree. Java is one of my favorite languages partly due to its verbosity. It's easy to figure out what I was doing when looking at my code later on. It's also easier to repurpose old code.
- barrkel 9y agoErgonomics changes your design decisions. When some idiom of code or data or both is awkward to express, you'll lean naturally to something else, which may have drawbacks not related to ergonomics, like coupling or over-abstraction or duplication, etc. Ergonomics goes into the mix of tradeoffs for design. Something like LINQ's expression trees, for example; not easy to do in Java not because ASTs can't be constructed, but because their construction isn't as ergonomic.
- sanderjd 9y agoI think in Java this is most notable in the boilerplate it takes to create value types (and related: typedefs). This often leaks from the realm of bad ergonomics into the realm of bad design; primitive proliferation is a common problem that I believe would be less pronounced were there a more convenient way to define a value type.
- haggy 9y ago> On the contrary, high verbosity means less information per token, making it easier in my opinion to read See I think this is actually backwards thinking. The more boilerplate you have to sift through, the more you're trying to retain before getting to what the program is actually doing. > A good example is the anonymous class. They are portly and wasteful of vertical space, often costing 5 lines when one would due. But, the amount thinking per line is greatly reduced Same applies for this statement. You're trying to absorb so much more code before knowing what is actually happening.
- lokedhs 9y agoI've been writing quite a bit of Kotlin, lack of semicolon isn't a great idea in my opinion. The problem is that while Java is newline-agnostic (newline is just another whitespace), Kotlin has some rather difficult rules to determine the what a newline does. Granted, the rules are not quite as insane as those of Javascript, but I find it very unsatisfying newlines are sometimes significant, and sometimes not. And just to make it clear where I'm coming from on this topic, my preferred language is Common Lisp, so I really prefer consistent syntax.
- andybak 9y agoYep. Consistency beats "syntactic noise". In fact lack of consistency is noise. If semicolons are always present, then my brain filters them out. If they are never present then great (yay! Python) However - occasionally present or absent semicolons means I have to think about each situation - at least to a small degree.
- morj239 9y agoThe only place when you really need semicolons Kotlin are the enum constants, everything else can be considered "strictly no semicolons", so pretty consistent.
- edem 9y agoI write Kotlin for more than a year and semicolon usage is pretty consistent: no semicolons. The only place I remember using them is in enum constants. I also came from the lisp world (CL -> Clojure).
- agumonkey 9y agoI tried kotlin again but personally (I would surely take it any day at work) it's in a bad spot. It makes me miss python cuteness (zero start time and dynamicity) too much, so if I need functional on JVM thing, I'll end up writing some clojure.
- lazaroclapp 9y agoNote that at least one of the features described, (static) Null-Safety, can be achieved in Java via annotation processors. For example, by a tool such as NullAway (https://github.com/uber/NullAway https://github.com/uber/NullAway). Yes, adding '@Nullable' is more verbose than '?' (thus is the Java way :) ). But there are plenty of projects were it still makes sense to use Java over Kotlin. This is particularly true for existing codebases, where adding annotations is certainly easier than rewriting everything in a different language. Disclaimer: I am a contributor to NullAway. There are plenty of other nullability static analysis tools for Java worth considering (Checker Framework, Eradicate, etc). NullAway's main strengths are a focus on performance and the idea that you should be able to use it as part of every (local) build of your code.
- cle 9y agoThere's a huge advantage to having language level support for null safety. Annotations are just metadata, and managing semantics of that metadata is pushed to the programmer: to configure a static analyzer to act on them, to figure out the differences between different backends, make sure it's enabled in the build, write/install IDE plugins, etc. Language level support for this stuff means programmers don't have to worry about any of that. Pragmatics of course necessitate that we sometimes need to tack these static analyzers on after the fact. But over time, the amount of tack ons and bandaids and unnecessary backwards-compatibility-necessitated complexity becomes so high that you're better off using a better language, if you and your team can handle it. For many people, this has already happened with Java. I kind of wish Java the language would just die already and stop adding just enough features to keep people from moving on to the next generation of languages. Science progresses one death at a time.
- lazaroclapp 9y ago> Annotations are just metadata, and managing semantics of that metadata is pushed to the programmer: to configure a static analyzer to act on them, to figure out the differences between different backends, make sure it's enabled in the build, write/install IDE plugins, etc. Sure, but keep in mind switching to a new language also entails a huge investment in tooling. There is definitely a higher cost in switching your build infrastructure, tools and IDE plugins to support a new language (nevermind porting an existing codebase) than the few configuration lines it takes to add an annotation processor. At a certain scale, the tools you need might not have even been built for Kotlin, while they have existed for years for Java. > For many people, this has already happened with Java. Yes. And, for many others, it hasn't. Look, I am not arguing that we should all be writing Java until the heat-death of the universe. Whether it is Kotlin or not, I am sure better ways to do things will pop up. But there are good reasons to start even many kinds of new projects in Java today: maturity/efficiency of the compiler, tooling support (including static analyzers, since nullability type systems are not the only feature you'd ever want to bolt onto a language), etc. Over time those reasons will be less, but even then, existing Java codebases will remain, and it will still be worth making them safer. Is not like C, C++ or even Fortran codebases are not around anymore.
- guelo 9y agoIn the opposite direction, the main thing I miss in kotlin is Java's package-private. Kotlin's internal is not really a replacement since it operates at the module level which makes it more useful for library developers. Kotlin's strict rules about not allowing tighter scope in the constructor of a looser scoped class breaks my Dependency Injection pattern of injecting package-private classes into the one public class in a package. Basically everything in kotlin ends up being public.
- bunderbunder 9y agoIt's funny, this article starts with my least favorite features of Kotlin - ones that save a few keystrokes, but at the expense of readability. And then it fails to mention so much of the very best stuff in Kotlin: Extension methods (No, Java 8's default methods are not a substitute.), unifying the type system, covariance and contravariance instead of those wacky wildcards, kicking checked exceptions to the curb. . .
- wisecoder 9y agoMostly looks like C# syntax to me
- donatj 9y agoI haven’t written more than a couple lines of Java in probably the last ten years(I did maintain a small Swing app for a while before that) but to me a lot of what he calls syntactic noise I call clarity. I would call having and using multiple syntax’s to do the same thing is nothing more than noise that hampers long term maintainability. Many, maybe most large teams maintaining large code bases end up telling their static analyzers to disallow this sort of thing for this exact reason. Many ways to do something is easier to write, a single way to do something is easier for future you to read.
- esfandia 9y agoRegarding the conciseness of lambdas and collections, why doesn't Java offer methods such as map(), filter() etc directly in AbstractCollection? The implementations would then delegate to stream internally, so no need for the programmer to invoke stream(). I realize there are different kinds of streams, but having a sequential stream as a default would be useful. That would be one step closer to the Kotlin example.
- krzyk 9y agoWhy in AbstractCollection and not in Collection interface as a default method? Apart from that I still miss the fact that Iterable doesn't have a `stream()` method, it would make it much more useful in post-java8 world.
- dajohnson89 9y agomaybe backwards compatibility?
- riku_iki 9y agoHow exactly adding map() is not backwards compatible?..
- xendo 9y agoAdding methods to non-final classes is always incompatoble. If you had your own implementation of list and its interface suddenly requires new method, how should it behave?
- riku_iki 9y agomap() in AbstractCollection will obviously have some default implementation, which will be used by all child classes.
- fnl 9y agoNice write-up of the pain points in Java. Though, aggregating and mapping over (in Java or not) (possibly infinite) streams is not really the same as doing that over containers. In other words, Java8 doesn’t have the ability to flatMap et al. an arbitrary container, unlike Kotlin or Scala.
- TeMPOraL 9y agoRe "data classes" example in this article - Common Lisp features I miss most in Java (besides macros): - multiple return values - basically doing what the author is doing here, without introducing a new class for it, - destructuring - assigning pieces of composite object into several variables (see e.g. destructuring-bind, or what pattern matching does in many languages). Basically, I often find myself wanting to write this: return {listWithResults, someBooleanFlag}; or return {listWithOneGroup, listWithOtherGroup}; and then use it like this: List<Foo> matchingResults; List<Foo> nonMatchingResults; boolean flag; {matchingResults, nonMatchingResults, flag} = functionReturningMultipleValues(); or: Foo first; Foo third; List<Foo> rest; {first, _, third | rest} = someList; etc.
- andrioni 9y agoIf you want to stay in the JVM, Scala supports some pattern matching, including your examples: def functionReturningMultipleValues() = (List(1), List(2), false) val (l1, l2, b) = functionReturningMultipleValues evaluates to: l1: List[Int] = List(1) l2: List[Int] = List(2) b: Boolean = false and val first :: _ :: third :: rest = List(1, 2, 3, 4, 5, 6) to first: Int = 1 third: Int = 3 rest: List[Int] = List(4, 5, 6) Of course, if you already are familiar with Common Lisp, Clojure might be a better choice.
- foxyv 9y agoMy favorite is the null checking. The '?.' operator is GOLD.
- nikolay 9y agoIs there any benefit of Kotlin vs C# except JVM support? Why not work on a great C# to JVM compiler instead of inventing a brand new language?