We have prepared a series of articles to share our experience in developing Android applications in Kotlin. We'll discuss the differences between Kotlin and Java in terms of syntax, usability, UI performance and asynchrony, so that you can decide which language is best for you.
Let's start with some basic grammatical differences. This is the first:
1. With Kotlin, you can do more with less code
One of Kotlin's main advantages is its simplicity. You get more functionality with less code. The less code you write, the fewer mistakes you make. It's simple. Let's look at the basics of Kotlin, starting with classes.
public final class Person {
private String name;
private int age;
private float height;
public Person(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
this.height = 1.8f;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
if (Float.compare(person.height, height) != 0) return false;
return name != null ? name.equals(person.name) : person.name == null
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
result = 31 * result + (height != +0.0f ? Float.floatToIntBits(height) : 0);
return result;
}
}
Above is a common Java class. It doesn't do much. It contains only some data. But it's painful to see how big the code is when you realize the shortcomings it brings to the table. To encourage you, we will give you an equivalent class written in Kotlin.
data class Person(var name: String,
var age: Int,
var height: Float = 1.8f)
Yes, you will automatically get the required getters, setters, equals (), hashcode (), toString () and copy () functions for your data class! Of course, you can easily rewrite these functions, but in most cases, just declaring classes and their attributes is enough.
That's what we mean when we say Kotlin is concise.
2. You can avoid NullPointerException
Now let's remind you of the biggest pain in many programming languages - null pointer exceptions. We can hardly imagine how many developers have suffered from null pointers since Tony Hall invented it in 1965, while trying to make things simpler.
Sadly, we can't come back in time to prevent Tony from making this mistake. But with Kotlin, we can now easily escape NullPointerException.
val person: Person? = null
...
person?.name = "John"
If the variable is empty, the compiler will not allow you to access it without proper checking. Kotlin forces you to use it? Operator. This prevents the application from crashing automatically.
How does it work under the hood? Let's review the generated bytecode.
L2
LINENUMBER 18 L2
ALOAD 3
DUP
IFNULL L3
LDC "John"
INVOKEVIRTUAL igalata/com/kotlinexample/Person.setName (Ljava/lang/String;)V
GOTO L4
L3
POP
As you can see, we have the same empty check here. JetBrains developers (who created Kotlin) know that checking our variables every time is the only way to avoid NullPointerException. But they also know that Android developers don't want to deal with NullPointerException in their projects. They might think, "Why not automatically generate this check if the variable is empty?
JetBrains developers just do that, making our lives easier!
3. You can get rid of util classes
Let's talk about the ugliness of using util classes. Do you have a project without them? We hardly remember all this. Kotlin has a smart solution - extensions - to help you get rid of all util classes once and for all.
The extension function is almost a normal Kotlin function. But when you declare it, you need to specify that the instance will have an extended class.
fun Context.toast(text: String) = Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
Notice that'this', we pass it as a parameter to the makeText () method? It is not an instance of a class, we declare this function, but a Context instance. Now you can call this function directly from your Activity or any other Context instance. For example:
toast("Hi")
You should remember that an extension function does not modify its extended classes in any way. So how does it work without changing the original class? Let's see bytecode again.
public final toast(Landroid/content/Context;Ljava/lang/String;)V
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
L0
ALOAD 1
LDC "$receiver"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
ALOAD 2
LDC "text"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 31 L1
ALOAD 1
ALOAD 2
CHECKCAST java/lang/CharSequence
ICONST_0
INVOKESTATIC android/widget/Toast.makeText (Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
INVOKEVIRTUAL android/widget/Toast.show ()V
L2
LINENUMBER 32 L2
RETURN
L3
LOCALVARIABLE this Ligalata/com/kotlinexample/MainActivity; L0 L3 0
LOCALVARIABLE $receiver Landroid/content/Context; L0 L3 1
LOCALVARIABLE text Ljava/lang/String; L0 L3 2
MAXSTACK = 3
MAXLOCALS = 3
Time to delete your util package!
4. You can forget view binding
Do you remember findViewById () method ()? We believe you don't like it. Neither are we. In addition, we don't want to declare variables and Butterknife annotations for every view we need to access.
You can forget the view binding with Kotlin Android Extensions. You no longer need to create variables and bound views. You can access your view directly using the identifiers declared in the xml layout.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
final TextView text = (TextView) findViewById(R.id.text);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
text.setText("You've clicked a button");
}
});
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
button.setOnClickListener { text.text = "You've clicked a button" }
}
}
It's too simple, isn't it?
Basically, the findViewById () method is still in use. But there's no need to write by yourself. Kotlin will do it for you.
When you use Android extensions, findCachedViewById () functions and HashMap instances are automatically generated. Each time your view is accessed through its identifier, it is replaced by a new function call. If the view is accessed for the first time, this function calls the usual findViewById () function and adds the received view to the HashMap to retrieve the view from it the next time the view is accessed.
5. You can use collections more easily.
Let's talk about Kotlin's collection. Because we often need to use data model sets to perform difficult operations. For example, we may have a list of students from which we need to retrieve three A-level students and two B-level students.
Look at Kotlin's solution:
var students = listOf(Student("John", 0), Student("Julia", 2), Student("Matt", 1),
Student("Katie", 0), Student("Dan", 0))
var firstList = students.filter { it.mark == 0 }.take(3)
var secondList = students.filter { it.mark == 1 }.take(2)
Here's how we can solve the same problem in Java:
ArrayList<Student> students = new ArrayList<Student>() {{
add(new Student("John", 0));
add(new Student("Julia", 2));
add(new Student("Matt", 1));
add(new Student("Katie", 0));
add(new Student("Dan", 0));
}};
ArrayList<Student> firstList = new ArrayList<>();
ArrayList<Student> secondList = new ArrayList<>();
for (Student student: students) {
boolean isFirstFilled = firstList.size() >= 3;
boolean isSecondFilled = secondList.size() >= 2;
if (isFirstFilled && isSecondFilled) break;
int mark = student.getMark();
if (mark == 0 && !isFirstFilled) {
firstList.add(student);
} else if (mark == 1 && !isSecondFilled) {
secondList.add(student);
}
}
This is just a small example of how to use collections in Kotlin and Java, but you can see the difference! Can you imagine how Kotlin would be different if we dealt with a collection of large projects?