• No se han encontrado resultados

Proceso de evaluación del programa

Grupo 2: Constituye el grupo control y está formado por 2.328 alumnos escolari- escolari-zados en 60 centros, en el mismo nivel educativo y con características

4.4. Variables e instrumentos de evaluación

The object keyword comes up in Kotlin in a number of cases, but they all share the same core idea: the keyword defines a class and creates an instance (in other words, an object) of that class at the same time. Let’s look at the different situations when it’s used:

Object declaration is a way to define a singleton.

Companion objects can contain factory methods and other methods that are related to this class but don’t require a class instance to be called. Their members can be accessed via class name.

Object expression is used instead of Java’s anonymous inner class.

Now we’ll discuss these Kotlin features in detail.

4.4.1 Object declarations: singletons made easy

A fairly common occurrence in the design of object-oriented systems is a class for which you need only one instance. In Java, this is usually implemented using the Singleton pattern: you define a class with a private constructor and a static field holding the only existing instance of //AU: Should "private" be code font? TT the class.

Kotlin provides first-class language support for this using the object declaration feature. The object declaration combines a class declaration and a declaration of a single instance of that class.

For example, you can use an object declaration to represent the payroll of an organization. You probably don’t have multiple payrolls, so using an object for this sounds reasonable:

As you can see, object declarations are introduced with the object keyword. An object declaration effectively defines a class and a variable of that class in a single

statement.

Just like a class, an object declaration can contain declarations of properties, methods, initializer blocks, and so on. The only things that aren’t allowed are constructors (either primary or secondary). Unlike instances of regular classes, object declarations are created immediately at the point of definition, not through constructor calls from other places in the code. Therefore, defining a constructor for an object declaration doesn’t make sense.

object Payroll {

val allEmployees = arrayListOf<Person>() fun calculateSalary() {

for (person in allEmployees) { } ...

} }

And just like a variable, an object declaration lets you call methods and access properties by using the object name to the left of the.character:

Object declarations can also inherit from classes and interfaces. This is often useful when the framework you’re using requires you to implement an interface, but your implementation doesn’t contain any state. For example, let’s take the

java.util.Comparator interface. AComparator implementation receives two objects and returns an integer indicating which of the objects is greater. Comparators almost never store any data, so you usually need just a single Comparator instance for a particular way of comparing objects. That’s a perfect use case for an object declaration.

As a specific example, let’s implement a comparator that compares file paths case-insensitively:

You use singleton objects in any context where an ordinary object (an instance of a class) can be used. For example, you can pass this object as an argument to a function that takes aComparator:

Here you’re using thesortedWith function, which returns a list sorted according to the specified comparator.

Payroll.allEmployees.add(Person(...)) Payroll.calculateSalary()

object CaseInsensitiveFileComparator : Comparator<File>

{ override fun compare(file1: File, file2: File): Int { return file1.getPath().compareTo(file2.getPath(),

ignoreCase = true) } }

>>> println(CaseInsensitiveFileComparator.compare(

... File("/User"), File("/user"))) 0

>>> val files = listOf(File("/Z"), File("/a"))

>>> println(files.sortedWith(CaseInsensitiveFileComparator)) [/a, /Z]

SIDEBAR Singletons and dependency injection

Just like the Singleton pattern, object declarations aren’t always ideal for use in large software systems. They’re great for small pieces of code that have few or no dependencies, but not for large components that interact with many other parts of the system. The main reason is that you don’t have any control over the instantiation of objects, and you can’t specify parameters for the constructors.

This means you can’t replace the implementations of the object itself, or other classes the object depends on, in unit tests or in different configurations of the software system. If you need that ability, you should use regular Kotlin classes together with a dependency injection framework such as Guice (https://github.com/google/guice), just as in Java.

You can also declare objects in a class. Such objects also have just a single instance;

they don’t have a separate instance per instance of the containing class. For example, it’s logical to place a comparator comparing objects of a particular class inside that class:

Now let’s look at a special case of objects nested inside a class: companion objects.

data class Person(val name: String) {

object NameComparator : Comparator<Person> {

override fun compare(p1: Person, p2: Person): Int = p1.name.compareTo(p2.name)

} }

>>> val persons = listOf(Person("Bob"), Person("Alice"))

>>> println(persons.sortedWith(Person.NameComparator)) [Person(name="Alice"), Person(name="Bob")]

/* Java */

CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);

