Search

5. Object-Oriented Programming

생성일
2021/05/22 12:05
태그

5.1 Interfaces

An interface describes the concept of a type. It is a prototype for all classes that implement the interface.
It describes what a class should do, but not how it should do it. An interface provides a form, but generally no implementation. It specifies an object's actions without detailing how those actions are performed. The interface describes the mission or goal of an entity, versus a class that contains implementation details.
One dictionary definition says that an interface is "The place at which independent and often unrelated systems meet and act on or communicate with each other." Thus, an interface is a means of communication between different parts of a system.
An Application Programming Interface (API) is a set of clearly defined communication paths between various software components. In object-oriented programming, the API of an object is the set of public members it uses to interact with other objects.
Code using a particular interface only knows what functions can be called for that interface. The interface establishes a "protocol" between classes. (Some object-oriented languages have a keyword called protocol to do the same thing.)
To create an interface, use the interface keyword instead of the class keyword. When defining a class that implements an interface, follow the class name with a : (colon) and the name of the interface:
Computer declares prompt() and calculateAnswer() but provides no implementations. A class that implements the interface must provide bodies for all the declared functions, making those functions concrete. In main() you see that different implementations of an interface express different behaviors via their function definitions.
When implementing a member of an interface, you must use the override modifier. override tells Kotlin you are intentionally using the same name that appears in the interface (or base class)—that is, you aren't accidentally overriding.
An interface can declare properties. These must be overridden in all classes implementing that interface:
Each subclass overrides the symbol property in a different way:
Food directly replaces the symbol value.
Robot has a custom getter that returns the value (see Property Accessors).
Wall overrides symbol inside the constructor argument list (see Constructors)
An enumeration can implement an interface:
The compiler ensures that each enum element provides a definition for feedback().

SAM Conversions

The Single Abstract Method (SAM) interface comes from Java, where they call member functions "methods." Kotlin has a special syntax for defining SAM interfaces: fun interface. Here we show SAM interfaces with different parameter lists:
When you say fun interface, the compiler ensures there is only a single member function.
You can implement a SAM interface in the ordinary verbose way, or by passing it a lambda; the latter is called a SAM conversion. In a SAM conversion, the lambda becomes the implementation for the single method in the interface. Here we show both ways to implement the three interfaces:
Comparing the "verbose" implementations to the "sam" implementations you can see that SAM conversions produce much more succinct syntax for a commonly-used idiom, and you aren't forced to define a class to create a single object.
You can pass a lambda where a SAM interface is expected, without first wrapping it into an object:
In main() we pass a lambda instead of an object that implements the Action interface. Kotlin automatically creates an Action object from this lambda.

5.2 Complex Constructors

For code to work correctly, objects must be properly initialized.
A constructor is a special function that creates a new object. In Constructors, we saw simple constructors that only initialize their arguments. Using var or val in the parameter list makes those parameters properties, accessible from outside the object:
In these cases, we don't write constructor code—Kotlin does it for us. For more customization, add constructor code in the class body. Code inside the init section is executed during object creation:
Constructor parameters are accessible inside the init section even if they aren't marked as properties using var or val.
Although defined as valcontent is not initialized at the point of definition. In this case, Kotlin ensures that initialization occurs at one (and only one) point during construction. Either reassigning content or forgetting to initialize it produces an error message.
A constructor is the combination of its constructor parameter list—initialized before entering the class body—and the init section(s), executed during object creation. Kotlin allows multiple init sections, which are executed in definition order. However, in a large and complex class, spreading out the init sections may produce maintenance issues for programmers who are accustomed to a single init section.

5.3 Secondary Constructors

When you require several ways to construct an object, named and default arguments are usually the easiest approach. Sometimes, however, you must create overloaded constructors.
The constructor is "overloaded" because you're making different ways to create objects of the same class. In Kotlin, overloaded constructors are called secondary constructors. The constructor parameter list (directly after the class name) combined with property initializations and the init block is called the primary constructor.
To create a secondary constructor, use the constructor keyword followed by a parameter list that's distinct from all other primary and secondary parameter lists. Within a secondary constructor, the this keyword calls either the primary constructor or another secondary constructor:
Calling another constructor from a secondary constructor (using this) must happen before additional constructor logic, because the constructor body may depend on those other initializations. Thus it precedes the constructor body.
The argument list determines the constructor to call. WithSecondary(1) matches the primary constructor, WithSecondary('D') matches the first secondary constructor, and WithSecondary("Last Constructor") matches the second secondary constructor. The this() call in [1] matches the first secondary constructor, and you can see the chain of calls in the output.
The primary constructor must always be called, either directly or through a call to a secondary constructor. Otherwise, Kotlin generates a compile-time error, as in [2]. Thus, all common initialization logic that can be shared between constructors should be placed in the primary constructor.
An init section is not required when using secondary constructors:
[1] Only the parameters of the primary constructor can be declared as properties via val or var.
[2] You cannot declare a return type for a secondary constructor.
[3] The material parameter has the same name as a property, so we disambiguate it using this.
[4] The secondary constructor body is optional (although you must still include an explicit this() call).
When calling the first secondary constructor in line [5], the property material is assigned twice. First, the Plastic value is assigned during the call to the primary constructor (in [2]) and initialization of all the class properties, then it's changed to the material parameter at [3].
The GardenItem class can be simplified using default arguments, replacing the secondary constructors with a single primary constructor.

5.4 Inheritance

