From the Baltic Sea to the Indian Ocean

The story will be about how my journey from Kotlin Island to Java Island began.

It all started with the fact that the audit of the software in the organization in which I have the honor to work showed the availability of a variety of software for all needs, and as an option, I was offered its unification, namely, to rewrite some of the projects in Java in order to support the current functionality It was convenient. Yes, and no one canceled interchangeability.

My share was a desktop application for testing the main software. It is written in the promising Kotlin language (version 1.1 was available at the time), but unification is unification. In this regard, I want to share a couple of moments that I encountered during the work, as well as the impressions that Kotlin made on me, since I worked with him for the first time. It is worth noting that there is a lot of information on this language, as well as its various comparisons with Java, I just want to share my impressions.

It’s worth starting with the fact that Kotlin is good friends with Java, you can see what the creators of this language were oriented to, and the presence of compilation in the JVM bytecode also pleases. This allows you to use various Java frameworks and libraries, and, if you wish, almost seamlessly cross these two languages ​​in one project, you just need to start by compiling the Kotlin classes. The project used TornadoFX and Exposed, the first is an analogue of JavaFX, and the second is a database framework.

The first sensation was, yes, this same Java only in profile, but with a lot of syntactic sugar, which allows you to write code faster and more compactly, but in some places much more complicated.

Looking at the code for the first time, you are slightly surprised at the abundance of “?”, The absence of getters and setters, the immutable and variable variables val and var, with automatic type inference or their presence, of course, if their initialization is postponed until later. Therefore, analyzing the code, you often find yourself wondering what kind of object the colleague had in mind, whose legacy I had to rewrite. Primitive data types, such as int, boolean, etc., are not observed in the language, as well as favorite ones since childhood “;” and "new".

To begin with, the language is considered null-safe, as a result of which, constructs such as:

Stand?.channell?.stream?.act() 

I really want to exclaim: “More questions to God of questions!”.
"?" It causes slight bewilderment at first, it is clear that null security is at the forefront, in connection with which, each property of an object has to be checked for null, but why then leave the ability to call methods without checks to null, only replacing "?" on the "!!".
As an example, the previous expression will look like

 Stand!!.channell!!.stream!!.act() 

The compiler will not check this expression. It will not check if the joint project is compiled with Java. The question is, then what is the advantage ?!

There are no checked exceptions in the language, and you will not see any IOException when reading the file. Is it bad or good to judge you.

Next, I want to share examples of Kotlin code and similar ones in Java to show how everything goes compactly
KotlinJava
private fun checkConnections(silently: Boolean = false): Boolean {
        var isNotFailed = true
        mqToClearTable.items.filter { isMqValid(it) }.forEach { mq -> if (mq.queue.isNotEmpty()) {
                isNotFailed = isNotFailed && checkMqConnection(MQContainer(mq.host, mq.port.toInt(), mq.channel, mq.queue, mq.manager), silently)
            }
        }
        return isNotFailed
    }
private Boolean checkConnections(Boolean tempSilently) {
        boolean silently = ObjectUtils.defaultIfNull(tempSilently, false);
        boolean isNotFailed = true;
        List filteredQueue = mqToClearTable.getItems().stream().filter(this::isMqValid).collect(toList());
        for (QueueToClearObservable mq : filteredQueue) {
            if (StringUtils.isNotEmpty(mq.getQueue())) {
                isNotFailed = isNotFailed && checkMqConnection(new MQContainer(mq.getHost(), mq.getPort(), mq.getChannel(), mq.getQueue(), mq.getManager()), silently);
            }
        }
        return isNotFailed;
    }
private fun armsTable() = armsTable.apply {
        columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
        items = currentStand?.arms?.observable()
        column("Сервер", ARM::hostProperty).useTextField(DefaultStringConverter())}
private void armsTable() {
        armsTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        if (currentStand != null) {
            if (CollectionUtils.isNotEmpty(currentStand.getArms())) {
                List  items = FXCollections.observableList(currentStand.getArms());
                armsTable.getItems().addAll(arms);
            }
        }
        TableColumn hostPropertyColumn = new TableColumn<>("Сервер");
        hostPropertyColumn.setCellValueFactory(cell -> cell.getValue().hostPropertyProperty());
        hostPropertyColumn.setCellFactory(TextFieldTableCell.forTableColumn());

The presence of default values ​​in call signatures in Kotlin cannot but rejoice, in Java such things have to be solved either through the Optional class, or via method reloading.
Yes, the functional style in Kotlin is better developed at the moment, which allows you to add the properties you need at the moment of initialization of the class object. For instance,

 factory = new MQQueueConnectionFactory().applay {
		channel =  mq.channel
		targetClientMatching = true
		hostname = mq.host.activ();
 }

In Java, however, you would have to organize several designers in which you could transfer the addition of these properties, or use additional methods after initializing them.

But there are, in my opinion, controversial things:

  • Functions are extensions that allow you to add a method for a class anywhere in the code, after which the method call will be available from anywhere in the code. On the one hand, it’s convenient, but on the other hand, looking into the code you catch yourself thinking, this is a method from the library used or a colleague simply expanded the functionality somewhere. In Java, a similar behavior could be obtained by writing the method exactly in the class where it is needed, passing an additional parameter.
  • Infix function notation. Looking at the record

     block with container

    It is not immediately possible to understand what is meant by calling the block object with the with method, where container are used as an argument. After all, the very next entry

      block.with(container)
  • Functions that takes functions as parameters. It’s not that Java analogues were absent, for such purposes it has built-in functional interfaces (Consumer, Supplier, etc.), but these interfaces must work with some object, and in Kotlin you can transfer a piece of code without thinking about object, using approximately the following construction:

    fun  showAndExecute(title: String, init: () -> T): T{
     alert= Alert(title)
    	{…}
    alert.showAndWait()
    if (result.isPresent() && result.get()){
    init()
    }
    

A little more about the functional part of Kotlin. The presence of similar functions run, let, apply, also with different nuances of application, which causes dissonance when several such functions are used within the same construction using the it keyword (implicit name of a single parameter), for example:

testInfo.also {
 it.endDateProperty.onChange { it?.let { update(Tests.endDate, it) } }
     }

It takes more time to parse the code than necessary to figure out what exactly one or another of it refers to, especially if the code is large and there is a nesting of functions.

A couple of comments about the designers at Kotlin. Not only can you set default values ​​for fields in the constructor, just like with methods, the Loner pattern is implemented at the language level, as well as data classes, unlike Java, where we write everything with pens over and over again and several times. However, in my opinion, the implementation of inheritance is better organized precisely in the latter, where, unlike Kotlin, all classes are initially open, and when inheriting from them, we can easily redefine them. In Kotlin, a class or method must have an open mark to redefine it further. Also, closed classes cannot be proxied. Implementation of static properties and methods in the language is also absent; instead, implementation through companion objects is proposed:

 class ExceptionHandler : Thread.UncaughtExceptionHandler {
 	companion object {
		fun foo{ ….}
		val interval  = 100
	} }	

In conclusion, I want to note that Kotlin, in general, made a good impression. It allows you to write simpler and more compact code, this is especially true when you work with the GUI, but in industrial development I do not see it yet.

Also popular now: