Skip to content

hamza94max/Kotlin-In-Action-Book-

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 

Repository files navigation

Kotlin-In-Action-Book-

Advantages of kotlin:

Kotlin is pragmatic, safe, concise, and interoperable, meaning it focuses on using proven solutions for common tasks, preventing common errors such as NullPointerExceptions, supporting compact and easy-to-read code, and unrestricted

Notes:

ch 1 : Intro to kotlin basics
val x = 1
  • Kotlin automatically determines that its type is Int. The ability of the compiler to determine types from context is called type inference

  • Most of the code that would lead to a NullPointerException in Java fails to compile in Kotlin, ensuring that you fix the error before the application gets to your

ch 2 : Defining
  • val (from value)—Immutable reference. A variable declared with val can’t be reassigned after it’s initialized. It corresponds to a final variable in Java.

  • var (from variable)—Mutable reference. The value of such a variable can be changed. This declaration corresponds to a regular (non-final)

  • Using immutable references, immutable objects, and functions without side effects makes your code closer to the functional style.

class Person(
val name: String,
var isMarried: Boolean
)
  • name Read-only property: generates a field and a trivial getter
  • isMarried Writable property: a field, a getter, and a set

* The concise syntax 1..5 creates a range. Ranges and progressions allow Kotlin to use a uniform syntax and set of abstractions in for loops and also work with the in and !in operators that check whether a value belongs to a range.
val percentage = if (number in 0..100) number

Lazy Keyword:

In Kotlin, lazy is a function that is used to create a lazily initialized property. A lazily initialized property is a property that is computed or initialized only when it is accessed for the first time, not when the object is created.

ch 3 : Defining and calling functions-:
  • Kotlin doesn’t have its own set of collection classes. All of your existing knowledge about Java collections

  • joinToString() :

/* Java */
collection.joinToString(/* separator */ " ", /* prefix */ " ", /* postfix */ ".");
collection.joinToString(separator = " ", prefix = " ", postfix = ".")

Note: in a call, you should also specify the names for all the arguments after that, to avoid confusion.

extension functions:

package strings

fun String.lastChar(): Char = get(length - 1)
import strings.lastChar
val c = "Kotlin".lastChar() // n

Working with maps-:

val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

NOTE: The word to in this line of code isn’t a built-in construct, but rather a method invocation of a special kind, called an infix call

Strings:

 println("12.345-6.A".split("\\.|-".toRegex()))
[12, 345, 6, A]

For instance, in Kotlin you use an extension function toRegex to convert a string into a regular expression

fully example for extenstion functions to avoid duplicated

class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(value: String, fieldName: String){
  if (value.isEmpty()) {
  throw IllegalArgumentException(
  "Can't save user ${user.id}: " +
  "$fieldName is empty")
  }}
validate(user.name, "Name")
validate(user.address, "Address")
// Save user to the database
}
>>> saveUser(User(1, "", ""))
java.lang.IllegalArgumentException: Cannot save user 1: Name is empty

NOTE: Local functions help you structure your code more cleanly and eliminate duplication

ch 4 : Classes-:
  • All classes and methods that aren’t specifically intended to be overridden in subclasses need to be explicitly marked as final

  • If you want to allow the creation of subclasses of a class, you need to mark the class with the open modifier. In addition, you need to add the open modifier to everyproperty or method that can be overridden: //AU: I’ve added blank lines between the code lines to make room for the annotations. OK? TT

open class RichButton : Clickable { //1
fun disable() {} //2
open fun animate() {} // 3
override fun click() {} //4
}

1-This class is open: others can inherit from it.
2-This function is "final": you can’t override it in a subclass.
3-This function is open: you may override it in a subclass
4-This function overrides an open function and is open as well.

book 1
book 2


  • Inner Class : the inner keyword is used to mark a nested class as an inner class. An inner class can access members of its outer class, including private members, and has a reference to an instance of its outer class.
class OuterClass {
    private val outerProperty = "Outer property"
    
    inner class InnerClass {
        fun printOuterProperty() {
            println(outerProperty) // output: Outer property
        }
    }
}

Usecases in Android: ViewHolder class in RecyclerView adapter

  • Sealed Class :

You mark a superclass with the sealed modifier, and that restricts the possibility of creating subclasses. All the direct subclasses must be nested in the superclass

can only be subclassed within the same file where it is declared.

  • companion:

. If you do that, you gain the ability to access the methods and properties of that object directly through the name of the containing class, without specifying the name of the object explicitly. The resulting syntax looks exactly like static method invocation in Java. Here’s a basic example showing the syntax:

class A {
 companion object{
  fun bar() {
  println("Companion object called")
  }
 }
}

A.bar()
Companion object called

ch 5 : Lambdas-: more concise and readable
  • This is program to find the oldest person in list using lambdas (maxBy)
 val people = listOf(Person("Alice", 29), Person("Bob", 31))
 println(people.maxBy { it.age })
 // output : Person(name=Bob, age=31)

Lambda expression syntax :

val sum = { x: Int, y: Int -> x + y }
 println(sum(1, 2))  // 3

3

filter function:

data class Person(val name: String, val age: Int)

val list = listOf(1, 2, 3, 4)
list.filter { it % 2 == 0 }
// output => [2, 4]

map function:-

The filter function can remove unwanted elements from a collection, but it doesn’t change the elements. Transforming elements is where map comes into play

val list = listOf(1, 2, 3, 4)
 list.map { it * it }
// output => [1, 4, 9, 16]

all & any :

val people = listOf(Person("Alice", 27), Person("Bob", 31))
println(people.all(canBeInClub27))
/ output => false

If you need to check whether there’s at least one matching element, use any:

println(people.any(canBeInClub27))
// true

Note that !all (not-all), !any (not-any) can be replaced with any with a negated condition and vice versa

groupBy :

For example, you want to group people of the same age together. It’s convenient to pass this quality directly as a parameter. The groupBy function can do this for you

 val people = listOf(Person("Alice", 31),
 Person("Bob", 29), Person("Carol", 31))
 println(people.groupBy { it.age })

// output => {29=[Person(name=Bob, age=29)],
// 31=[Person(name=Alice, age=31), Person(name=Carol, age=31)]

so the result type is Map<Int, List>

flatMap:

The flatMap function does two things: at first it transforms (or maps) each element to a collection according to the function given as an argument, and then it combines (or flattens) several lists into one

val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() })
// output => [a, b, c, d, e, f]
Alt Text

sequences:

The Kotlin standard library reference says that both filter and map return a list. That means this chain of calls will create two lists: one to hold the results of the filter function and another for the results of map. This isn’t a problem when the source list contains two elements, but it becomes much less efficient if you have a million. To make this more efficient => you can convert the operation so it uses sequences instead of using collections directly:

people.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList()

The with function:

use to perform multiple operations on the same object without repeating its name (make it short)

see the diff between two codes 1.

fun alphabet(): String {
   val result = StringBuilder()
   for (letter in 'A'..'Z') {
   result.append(letter)
   }
   result.append("\nNow I know the alphabet!")
   return result.toString()
}
fun alphabet(): String {
  val stringBuilder = StringBuilder()
  return with(stringBuilder){
   for (letter in 'A'..'Z'){
    this.append(letter)
  }
  append("\nNow I know the alphabet!")
  this.toString()
}}

The apply function:-

As you can see, apply is an extension function, works almost exactly the same as with; the only difference is that apply() always returns the object passed to it as a parameter

fun alphabet() = StringBuilder().apply{
 for (letter in 'A'..'Z') {
  append(letter)
  }
 append("\nNow I know the alphabet!")
 }.toString()

// end of Ch 5

ch 6 : The Kotlin type system

Nullability:-

Nullability is a feature of the Kotlin type system that helps you avoid NullPointerException errors.

Kotlin convert this problem from runtime errors to compile errors

gg

a type without a question mark ? denotes that variables of this type can’t store null references.

Safe call operator: ?.