Inheritance is a mechanism for creating a new class by reusing and modifying an existing class.
Objects store data in properties and perform actions via member functions. Each object occupies a unique place in storage so one object's properties can have different values from every other object. An object also belongs to a category called a class, which determines the form (properties and functions) for its objects. Thus, an object looks like the class that formed it.
Creating and debugging a class can require extensive work. What if you want to make a class that's similar to an existing class, but with some variations? It seems wasteful to build a new class from scratch. Object-oriented languages provide a mechanism for reuse called inheritance.
Inheritance follows the concept of biological inheritance. You say, "I want to make a new class from an existing class, but with some additions and modifications."
The syntax for inheritance is similar to implementing an interface. To inherit a new class Derived from an existing class Base, use a :(colon):
The subsequent atom explains the reason for the parentheses after Base during inheritance.
The terms base class and derived class (or parent class and child class, or superclass and subclass) are often used to describe the inheritance relationship.
The base class must be open. A non-open class doesn't allow inheritance—it is closed by default. This differs from most other object-oriented languages. In Java, for example, a class is automatically inheritable unless you explicitly forbid inheritance by declaring that class to be final. Although Kotlin allows it, the final modifier is redundant because every class is effectively final by default:
Kotlin forces you to clarify your intent by using the open keyword to specify that a class is designed for inheritance.
In the following example, GreatApe is a base class, and has two properties with fixed values. The derived classes BonoboChimpanzee and BonoboB are new types that are identical to their parent class:
info() is an extension for GreatApe, so naturally you can call it on a GreatApe. But notice that you can also call info() on a Bonobo, a Chimpanzee, or a BonoboB! Even though the latter three are distinct types, Kotlin happily accepts them as if they were the same type as GreatApe. This works at any level of inheritance—BonoboB is two inheritance levels away from GreatApe.
Inheritance guarantees that anything inheriting from GreatApe is a GreatApe. All code that acts upon objects of the derived classes knows that GreatApe is at their core, so any functions and properties in GreatApe will also be available in its child classes.
Inheritance enables you to write a single piece of code (the info() function) that works not just with one class, but also with every class that inherits that class. Thus, inheritance creates opportunities for code simplification and reuse.
GreatApe.kt is a bit too simple because all the classes are identical. Inheritance gets interesting when you start overriding functions, which means redefining a function from a base class to do something different in a derived class.
Let's look at another version of GreatApe.kt. This time we include member functions that are modified in the subclasses:
Every GreatApe has a call(). They store energy when they eat() and they expend energy when they climb().
As described in Constraining Visibility, the derived class can't access the private members of the base class. Sometimes the creator of the base class would like to take a particular member and grant access to derived classes but not to the world in general. That's what protecteddoes: protected members are closed to the outside world, but can be accessed or overridden in subclasses.
If we declare energy as private, it won't be possible to change it whenever GreatApe is used, which is good, but we also can't access it in subclasses. Making it protected allows us to keep it accessible to subclasses but invisible to the outside world.
call() is defined the same way in Bonobo and Chimpanzee as it is in GreatApe. It has no parameters and type inference determines that it returns a String.
Both Bonobo and Chimpanzee should have different behaviors for call() than GreatApe, so we want to change their definitions of call(). If you create an identical function signature in a derived class as in a base class, you substitute the behavior defined in the base class with your new behavior. This is called overriding.
When Kotlin sees an identical function signature in the derived class as in the base class, it decides that you've made a mistake, called an accidental override. If you write a function that has the same name as a function in the base class, you get an error message saying you forgot the override keyword. Kotlin assumes you've unintentionally chosen the same name, parameters and return type unless you use the override keyword (which you first saw in Constructors) to say "yes, I mean to do this." The override keyword also helps when reading the code, so you don't have to compare signatures to notice the overrides.
Kotlin imposes an additional constraint when overriding functions. Just as you cannot inherit from a base class unless that base class is open, you cannot override a function from a base class unless that function is defined as open in the base class. Note that climb() and energyLevel() are not open, so they cannot be overridden. Inheritance and overriding cannot be accomplished in Kotlin without clear intentions.
It's especially interesting to take a Bonobo or a Chimpanzee and treat it as an ordinary GreatApe. Inside talk()call() produces the correct behavior in each case. talk() somehow knows the exact type of the object and produces the appropriate variation of call(). This is polymorphism.
Inside talk(), you can only call GreatApe member functions because talk()'s parameter is a GreatApe. Even though Bonobo defines run() and Chimpanzee defines jump(), neither function is part of GreatApe.
Often when you override a function, you want to call the base-class version of that function (for one thing, to reuse the code), as seen in the overrides for eat(). This produces a conundrum: If you simply call eat(), you call the same function you're currently inside (as we've seen in Recursion). To call the base-class version of eat(), use the super keyword, short for "superclass."

5.5 Base Class Initialization

When a class inherits another class, Kotlin guarantees that both classes are properly initialized.
Kotlin creates valid objects by ensuring that constructors are called:
Constructors for member objects.
Constructors for new objects added in the derived class.
The constructor for the base class.
In the Inheritance examples, the base classes didn't have constructor parameters. If a base class does have constructor parameters, a derived class must provide those arguments during construction.
Here's the first GreatApe example, rewritten with constructor parameters:
When inheriting from GreatApe, you must pass the necessary constructor arguments to the GreatApe base class, otherwise you'll get a compile-time error message.
After Kotlin creates memory for your object, it calls the base-class constructor first, then the constructor for the next-derived class, and so on until it reaches the most-derived constructor. This way, all constructor calls can rely on the validity of all the sub-objects created before them. Indeed, those are the only things it knows about; a Bonobo knows it inherits from GreatApe and the Bonobo constructor can call functions in the GreatApe class, but a GreatApe cannot know whether it's a Bonobo or a Chimpanzee, or call functions specific to those subclasses.
When inheriting from a class you must provide arguments to the base-class constructor after the base class name. This calls the base-class constructor during object construction:
When there are no base-class constructor parameters, Kotlin still requires empty parentheses after the base class name, to call that constructor without arguments.
If there are secondary constructors in the base class you may call one of those instead:
When VacationHouse inherits from House it passes the appropriate arguments to the primary House constructor. It also adds its own parameters startMonth and endMonth—you aren't limited by the number, type or order of the parameters in the base class. Your only responsibility is to provide the correct arguments in the call to the base-class constructor.
You call an overloaded base-class constructor by passing the matching constructor arguments in the base-class constructor call. You see this in the definitions of VacationHouse and TreeHouse. Each calls a different base-class constructor.
Inside a secondary constructor of a derived class you can either call the base-class constructor or a different derived-class constructor:
To call the base-class constructor, use the super keyword, passing the constructor arguments as if it is a function call. Use this to call another constructor of the same class.

5.6 Abstract Classes