SIDEBAR Using Kotlin objects from Java

An object in Kotlin is compiled as a class with a static field holding its single instance, which is always named INSTANCE. If you implemented the Singleton pattern in Java, you’d probably do the same thing by hand.

Thus, to use a Kotlin object from the Java code, you access the static

INSTANCE field:

In this example, the INSTANCE field has the type

CaseInsensitiveFileComparator.

4.4.2 Companion objects: a place for factory methods and static members

Classes in Kotlin can’t have static members; Java’sstatic keyword isn’t part of the Kotlin language. As a replacement, Kotlin relies on package-level functions (which can replace Java’s static methods in many situations) and object declarations (which replace Java static methods in other cases, as well as static fields). In most cases, it’s recommended that you use package-level functions. But package-level functions can’t access private members of a class. Thus, if you need to write a function that can be called without having a class instance but needs access to the internals of a class, you can write it as a member of an object declaration inside that class. An example of such a function would be a factory method.

Figure 4.5 Private members can’t be used in top-level util functions outside of the class

One of the objects defined in a class can be marked with a special keyword,

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:

Remember when we promised you a good place to call a private constructor? That’s the companion object. The companion object has access to all private members of the class, and it’s an ideal candidate to implement the factory pattern.

Let’s look at an example of declaring two constructors, and then change it to use factory methods declared in the companion object. We’ll build on the example from

class A {

companion object { fun bar() {

println("Companion object called") } }

}

>>> A.bar()

Companion object called

earlier in the chapter, with FacebookUser and SubscribingUser. Previously, these entities were different classes implementing the common interfaceUser. Now you decide to manage with only one class, but to provide different means of creating it:

Secondary constructors

An alternative approach to express the same logic, which may be beneficial for many reasons, is to use factory methods to create instances of the class.

TheUserinstance is created through factory methods, not via multiple constructors:

Declares the companion object

Factory method creating a new user by email

Factory method creating a new user by Facebook account ID

You can invoke the methods ofcompanion object via the class name:

Factory methods are very useful. They can be named according to their purpose, as shown in the example. In addition, a factory method can return subclasses of the class where the method is declared, as in the example when SubscribingUser and

class User {

val nickname: String

constructor(email: String) {

nickname = email.substringBefore('@') }

constructor(facebookAccountId: Int) {

nickname = getFacebookName(facebookAccountId) } }

class User(val nickname: String) { companion object {

fun newSubscribingUser(email: String) = User(email.substringBefore('@')) fun newFacebookUser(accountId: Int) =

User(getFacebookName(accountId)) } }

>>> val subscribingUser = User.newSubscribingUser("[email protected]")

>>> val facebookUser = User.newFacebookUser(4)

>>> println(subscribingUser.nickname) bob

FacebookUser are classes. You can also avoid creating new objects when it’s not necessary. For example, you can ensure that every email corresponds to a unique User instance, and return an existing instance instead of a new one when the factory method is called with an email that’s already in the cache. But if you need to extend such classes, using several constructors may be a better solution, because companion object members can’t be overridden in subclasses.

4.4.3 Companion objects as regular objects

A companion object is a regular object that is declared in a class. It can be named, implement an interface, or have extension functions or properties. In this section, we’ll look at an example.

Suppose you’re working on a web service for a company’s payroll, and you need to serialize and deserialize objects as JSON. You can place the serialization logic in a companion object:

You can use both ways to call fromJSON.

In most cases, you refer to the companion object through the name of its containing class, so you don’t need to worry about its name. But you can specify it if needed, as in the example: companion object Loader. If you omit the name of the companion object, the default name assigned to it is Companion. You’ll see some examples using this name later, when we talk about companion-object extensions.