6 ms·
The post hits a lot of real pain point when working with Kotlin. In particular, nullable types seem like a good idea (I thought so as well) but in practice are
by norswap 10y ago
The post hits a lot of real pain point when working with Kotlin.
In particular, nullable types seem like a good idea (I thought so as well) but in practice are a real PITA. That's most acute when defining fields, where non-nullable fields have to be initialized directly.
JetBrains themselves realized it was painful, and so we have `lateinit` to define mutable non-nullable fields that can be initialized later. But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here.
I must now have spent an order of magnitude time dealing with the initialization of non-nullable fields than I ever did tracking null pointer exceptions.
That being said, Kotlin is still a net improvement over coding in Java. In fact, if you're going for a large codebases, I think it's one of the most tractable languages out there (C# is a strong contender as well).
- snuxoll 10y ago> In particular, nullable types seem like a good idea (I thought so as well) but in practice are a real PITA. That's most acute when defining fields, where non-nullable fields have to be initialized directly. > JetBrains themselves realized it was painful, and so we have `lateinit` to define mutable non-nullable fields that can be initialized later. But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here. I'm curious, where have you found the need for lateinit? Forcing initialization of non-nullable types has always seemed like a great idea to me, and learning to deal with it in Kotlin has made picking up Rust a lot easier.
- dimillian 10y agoJSON and reflection, you know you gonna get a value once you'll have instantiated your object from a JSON description. So my var are lateninit because I don't need a ? or !!
- snuxoll 10y agoInitialize them with default values, then? Or if you're using Jackson there is a module from FasterXML that provides support for Kotlin data classes. lateinit is a crutch to get things working, not something you want to use long-term.
- dimillian 10y agoNever really thought about it, didn't even realise it was tied to Jetbrain and not build into Kotlin itself. I feel initialise a default value counter intuitive, what the default of a string , "", "default", "test". I know it works because it'll be reassigned with the JSON value later, but still feel dirty. I prefer the Swift way let value: String!
- nostrademons 10y agoWhy not use Jackson? I define the bulk of my JSON datatypes as: data class Foo( val bar: String, val baz: String, val quux: Int ) No nullables needed, unless the actual JSON spec calls for a nullable field, in which case I want the compiler to remind me to handle the null case. Jackson handles everything out-of-the-box; it has no problem with constructor injection, and requires no annotations for the default case. For circular structures & injected values, I make them 'var' and inject after construction. This is a bit icky but generally manageable; if I get too uncomfortable with it, I can move the property declaration to the body, make it private set, and provide an accessor to handle all the injection at once.
- afastow 10y agoLateinit is good for what it is designed for which is allowing for fields that are always going to be initialized before use but after construction, normally by a library. I wouldn't describe it as a crutch in that case. It'd be ideal if libraries were better about using constructor injection when possible, but I think there are some valid scenarios where circular dependencies make it impossible to use only constructor injection.
- snuxoll 10y ago> It'd be ideal if libraries were better about using constructor injection when possible, but I think there are some valid scenarios where circular dependencies make it impossible to use only constructor injection. Thankfully it is rare to require circular dependencies, but this is probably the one use case where lateinit is certainly warranted.
- deleted 10y ago[deleted]
- lenkite 10y agoYou can use constructors for JSON now. See https://github.com/FasterXML/jackson-module-kotlin https://github.com/FasterXML/jackson-module-kotlin
- relics443 10y agoI use it a lot in Android, where certain objects are created by the framework, and I don't want a nullable reference for one of its properties.
- hota_mazi 10y ago> I'm curious, where have you found the need for lateinit? It's useful as soon as a field is managed by a container, which means it will most likely not be initialized at construction time of the object. Examples of containers: Android, JavaEE, web servlet containers, DI frameworks, etc... So basically, they are pretty much ubiquitous.
- snuxoll 10y ago> Android, JavaEE, web servlet containers, DI frameworks, etc... Most of which support constructor injection.
- hota_mazi 10y agoEr... none of them except maybe Guice? They all require a parameterless constructor so they can instantiate your objects before reflecting them.
- mejari 10y agoExactly. I spent some time testing out converting a small part of our large codebase that uses Spring to Kotlin and was able to easily switch over to constructor injection. I think it actually made the injection cleaner. It was nicer for my tests too, instead of @Mock/@InjectMocks magic.
- weitzj 10y agoIf you use Android along with dependency injection (Dagger2). This was at least necessary when Kotlin was close to 1.0. This was the only part where I ever used lateinit.
- snuxoll 10y agoDagger supports constructor injection, why would lateinit be necessary?
- dpkirchner 10y agoMany of the classes you'd write as an Android developer are instantiated by the system somewhere using only those constructors that the system expects to exist. Views, for example, are typically instantiated based on XML layout files that simply reference views by name, without a way to specify a specific constructor to use.
- snuxoll 10y agoAh, yes - layout inflation is such fun. Still, there are libraries you can use that don't require usage of lateinit. Kotterknife [1] is an option, the Kotlin android extensions [2] is another option though relying on code generation it may not be the first choice of people. Delegates in Kotlin are a powerful tool, and a great way to handle cases where you think lateinit is necessary but really isn't. Otherwise, if you're going with Kotlin anyway you can just get rid of layout XML altogether with anko [3] or even give MVVM a shot with KBinding [4]. [1]: https://github.com/JakeWharton/kotterknife https://github.com/JakeWharton/kotterknife [2]: https://kotlinlang.org/docs/tutorials/android-plugin.html https://kotlinlang.org/docs/tutorials/android-plugin.html [3]: https://github.com/Kotlin/anko https://github.com/Kotlin/anko [4]: https://github.com/BennyWang/KBinding https://github.com/BennyWang/KBinding
- dpkirchner 10y agoI haven't seriously tried Kotterknife, anko (been meaning to!), or KBinding (hadn't heard of it before, will check it out) -- I'm currently enjoying the Kotlin android extensions for referencing views inside custom viewgroups. I use field injection to pull in instantiated Picasso/Glide image libraries and to create/bind to persistent view presenters (it's a Mortar/Flow based app currently). I'm not sure if this is best practice or what but I feel reasonably comfortable having image loaders in the view (when those image loaders do their real work off the UI thread of course). Speaking of Kotterknife, I'm actually in the process of migrating away from Butterknife as I slowly port the app over to Kotlin. Butterknife is immensely useful for Java-based apps, IMO, but the Kotlin android extensions provide most of what I need and Kotlin's syntax largely takes care of the boilerplate I was avoiding by going with BK in the first place. I do miss string/integer/other binding but it's not so bad and, IIRC, kotterknife doesn't provide that anyway -- I explored adding it [1] but I didn't enjoy writing all that code and ended up giving up. I forgot to mention another use-case for lateinit: junit tests. I use lateinit in the test class when testing code that uses constructor injection. This is probably less controversial because it is just tests, though. [1] https://github.com/dpkirchner/kotterknife/commit/39dda1965ee29740cab2e95c3930d968855f1f8b https://github.com/dpkirchner/kotterknife/commit/39dda1965ee...
- realharo 10y ago>But sometimes it really is nice to be able to check if a field has already been initialized, which you can't do here. If you need a run-time check to figure out whether a supposedly non-nullable field has been initialized, that sounds like a code smell to me. I see the fact that something can only be initialized late as an internal matter of a library/module/part of project/etc. that should never leak outside of it (i.e. the outside world should never have to deal with potentially uninitialized objects - if it has to, then the field is not really non-nullable).
- afastow 10y agoAgreed. If you need to find out if a lateinit field has actually been initialized, and you aren't writing a library that is responsible for initializing it, that means your field is really nullable so just mark it as such. If you are writing a library I think you should be able to use reflection to figure out if it has actually been initialized, or worst case scenario just catch the exception that is thrown if it isn't initialized.
- norswap 10y agoBasically, complex initialization logic, when a field can be initialized in multiple different ways. Once the initialization step is complete, the field will never be null and no checking will be required ever again. But in the current state of affairs, you just have to make it nullable and sprinkle the `!!` operator all over your code.
- vbezhenar 10y agoUse two properties. First is nullable and holds real value, so you can check it. Second is non-nullable and just a getter for first.
- amorphid 10y agoI haven't used Kotlin, so... Why are nullable types a pain in the ass? If I recall correctly, isn't a nullable something you have to opt into? Basically you can't get null on accident, but you can opt into it when null makes sense?
- vbezhenar 10y agoI don't agree that it's a pain, it's a relief, actually. Though Kotlin's solution is half-baked. Java library is full of Type!, so Kotlin doesn't really help there, even when there are clear contracts which types are nullable or not. They tried to implement external annotations, but for some reason ditched that idea, and I think, that it should be done. Of course there will be places with Type!, but for majority of cases, even static bytecode analysis should be able to annotate majority of types as either nullable or not. So even if Kotlin is better than plain Java in that regard (it checks for not-null at Java-Kotlin boundaries and detects NPE earlier), it still can be improved.
- rbygrave 10y ago> Java-Kotlin boundaries ... For me so far this boils down to annotating Java API methods that return null with @Nullable. If Java API never returns null collections then this is not too bad in practice.
- deleted 10y ago[deleted]