An abstract class is like an ordinary class except one or more functions or properties is incomplete: a function lacks a definition or a property is declared without initialization. An interface is like an abstract class but without state.
You must use the abstract modifier to mark class members that have missing definitions. A class containing abstract functions or properties must also be marked abstract. Try removing any of the abstract modifiers below and see what message you get:
WithProperty declares x with no initialization value (a declaration describes something without providing a definition to create storage for a value or code for a function). If there isn't an initializer, Kotlin requires references to be abstract, and expects the abstract modifier on the class. Without an initializer, Kotlin cannot infer the type, so it also requires type information for an abstract reference.
WithFunctions declares f() and g() but provides no function definitions, again forcing you to add the abstract modifier to the functions and the containing class. If you don't give a return type for the function, as with g(), Kotlin assumes it returns Unit.
Abstract functions and properties must somehow exist (be made concrete) in the class that you ultimately create from the abstract class.
All functions and properties declared in an interface are abstract by default, which makes an interface similar to an abstract class. When an interface contains a function or property declaration, the abstract modifier is redundant and can be removed. These two interfaces are equivalent:
The difference between interfaces and abstract classes is that an abstract class can contain state, while an interface cannot. State is the data stored inside properties. In the following, the state of IntList consists of the values stored in the properties name and list.
An interface may declare properties, but actual data in only stored in classes that implement the interface. An interface isn't allowed to store values in its properties:
Both interfaces and abstract classes can contain functions with implementations. You can call other abstract members from such functions:
Parent declares an abstract property ch and an abstract function f() that must be overridden in any implementing classes. Lines [1]-[4]show different implementations of these members in subclasses.
Parent.g() uses abstract members that have no definitions at the point where g() is defined. Interfaces and abstract classes guarantee that all abstract properties and functions are implemented before any objects can be created—and you can't call a member function unless you've got an object. Lines [5] and [6] call different implementations of ch and f().
Because an interface can contain function implementations, it can also contain custom property accessors if the corresponding property doesn't change state:
You might wonder why we need interface when abstract classes are more powerful. To understand the importance of "a class without state," let's look at the concept of multiple inheritance, which Kotlin doesn't support. In Kotlin, a class can only inherit from a single base class:
Trying to compile the commented code produces an error: Only one class may appear in a supertype list.
Java works the same way. The original Java designers decided that C++ multiple inheritance was a bad idea. The main complexity and dissatisfaction at that time came from multiple state inheritance. The rules managing inheritance of multiple states are complicated and can easily cause confusion and surprising behavior. Java added an elegant solution to the problem by introducing interfaces, which can't contain state. Java forbids multiple state inheritance, but allows multiple interface inheritance, and Kotlin follows this design:
Note that, just like classes, interfaces can inherit from each other.
When inheriting from several interfaces, it's possible to simultaneously override two or more functions with the same signature (the name combined with the parameters and return type). If function or property signatures collide, you must resolve the collisions by hand, as seen in class C:
The functions f() and g() and the property n have identical signatures in interfaces A and B, so Kotlin doesn't know what to do and produces an error message if you don't resolve the issue (try individually commenting the definitions in C). Member functions and properties can be overridden with new definitions as in f(), but functions can also access the base versions of themselves using the super keyword, specifying the base class in angle brackets, as in the definition of C.g() and C.n.
Collisions where the identifier is the same but the type is different are not allowed in Kotlin and cannot be resolved.

5.7 Upcasting

Taking an object reference and treating it as a reference to its base type is called upcasting. The term upcast refers to the way inheritance hierarchies are traditionally represented with the base class at the top and derived classes fanning out below.
Inheriting and adding new member functions is the practice in Smalltalk, one of the first successful object-oriented languages. In Smalltalk, everything is an object and the only way to create a class is to inherit from an existing class, often adding new member functions. Smalltalk heavily influenced Java, which also requires everything to be an object.
Kotlin frees us from these constraints. We have stand-alone functions so everything doesn't need to be contained within classes. Extension functions allow us to add functionality without inheritance. Indeed, requiring the open keyword for inheritance makes it a very conscious and intentional choice, not something to use all the time.
More precisely, it narrows inheritance to a very specific use, an abstraction that allows us to write code that can be reused across multiple classes within a single hierarchy. The Polymorphism atom explores these mechanics, but first you must understand upcasting.
Consider some Shapes that can be drawn and erased:
The show() function accepts any Shape:
In main()show() is called with three different types: CircleSquare, and Triangle. The show() parameter is of the base class Shape, so show() accepts all three types. Each of those types is treated as a basic Shapewe say that the specific types are upcast to the basic type.
We typically draw a diagram for this hierarchy with the base class at the top:
When we pass a CircleSquare, or Triangle as an argument of type Shape in show(), we cast up this inheritance hierarchy. In the process of upcasting, we lose the specific information about whether an object is of type CircleSquare, or Triangle. In each case, it becomes nothing more than a Shape object.
Treating a specific type as a more general type is the entire point of inheritance. The mechanics of inheritance exist solely to fulfill the goal of upcasting to the base type. Because of this abstraction ("everything is a Shape"), we can write a single show() function instead of writing one for every type of element. Upcasting is a way to reuse code for objects.
Indeed, in virtually every case where there's inheritance without upcasting, inheritance is being misused—it's unnecessary, and it makes the code needlessly complicated. This misuse is the reason for the maxim:
Prefer composition to inheritance.
If the point of inheritance is the ability to substitute a derived type for a base type, what happens to the extra member functions: color() in Square and rotate() in Triangle?
Substitutability, also called the Liskov Substitution Principle, says that, after upcasting, the derived type can be treated exactly like the base type—no more and no less. This means that any member functions added to the derived class are, in effect, "trimmed off." They still exist, but because they are not part of the base-class interface, they are unavailable within show():
You can't call color() in line [1] because the Square instance was upcast to a Shape, and you can't call rotate() in line [2] because the Triangle instance is also upcast to a Shape. The only member functions available are the ones that are common to all Shapes—those defined in the base type Shape.
Note that the same applies when you directly assign a subtype of Shape to a general Shape. The specified type determines the available members:
After an upcast, you can only call members of the base type.

5.8 Polymorphism

Polymorphism is an ancient Greek term meaning "many forms." In programming, polymorphism means an object or its members have multiple implementations.
Consider a simple hierarchy of Pet types. The Pet class says that all Pets can speak(). Dog and Cat override speak() member function:
Notice the talk() function parameter. When passing a Dog or a Cat to talk(), the specific type is forgotten and becomes a plain Pet—both Dogs and Cats are upcast to Pet. The objects are now treated as plain Pets so shouldn't the output for both lines [1] and [2] be "Pet"?
talk() doesn't know the exact type of Pet it receives. Despite that, when you call speak() through a reference to the base-class Pet, the correct subclass implementation is called, and you get the desired behavior.
Polymorphism occurs when a parent class reference contains a child class instance. When you call a member on that parent class reference, polymorphism produces the correct overridden member from the child class.
Connecting a function call to a function body is called binding. Ordinarily, you don't think much about binding because it happens statically, at compile time. With polymorphism, the same operation must behave differently for different types—but the compiler cannot know in advance which function body to use. The function body must be determined dynamically, at runtime, using dynamic binding. Dynamic binding is also called late binding or dynamic dispatch. Only at runtime can Kotlin determine the exact speak() function to call. Thus we say that the binding for the polymorphic call pet.speak() occurs dynamically.
Consider a fantasy game. Each Character in the game has a name and can play(). We combine Fighter and Magician to build specific characters:
In main(), each object is upcast to Character as it is placed into the List. The trace shows that calling playTurn() on each Character in the List produces different output.
playTurn() is an extension function on the base type Character. When called in line [3], it is statically bound, which means the exact function to be called is determined at compile time. In line [3], the compiler determines that there is only one playTurn() function implementation—the one defined on line [1].
When the compiler analyzes the play() function call on line [2], it doesn't know which function implementation to use. If the Character is an Elf, it must call Elf's play(). If the Character is a FightingElf, it must call FightingElf's play(). It might also need to call a function from an as-yet-undefined subclass. The function binding differs from invocation to invocation. At compile time, the only certainty is that play() on line [2] is a member function of one of the Character subclasses. The specific subclass can only be known at runtime, based on the actual Character type.
Dynamic binding isn't free. The additional logic that determines the runtime type slightly impacts performance compared to static binding. To force clarity, Kotlin defaults to closed classes and member functions. To inherit and override, you must be explicit.
A language feature such as the when statement can be learned in isolation. Polymorphism cannot—it only works in concert, as part of the larger picture of class relationships. To use object-oriented techniques effectively, you must expand your perspective to include not just members of an individual class, but also the commonality among classes and their relationships with each other.