?. It allows you to combine a null check and a method call into a single operation. For example, the expression s?.toUpperCase() is equivalent to the following, more cumbersome one: if (s != null) s.toUpperCase() else null

Operator: ?:

fun foo(s: String?) {
val t: String = s ?: ""  // If "s" is null, the result is an empty string
}

Operator: as?

The as? operator tries to cast a value to the specified type and returns null if the value doesn’t have the proper type

Not_null assertions: !!

It’s represented by a double exclamation mark and converts any value to a non-null type

The let function:-

to deal with a nullable argument that should be passed to a function that expects a non-null parameter

email?.let { email -> sendEmailTo(email) } == if (email != null) sendEmailTo(email)

6

Primitive types: Int, Boolean, and more :-

Kotlin doesn’t distinguish between primitive types and wrapper types (ex: Integer) You always use the same type (for example, Int)

The Unit type: Kotlin’s "void" :

The Unit type in Kotlin fulfills the same function as void in Java. It can be used as a return type of a function that has nothing interesting to return:

fun f(): Unit { ... }  == fun f() { ... }

Collections :

7

8

9

ch 7 : Operator overloading and other conventions

Overloading binary arithmetic operations:-

data class Point(val x: Int, val y: Int){
  operator fun plus(other: Point): Point{
    return Point(x + other.x, y + other.y)
 }
}

** NOTE: the operator keyword to declare the plus function. All functions used to overload operators need to be marked with that keyword**

another example

operator fun Point.unaryMinus(): Point{ 
return Point(-x, -y)
}
>>> val p = Point(10, 20)
>>> println(-p)
Point(x=-10, y=-20)

Equality operators: equals:-

Note how you use the identity equals operator (===) to check whether the parameter to equals is the same object as the one on which equals is called.

Ordering operators: compareTo:-

gg

Lazy delegated properties :

==> creating part of an object on demand, when it’s accessed for the first time

what is the benefit of lazy ?

benefits of using the lazy keyword:

1.Efficient resource utilization ==> With lazy initialization, resources are only allocated when they are actually needed

2.Thread-safe initialization ==> The lazy delegate ensures that the initialization of the property is thread-safe.

3.Cleaner code ==> The lazy keyword helps to simplify code by encapsulating the lazy initialization logic in a concise and readable manner

4.Support for immutable properties ==> The lazy keyword can be used with val properties

so you can use it together with the by keyword to create a delegated property. The parameter of lazy is a lambda that it calls to initialize the value. The lazy function is thread-safe by default

val emails by lazy { loadEmails(this)}

ch 8 : Higher-order functions

Function types:-

val action: () -> Unit = { println(42) }

gg

The Unit type is used to specify that a function returns no meaningful value

Calling functions :-

gg

Inline functions :-

The inline keyword in Kotlin is used to declare an inline function or an inline property. When a function or property is marked as inline, the compiler replaces the call sites of that function or property with the actual code defined in it during the compilation process. This results in the elimination of the function call overhead, as the code is directly inserted at the call site.


ch 9 : Generics

Using generics in Kotlin provides several benefits:

Type Safety: Generics allow you to define types that a class or function can operate on. This helps in catching type errors at compile-time rather than runtime. It ensures that the code operates on the correct types and helps avoid ClassCastException errors.

Code Reusability: Generics promote code reuse by allowing you to write generic algorithms and data structures that can work with different types. This eliminates the need to duplicate code for similar functionality with different types.

Abstraction: Generics provide a level of abstraction by allowing you to write code that is independent of specific types. This makes the code more flexible and adaptable to different data types.

Performance: Generics can help improve performance by avoiding unnecessary type conversions. With generics, you can write algorithms and data structures that work directly with the desired types, eliminating the need for casting or converting objects.

Generic functions:

If you’re going to write a function that works with a list, and you want it to work with any list (a generic one), not a list of elements of a specific type, you need to write a generic function

val <T> List<T>.penultimate: T
get() = this[size - 2]
>>> println(listOf(1, 2, 3, 4).penultimate)
// output ==> 3

About

Notes for "Kotlin In-Action" Book 📚

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published