The Programmers Guide To Kotlin - The Class & The Object |
Written by Mike James | |||||||
Tuesday, 29 October 2024 | |||||||
Page 5 of 6
Object ExpressionsThe ability to create single instances of an object is very useful, but often it is more convenient to use the object at once without the bother of giving it a name or a long and permanent life. For example, if a function accepts an object as a parameter, then going to the trouble of creating a class with modifications and an instance is far too much work. Even declaring and customizing a top-level object complete with a name is more work than the problem warrants, and the object exists for the lifetime of the program. Put simply, sometimes all we want is an anonymous object that is a slight modification of an existing class and which will have a relatively short life. In Java the solution is to use an anonymous inner class. In Kotlin the solution is to use an object expression. The way to think about an object expression is that it is an expression which is evaluated at the point in the program at which it occurs and which evaluates to an object. Unlike an object declaration, which cannot be local, an object expression has to be local. Finding an example of using an object expression that is simple isn't easy because the most common usage is to pass an object to a Java function, usually an event handler. Although Kotlin has its own timer object, we can make use of the Swing timer, the constructor of which takes an integer delay in milliseconds, and an instance of ActionListener. ActionListener is an interface which has a single actionPerformed method, which has to be defined to handle the timer interrupt. If you don't know about interfaces, they are covered in the next chapter. The important point is that we are creating an object directly from a class or interface definition. We can use the Swing timer by adding an import: import javax.swing.Timer To create an instance of Timer we have to supply the ActionListener object and, as it will only be used by the timer, an object expression is the obvious way to do the job; var timer = Timer(1000, If you are using IntelliJ you can use it to automatically generate the override. Notice that this has to be inside a function, for example main, for it to work. If you now start the timer and put the program into an infinite loop to keep it running, you will see the message printed every second: timer.start() while(true){} Notice that the only difference between an object declaration and an object expression is that it is used as an expression, i.e. it is assigned to a variable or passed as a parameter. If you want to, you can store the result of an object expression in a variable: val action=object :java.awt.event.ActionListener { override fun actionPerformed( and then this can be passed to the timer constructor: var timer = Timer(1000,action) timer.start() while (true) {} An object expression can inherit from one class and any number of interfaces. You can use any variables that are in scope when the expression is evaluated, i.e. local and global variables accessible in the function at the point the expression is being evaluated. Java inner classes, on the other hand, can only access variables that have been declared final. The only restriction is that, if you return an object expression as the result of a function, it has the type of its superclass and any members you have added are not accessible. The Kotlin documentation makes a point of emphasizing the differences between an object declaration and an object expression, but if you understand the difference between a general expression and declaration these should be obvious. Object expressions are evaluated when they are used, i.e. when they are executed in the flow of control. By contrast, object declarations are evaluated lazily, i.e only when the object they declare is needed. A companion object can only be a declaration and it is initialized when its class is loaded. Value ClassesValue classes are associated with primitive data types and are connected with the idea of boxing. A primitive value like 300 is an instance of the Int class, but for reasons of efficiency it is generally represented as a simple value. Only when you actually make use of it as an object is it converted or boxed to an object. If you don’t look too carefully, it looks as if all primitive data types are indeed objects, fulfilling the promise that in Kotlin everything is an object. This said, there are some differences between these “value” classes and standard classes. The key difference is that value classes do not have any clear notion of identity. That is, if you have two instances of the Int 300 then you can test for reference equality, but the result that you get isn’t stable and indeed the plan is to remove support for the === operator working with primitive types in the future. It is deprecated in Kotlin 2.0. Consider: val A: Int = 300 val B: Int = 300 println(A === B) println(A == B) In this case the result is True, True. The reason is that A and B initially reference the same primitive value which is autoboxed to the same object when we use the equality of reference operator. Notice that the first test is a test to see if A and B reference the same object and the second compares values. If we force the boxing to occur when A and B are initialized by making them nullable then the result is very different: val A: Int? = 300 val B: Int? = 300 println(A === B) println(A == B) Now we see False followed by True. The two Int objects that A and B reference are now different but their value is the same. The notion of object identity when it comes to boxed primitive types is subtle and to be avoided. |
|||||||
Last Updated ( Tuesday, 29 October 2024 ) |