Previous: [Add pictures to the Dice Roller app]
1. Preparation
prerequisite
- Get familiar with how to modify Kotlin programs using Kotlin Playground .
- Get an introduction to the basic concepts of Kotlin programming introduced in Unit 1 of this course (previous articles), especially
main()
procedures , functions with parameters that return values, variables, data types and operations, andif/else
statements. - Ability to define classes in Kotlin, create object instances from that class, and access its properties and methods.
Learning Content
- Create a Kotlin program that utilizes inheritance to implement class hierarchies.
- Extend a class, replace its existing functions, and add new functions.
- Choose the correct visibility modifier for the variable.
build content
- A Kotlin program containing different types of housing implemented as a class hierarchy.
required conditions
- A computer with internet access to access the Kotlin Playground
Second, what is the class hierarchy?
It's natural for people to divide things with similar properties and behavior into groups, and even to create some type of hierarchy between these groups. For example, you can have a broad category such as Vegetables, and within that category you can have more specific types such as Beans. Within legumes, you can break down more specific types such as peas, soybeans, lentils, chickpeas, and soybeans.
This can be represented as a hierarchy, since beans contain or inherit all the properties of vegetables (for example, they belong to plants and are edible). Likewise, peas, soybeans, and lentils all share the properties of legumes, but also have their own unique properties.
Let's see how to express this relationship in programming terms. If you will Vegetable
be a class in Kotlin, you can create a child or subclass of Legume
the As Vegetable
class . This means that Vegetable
all properties and methods of the class are inherited by Legume
the class (ie, these properties and methods are also available for the latter).
You can represent this relationship in a class hierarchy diagram, as shown below. You can Vegetable
refer to (vegetables) as Legume
the parent or superclass of (beans) class.
You can continue to extend the class hierarchy by Legume
creating subclasses of such as Lentil
(lentils) and Chickpea
(garbanzopeas). This would make Legume
is both Vegetable
a child or subclass of Lentil
and Chickpea
a parent or superclass of and . Vegetable
Is the root or top-level class (also known as the base class) of this hierarchy.
Note: Terminology Summary
The following summarizes the terms used in this article and what they mean in the context of Kotlin classes.
- class hierarchy . An arrangement that organizes classes into a parent and child hierarchy. Hierarchy diagrams are usually drawn with parents on top and children on the bottom.
- child or subclass . Any class that is below another class in the hierarchy.
- parent, parent class, or base class . Any class with one or more subclasses.
- Root class or top-level class . A class at the top (or root) of a class hierarchy.
- inherit . A case where a subclass contains (or inherits) all the properties and methods of its parent class. With inheritance, you can share and reuse code, making programs easier to understand and maintain.
Inheritance in Android classes
As you've done in previous articles, while you can write Kotlin code without classes, many parts of Android are provided to you in the form of classes, including activities, views, and view groups. Therefore, understanding the class hierarchy is fundamental to Android application development and will help you take advantage of the many capabilities provided by the Android framework.
For example, Android has a View
class that represents a rectangular area on the screen and is responsible for drawing and event handling. TextView
The class is a subclass of View
the class , which means that TextView
inherits all the properties and functions of View
the class , and adds specific logic for displaying text to the user.
One level down, EditText
and Button
the class is TextView
a child of the class. They TextView
inherit View
all the properties and methods of the and classes, and add their own specific logic. For example, EditText
added its own functionality to be able to modify text on the screen.
Instead of copying all the logic from the and classes and pasting it into the class , View
you can directly subclass the class , which in turn subclasses the class. The code in the class can then focus on other aspects of making this UI component different from other views.TextView
EditText
EditText
TextView
View
EditText
At the top of the documentation page on the Android classes on the developer.android.com website , you can see a diagram of the class hierarchy. If you see it at the top of the hierarchy kotlin.Any
, it's because in Kotlin all classes have a common parent Any. Please click here for details.
As you can see, learning to take advantage of inheritance relationships between classes can make your code easier to write, reuse, read, and test.
3. Create a base class
Housing Class Hierarchy
In this article, you'll build a Kotlin program that demonstrates how class hierarchies work using the example of a house (where people live) with building spaces, floors, and occupants.
Below is a diagram of the class hierarchy you'll build. At the root, there is a dwelling ( Dwelling
), which specifies properties and functions that apply to all dwellings, similar to a blueprint. Then there are classes for square cabins ( SquareCabin
), round cabins ( RoundHut
), and round towers ( RoundTower
) (that is, multi-story ones RoundHut
).
The class you will implement:
Dwelling
: A base class representing a non-specific housing that stores information common to all housing.SquareCabin
: A square log cabin built of wood with a square building plane.RoundHut
: A circular hut made of straw, with a circular building plane, which is the parentRoundTower
of .RoundTower
: A multi-storey circular tower built of stone, with a circular building plan.
Create Dwelling abstract class
Any class can be the base class of a class hierarchy or the parent class of other classes.
An "abstract" class is one that cannot be instantiated because it is not fully implemented. You can think of it as a sketch. Sketches contain ideas and plans for something, but often not enough information to complete a build. You can use a sketch (abstract class) to create a blueprint (class) from which to build actual object instances.
A common benefit of creating a parent class is the ability to contain properties and functions that are common to all its subclasses. If the value of the attribute and the implementation of the function are unknown, it can be used as an abstract class. For example, Vegetables
there are many properties that are common to all vegetables, but you can't create an instance of a non-specific vegetable because you don't know something about it, such as its shape or color. Therefore, Vegetable
it is an abstract class, and the specific details of each vegetable are determined by subclasses.
The declaration of an abstract class begins with abstract
the keyword .
Note: Later in this course, you'll encounter some of the abstract classes in Android that require subclassing.
Dwelling
Vegetable
Like , will also be an abstract class. It will contain many properties and functions that are common to classes of housing, but the exact values of the properties and implementation details of the functions are unknown.
-
Visit the Kotlin Playground at: https://developer.android.com/training/kotlinplayground .
-
In the editor, delete the in
main()
the functionprintln("Hello, world!")
. -
Then, add the following code below
main()
the function to create a classDwelling
named .abstract
abstract class Dwelling(){
}
Add properties about building materials
In this Dwelling
class , you can define attributes that apply to all housing even though their values may vary from housing to housing. All housing is constructed from certain building materials.
Dwelling
In , create a variableString
of type to represent the building material.buildingMaterial
Since the building material doesn't change, you can useval
to make it immutable.
val buildingMaterial: String
- Running the program, the system displays the following error message.
Property must be initialized or be abstract
buildingMaterial
Property has no value. You can't actually provide a value for it, because a neutral building isn't built from any specific building material. Therefore, as the error message indicates, you can abstract
prefix buildingMaterial
the declaration with the keyword to indicate that the property will not be defined there.
- Add
abstract
the keyword to the variable definition.
abstract val buildingMaterial: String
- Run your code, although it doesn't do anything, it now compiles without any errors.
- Build an instance in
main()
the functionDwelling
and run the code.
val dwelling = Dwelling()
- You will get an error message because you cannot create an instance of the Dwelling abstract class.
Cannot create an instance of an abstract class
- Remove this error code.
Your completed code so far looks like this:
abstract class Dwelling(){
abstract val buildingMaterial: String
}
Add properties about capacity.
Another attribute of housing is capacity, or how many people can live in the house.
There will be no change to the occupancy of any housing. However, the capacity cannot be set in Dwelling
the parent class, but should be defined in the subclass for the particular type of housing.
Dwelling
In , add an integercapacity
named .abstract
val
abstract val capacity: Int
Add a private property about the number of guests
All houses have a certain number residents
(that is, the number of people living in the house, which may be less than or equal to capacity
), Dwelling
so residents
the property should be defined in the parent class so that all subclasses can inherit and use it.
- You can pass
residents
asDwelling
an argument to the class constructor.residents
property is onevar
because the number of occupants may change after the instance is created.
abstract class Dwelling(private var residents: Int) {
Note that residents
attributes are marked with private
the keyword . Private is a visibility modifier in Kotlin , indicating that residents
a property is only visible to (and can only be used within) this class. It cannot be accessed from elsewhere in the program. You can mark a property or method with the private keyword. Otherwise, if no visibility modifier is specified, the corresponding properties and methods default to public
, accessible from other parts of the program. Since the number of occupants of a dwelling is usually private information (as opposed to information about building materials or occupancy), it is wise to mark it private.
With the and current occupants capacity
of the house defined , you can create a function to determine if the house can accommodate additional occupants. You can define and implement the function in the class because the formula for calculating the availability of additional occupants is the same for all housing units. If the number of people is less than , it means that the corresponding can accommodate other guests, and the function should return or according to this comparison method .residents
hasRoom()
Dwelling
hasRoom()
residents
capacity
Dwelling
true
false
- Add
hasRoom()
the function toDwelling
the class.
fun hasRoom(): Boolean {
return residents < capacity
}
- You can run this code and there should be no errors. The code has not performed any visible operations.
The finished code should look like this:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
4. Create subclasses
Create a SquareCabin subclass
Dwelling
Under the class , create aSquareCabin
class called .
class SquareCabin
- Next, you need to indicate
SquareCabin
that isDwelling
related to . In your code, you need to indicateSquareCabin
that extends fromDwelling
(or isDwelling
a subclass of) becauseSquareCabin
will provideDwelling
the implementation of the abstraction.
SquareCabin
Indicate this inheritance by adding a colon ( :
) after the class name, followed by a call to initialize the parent Dwelling
class . Don't forget to add parentheses after Dwelling
the class name.
class SquareCabin : Dwelling()
- If extending from a parent class, you must pass in the parameters required by the parent class. The number of people
Dwelling
is required .residents
You can pass in a fixed number of guests eg3
.
class SquareCabin : Dwelling(3)
However, you want your program to be more flexible and allow SquareCabins
for varying numbers of guests. Therefore SquareCabin
, residents
it will be used as a parameter in the class definition. Do not residents
declare as val
, because you are reusing a property already declared Dwelling
in .
class SquareCabin(residents: Int) : Dwelling(residents)
Note: After using these class definitions, the system performs many operations behind the scenes.
In the class header you'll see
class SquareCabin(residents: Int) ...
This is actually
class SquareCabin constructor(residents: Int) ...
a shorthand forThe system is called when you create an instance of an object through a class
constructor
. For example,SquareCabin(4)
when , is
SquareCabin
calledconstructor
to initialize the object instance.
constructor
An instance is constructed from all the information in the corresponding class, including the parameters passed in. When a class inherits properties and functions from a parent,constructor
the parent class is calledconstructor
to complete the initialization of the object instance.Therefore, when you
SquareCabin(4)
create an instance with , the system willSquareCabin
execute the constructor
constructor
, due to the inheritance relationship . The class definition specifies that its constructor requires a parameter, so you 'll see it passed to the constructor in the class definition . In a later article, you'll learn more about constructors.Dwelling
Dwelling
residents
SquareCabin
Dwelling
residents
- to run your code.
- This causes an error to occur. Let's take a look:
Class 'SquareCabin' is not abstract and does not implement abstract base class member public abstract val buildingMaterial: String defined in Dwelling
When you declare abstract functions and variables, you promise to provide them with values and implementations later. For variables, this means that any subclasses of that abstract class need to provide it with a value. For functions, this means that any subclasses need to implement the function body.
In Dwelling
the class , you define a abstract
variable buildingMaterial
. isSquareCabin
a subclass of , so it must provide a value for . Use the keyword to indicate that this property is defined in the parent class and will be replaced in this classDwelling
buildingMaterial
override
- In
SquareCabin
the class ,override buildingMaterial
attribute and assign a value to it"Wood"
. capacity
Do the same for and show that 6 people can live in a ` SquareCabin .
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
The completed code should look like this.
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
To test your code, create SquareCabin
an instance .
Using Square Cabin
- Insert an empty function
Dwelling
and class definitions.SquareCabin
main()
fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
- In
main()
the function , create an instancesquareCabin
named with 6 guests .SquareCabin
Add output statements for building materials, capacity, andhasRoom()
functions .
fun main() {
val squareCabin = SquareCabin(6)
println("\nSquare Cabin\n============")
println("Capacity: ${
squareCabin.capacity}")
println("Material: ${
squareCabin.buildingMaterial}")
println("Has room? ${
squareCabin.hasRoom()}")
}
Note that hasRoom()
the function is not defined in SquareCabin
the class , but in Dwelling
the class. Since SquareCabin
is Dwelling
a subclass of the class, hasRoom()
functions are inherited directly without task operations. The function can SquareCabin
now be called on all instances of hasRoom()
, as squareCabin.hasRoom()
shown .
- Run the code, the output should look like this:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
You created with a squareCabin
guest count of 6, which is equal to capacity
the value of , so hasRoom()
returns false
. You can try residents
initializing SquareCabin
, and when you run the program again, hasRoom()
it should return true
.
Use with to simplify code
In println()
the statement , each squareCabin
time a property or function of is referenced, it must be typed repeatedly squareCabin.
. This creates duplication and can cause errors when you copy and paste output statements.
When you are working with a specific instance of a class and need to access multiple properties and functions of that instance, you can use with
the statement to say "do all of the following with this instance object". Enter the keyword first with
, then the instance name within parentheses, and then enter the curly brackets indicating what you want to do within them.
with (instanceName) {
// all operations to do with instanceName
}
- In
main()
the function , change your output statement to usewith
. - Remove the " " in the output statement
squareCabin.
.
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${
capacity}")
println("Material: ${
buildingMaterial}")
println("Has room? ${
hasRoom()}")
}
- Run the code again to make sure it runs without any errors and outputs the same results.
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
The completed code looks like this:
fun main() {
val squareCabin = SquareCabin(6)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${
capacity}")
println("Material: ${
buildingMaterial}")
println("Has room? ${
hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Create a RoundHut subclass
- Add another subclass for in
SquareCabin
the same .Dwelling
RoundHut
- Replace
buildingMaterial
and give it the value "Straw
".
Replacecapacity
and set it to 4.
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
main()
In , create anRoundHut
instance of with 3 guests.
val roundHut = RoundHut(3)
- Add the following code to
roundHut
output information about .
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
}
- Running the code, the output of the entire program should look like this:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
You now have a class hierarchy as shown below, where Dwelling
is the root class SquareCabin
and RoundHut
is Dwelling
a subclass of .
Create a RoundTower subclass
The final class in this class hierarchy is the circular tower. You can think of the circular tower as a multi-storey circular lodge built of stone. Therefore, you can RoundTower
use as RoundHut
a subclass of .
- Create a
RoundTower
class that isRoundHut
a subclass of .residents
Add the parameterRoundTower
to the constructor of and pass that parameter to the constructor ofRoundHut
the parent class. buildingMaterial
Replace with"Stone"
.capacity
Set to4
.
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- Run this code and you'll get an error message.
This type is final, so it cannot be inherited from
This error means that RoundHut
the class cannot be subclassed (or inherited). By default, in Kotlin, classes are final and cannot be subclassed. You can only inherit from abstract
classes or open
classes marked with the keyword. Therefore, you need open
to mark RoundHut
the class with the keyword so that it can be inherited.
Note: You do not need to use the keyword defining an abstract class .
open
For example, there is no needopen
to markDwelling
the class with the keyword. The system already assumes that you need to subclass abstract classes to implement abstract properties and functions.
- Add the keyword at the beginning of
RoundHut
the declarationopen
.
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
main()
In , create anroundTower
instance of and output information about it.
val roundTower = RoundTower(4)
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
}
Below is the complete code.
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${
capacity}")
println("Material: ${
buildingMaterial}")
println("Has room? ${
hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- to run your code. It should now run without any errors and produce the following output.
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
Round Tower
==========
Material: Stone
Capacity: 4
Has room? false
Add multiple floors to RoundTower
As the name suggests, RoundHut
are single-story buildings, whereas towers often have multiple levels (floors).
Regarding capacity, the more floors a tower has, the more people it should be able to accommodate.
You can RoundTower
modify to have multiple floors and adjust the capacity based on the number of floors.
- Update the
RoundTower
constructor to accept another integer parameterval floors
as the number of floors. Put itresidents
after . Note that you don't need to pass this parameter to the parentRoundHut
constructor , becausefloors
is definedRoundTower
in andRoundHut
doesn'tfloors
.
class RoundTower(
residents: Int,
val floors: Int) : RoundHut(residents) {
...
}
Note: If you have multiple parameters, and the class or function definition is too long to fit on one line, you can split it into multiple lines, as shown in the code above.
- to run your code.
main()
An error occurred while creating in the methodroundTower
because you did not provide a number forfloors
the parameter . You can add missing parameters.
Alternatively, you RoundTower
can floors
add a default value for in the class definition of as shown below. The system can then create object instances with default values if no floors
value is passed to the constructor.
- In your code, add after
floors
the declaration= 2
to assign it a default value of 2.
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
...
}
- to run your code. It should compile without a hitch,
RoundTower(4)
sinceRoundTower
the object instance will now be created with the default value of 2 floors. - In
RoundTower
the class , updatecapacity
, multiply the number of floors by the previous value.
override val capacity = 4 * floors
- Run the code and notice that
RoundTower
the occupancy is now 8 people (2 floors).
Below is the finished code.
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${
capacity}")
println("Material: ${
buildingMaterial}")
println("Has room? ${
hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
}
5. Modify the classes in the hierarchy
Calculate floor area
In this exercise, you will learn how to declare an abstract function in an abstract class and then implement its function in a subclass.
All dwellings have floor area, but how it is calculated varies by dwelling shape.
Define floorArea() in the Dwelling class
- First
Dwelling
, add aabstract
floorArea()
function to the class. returnDouble
. Double, likeString
andInt
, is a data type; it can be used for floating-point numbers (that is, numbers with a decimal point followed by a fractional part, such as 5.8793).
abstract fun floorArea(): Double
All abstract methods defined in an abstract class must be implemented in any of its subclasses. You need to implement it in your subclass floorArea()
before you can run your code.
Implement floorArea() for SquareCabin
As with implementing buildingMaterial
and capacity
, since abstract
the function is defined in the parent class, you need to use override
the keyword.
- In
SquareCabin
the class , enter the keyword firstoverride
, followed by the actual implementation offloorArea()
the function , as shown below.
override fun floorArea(): Double {
}
- Returns the calculated building area. The area of a rectangle or square is calculated by multiplying the length of one side by the length of the other side. The function body is
return length * length
.
override fun floorArea(): Double {
return length * length
}
In this class, the length is a variable that varies from instance to instance, so you can add it as a constructor argument to SquareCabin
the class .
SquareCabin
Change the class definition of to add a parameterDouble
of type .length
Declare this property asval
, since the length of the building will not vary.
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
Dwelling
Has a constructor parameter residents
, so all its subclasses have that parameter. Since this is the first parameter in Dwelling
the constructor , it's a good idea to also make it the first parameter in all subclass constructors, with the parameters in the same order in all class definitions. Therefore residents
, a new length
parameter is inserted after the parameter.
main()
In , updatesquareCabin
the code that creates an instance of . Will50.0
belength
passed asSquareCabin
constructor .
val squareCabin = SquareCabin(6, 50.0)
squareCabin
Inwith
the statement for , add an output statement for Floor Area.
println("Floor area: ${
floorArea()}")
Your code won't run because you also need to implement RoundHut
in floorArea()
.
Implement floorArea() for RoundHut
RoundHut
Realize the building area for in the same way . RoundHut
is Dwelling
also a direct subclass of , so you need to use override
the keyword.
The calculation method of circular housing building area is, PI * radius * radius.
PI
is a mathematical value. It will be defined in the math library. A library is a predefined collection of functions and values, defined outside a program, but available to the program. In order to use a library function or value, you need to tell the compiler that you will use that function or value. The method is to import the function or value into the program. To use it in a program PI
, you need to import it kotlin.math.PI
.
- Imported from the Kotlin math library
PI
. Put this import statement at the top of the file,main()
before .
import kotlin.math.PI
RoundHut
ImplementfloorArea()
the function for .
override fun floorArea(): Double {
return PI * radius * radius
}
Warning: If you don't import kotlin.math.PI , you will encounter errors, so please import this library before using it. Alternatively, you can write out the fully qualified version of PI, as in "kotlin.math.PI * radius * radius", then you don't need to add import statements.
- Update the
RoundHut
constructor to pass inradius
.
open class RoundHut(
residents: Int,
val radius: Double) : Dwelling(residents) {
main()
In , update the initialization of byRoundHut
passing in the constructor .radius
10.0
roundHut
val roundHut = RoundHut(3, 10.0)
roundHut
Add an output statement to the statement of thewith
.
println("Floor area: ${
floorArea()}")
Implement floorArea() for RoundTower
Your code doesn't work yet, it fails with the following error message:
Error: No value passed for parameter 'radius'
RoundTower
In , you don't need to implement in order for the program to compile successfully floorArea()
, since it RoundHut
inherits , but you need to update RoundTower
the class definition to have RoundHut
the same radius
.
- Change the RoundTower constructor to accept the same
radius
.radius
Putresidents
after,floors
before. It is recommended to list variables with default values at the end. Remember toradius
pass to the parent class constructor.
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
main()
Update the code that initializes inroundTower
.
val roundTower = RoundTower(4, 15.5)
- Add an output statement
floorArea()
that .
println("Floor area: ${
floorArea()}")
- Now you can run your code!
- Note that
RoundTower
the calculation of is incorrect because it inherits fromRoundHut
, and does not take into accountfloors
the quantity . RoundTower
In ,override floorArea()
to give it a different implementation, multiply the area by the number of floors. Note that you canDwelling
define a function in an abstract class ( ), implement it in a subclass ( ), and then replace it again inRoundHut
a subclass of the subclass ( ).RoundTower
This is the best of both worlds, you can inherit the functions you need and replace the functions you don't need.
override fun floorArea(): Double {
return PI * radius * radius * floors
}
This code works fine, but there is a way to avoid reusing code already RoundHut
in . You can call the function RoundHut
from floorArea()
, which returns it PI * radius * radius
. Then multiply that result by floors
the number .
RoundTower
In ,floorArea()
update to usefloorArea()
the implementation . Usingsuper
the keyword , a function defined in the parent is called.
override fun floorArea(): Double {
return super.floorArea() * floors
}
- Running your code again
RoundTower
will output the correct floor area for the corresponding number of floors.
is the finished code:
import kotlin.math.PI
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${
capacity}")
println("Material: ${
buildingMaterial}")
println("Has room? ${
hasRoom()}")
println("Floor area: ${
floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
println("Floor area: ${
floorArea()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Has room? ${
hasRoom()}")
println("Floor area: ${
floorArea()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
abstract fun floorArea(): Double
}
class SquareCabin(residents: Int,
val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
override fun floorArea(): Double {
return length * length
}
}
open class RoundHut(residents: Int,
val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
override fun floorArea(): Double {
return PI * radius * radius
}
}
class RoundTower(residents: Int, radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
The output should look like this:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Floor area: 2500.0
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
Floor area: 314.1592653589793
Round Tower
==========
Material: Stone
Capacity: 8
Has room? true
Floor area: 1509.5352700498956
Note: For area values, the user experience is better if only 2 decimal places are displayed. That's outside the scope of this article, but you can use the following statement to output floor area:
println("Floor area: %.2f".format(floorArea()))
Allow new guests to get room
A room is made available to new guests using getRoom()
the function , which increments the number of guests by 1. Since this logic applies to all housings, you can implement the function Dwelling
in so that it works for all subclasses and their children. marvelous!
Notice:
- Use
if
the statement to only add guests if the capacity limit is not reached. - Print a message on the result.
- You can use
residents++
asresidents = residents + 1
a shorthand for incrementingresidents
the variable 1.
- Implement the function in
Dwelling
the classgetRoom()
.
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
Tip: In the code block above, you can replace
if
the condition to usehasRoom()
the function -if(hasRoom()) {...}
.hasRoom()
The function defined above compares the capacity to the number of guests.
- Add some output statements
roundHut
to the statement block of to use both and to observe changes.with
getRoom()
hasRoom()
println("Has room? ${
hasRoom()}")
getRoom()
println("Has room? ${
hasRoom()}")
getRoom()
The output of these output statements is as follows:
Has room? true
You got a room!
Has room? false
Sorry, at capacity and no rooms left.
See the solution code for details.
Note: This is an example to illustrate why inheritance is so powerful. You
Dwelling
can callgetRoom()
the function on all subclasses of without adding extra code to those classes.
Getting the Right Rug for a Round Housing
Let's say you need to know what side rug to get for your RoundHut
or . RoundTower
Put the corresponding function RoundHut
in to make it available for all circular housings.
- First
kotlin.math
, importsqrt()
the function from the library.
import kotlin.math.sqrt
- Implement the function in
RoundHut
the classcalculateMaxCarpetLength()
. The formula for calculating the length of a square rug that fits in a circle issqrt(2) * radius
. The figure above illustrates this.
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
Passing Double
value 2.0
into a math function sqrt(2.0)
because the function's return type is Double
not Integer
.
calculateMaxCarpetLength()
MethodsRoundHut
can nowRoundTower
be called on and instances. Inmain()
the function , addroundHut
output statements for and .roundTower
println("Carpet Length: ${
calculateMaxCarpetLength()}")
See the solution code for details.
Note: you
calculateMaxCarpetLength()
added toRoundHut
,RoundTower
which inherits the function. However, you cannot call this function onsquareCabin
an instance , because squareCabin does not define this function, nor does it define its parent class.
Congratulations! You've created a complete class hierarchy with properties and functions, and learned everything you need to know to create more utility classes!
6. Solution code
Below is the complete solution code for this post, including comments.
/**
* 为不同类型的住宅实现类的程序。
*演示实现:
* 创建具有继承性的类层次结构、变量和函数,
* 抽象类、重写和私有变量与公共变量。
*/
import kotlin.math.PI
import kotlin.math.sqrt
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${
capacity}")
println("Material: ${
buildingMaterial}")
println("Floor area: ${
floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Floor area: ${
floorArea()}")
println("Has room? ${
hasRoom()}")
getRoom()
println("Has room? ${
hasRoom()}")
getRoom()
println("Carpet size: ${
calculateMaxCarpetLength()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${
buildingMaterial}")
println("Capacity: ${
capacity}")
println("Floor area: ${
floorArea()}")
println("Carpet Length: ${
calculateMaxCarpetLength()}")
}
}
/**
* 定义所有住宅共有的属性。
* 所有的住宅都有楼面面积,
* 但它的计算方式是特定于子类的。
* 在这里实现检查和获得一个房间
* 因为它们对于所有的住宅子类都是一样的。
*
* @param residents 目前居民的数量
*/
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
/**
* 计算住宅的建筑面积。
* 由确定子类的形状实现。
*
* @return 楼层面积
*/
abstract fun floorArea(): Double
/**
* 检查是否有其他居民的空间。
*
* @return true 如果还有房间的话, false 如果没有房间的话
*/
fun hasRoom(): Boolean {
return residents < capacity
}
/**
* 将容量与居民人数进行比较
* 如果容量大于居民人数
* 通过增加居民人数来增加居民。
* 打印结果
*/
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
}
/**
* 一个方形的小屋住宅。
*
* @param residents 目前居民人数
* @param length 长度
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
/**
* 计算方形住宅的面积。
*
* @return 楼层面积
*/
override fun floorArea(): Double {
return length * length
}
}
/**
* 圆楼面面积住宅
*
* @param residents 目前居民的数量
* @param radius 半径
*/
open class RoundHut(
residents: Int, val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
/**
* 计算圆形住宅的面积。
*
* @return 楼层面积
*/
override fun floorArea(): Double {
return PI * radius * radius
}
/**
* 计算方形地毯的最大长度
* 适合圆形的地板
*
* @return 方形地毯的长度
*/
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
}
/**
* 多层圆塔
* * @param residents 目前居民的数量
* @param radius 半径
* @param floors 楼层数
*/
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
// 容量取决于层数。
override val capacity = floors * 4
/**
* 计算塔式住宅的总建筑面积
*多层面积和
*
* @return 楼面面积
*/
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
7. Summary
In this article, you learned how to:
-
Creates a class hierarchy, which is a tree structure containing classes where children inherit functions from parent classes. Subclasses inherit properties and functions.
-
Create a
abstract
class in which some functions are left to subclasses to implement. Therefore,abstract
the class cannot be instantiated. -
Create a subclass of
abstract
the class . -
Use
override
the keyword to replace properties and functions in subclasses. -
Use
super
the keyword to refer to functions and properties in the parent class. -
Marks a class
open
as enabling it to be subclassed. -
Marks a property so
private
that it can only be used within the corresponding class. -
Use the
with
constructor to make multiple calls on the same object instance. -
import function from
kotlin.math
library