The body of the

There is no doubt that Kotlin is now so popular that some in the industry even think it will displace Java. It provides Null security, which is really better than Java. Does that mean developers should embrace Kotlin without hesitation or be left behind?

Wait, maybe that’s not the case.

Before you start programming with Kotlin, this article wants to share a story with you. In this story, the authors started out using Kotlin to write a project, then Kotlin’s weird patterns and other obstacles became so annoying that they decided to rewrite the project.

The following is the translation:

I’ve always been a fan of JVM-based languages. I usually write my main program in Java and write my test code in Groovy, and the two work well together.

In the summer of 2017, the team launched a new microservices project, and as usual, we needed to select the programming language and technology. Some of the team members were Kotlin supporters, and we all wanted to try something new, so we decided to use Kotlin to develop the project. Since the Spock testing framework does not support Kotlin, we decided to stick with Groovy for testing.

In the spring of 2018, after several months of development with Kotlin, we summarized the pros and cons of Kotlin and concluded that Kotlin was reducing our productivity.

So we used Java to rewrite the microservice project.

So what are Kotlin’s main drawbacks? Let’s explain them one by one.

The name of the cover

That’s what shocked me most about Kotlin. Consider the following method:

fun inc(num : Int) {
    val num = 2
    if (num > 0) {
        val num = 3
    }
    println ("num: " + num)
}
Copy the code

What does it print when you call inc(1)? In Kotlin, method parameters cannot be modified, so you cannot change num in this case. This design is good because you should not change the method’s input parameters. But you can define another variable with the same name and initialize it.

This gives the method scope two variables named num. Of course, you can only access one of the num at a time, but the num value will be changed.

Add another num to the if statement. Num is not modified for scoping reasons.

Thus, in Kotlin, inc(1) outputs 2. The Java code for the same effect is shown below, though it cannot be compiled:

void inc(int num) {
    int num = 2; //error: variable 'num' is already defined in the scope
    if (num > 0) {
        int num = 3; //error: variable 'num' is already defined in the scope
    }
    System.out.println ("num: " + num);
}
Copy the code

Kotlin did not invent name masking, which is common in programming languages. In Java we use method parameters to map class fields:

public class Shadow { int val; public Shadow(int val) { this.val = val; }}Copy the code

The name shadowing is a bit severe in Kotlin, which is a design flaw of Kotlin’s team.

The IDEA team tried to solve this problem by displaying a warning message to each shadowing variable. The two teams work in the same company, so perhaps they can talk to each other and reach a consensus on shadowing. I personally favor IDEA because I can’t think of any application scenarios where method parameters need to be obscured.

Type inference

In Kotlin, when you declare a var or val, you usually ask the compiler to guess the type of the variable from the expression type on the right. We call this local variable type inference, and it’s a big improvement for programmers. It allows us to simplify code without affecting static type checking.

For example, this Kotlin code:

var a = "10"
Copy the code

The Kotlin compiler translates this to:

var a : String = "10"
Copy the code

Java also has this feature. An example of type inference in Java 10 is as follows:

var a = "10";
Copy the code

To be honest, Kotlin has the upper hand on this one. Of course, type inference can also be applied to multiple scenarios. To learn more about local variable type inference in Java 10, click the link below:

  • medium.com/@afinlay/ja…

Null security type

Null security types are Kotlin’s killer feature.

This is a good idea. In Kotlin, types cannot be null by default. If you need to add a nullable type, you can do this:

val a: String? = null      // ok
val b: String = null       // compilation error
Copy the code

If you use nullable variables but do not check for null values, this will not compile in Kotlin, for example:

println (a.length) // compilation error println (a? .length) // fine, prints null println (a? .length ? : 0) // fine, prints 0Copy the code

Can you avoid NullPointerException, the most common NullPointerException in Java, if you have both non-nullable and nullable variables? The truth is not as simple as it seems.