5.9 Composition

One of the most compelling arguments for object-oriented programming is code reuse.
You may first think of "reuse" as "copying code." Copying seems like an easy solution, but it doesn't work very well. As time passes, your needs evolve. Applying changes to code that's been copied is a maintenance nightmare. Did you find all the copies? Did you make the changes the same way for each copy? Reused code can be changed in just one place.
In object-oriented programming you reuse code by creating new classes, but instead of creating them from scratch, you use existing classes that someone has already built and debugged. The trick is to use the classes without soiling the existing code.
Inheritance is one way to achieve this. Inheritance creates a new class as a type of an existing class. You add code to the form of the existing class without modifying the original. Inheritance is a cornerstone of object-oriented programming.
You can also choose a more straightforward approach, by creating objects of existing classes inside your new class. This is called composition, because the new class is composed of objects of existing classes. You're reusing the functionality of the code, not its form.
Composition is used frequently in this book. Composition is often overlooked because it seems so simple—you just put an object inside a class.
Composition is a has-a relationship. "A house is a building and has a kitchen" can be expressed like this:
Inheritance describes an is-a relationship, and it's often helpful to read the description aloud: "A house is a building." That sounds right, doesn't it? When the is-a relationship makes sense, inheritance usually makes sense.
If your house has two kitchens, composition yields an easy solution:
To allow any number of kitchens, use composition with a collection:
We spend time and effort understanding inheritance because it's more complex, and that complexity might give the impression that it's somehow more important. On the contrary:
Prefer composition to inheritance.
Composition produces simpler designs and implementations. This doesn't mean you should avoid inheritance. It's just that we tend to get bound up in more complicated relationships. The maxim prefer composition to inheritance is a reminder to step back, look at your design, and wonder whether you can simplify it with composition. The ultimate goal is to properly apply your tools and produce a good design.
Composition appears trivial, but is powerful. When a class grows and becomes responsible for different unrelated things, composition helps pull them apart. Use composition to simplify the complicated logic of a class.

Choosing Between Composition and Inheritance

Both composition and inheritance put subobjects inside your new class—composition has explicit subobjects while inheritance has implicit subjobjects. When do you choose one over the other?
Composition provides the functionality of an existing class, but not its interface. You embed an object to use its features in your new class, but the user sees the interface you've defined for that new class rather than the interface of the embedded object. To hide the object completely, embed it privately:
The Features class provides implementations for the operations of Form, but the client programmer who uses Form has no access to features—indeed, the user is effectively unaware of how Form is implemented. This means that if you find a better way to implement Form, you can remove features and change to the new approach without any impact on code that calls Form.
If Form inherited Features, the client programmer could expect to upcast Form to Features. The inheritance relationship is then part of Form—the connection is explicit. If you change this, you'll break code that relies upon that connection.
Sometimes it makes sense to allow the class user to directly access the composition of your new class; that is, to make the member objects public. This is relatively safe, assuming the member objects use appropriate implementation hiding. For some systems, this approach can make the interface easier to understand. Consider a Car:
The composition of a Car is part of the analysis of the problem, and not simply part of the underlying implementation. This assists the client programmer's understanding of how to use the class and requires less code complexity for the creator of the class.
When you inherit, you create a custom version of an existing class. This takes a general-purpose class and specializes it for a particular need. In this example, it would make no sense to compose a Car using an object of a Vehicle class—a Car doesn't contain a Vehicle, it is a Vehicle. The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition.
The cleverness of polymorphism can make it can seem that everything ought to be inherited. This will burden your designs. In fact, if you choose inheritance first when you're using an existing class to build a new class, things can become needlessly complicated. A better approach is to try composition first, especially when it's not obvious which approach works best.

5.10 Inheritance & Extensions

Inheritance is sometimes used to add function to a class as a way to reuse it for a new purpose. This can lead to code that is difficult to understand and maintain.
Suppose someone has created a Header class along with functions that act upon a Heater:
For the sake of argument, imagine that Heater is far more complex than this, and that there are many adjunct functions such as warm(). We don't want to modify this library—we want to reuse it as-is.
If what we actually want is an HVAC (Heating, Ventilation and Air Conditioning) system, we can inherit Heater and add a cool() function. The existing warm() function, and all other functions that act upon a Heater, still work with our new HVAC type—which would not be true if we had used composition:
This seems practical: Heater didn't do everything we wanted, so we inherited HVAC from Heater and tacked on another function.
As you saw in Upcasting, object-oriented languages have a mechanism to deal with member functions added during inheritance: the added functions are trimmed off during upcasting and are unavailable to the base class. This is the Liskov Substitution Principleaka "Substitutability," which says functions that accept a base class must be able to use objects of derived classes without knowing it. Substitutability is why warm() still works on an HVAC.
Although modern OO programming allows the addition of functions during inheritance, this can be a "code smell"—it appears to be reasonable and expedient but can lead you into trouble. Just because it seems to work doesn't mean it's a good idea. In particular, it might negatively impact a later maintainer of the code (which might be you). This kind of problem is called technical debt.
Adding functions during inheritance can be useful when the new class is rigorously treated as a base class throughout your system, ignoring the fact that it has its own bases. In Type Checking you'll see more examples where adding functions during inheritance can be a viable technique.
What we really wanted when creating the HVAC class was a Heater class with an added cool() function so it works with warmAndCool(). This is exactly what an extension function does, without inheritance:
Instead of inheriting to extend the base class interface, extension functions extend the base class interface directly, without inheritance.
If we had control over the Heater library, we could design it differently, to be more flexible:
In this approach, we control the temperature by choosing among multiple strategies. We could also have made heat() and cool() member functions instead of extension functions.

Interface by Convention

An extension function can be thought of as creating an interface containing a single function:
Both X and Y now appear to have a member function called f(), but we don't get polymorphic behavior so we must overload callF() to make it work for both types.
This "interface by convention" is used extensively in the Kotlin libraries, especially when dealing with collections. Although these are predominantly Java collections, the Kotlin library turns them into functional-style collections by adding a large number of extension functions. For example, on virtually any collection-like object, you can expect to find map() and reduce(), among many others. Because the programmers comes to expect this convention, it makes programming easier.
The Kotlin standard library Sequence interface contains a single member function. The other Sequence functions are all extensions—there are well over one hundred. Initially, this approach was used for compatibility with Java collections, but now it's part of the Kotlin philosophy: Create a simple interface containing only the methods that define its essence, then create all auxiliary operations as extensions.
어렵다... 나중에 한번 더 봐야지

