Write at the beginning: I plan to write a Kotlin series of tutorials, one is to make my own memory and understanding more profound, two is to share with the students who also want to learn Kotlin. The knowledge points in this series of articles will be written in order from the book “Kotlin In Action”. While displaying the knowledge points in the book, I will also add corresponding Java code for comparative learning and better understanding.
Kotlin Tutorial (1) Basic Kotlin Tutorial (2) Functions Kotlin Tutorial (3) Classes, Objects and Interfaces Kotlin Tutorial (4) Nullability Kotlin Tutorial (5) Types Kotlin Tutorial (6) Lambda programming Kotlin Tutorial (7) Operator overloading and Other conventions Higher-order functions Kotlin tutorial (9) Generics
Define the class inheritance structure
Interfaces in Kotlin
Kotlin’s interfaces are similar to those in Java 8: They can contain definitions of abstract methods (methods = functions) and implementations of non-abstract methods (similar to the default methods in Java 8), but they cannot contain any state. Use the interface keyword to define an interface:
interface Clickable {
fun click()
}
Copy the code
We declare an interface with an abstract method named click. All non-abstract classes that implement this interface need to provide an implementation of this method. Let’s implement the following interface:
class Button : Clickable {
override fun click() = println("i was clicked")}Copy the code
Kotlin replaces the extends and implements keywords in Java with a colon after class names. As with Java, a class can implement any number of interfaces, but can only inherit from one class. Similar to the @Override annotation in Java, Kotlin uses the Override modifier to mark methods and attributes of overridden parent classes or interfaces. Using the Override modifier is mandatory and will not compile without the Override annotation. This avoids accidental overrides caused by writing out the implementation method before adding the abstract method. Interface methods can have a default implementation. Java 8 requires you to tag such implementations with the default keyword. Kotlin doesn’t need a special identifier, just a method body:
interface Clickable {
fun click()
fun showOff() = println("i'm Clickable!"Class Button: Clickable {override fun click() = println(Override fun click() = println("i was clicked")}Copy the code
When implementing this interface in Kotlin, methods that have a default implementation need not be implemented. Note, however, that if you implement the Kotlin interface in Java code, all methods are implemented. There is no default implementation.
class Abc implements Clickable {
@Override
public void click() {
}
@Override
public void showOff() {// must implement}}Copy the code
This has to do with the way Kotlin’s default methods are implemented. Let’s look at the implementation first to see why all methods are implemented in Java. We convert the above interface and implementation classes into Java code:
public interface Clickable {
void click();
void showOff();
public static final class DefaultImpls {
public static void showOff(Clickable $this) {
String var1 = "i'm Clickable!";
System.out.println(var1);
}
}
}
public final class Button implements Clickable {
public void click() {
String var1 = "i was clicked";
System.out.println(var1);
}
public void showOff() { Clickable.DefaultImpls.showOff(this); }}Copy the code
You can see how Kotlin implements the default method of the interface: DefaultImpls, defines a static inner class in this class implements the default method, and parameters is Clickable object, and then give each implementation class (Button) with the implementation and the call by default Clickable. DefaultImpls. ShowOff (this); . Kotlin needs to be compatible with Java 6, so it doesn’t use Java 8’s interface features. Do you find this implementation very similar to the extension functions in the previous chapter?
There is a special case: if your class implements two interfaces, and each interface has a default implementation method of the same name, then the class will adopt the default implementation of that interface. The answer is: none. Instead, you will get a compilation error if you do not show implementation of the interface with the same name.
interface Clickable {
fun click()
fun showOff() = println("i'm Clickable!")
}
interface Focusable {
fun showOff() = println("i'm Focusable!")
}
class Button : Clickable, Focusable {
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
override fun click() = println("i was clicked")}Copy the code
Here we implement showOff of the same name and invoke the implementation of the parent type. We used the same keyword super as Java. But the syntax is slightly different. In Java, you can put the name of the base class before the super keyword, like Clickable.super.showoff (). In Kotlin, you need to put the name of the base class in Angle brackets: super
.showoff ().
Open, final, and Abstract modifiers: Final by default
The default classes in Java can be inherited and overwritten unless the final keyword is explicitly used, which is usually convenient but poses some problems. Making changes to a base class can lead to incorrect behavior, which is known as a fragile base class problem. Effective Java also advises: Either design and document inheritance, or forbid it. So Kotlin takes the idea that the default is final. If you want to allow subclasses of a class to be created, use the open modifier to identify the class and add the open modifier to every property or method that can be overridden.
Open class RichButton: Clickable {// Open modifier means you can subclass fundisable() {} // This function is final and cannot be overridden by subclasses of Open funanimate() {} // The function is open and can be overridden by subclasses override funclick() {// This function overwrites an open function, so it is also open}Copy the code
If you override a member of a base class or interface, the overridden member also defaults to open. If you want to change this behavior and prevent subclasses from continuing to overwrite, you can explicitly mark the overridden member as final:
open class RichButton : Clickable {
final override fun click() {} // display final to prevent subclass overwriting}Copy the code
There is also an Abstract class in Kotlin, which is basically the same as Java except that the default is final:
Abstract class Animated {// Can not create an instance of abstract animate()// Abstract methods must be overwritten by subclasses open funstopAnimating() {}// Display modifiers open funanimateTwice() {}// The normal method defaults to final}Copy the code
Although the interface can be implemented by default, we still use it according to the Java custom. We do not define the default implementation in the interface, but define the default implementation as an Abstract class.
The meaning of the template modifier in the class
The modifier | Members of the relevant | commentary |
---|---|---|
final | Can’t be rewritten | Class members used by default |
open | Can be rewritten | It needs to be clearly stated |
abstract | Must be heavy | Can only be used in abstract classes. Abstract members cannot have implementations |
override | Overrides a member of a parent class or interface | If final is not used, the overridden member is open by default |
Visibility modifier: Defaults to public
In general, visibility modifiers in Kotlin are similar to those in Java. You can also use public, protected, and private modifiers. But the default visibility is different, and if the modifier is omitted, the declaration is public. Default visibility in Java — package private. It’s not used in Kotlin. Kotlin uses packages only as a way to organize code in namespaces, not as visibility control. As an alternative, Kotlin provides a new modifier: internal, which means only visible inside the module. A module is a set of Kotlin files compiled together, which could be an Intellij IDEA module, an Eclipse project, a Maven or Gradle project, or a set of files compiled using calls to Ant tasks. The advantage of internal visibility is that it provides a true encapsulation of module implementation details. With Java, this encapsulation is easily broken because external code can define classes in the same package as your code and gain access to your package’s private declarations. There are unique top-level declarations in Kotlin. If you use private visibility in top-level declarations, including classes, functions, and attributes, these declarations will be visible in the file that declares them.
Kotlin’s visibility modifier
The modifier | Members of the class | The top statement |
---|---|---|
Public (the default) | Visible everywhere | Visible everywhere |
internal | Visible in module | Visible in module |
protected | Visible in subclasses | – |
private | Seen in class | Visible in the file |
Notice how the protected modifier behaves differently in Java and Kotlin. In Java, you can access a protected member from the same package, but in Kotlin the protected member is only visible to the class and its subclasses, meaning that the same package is not visible. Also note that extension functions of the class do not have access to protected members of the class.
The public, protected, and private modifiers in Kotlin are retained when codified into Java bytecode. You use these Kotlin declarations from Java code as if they had declared the same visibility in Java. The only exception is that private classes are compiled into package private declarations (you can’t declare classes private in Java). But what happens to the internal modifier, you might ask? There is no direct equivalent in Java. Package private visibility is an entirely different matter, a module will often consist of multiple packages, and different modules may contain declarations from the same package. So the internal modifier becomes public in bytecode. The correspondence between these Kotlin declarations and their Java equivalents (or their bytecode rendering) explains why you can sometimes access an internal class or top-level declaration from Java code, or access a protected member from Java code in the same package (similar to what you would do in Java). But you should try to avoid this situation to break the visibility constraints.
In addition, there is another difference in visibility rules between Kotlin and Java: an external class in Kotlin cannot see the private members of its internal (or nested) classes.
Inner and nested classes: The default is nested classes
If you’re not sure about Java inner and nested classes, or you can’t remember the details, check out this blog post: Understanding Java nested and inner classes, anonymous Classes
Class Outer {class Inner {// Inner {Nested}}Copy the code
In Java, inner classes hold external class references, which are often easy to ignore and cause memory leaks and unexpected problems. So the default in Kotlin is nested classes, and if you want to declare them as inner classes, you need to use the inner modifier.
The mapping of nested and inner classes in Java versus Kotlin
A declaration of class A in another class B | In Java | In the Kotlin |
---|---|---|
Nested classes (do not store references to external classes) | static class A | class A |
Inner class (stores references to external classes) | class A | inner class A |
In Java, the inner class gets the object of the Outer class through Outer. This, whereas in Kotlin it gets the object of the Outer class through this@Outer.
class Outer {
inner class Inner {
fun getOuter(): Outer = this@Outer
}
}
Copy the code
Sealed class: Defines a restricted class inheritance structure
Kotlin provides a sealed modifier for the class to restrict that subclasses must be nested within their parent class.
sealed class Father {
class ChildA : Father()
class ChildB : Father()
}
Copy the code
Sealed modifiers imply that this class is an open class, so you no longer need to display the open modifier.
What good is that? When you handle all subclasses of sealed in the WHEN expression, you no longer need to provide the default branch:
fun a(c: Father): Int =
when (c) {
is ChildA -> 1
is ChildB -> 2
// else-> 3 // covers all possible cases, so it is no longer needed}Copy the code
Classes that declare sealed modifiers can only call the private constructor internally and cannot declare an interface to sealed. Why is that? Remember the rules of visibility when converted to Java bytecode? Otherwise, the Kotlin compiler cannot guarantee that this interface is implemented in Java code.
In Kotlin 1.0, sealed is quite restrictive. All subclasses must be nested, and subclasses cannot be created as data classes (mentioned later). Kotlin 1.1 lifts these restrictions and allows a subclass of Sealed to be defined anywhere in the same file.
Declare a class with a non-default constructor or attribute
One or more constructors can be declared in Java, and Kotlin is similar, with a twist: a distinction is made between primary constructors (usually the primary and concise methods that initialize a class and are declared outside the class body) and slave constructors (declared inside the class body). It is also possible to add additional initialization logic to the initialization statement block.
Initialization class: main constructor and initialization statement block
We’ve seen how to declare a simple class before:
class User (val nickName: String)
Copy the code
The bracketed block (val nickName: String) is called the primary constructor. There are two main purposes: to specify the constructor parameters and to define the properties initialized with those parameters. To see how it works, look at the transformed Java code:
public final class User {
@NotNull
private final String nickName;
@NotNull
public final String getNickName() {
returnthis.nickName; } public User(@NotNull String nickName) { this.nickName = nickName; }}Copy the code
We could also implement this logic in Kotlin (which is not necessary at all, just to learn the keyword example, and write it exactly the same as above) :
class User constructor(_nickName: String) {
val nickName: String
init {
nickName = _nickName
}
}
Copy the code
Two new keywords appear: constructor to start a main constructor or declaration from a constructor (which can be omitted when defining the main constructor with the class name); The init keyword is used to introduce an initialization statement block, much like a construction code block in Java. This is exactly the same as class User (val nickName: String). Notice that the simple script contains the val keyword, which means that the corresponding property is initialized using the constructor’s argument.
Constructors can also be set to default values like function arguments:
class User @JvmOverloads constructor(val nickName: String, val isSubscribed: Boolean = true)
Copy the code
The default parameter reduces the need to define overloaded constructs, and @jvMoverloads allow Java code to create instances with default parameters.
If your class has a parent, the main constructor also needs to initialize the parent. You can do this by providing the parent constructor parameter in the parent reference of the base class list:
open class User(val nickName: String)
class TwitterUser(nickName: String) : User(nickName)
Copy the code
If no constructor is declared for a class, a default constructor is generated that does nothing. Classes that inherit from the class must also display the constructor of the calling parent class:
open class Button
class RadioButton : Button()
Copy the code
Notice the () after Button()? This is also different from interfaces, which have no constructors, so there is no () after the interface:
interface Clickable
class RadioButton : Button(), Clickable
Copy the code
If you want to ensure that the class is not instantiated by other code, add private:
class Secretive private constrauctor()
Copy the code
A more general meaning can be expressed in Java by using the private constructor to disallow instantiation of the class: the class is a container or singleton of static utility classes. Kotlin has built-in language-level functionality for this purpose. You can use top-level functions as static utilities. To represent singletons, you can use object declarations, which will be seen in a later section.
Constructor: Initialize the parent class in different ways
The default arguments already avoid constructor overloading. But if you must declare multiple construction parameters, that’s fine.
open class View {
constructor(context: Context)
constructor(context: Context, attributes: Attributes)
}
Copy the code
This class does not declare the main constructor, but does declare two slave constructors, which must be elicited using the constructor keyword. If you want to extend this class, you can declare the same constructor and call the corresponding parent constructor using the super keyword:
class Button : View {
constructor(context: Context) : super(context)
constructor(context: Context, attributes: Attributes) : super(context, attributes)
}
Copy the code
Just as in Java, you can use the this keyword to call one constructor from another constructor in a class.
class Button : View {
constructor(context: Context) : super(context)
constructor(context: Context, attributes: Attributes) : this(context)
}
Copy the code
Note that if a primary constructor is defined, all secondary constructors must call the primary constructor directly or indirectly:
open class View() {
constructor(context: Context) : this()
constructor(context: Context, attributes: Attributes) : this(context)
}
Copy the code
Implements properties declared in the interface
In Kotlin, interfaces can contain abstract property declarations:
interface User {
val nickName: String
}
Copy the code
The property is not a variable (field), but val represents the getter method, and the corresponding Java code:
public interface User {
@NotNull
String getNickName();
}
Copy the code
We implement this interface in several ways:
class PrivateUser(override val nickName: String) : User
class SubscribingUser(val email: String) : User {
override val nickName: String
get() = email.substringBefore("@") // Only getter method} class FacebookUser(val accountId: Int) : User {override val nickName = getFacebookName(accountId)}Copy the code
The PrivateUser class uses a concise syntax to declare an attribute in the main constructor that implements an abstract attribute from User, so override needs to be marked. The nickName property is implemented through a custom getter that has no support for storing its value and only a getter that gets a nickName from the email each time it is called. The FacebookUser class associates the nickName attribute with the value at initialization. The getFacebookName method is expensive to get user information by associating it with Facebook, so it is called only once during initialization. In addition to abstract property declarations, interfaces can also contain properties with getters and setters, as long as they do not reference a support field (which requires storing state in the interface, which is not allowed) :
interface User {
val email: String
val nickName: String
get() = email.substringBefore("@")}Copy the code
Access support fields through getters or setters
There are two types of properties: fields or variables, which Kotlin states generate default getters and setters. The other has no fields, just getters and setters, and because they are represented the same in Kotlin, they are called properties. The corresponding Java code shows the difference more clearly:
class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return name.length() > 0 ? name.substring(0, 1) : ""; }}Copy the code
The name attribute is field supported, while the Surname attribute only has the get method. These two attributes are defined in Kotlin as follows:
class Student {
var name: String = ""
val surname: String
get() = if (name.isNotEmpty()) name.substring(0, 1) else ""
}
Copy the code
The field properties declared in Kotlin generate default getters and setters, or you can change the default generation:
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println("""
Address was changed for $name:"$field"- >"$value".""".trimIndent())
field = value
}
}
Copy the code
Getter and setter methods can also be defined below the fields in the same way that custom accessors are defined, using the field identifier in the method to indicate the support field. The differences between the two attributes found in Kotlin are subtle: unspecified: = “Unspecified”, and the field field is used.
Modify the visibility of the accessor
The visibility of accessors is the same as that of properties. But it can be modified if needed by placing visibility modifiers before the get and set keywords:
class LengthCounter {
var counter: Int = 0
private set
private var other: Int = 0
}
Copy the code
What’s the difference between placing private directly in front of a property and placing it in front of a set or get accessor? Take a look at the converted Java code:
public final class LengthCounter {
private int counter;
private int other;
public final int getCounter() {
return this.counter;
}
private final void setCounter(int var1) { this.counter = var1; }}Copy the code
Private directly modifies properties without generating getter and setter methods. Modifying a set generates setter methods for private.
Methods generated by the compiler: data classes and class delegates
Generic object method
Let’s start by looking at how the toString, equals, and hashCode methods common in Java are copied in Kotlin.
toString()
class Client(val name: String, val postalCode: Int) {
override fun toString(): String = "Client(name=$name, postalCode=$postalCode)"
}
Copy the code
equals()
In Java, the == operator compares values when applied to primitive data types and references when applied to reference types. Therefore, equals is usually always called in Java. In Kotlin == is equal to Equals in Java. If you want to compare references in Kotlin, use the === operator.
class Client(val name: String, val postalCode: Int) { override fun equals(other: Any?) : Boolean {if(other == null || other ! Is Client) {// Check if it is a Clientreturn false
}
return name == other.name && postalCode == other.postalCode
}
}
Copy the code
Any is a simulation of java.lang.Object: the parent of all classes in Kotlin. Nullable type Any? Means that other may be null. In Kotlin, all possible null cases need to be indicated, that is, after the type? , which will be explained in the following sections.
hashCode()
The hashCode method is usually overridden with equals because of the generic hashCode contract: if two objects are equal, they must have the same hash value.
class Client(val name: String, val postalCode: Int) {
override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}
Copy the code
These three methods are usually overridden in the data container bean and are almost automatically generated by the tool, which the Kotlin compiler can now do for us.
Data classes: The practice of automatically generating common methods
We simply add the data keyword to class to define a class that implements toString, equals, and hashCode:
data class Client(val name: String, val postalCode: Int)
Copy the code
Although the attribute of a data class is not required to be val, it is highly recommended that only read-only attributes be used to make instances of the data class immutable. To make it easier to use immutable objects’ data classes, the Kotlin compiler generates one more method for them, one that allows you to copy instances of the class and modify the values of some properties while copying. Here’s what the copy method looks like manually:
data class Client(val name: String, val postalCode: Int) {
fun copy(name:String = this, postalCode:Int = this.postalCode) = Client(name, postalCode)
}
Copy the code
Class delegate: Use the “by” keyword
The decorator pattern is commonly used in Java to add some behavior to other classes. The essence of this pattern is to create a new class that implements the same interface as the original class and stores the instance of the original class as a field. Methods that have the same behavior as the original class do not need to be modified and only need to be forwarded directly to the instance of the original class. One disadvantage of this approach is that it requires quite a bit of template code. For example, let’s implement a decorator for the Collection interface, even if you don’t need to modify any behavior:
class DelegatingCollection<T> : Collection<T> {
private val innerList = arrayListOf<T>()
override val size: Int = innerList.size
override fun contains(element: T): Boolean = innerList.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
override fun isEmpty(): Boolean = innerList.isEmpty()
override fun iterator(): Iterator<T> = innerList.iterator()
}
Copy the code
Now Kotlin has first-class support for delegation as a language-level feature. Whenever you implement an interface, you can delegate the implementation of the interface to another object using the by keyword. Here’s how to rewrite the previous example with recommendations:
class DelegatingCollection<T>(val innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList
Copy the code
All method implementations in the class are gone, the compiler generates them and the implementation is similar to the DelegatingCollection example. In this case, we just need to rewrite the way we need to change behavior:
class CountingSet<T>(
val innerSet: MutableCollection<T> = HashSet<T>()
) : MutableCollection<T> by innerSet {
var objectAdded = 0
override fun add(element: T): Boolean {
objectAdded++
return innerSet.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
objectAdded++
return innerSet.addAll(elements)
}
}
Copy the code
This example counts by overwriting the add and addAll methods and delegating the remaining implementation of the MutableCollection interface to the wrapped container.
The object keyword: combines declaring a class with creating an instance
The object keyword in Kotlin comes up in a variety of ways, but they all follow the same core idea: this keyword defines a class and creates an instance (object) at the same time. Let’s look at different scenarios in which it can be used:
- Object declarations are a way of defining singletons.
- A companion object can hold factory methods and other methods that are associated with the class but do not depend on the class instance when invoked. Their members can be accessed by the class name.
- Object expressions are used to replace Java’s anonymous inner classes.
Object declarations: Creating singletons is a snap
The singleton pattern is one of the most commonly used design patterns in Java. Kotlin provides the highest level of language support for all of this by using object declaration capabilities. Object declarations combine a class declaration with a single instance declaration of that class.
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person inallEmployees) { ... }}}Copy the code
Like classes, an object declaration can contain declarations for attributes, methods, initialization blocks, and so on. The only thing that is not allowed is constructors. Unlike an instance of a normal class, object declarations are created immediately upon definition, without needing to invoke constructors elsewhere in the code. Like variables, object declarations allow you to use object names. Characters to call methods and access properties:
Payroll.allEmployees.add(Person(...) ) Payroll.calculateSallary()Copy the code
Want to know how it works? Let’s also look at the converted Java code:
public final class Payroll {
@NotNull
private static final ArrayList allEmployees;
public static final Payroll INSTANCE;
private Payroll(){
}
@NotNull
public final ArrayList getAllEmployees() {
return allEmployees;
}
public final void calculateSalary() {... } static { Payroll var0 = new Payroll(); INSTANCE = var0; allEmployees = new ArrayList(); }}Copy the code
You can see that the constructor is privatized and the Payroll INSTANCE is initialized via a static code block, stored in the INSTANCE field, which is why it is used this way in Java:
Payroll.INSTANCE.calculateSalary()
Copy the code
This INSTANCE is the INSTANCE that will be created when the Payroll class is loaded into memory. Therefore, it is not recommended to declare classes that are too dependent or expensive as singletons using Object.
You can also create a singleton in a class using an object declaration that accesses the private property in the external class:
Data class Person(val name: String) {// Define object NameComparator: Comparator<Person> {override fun compare(o1: Person, o2: Person): Int = o1.name.compareTo(o2.name) } } val persons = listOf(Person("Bob"), Person("Alice"(persons.namecomparator) // callCopy the code
Companion objects: factory methods and static member sites
Classes in Kotlin cannot have static members: The Java static keyword is not part of the Kotlin language. Instead, Kotlin relies on package-level functions (which replace Java’s static methods in most cases) and object declarations (which replace Java’s static methods in other cases, as well as static fields). In most cases, top-level functions are still recommended, but they do not have access to the private members of the class. In particular, how do you define static members that are used in common Java factory methods and classes? Something like this:
static class B {
public static final String tag = "tag";
private B() {
}
public static B newInstance() {
returnnew B(); }}Copy the code
This is where the companion object comes in. Companion objects are marked by a special keyword added to the object defined in the class: Companion. By doing so, we gain the ability to access the object’s methods and properties directly from the container class name, without having to specify the object’s name. The resulting syntax looks a lot like static method calls in Java:
class A private constructor() {
companion object {
fun newInstance() = A()
val tag = "tag"
}
}
A.newInstance()
A.tag
Copy the code
An accompanying object used as a normal object
A companion object is essentially a normal object, and a normal object can do everything that a companion object can do, such as implementing an interface. It looks strange because we just left out the class name, or we can add the class name to it:
class A private constructor() {
companion object C{
val tag = "tag"Fun newInstance() = A()}} a.c.newinstance ()Copy the code
If you omit the name of the Companion object, the default name will be Companion. This occurs when the code is converted to Java code:
public final class A {
@NotNull
private static final String tag = "tag";
public static final A.Companion Companion = new A.Companion();
private A() {
}
public static final class Companion {
@NotNull
public final String getTag() {
return A.tag;
}
@NotNull
public final A newInstance() {
return new A((DefaultConstructorMarker)null);
}
private Companion() {}}}Copy the code
So, you should understand that calling Companion objects in Java looks like this: A. Companion. NewInstance (). To make the calls in Java feel consistent, use the @jVMStatic annotation on the corresponding member to do this. If you want to declare a static field, you can use the @jVMField annotation on a top-level attribute or on an attribute declared in Object.
class A private constructor() {
companion object{
@JvmField
val tag = "tag"
@JvmStatic
fun newInstance() = A()
}
}
Copy the code
Since the associated object is a normal class, it is possible to declare extension functions:
fun A.Companion.getFlag() = "flag"
A.getFlag()
Copy the code
Object expressions: Anonymous inner classes written differently
The object keyword can be used to declare not only singleton objects, but also anonymous objects. Let’s look at Java code that uses anonymous inner classes as follows:
public static void main(String[] args) {
new B().setListener(new Listener() {
@Override
public void onClick() {}}); } interface Listener { void onClick(); } static class B { private Listener listener; public voidsetListener(Listener listener) { this.listener = listener; }}Copy the code
Using anonymous inner classes in Kotlin:
fun main(args: Array<String>) {
B().setListener(object : Listener {
override fun onClick() {}})}Copy the code
The syntax is the same as the object declaration, except that the name of the object is removed. An object expression declares a class and creates an instance of the class, but does not assign a name to the class or instance. Usually they don’t need a name, because you’ll use the object as an argument to a function call. If you need to assign a name to an object, you can store it in a variable. Unlike Java anonymous inner classes, which can only extend one class or implement one interface, Kotlin’s anonymous objects can implement multiple interfaces. And access to variables in functions that create anonymous inner classes is not limited to final variables, you can also modify the value of variables in object expressions:
fun main(args: Array<String>) {
var clickCount = 0
B().setListener(object : Listener {
override fun onClick() {clickCount++ // modify variable}})}Copy the code
Again, we examine why these differences can be made by looking at the Java code for the transformation:
public static final void main(@NotNull String[] args) {
final IntRef clickCount = new IntRef();
clickCount.element = 0;
(new B()).setListener((Listener)(new Listener() {
public void onClick() { int var1 = clickCount.element++; }})); }Copy the code
You can see that the clickCount we defined is wrapped with IntRef, so the final property is declared on the wrapper class. How does Kotlin’s anonymous object implement multiple interfaces? I’ve defined a new interface that lets anonymous inner classes implement both interfaces:
fun main(args: Array<String>) {
var clickCount = 0
val niming = object : Listener, OnLongClickListener {
override fun onLongClick() {
}
override fun onClick() {
clickCount++
}
}
B().setListener(niming)
View().onLongClickListener = niming
}
interface OnLongClickListener {
fun onLongClick()
}
class View {
var onLongClickListener: OnLongClickListener? = null
}
Copy the code
public static final void main(@NotNull String[] args) {
final IntRef clickCount = new IntRef();
clickCount.element = 0;
<undefinedtype> niming = new Listener() {
public void onLongClick() {
}
public void onClick() { int var1 = clickCount.element++; }}; (new B()).setListener((Listener)niming); (new View()).setOnLongClickListener((OnLongClickListener)niming); }Copy the code
A new thing
is literally an undefinedtype and can be cast to the corresponding interface. This may not be Java content, and it is not clear what the implementation is.