Things get ugly when Kotlin code has to call Java code, such as libraries written in Java, which I’m sure is quite common. This led to the emergence of a third type, known as platform types. Kotlin cannot represent this strange type, which can only be inferred from Java types. It can be misleading because it is very lenient on NULL values and disables Kotlin’s NULL security mechanism.

Take a look at the following Java method:

public class Utils { static String format(String text) { return text.isEmpty() ? null : text; }}Copy the code

Suppose you want to call format(String). What type should be used to get the result of this Java method? You have three options.

The first way: You can use strings. The code looks safe, but NullPointerException is thrown.

fun doSth(text: String) {
    val f: String = Utils.format(text)       // compiles but assignment can throw NPE at runtime
    println ("f.len : " + f.length)
}
Copy the code

Then you need Elvis to solve the problem:

fun doSth(text: String) { val f: String = Utils.format(text) ? : "" // safe with Elvis println ("f.len : " + f.length) }Copy the code

Second method: You can use String, which is null-safe.

fun doSth(text: String) { val f: String? = Utils.format(text) // safe println ("f.len : " + f.length) // compilation error, fine println ("f.len : " + f? .length) // null-safe with ? operator }Copy the code

Third way: How about having Kotlin do local variable type inference?

fun doSth(text: String) {
    val f = Utils.format(text)            // f type inferred as String!
    println ("f.len : " + f.length)       // compiles but can throw NPE at runtime
}
Copy the code

Bad idea! The Kotlin code looks safe and compilable, but it tolerates null values, just as it does in Java.

There is another way to force f to be inferred to String:

fun doSth(text: String) {
    val f = Utils.format(text)!!          // throws NPE when format() returns null
    println ("f.len : " + f.length)
}
Copy the code

All of Kotlin’s Scala-like type systems are, in my opinion, too complex. Java interoperability seems to compromise the heavyweight function of Kotlin type inference.

Class name literal constants

Literal-like characters are common when using Java libraries like Log4j or Gson.

Java uses the.class suffix to write class names:

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateAdapter()).create();
Copy the code

Groovy takes classes a step further. You can ignore.class; it doesn’t matter if it’s Groovy or a Java class.

def gson = new GsonBuilder().registerTypeAdapter(LocalDate, new LocalDateAdapter()).create()
Copy the code

Kotlin distinguishes the Kotlin class from the Java class and provides a syntax specification for it:

val kotlinClass : KClass<LocalDate> = LocalDate::class
val javaClass : Class<LocalDate> = LocalDate::class.java
Copy the code

So in Kotlin, you have to write it like this:

val gson = GsonBuilder().registerTypeAdapter(LocalDate::class.java, LocalDateAdapter()).create()
Copy the code

It looks very ugly.

Reverse type declaration

The C series programming languages have a standard way of declaring types. In short, you specify a type first, and then anything that conforms to the type, such as variables, fields, methods, and so on.

The representation in Java is:

int inc(int i) {
    return i + 1;
}
Copy the code

Kotlin says:

fun inc(i: Int): Int {
    return i + 1
}
Copy the code

This approach is annoying for several reasons.

First, you need to put this extra colon between the name and type. What is the purpose of this extra role? Why is a name separated from its type? I don’t know. Sadly, this makes your job at Kotlin that much more difficult.

Second, when you read a method declaration, you see the name and return type first, followed by the parameters.

In Kotlin, the return type of a method may be far away at the end of the line, so it takes a lot of code browsing to see:

private fun getMetricValue(kafkaTemplate : KafkaTemplate<String, ByteArray>, metricName : String) : Double {
    ...
}
Copy the code

Or, if the parameters are in line-by-line format, you need to search. So how much time do we need to find the return type of this method?

@Bean
fun kafkaTemplate(
        @Value("\${interactions.kafka.bootstrap-servers-dc1}") bootstrapServersDc1: String,
        @Value("\${interactions.kafka.bootstrap-servers-dc2}") bootstrapServersDc2: String,
        cloudMetadata: CloudMetadata,
        @Value("\${interactions.kafka.batch-size}") batchSize: Int,
        @Value("\${interactions.kafka.linger-ms}") lingerMs: Int,
        metricRegistry : MetricRegistry
): KafkaTemplate<String, ByteArray> {
    val bootstrapServer = if (cloudMetadata.datacenter == "dc1") {
        bootstrapServersDc1
    }
    ...
}
Copy the code

The third problem is that automation support in the IDE is not good enough. Standard practice starts with the type name, and it’s easy to find the type. Once a type is selected, the IDE provides some suggestions for variable names derived from the selected type, so you can quickly enter variables like this:

MongoExperimentsRepository repository
Copy the code

Kotlin despite powerful ides like IntelliJ, input variables are still difficult. If you have multiple repositories, it is difficult to implement proper auto-complete in the list, which means you have to manually enter the full variable names.

repository : MongoExperimentsRepository
Copy the code

Associated object

A Java programmer came up to Kotlin.

“Hi, Kotlin. I’m new, can I use static members?” He asked.

“No. I’m object-oriented, static members are not object-oriented.” Kotlin answer.

“Ok, but I need MyClass logger, what do I do?”

“That’s fine, just use companion objects.”

“What’s that?” “This is limited to individual objects of your class. Place your Logger in a companion object.” Kotlin explains.

“I see. Is that right?”

class MyClass {
    companion object {
        val logger = LoggerFactory.getLogger(MyClass::class.java)
    }
}
Copy the code

“Correct!

“Verbose syntax,” the programmer looked puzzled, “but never mind, now I can call my Logger like myClass.logger, like a static member in Java?”

“HMM… Yes, but it’s not a static member! There are only objects. Think of it as an anonymous inner class that has been instantiated as a singleton. In fact, this class is not anonymous; its name is Companion, but you can omit that name. See? It’s very simple.”

I love the concept of object declarations — singletons are useful. But removing static members from the language is impractical. In Java we use a static Logger classically, it’s just a Logger, so we don’t care about object-oriented purity. It works and never does any harm.

Because sometimes you have to use static. The older version of public static void Main () is still the only way to launch Java applications.

class AppRunner {
    companion object {
        @JvmStatic fun main(args: Array<String>) {
            SpringApplication.run(AppRunner::class.java, *args)
        }
    }
}
Copy the code

Set literal

In Java, initializing lists is tedious:

import java.util.Arrays; . List<String> strings = Arrays.asList("Saab", "Volvo");Copy the code

Initializing maps is tedious, and many people use Guava:

import com.google.common.collect.ImmutableMap; . Map<String, String> string = ImmutableMap.of("firstName", "John", "lastName", "Doe");Copy the code

In Java, we are still waiting for the new syntax to express collections and maps. Syntax is very natural and convenient in many languages.

JavaScript:

const list = ['Saab', 'Volvo']
const map = {'firstName': 'John', 'lastName' : 'Doe'}
Copy the code

Python:

list = ['Saab', 'Volvo']
map = {'firstName': 'John', 'lastName': 'Doe'}
Copy the code

Groovy:

def list = ['Saab', 'Volvo']
def map = ['firstName': 'John', 'lastName': 'Doe']
Copy the code

In short, the neat syntax of collection literals is what you would expect from a modern programming language, especially if it was created from scratch. Kotlin provides a number of built-in functions, such as listOf(), mutableListOf(), mapOf(), hashMapOf(), and more.

Kotlin:

val list = listOf("Saab", "Volvo")
val map = mapOf("firstName" to "John", "lastName" to "Doe")
Copy the code

In the map, keys and values are paired with the TO operator, which is fine. But why hasn’t it been widely used? Disappointing.

Maybe

Functional languages (such as Haskell) do not have null values. Instead, they provide strategized monad (if you are not familiar with monad, read Tomasz Nurkiewicz this article: www.nurkiewicz.com/2016/06/fun…

Maybe was introduced to the JVM world by Scala as an Optiona long time ago, and then adopted as Optional in Java 8. Today, Optional is a very popular way to handle null values in return types at API boundaries.

Kotlin doesn’t have an Optional equivalent, so you should probably use Kotlin’s nullable type. Let’s look into this question.

Usually, when you have an Optional, you want to apply a bunch of invalid conversions.

For example, in Java:

public int parseAndInc(String number) {
    return Optional.ofNullable(number)
                   .map(Integer::parseInt)
                   .map(it -> it + 1)
                   .orElse(0);
}
Copy the code

In Kotlin, you can use the let function for mapping:

fun parseAndInc(number: String?) : Int { return number.let { Integer.parseInt(it) } .let { it -> it + 1 } ? : 0}Copy the code

The above code is wrong. ParseInt () throws an NPE. Map () is executed only if it has a value. Otherwise, Null skips, which is why map() is so convenient. Unfortunately, Kotlin’s let doesn’t work that way. It is called from everything on the left, including null values.

To keep this code Null safe, you must add a let before each code:

fun parseAndInc(number: String?) : Int { return number? .let { Integer.parseInt(it) } ? .let { it -> it + 1 } ? : 0}Copy the code

Now, compare the readability of the Java and Kotlin versions. Which do you prefer?

Data classes

Data classes are the approach Kotlin uses when implementing Value Objects to reduce the boilerplate problems that are inevitable in Java.

For example, in Kotlin, you write only one Value Object:

data class User(val name: String, val age: Int)
Copy the code

Kotlin has good implementations of equals(), hashCode(), toString(), and copy(). It is very useful when implementing simple Dtos. But keep in mind that data classes have serious limitations. You can’t extend or abstract data classes, so you probably won’t use them in the core model.

This limitation is not Kotlin’s fault. There is no way to produce correct value-based data without equals() violating Liskov’s principle.

This is why Kotlin doesn’t allow data class inheritance.

Open classes

The Kotlin class defaults to final. If you want to extend a class, you must add the open modifier.

The inheritance syntax looks like this:

open class Base
class Derived : Base()
Copy the code

Kotlin changes the extends keyword to the: operator, which is used to separate a variable name from its type. So back to C ++ syntax? It’s confusing to me.

The argument here is that classes are final by default. Perhaps Java programmers overuse inheritance and should probably think three times before extending a class. But we live in the framework world, and Spring uses the Cglib, Jassist libraries to generate dynamic proxies for your beans. Hibernate extends your entity to enable lazy loading.

If you use Spring, you have two options. You can add open before all bean classes, or use this compiler plugin:

buildscript {
    dependencies {
        classpath group: 'org.jetbrains.kotlin', name: 'kotlin-allopen', version: "$versions.kotlin"
    }
}
Copy the code

Steep learning curve

If you think you can quickly learn Kotlin because you have A Java background, think again. Kotlin will throw you in the deep end. In fact, Kotlin’s syntax is closer to Scala’s. It’s a gamble that you’ll have to forget about Java and switch to a completely different language.

On the contrary, learning Groovy is an enjoyable process. The Java code is the correct Groovy code, so you can change the file extension from.java to.groovy.

Final thoughts

Learning a new technology is like an investment. We put in the time and the new technology pays off. I’m not saying Kotlin is a bad language, but in our case, the costs far outweigh the benefits.

The above was compiled From Java to Kotlin and Back Again by Kotlin Ketckup.

He is a software engineer with over 15 years of professional experience, focusing on the JVM. At Allegro, he was a development team leader, JaVers Project Leader, and Spock Advocate. In addition, he is the editor-in-chief of Allegro.tech /blog.

Kotlin language advocate Marton Braun strongly disagreed with this article.

Marton Braun is a big fan of Kotlin programming, currently # 3 on StackOverflow’s list of top users for the Kotlin TAB, and is the creator of two open source Kotlin libraries, most notably MaterialDrawerKt. He is also an Android developer at Autosoft and is currently pursuing a Master’s degree in computer engineering at the Budapest University of Technical Economics.

Here’s his rebuttal:

When I first saw this article I wanted to forward it to see what people thought and I was sure it would be a controversial topic. Then I read the article, and sure enough, it proved to be subjective, untrue and even condescending.

Some people have already made reasonable criticisms under the original post, and I would like to express my own views on this.

The name of the cover

The “IDEA team” (or the Kotlin plugin team) and the “Kotlin team” are definitely the same people, and I never think internal conflict is a good thing. The language gives it to you, use it if you need it, and if you hate it, just adjust the check Settings.

Type inference

Kotlin’s type inference is ubiquitous, and the authors can also be joking when they say Java 10.

Kotlin’s approach goes beyond inferring local variable types or function types that return expression bodies. The two examples presented here are for those who have just watched Kotlin’s first introductory talk, not for those who have spent half a year learning the language.

For example, how could you not mention the way Kotlin inferred generic type parameters? This is not a one-off feature of Kotlin; it is deeply integrated into the language.

Null-safe at compile time

This criticism is true, Null security is indeed broken when you interoperate with Java code. The team behind the language has stated many times that they initially tried to make Java nullable for each type, but they found that it actually made the code worse.

Kotlin is no worse than Java, you just have to be careful how you use a given library, just as you would use it in Java, because it doesn’t take Null security into account. If Java libraries are concerned about Null security, they have a number of supporting annotations to add.

It might be possible to add a compiler flag so that each Java type can be null, but that would cost Kotlin’s team a lot of extra resources.

Class name literal constants

:: class gives you an instance of KClass to use with Kotlin’s own reflection API, while :: class. Java gives you regular Java class instances for Java reflection.

Reverse type declaration

For clarity, reverse order exists so that you can omit explicit types in a reasonable way. The colon is just syntax, which is fairly common in modern languages such as Scala, Swift, etc.

I don’t know what IntelliJ the author is using, but the variable names and types I use are auto-complete. For parameters, IntelliJ will even give you suggestions for names and types of the same type, which is actually better than Java.

Associated object

The original text says:

Sometimes you have to use static. The older version of public static void Main () is still the only way to launch Java applications.

class AppRunner {
    companion object {
        @JvmStatic fun main(args: Array<String>) {
            SpringApplication.run(AppRunner::class.java, *args)
        }
    }
}
Copy the code

In fact, this is not the only way to launch a Java application. Here’s what you can do:

Fun main(args: Array <String>){springApplication.run (AppRunner :: class.java, * args)}Copy the code

Or this:

Fun main(args: Array <String>){runApplication <AppRunner>(* args)}Copy the code

Set literal

You can use array literals in comments. But, beyond that, the functionality of these collection factories is pretty neat, and they’re another thing that’s “built” into the language, when they’re really just library functions.

You just complain about using: to make type declarations. And, to reap the benefits of it not having to be a separate language construct, it’s just a feature that anyone can implement.

Maybe

If you like Optional, you can use it. Kotlin runs on the JVM.

It’s a little ugly for the code. But you shouldn’t use parseInt in Kotlin code, you should (I don’t know why you missed this in your 6 months of using the language). Why would you explicitly name a Lambda argument?

Data classes

The original text says:

This limitation is not Kotlin’s fault. There is no way to produce correct value-based data without equals() violating Liskov’s principle.

This is why Kotlin doesn’t allow data class inheritance.

I don’t know why you bring this up. If you need more complex classes, you can still create them and manually maintain their equals, hashCode, and so on. A data class is simply a convenient way to use a simple case, and for many people it is common.

Public class

The author despises again and I have nothing to say about it.

Steep learning curve

The author thinks it’s hard to learn Kotlin, but I personally don’t think so.

Final thoughts

From the examples the author gives, I get the feeling that he has only scratched the surface of language.

It’s hard to imagine he put much time into it.

Add wechat directly: XianXian010501 free access to information