The Adapter Pattern

A library often defines a type and provides functions that accept parameters of that type and and/or return that type:
To use this library, you must somehow convert your existing class to LibType. Here, we inherit from an existing MyClass to produce MyClassAdaptedForLib, which implements LibType and can thus be passed to the functions in UsefulLibrary.kt:
Although this does extend a class during inheritance, the new member functions are used only for the purpose of adapting to UsefulLibrary. Note that everywhere else, objects of MyClassAdaptedForLib can be treated as MyClass objects, as in the call to useMyClass(). There's no code that uses the extended MyClassAdaptedForLib where users of the base class must know about the derived class.
Adapter.kt relies on MyClass being open for inheritance. What if you don't control MyClass and it's not open? Fortunately, adapters can also be built using composition. Here, we add a MyClass field inside MyClassAdaptedForLib:
This is not quite as clean as Adapter.kt—you must explicitly access the MyClass object as seen in the call to useMyClass(mc.field). But it still handily solves the problem of adapting to a library.
Extension functions seem like they might be very useful for creating adapters. Unfortunately, you cannot implement an interface by collecting extension functions.

Members versus Extensions

There are cases where you are forced to use member functions rather than extensions. If a function must access a private member, you have no choice but to make it a member function:
The member function increment() can manipulate j, but the extension function decrement() doesn't have access to j because j is private.
The most significant limitation to extension functions is that they cannot be overridden:
The trace output shows that polymorphism works with the member function f() but not the extension function g().
When a function doesn't need overriding and you have adequate access to the members of a class, you can define it as either a member function or an extension function—a stylistic choice that should maximize code clarity.
A member function reflects the essence of a type; you can't imagine the type without that function. Extension functions indicate "auxiliary" or "convenience" operations that support or utilize the type, but are not necessarily essential to that type's existence. Including auxiliary functions inside a type makes it harder to reason about, while defining some functions as extensions keeps the type clean and simple.
Consider a Device interface. The model and productionYear properties are intrinsic to Device because they describe key features. Functions like overpriced() and outdated() can be defined either as members of the interface or as extension functions. Here they are member functions:
If we assume overpriced() and outdated() will not be overridden in subclasses, they can be defined as extensions:
Interfaces that only contain descriptive members are easier to comprehend and reason about, so the Device interface in the second example is probably a better choice. Ultimately, however, it's a design decision.
Languages like C++ and Java allow inheritance unless you specifically disallow it. Kotlin assumes that you won't be using inheritance—it actively prevents inheritance and polymorphism unless they are intentionally allowed using the open keyword. This provides insight into Kotlin's orientation:
Often, functions are all you need. Sometimes objects are very useful. Objects are one tool among many, but they're not for everything.
If you're pondering how to use inheritance in a particular situation, consider whether you need inheritance at all, and apply the maxim Prefer extension functions and composition to inheritance (modified from the book Design Patterns).

5.11 Class Delegation

Both composition and inheritance place subobjects inside your new class. With composition the subject is explicit and with inheritance it is implicit.
Composition uses the functionality of an embedded object but does not expose its interface. For a class to reuse an existing implementation and implement its interface, you have two options: inheritance and class delegation.
Class delegation is midway between inheritance and composition. Like composition, you place a member object in the class you're building. Like inheritance, class delegation exposes the interface of the subobject. In addition, you can upcast to the member type. For code reuse, class delegation makes composition as powerful as inheritance.
How would you achieve this without language support? Here, a spaceship needs a control module:
If we want to expand the functionality of the controls or adjust some commands, we might try inheriting from SpaceShipControls. This doesn't work because SpaceShipControls is not open.
To expose the member functions in Controls, you can create an instance of SpaceShipControls as a property and explicitly delegate all the exposed member functions to that instance:
The functions are forwarded to the underlying controls object, and the resulting interface is the same as if you had used regular inheritance. You can also provide implementation changes, as with turboBoost().
Kotlin automates the process of class delegation, so instead of writing explicit function implementations as in ExplicitDelegation.kt, you specify an object to use as a delegate.
To delegate to a class, place the by keyword after the interface name, followed by the member property to use as the delegate:
Read this as "class B implements interface AI by using the a member object." You can only delegate to interfaces, so you can't say A by a. The delegate object (a) must be a constructor argument.
ExplicitDelegation.kt can now be rewritten using by:
When Kotlin sees the by keyword, it generates code similar to what we wrote for ExplicitDelegation.kt. After delegation, the functions of the member object are accessible via the outer object, but without writing all that extra code.
Kotlin doesn't support multiple class inheritance, but you can simulate it using class delegation. In general, multiple inheritance is used to combine classes that have completely different functionality. For example, suppose you want to produce a button by combining a class that draws a rectangle on the screen with a class that manages mouse events:
The class Button implements two interfaces: Rectangle and MouseManager. It can't inherit from implementations of both ButtonImageand UserInput, but it can delegate to both of them.
Notice that the definition for image in the constructor argument list is both public and a var. This allows the client programmer to dynamically replace the ButtonImage.
The last two lines in main() show that a Button can be upcast to both of its delegated types. This was the goal of multiple inheritance, so delegation effectively solves the need for multiple inheritance.
Inheritance can be constraining. For example, you cannot inherit a class when the superclass is not open, or if your new class is already extending another class. Class delegation releases you from these and other limitations.
Use class delegation with care. Among the three choices—inheritance, composition and class delegation—try composition first. It's the simplest approach and solves the majority of use cases. Inheritance is necessary when you need a hierarchy of types, to create relationships between those types. Class delegation can work when those options don't.

5.12 Downcasting

Downcasting discovers the specific type of a previously-upcast object.
Upcasts are always safe because the base class cannot have a bigger interface than the derived class. Every base-class member is guaranteed to exist and is therefore safe to call. Although object-oriented programming is primarily focused on upcasting, there are situations where downcasting can be a useful and expedient approach.
Downcasting happens at runtime, and is also called run-time type identification (RTTI).
Consider a class hierarchy where the base type has a narrower interface than the derived types. If you upcast an object to the base type, the compiler no longer knows the specific type. In particular, it cannot know what extended functions are safe to call:
To solve this problem, there must be some way to guarantee that a downcast is correct, so you don't accidentally cast to the wrong type and call a non-existent member.

Smart Casts

Smart casts in Kotlin are automatic downcasts. The is keyword checks whether an object is a particular type. Any code within the scope of that check assumes that it is that type:
If b1 is of type Derived1, you can call g(). If b2 is of type Derived2, you can call h().
Smart casts are especially useful inside when expressions that use is to search for the type of the when argument. Note that, in main(), each specific type is first upcast to a Creature, then passed to what():
In main(), upcasting happens when assigning a Human to Creature, passing a Dog to what(), passing an Alien to what(), and passing a Who to what().
Class hierarchies are traditionally drawn with the base class at the top and derived classes fanning down below it. what() takes a previously-upcast Creature and discovers its exact type, thus casting that Creature object down the inheritance hierarchy, from the more-general base class to a more-specific derived class.
when expression that produces a value requires an else branch to capture all remaining possibilities. In main(), the else branch is tested using an instance of the local class Who.
Each branch of the when uses c as if it is the type we checked for: calling greeting() if c is Humanbark() if it's a Dog and mobility() if it's an Alien.

The Modifiable Reference

Automatic downcasts are subject to a special constraint. If the base-class reference to the object is modifiable (a var), then there's a possibility that this reference could be assigned to a different object between the instant that the type is detected and the instant when you call specific functions on the downcast object. That is, the specific type of the object might change between type detection and use.
In the following, c is the argument to when, and Kotlin insists that this argument be immutable so that it cannot change between the isexpression and the call made after the ->:
The c constructor argument is a val in SmartCast1 and a var in SmartCast2. In both cases c is passed into the when expression, which uses a series of smart casts.
In [1], the expression val c = c looks odd, and only used here for convenience—we don't recommend "shadowing" identifier names in normal codeval c creates a new local identifier c that captures the value of the property c. However, the property c is a var while the local (shadowed) c is a val. Try removing the val c =. This means that the c will now be the property, which is a var. This produces an error message for line [2]:
Smart cast to 'Human' is impossible, because 'c' is a mutable property that could have been changed by this time
is Dog and is Alien produce similar messages. This is not limited to while expressions; there are other situations that can produce the same error message.
The change described in the error message typically happens through concurrency, when multiple independent tasks have the opportunity to change c at unpredictable times. (Concurrency is an advanced topic that we do not cover in this book).
Kotlin forces us to ensure that c will not change from the time that the is check is performed and the time that c is used as the downcast typeSmartCast1 does this by making the c property a val, and SmartCast2 does it by introducing the local val c.
Similarly, complex expressions cannot be smart-cast because the expression might be re-evaluated. Properties that are open for inheritance can't be smart-cast because their value might be overridden in subclasses, so there's no guarantee the value will be the same on the next access.

The as Keyword

The as keyword forcefully casts a general type to a specific type:
dogBarkUnsafe2() shows a second form of as: if you say c as Dog, then c is treated as a Dog throughout the rest of the scope.
A failing as cast throws a ClassCastException. A plain as is called an unsafe cast.
When a safe cast as? fails, it doesn't throw an exception, but instead returns null. You must then do something reasonable with that nullto prevent a later NullPointerException. The Elvis operator (described in Safe Calls & the Elvis Operator) is usually the most straightforward approach:
If c is not a Dog, as? produces a null. Thus, (c as? Dog) is a nullable expression and we must use the safe call operator ?. to call bark(). If as? produces a null, then the whole expression (c as? Dog)?.bark() will also produce a null, which the Elvis operator handles by producing "Not a Dog".

Discovering Types in Lists

When used in a predicate, is finds objects of a given type within a List, or any iterable (something you can iterate through):
Because group contains Creatures, find() returns a Creature. We want to treat it as a Dog, so we explicitly cast it at the end of line [1]. There might be zero Dogs in group, in which case find() returns a null so we must cast the result to a nullable Dog?. Because dog is nullable, we use the safe call operator in line [2].
You can usually avoid the code in line [1] by using filterIsInstance(), which produces all elements of a specific type:
filterIsInstance() is a more readable way to produce the same result as filter(). However, the result types are different: while filter() return a List of Creature (even though all the resulting elements are Human, filterIsInstance() returns a list of the target type Human. We've also eliminated the nullability issues seen in FindType.kt.

5.13 Sealed Classes

To constrain a class hierarchy, declare the superclass sealed.
Consider a trip taken by travelers using different modes of transportation:
Train and Bus each contain different details about their Transport mode.
travel() contains a when expression that discovers the exact type of the transport parameter. Kotlin requires the default else branch, because there might be other subclasses of Transport.
travel() shows downcasting's inherent trouble spot. Suppose you inherit Tram as a new type of Transport. If you do this, travel()continues to compile and run, giving you no clue that you should modify it to detect Tram. If you have many instances of downcasting scattered throughout your code, this becomes a maintenance challenge.
We can improve the situation using the sealed keyword. When defining Transport, replace open class with sealed class:
All direct subclasses of a sealed class must be located in the same file as the base class.
Although Kotlin forces you to exhaustively check all possible types in a when expression, the when in travel() no longer requires an elsebranch. Because Transport is sealed, Kotlin knows that no additional subclasses of Transport exist other than the ones present in this file. The when expression is now exhaustive without an else branch.
sealed hierarchies discover errors when adding new subclasses. When you introduce a new subclass, you must update all the code that uses the existing hierarchy. The travel() function in UnSealed.kt will continue to work because the else branch produces "$transport is in limbo!" on unknown types of transportation. However, that's probably not the behavior you want.
sealed class reveals all the places to modify when we add a new subclass such as Tram. The travel() function in SealedClasses.ktwon't compile if we introduce the Tram class without making additional changes. The sealed keyword makes it impossible to ignore the problem, because you get a compilation error.
The sealed keyword makes downcasting more palatable, but you should still be suspicious of designs that make excessive use of downcasting. There is often a better and cleaner way to write that code using polymorphism.

sealed vs. abstract

Here we show that both abstract and sealed classes allow identical types of functions, properties, and constructors:
A sealed class is basically an abstract class with the extra constraint that all direct subclasses must be defined within the same file.
Indirect subclasses of a sealed class can be defined in a separate file:
ThirdLevel doesn't directly inherit from Sealed so it doesn't need to reside in SealedVsAbstract.kt.
Although a sealed interface seems like it would be a useful construct, Kotlin doesn't provide it because Java classes cannot be prevented from implementing the same interface.

Enumerating Subclasses

When a class is sealed, you can easily iterate through its subclasses:
Creating a class generates a class object. You can access properties and member functions of that class object to discover information, and to create and manipulate objects of that class. ::class produces a class object, so Top::class produces the class object for Top.
One of the properties of class objects is sealedSubclasses, which expects that Top is a sealed class (otherwise it produces an empty list). sealedSubclasses produces all the class objects of those subclasses. Notice that only the immediate subclasses of Top appear in the result.
The toString() for a class object is slightly verbose. We produce the class name alone by using the simpleName property.
sealedSubclasses uses reflection, which requires that the dependency kotlin-reflection.jar be in the classpath. Reflection is a way to dynamically discover and use characteristics of a class.
sealedSubclasses can be an important tool when building polymorphic systems. It can ensure that new classes will automatically be included in all appropriate operations. Because it discovers the subclasses at runtime, however, it may have a performance impact on your system. If you are having speed issues, be sure to use a profiler to discover whether sealedSubclasses might be the problem (as you learn to use a profiler, you'll discover that performance problems are usually not where you guess them to be).

5.14 Type Checking

In Kotlin you can easily act on an object based on its type. Normally this activity is the domain of polymorphism, so type checking enables interesting design choices.
Traditionally, type checking is used for special cases. For example, the majority of insects can fly, but there are a tiny number that cannot. it doesn't make sense to burden the Insect interface with the few insects that are unable to fly, so in basic() we use type checking to pick those out:
There are also a very small number of insects that can walk on water or swim underwater. Again, it doesn't make sense to put those special-case behaviors in the base class to support such a small fraction of types. Instead, Insect.water() contains a when expression that selects those subtypes for special behavior and assumes standard behavior for everything else.
Selecting a few isolated types for special treatment is the typical use case for type checking. Notice that adding new types to the system doesn't impact the existing code (unless a new type also requires special treatment).
To simplify the code, name produces the type of the object pointed to by the this under question:
name takes an Any and gets the associated class reference using ::class, then produces the simpleName of that class.
Now consider a variation of the "shape" example:
There are several reasons why you might add rotate() to Square instead of Shape:
The Shape interface is out of your control, so you cannot modify it.
Rotating Square seems like a special case that shouldn't burden and/or complicate the Shape interface.
You're just trying to quickly solve a problem by adding Square and you don't want to take the trouble of putting rotate() in Shape and implementing it in all the subtypes.
There are certainly situations when this solution doesn't negatively impact your design, and Kotlin's when produces clean and straightforward code.
If, however, you must evolve your system by adding more types, it begins to get messy:
The polymorphic call in shapes.map { it.draw() } adapts to the new Triangle class without any changes or errors. Also, Kotlin disallows Triangle unless it implements draw().
The original turn() doesn't break when we add Triangle, but it also doesn't produce the result we want. turn() must become turn2()to generate the desired behavior.
Suppose your system begins to accumulate more functions like turn(). The Shape logic is now distributed across all these functions, rather than being centralized within the Shape hierarchy. If you add more new types of Shape, you must search for every function containing a when that switches on a Shape type, and modify it to include the new case. If you miss any of these functions, the compiler won't catch it.
turn() and turn2() exhibit what is often called type-check coding, which means testing for every type in your system. (If you are only looking for one or a few special types it is not usually considered type-check coding).
In traditional object-oriented languages, type-check coding is usually considered an antipattern because it invites the creation of one or more pieces of code that must be vigilantly maintained and updated whenever you add or change types in your system. Polymorphism, on the other hand, encapsulates those changes into the types that you add or modify, and those changes are then transparently propagated through your system.
Note that the problem only occurs when the system needs to evolve by adding more Shape types. If that's not how your system evolves, you won't encounter the issue. If it is a problem it doesn't usually happen suddenly, but becomes steadily more difficult as your system evolves.
We shall see that Kotlin significantly mitigates this problem through the use of sealed classes. The solution isn't perfect, but type checking becomes a much more reasonable design choice.

Type Checking In Auxiliary Functions

The essence of a BeverageContainer is that it holds and delivers beverages. It seems to make sense to treat recycling as an auxiliary function:
By defining recycle() as an auxiliary function it captures the different recycling behaviors in a single place, rather than having them distributed throughout the BeverageContainer hierarchy by making recycle() a member function.
Acting on types with when is clean and straightforward, but the design is still problematic. When you add a new type, recycle() quietly uses the else clause. Because of this, necessary changes to type-checking functions like recycle() might be missed. What we'd like is for the compiler to tell us that we've forgotten a type check, just as it does when we implement an interface or inherit an abstract class and it tells us we've forgotten to override a function.
sealed classes provide a significant improvement here. Making Shape a sealed class means that the when in turn() (after removing the else) requires that each type be checked. Interfaces cannot be sealed so we must rewrite Shape into a class:
If we add a new Shape, the compiler tells us to add a new type-check path in turn().
But let's look at what happens when we try to apply sealed to the BeverageContainer problem. In the process, we create additional Canand Bottle subtypes:
Note that the intermediate classes Can and Bottle must also be sealed for this approach to work.
As long as the classes are direct subclasses of BeverageContainer, the compiler guarantees that the when in recycle() is exhaustive. But subclasses like GlassBottle and AluminumCan are not checked. To solve the problem we must explicitly include the nested whenexpressions seen in recycle2(), at which point the compiler does require exhaustive type checks (try commenting one of the specific Can or Bottle types to verify this).
To create a robust type-checking solution you must rigorously use sealed at each intermediate level of the class hierarchy, while ensuring that each level of subclasses has a corresponding nested when. In this case, if you add a new subtype of Can or Bottle the compiler ensures that recycle2() tests for each subtype.
Although not as clean as polymorphism, this is a significant improvement over prior object-oriented languages, and allows you to choose whether to write a polymorphic member function or auxiliary function. Notice that this problem only occurs when you have multiple levels of inheritance.
For comparison, let's rewrite BeverageContainer2.kt by bringing recycle() into BeverageContainer, which can again be an interface:
By making Can and Bottle abstract classes, we force their subclasses to override recycle() in the same way that the compiler forces each type to be checked inside recycle2() in BeverageContainer2.kt.
Now the behavior of recycle() is distributed among the classes, which might be fine—it's a design decision. If you decide that recycling behavior changes often and you'd like to have it all in one place, then using the auxiliary type-checked recycle2() from BeverageContainer2.kt might be a better choice for your needs, and Kotlin's features make that reasonable.

5.15 Nested Classes

Nested classes enable more refined structure within your objects.
A nested class is simply a class within the namespace of the outer class. The implication is that the outer class "owns" the nested class. This feature is not essential, but nesting a class can clarify your code. Here, Plane is nested within Airport:
In contact(), the nested class Plane has access to the private property code in the airport argument, whereas an ordinary class would not have this access. Other than that, Plane is simply a class inside the Airport namespace.
Creating a Plane object does not require an Airport object, but if you create it outside the Airport class body, you must ordinarily qualify the constructor call in [1]. By importing nestedclasses.Airport.Plane we avoid this qualification.
A nested class can be private, as with PrivatePlane. Masking it private means that PrivatePlane is completely invisible outside the body of Airport, so you cannot call the PrivatePlane constructor outside of Airport. If you define and return a PrivatePlane from a member function, as seen in privatePlane(), the result must be upcast to a public type (assuming it extends a public type), and cannot be downcast to the private type, as seen in [2].
Here's an example of nesting where Cleanable is a base class for both the enclosing class House and all the nested classes. clean() goes through a List of parts and calls clean() for each one, producing a kind of recursion:
Notice the multiple levels of nesting. For example, Bedroom contains Bathroom which contains Toilet and Sink.

Local Classes

Classes that are nested inside functions are called local classes:
Amphibian looks like a candidate to be an interface rather than an open class. However, local interfaces are not allowed.
Local open classes should be rare; if you need one, what you're trying to make is probably significant enough to create a regular class.
Amphibian and Frog are invisible outside localClasses(), so you can't return them from the function. To return objects of local classes, you must upcast them to a class or interface defined outside the function:
Frog is still invisible outside createAmphibian()—in main(), you cannot cast amphibian to a Frog because Frog isn't available, so Kotlin reports the attempt to use Frog as an "unresolved reference."

Classes Inside Interface

Classes can be nested within interfaces:
In Bolt, the val type must be overridden and assigned using the qualified class name Item.Type.

Nested Enumerations

Enumerations are classes, so they can be nested inside other classes:
upgrade() adds one to the ordinal value of the seat, then uses the library function coerceAtMost() to ensure the new value does not exceed First.ordinal before indexing into values() to produce the new Seat type. Following functional programming principles, upgrading a Ticket produces a new Ticket rather than modifying the old one.
meal() uses when to test every type of Seat and this suggests we could use polymorphism instead.
Enumerations cannot be nested within functions, and cannot inherit from other classes (including other enumerations).
Interfaces can contain nested enumerationsFillIt is a game-like simulation that fills a square grid with randomly-chosen X and O marks:
이 예제는 나중에 한번 더 보자. 이해가 잘 안되넹
For testability, we seed a Random object with randomSeed to produce identical output each time the program runs. Each element of grid is initialized with Blank. In turn(), we first find all cells containing Blank, along with their indices. If there are no more Blank cells then the simulation is complete. Otherwise, we use random() with our seeded generator to select one of the Blank cells. Because we used withIndex() earlier, we must select the index property to produce the location of the cell we want to change.
To display the List in the form of a two-dimensional grid, toString() uses the chunked() library function to break the List into pieces, each of length side, then joins these together with newlines.
Try experimenting with FillIt using different sides and randomSeeds.

5.16 Objects

The object keyword defines something that looks roughly like a class. However, you can't create instances of an object—there's only one. This is sometimes called the Singleton pattern.
An object is a way to combine functions and properties that logically belong together, but this combination either doesn't require multiple instances, or you want to explicitly prevent multiple instances. You never create an instance of an object — there's only one and it's available once the object has been defined:
Here, you can't say JustOne() to create a new instance of a class JustOne. That's because the object keyword defines the structure and creates the object at the same time. In addition, it places the elements inside the object's namespace. If you only want the object to be visible within the current file, you can make it private.
[1] The this keyword refers to the single object instance.
You cannot provide a parameter list for an object.
Naming conventions are slightly different when using object. Typically, when we create an instance of a class, we lower-case the first letter of the instance name. When you create an object, however, Kotlin defines the class and creates a single instance of that class. We capitalize the first letter of the object name because it also represents a class.
An object can inherit from a regular class or interface:
There's only a single instance of an object, so that instance is shared across all code that uses it. Here's an object in its own package:
We can now use Shared in a different package:
And within a third package:
You can see from the results that Shared is the same object in all packages, which makes sense because object creates a single instance. If you make Shared private, it's not available in the other files.
objects can't be placed inside functions, but they can be nested inside other objects or classes (as long as those classes are not themselves nested within other classes):
There's another way to put an object inside a class: a companion object, which you'll see in the Companion Objects atom.

5.17 Inner Classes

Inner classes are like nested classes, but an object of an inner class maintains a reference to the outer class.
An inner class has an implicit link to the outer class. In the following example, Hotel is like Airport from Nested Classes, but it uses inner classes. Note that reception is part of Hotel, but callReception(), which is a member of the nested class Room, accesses reception without qualification:
// 나중에 다시 지금 당장은 급해보이지 않음

5.18 Companion Objects

Member functions act on particular instances of a class. Some functions aren't "about" an object, so they don't need to be tied to that object.
Function and fields inside companion objects are about the class. Regular class elements can access the elements of the companion object, but the companion object elements cannot access the regular class elements.
As you saw in Objects, it's possible to define a regular object inside a class, but that doesn't provide an association between the object and the class. In particular, you're forced to explicitly name the nested object when you refer to its members. If you define a companion object inside a class, its elements become transparently available to that class:
Outside the class, you access members of the companion object using the class name, as in WithCompanion.i and WithCompanion.f(). Other members of the class can access the companion object elements without qualification, as you see in the definition of g().
h() is an extension function to the companion object.
If a function doesn't require access to private class members, you can choose to define it at file scope rather than putting it in a companion object.
Only one companion object is allowed per class. For clarity, you can give the companion object a name:
Even when you name the companion object you can still access its elements without using the name. if you don't give the companion object a name, Kotlin assigns it the name Companion.
If you create a property inside a companion object, it produce a single piece of storage for that field, shared with all instances of the associated class:
The tests in main() show that n has only a single piece of storage, no matter how many instances of WithObjectProperty are created. a and b both access the same memory for n.
increment() shows that you can access private members of the companion object from its surrounding class.
When a function in only accessing properties in the companion object, it makes sense to move that function inside the companion object:
You no longer need a CompanionObjectsFunction instance to call increment().
Suppose you'd like to keep a count of every object you create, to give each one a unique readable identifier:
A companion object can be an instance of a class defined elsewhere:
ZICompanion uses a ZIOpen object as its companion object, and ZICompanionInheritance creates a ZIOpen object while overriding and extending ZIOpenZIClass shows that you can implement an interface while crating the companion object.
If the class you want to use as a companion object is not open, you cannot use it directly as we did above. However, if that class implements an interface you can still use it via Class Delegation:
ZIDelegationInheritance shows that you can take the non-open class ZIClosed, delegate it, then override and extend that delegate. Delegation forwards the methods of an interface to the instance that provides an implementation. Even if the class of that instance is final, we can still override and add methods to the delegation receiver.
Here's a small brain-teaser:
이 예제 어렵다...
In Extend, the ZI interface is implemented using its own companion object, which has the default name Companion. But we are also implementing the Extended interface, which is the ZI interface plus an extra function u(). The ZI portion of Extended is already implemented, via Companion, so we only need to override the additional function u() to complete Extend. Now an Extend object can be upcast to Extended as the argument to test().
A common use for a companion object is controlling object creation—this is the Factory Method pattern. Suppose you'd like to only allow the creation of Lists of Numbered2 objects, and not individual Numbered2 objects:
The Numbered2 constructor is private. This means there's only one way to create an instance—via the create() factory function. A factory function can sometimes solve problems that regular constructors cannot.
Constructors in companion objects are initialized when the enclosing class is instantiated for the first time in a program:
You can see from the output that the companion object is constructed only once, the first time a CompanionInit() object is created.