九、Kotlin 函数

功能是我们应用的基石。我们编写执行特定任务的函数,然后在需要执行特定任务时调用它们。由于我们需要在应用中执行的任务会有很大不同,我们的功能需要适应这一点,并且非常灵活。Kotlin 函数非常灵活,比其他安卓相关语言更加灵活。因此,我们需要花一整章的时间来了解它们。函数与面向对象编程密切相关,一旦我们理解了函数的基础知识,我们就能很好地接受面向对象编程的更广泛学习。

这就是我们为本章准备的内容:

  • 功能基础和概述
  • 函数返回类型和 return 关键字
  • 单表达式函数
  • 默认参数
  • 更多与功能相关的主题

我们已经对函数有了一点了解,所以简单回顾一下。

功能基础和概述

我们已经看到并使用了功能。有些是安卓应用编程接口为我们提供的,比如onCreate和其他生命周期功能。

我们自己写别人;例如topClickbottomClick。然而,我们还没有很好地解释它们,功能比我们目前看到的更多。

你会经常听到另一个与功能密切相关且几乎同义的术语。如果您以前学习过 Java 或其他面向对象语言,情况尤其如此。我指的是这个词。从技术角度来看,方法和函数之间的区别很少是重要的,部分区别在于,在我们的代码中,函数/方法是在哪里声明的。如果你想在程序上正确,你可以阅读这篇文章,它深入探讨并提供了多种观点:

https://stackoverflow . com/questions/155609/方法和功能有什么区别

在这本书里,我将把所有的方法/函数称为函数。

基本功能声明

下面是一个非常简单的函数的例子:

fun printHello(){ 
  Log.i("Message=","Hello") 
}

我们可以这样调用printHello函数:

printHello()

结果将是 logcat 窗口中的输出:

Message=: Hello

函数的第一行是声明,左右花括号内包含的所有代码都是函数。我们使用fun关键字,后跟函数的名称,后跟一个左右括号。名称是任意的,但是最好使用描述函数功能的名称。

功能参数列表

声明可以采取多种形式,这给了我们很大的灵活性和力量。让我们再看一些例子:

fun printSum(a: Int, b: Int) { 
  Log.i("a + b = ","${a+b}") 
}

前面的printSum函数可以如下调用:

printSum(2, 3)

调用printSum函数的结果是将以下消息输出到 logcat 窗口:

a + b =: 5

请注意,传递给函数的23值是任意的。我们可以传递任何我们喜欢的价值观,只要它们是Int类型的。

申报(a: Int, b: Int)的这部分叫做参数表,或者只是参数。它是函数为了成功执行而期望和需要的类型列表。参数表可以有多种形式,任何 Kotlin 类型都可以是参数表的一部分,包括完全没有参数(如我们在第一个例子中看到的)。

当我们调用带有参数列表的函数时,我们必须在调用时提供匹配的参数。以下是我们可以调用前面的printSum函数示例的其他几种可能方式:

val number1 = 35
val number2 = 15
printSum(9, 1)// Prints a + b: = 10
printSum(10000, 1)// Prints a + b: = 10001
printSum(number1, number2)// Prints a + b: = 50
printSum(65, number1)// Prints a + b: = 100

如前面的例子所示,任何两个Int值的组合都可以作为参数。我们甚至可以使用表达式作为参数,只要它们等于Int值。这个调用也可以,例如:

printSum(100 - 50, number1 + number2)// Prints a + b = 100

在前面的例子中,从 100 中减去 50,结果(50)作为第一个参数传递,将number1加到number2中,结果作为第二个参数传递。

下面是另一对带有不同参数的函数,接下来是我们如何调用它们的例子:

// These functions would be declared(typed) 
// outside of other functions
// As we did for topClick and bottomClick
fun printName(first: String, second: String){
  Log.i("Joined Name =","$first $second")
}

fun printAreaCircle(radius: Float){
  Log.i("Area =","${3.14 * (radius *radius)}")
}
//
// This code calls the functions
// Perhaps from onCreate or some other function
val firstName = "Gabe"
val secondName = "Newell"

// Call function using literal String
printName("Sid","Meier")

// Call using String variables
printName(firstName, secondName)

// If a circle has a radius of 3 
// What is the area
printAreaCircle(3f)

在我们讨论代码之前,让我们看一下从中获得的输出:

Joined Name =: Sid Meier
Joined Name =: Gabe Newell
Area =: 28.26

在前面的代码中,我们声明了两个函数。第一个叫做printName,它有两个String参数。接下来将再次显示该声明以及高亮显示的参数名称。名称是任意的,但是使用有意义的名称将使代码更容易理解:

fun printName(first: String, second: String){
  Log.i("Joined Name =","$first $second")
}

试图用两个String值以外的任何值作为参数来调用函数将导致错误。当我们调用这个函数时,firstsecond参数被初始化为变量,然后我们在一个字符串模板中使用这些变量将连接的名称打印到 logcat 窗口中。实现这一点的代码行再次显示如下,变量高亮显示:

Log.i("Joined Name =","$first $second")

注意代码中$first$second之间的空格。请注意,这个空间也存在于我们之前看到的输出中。

第二个功能是printAreaCircle。它有一个名为radiusFloat参数。这里还是为了方便参考:

fun printAreaCircle(radius: Float){
  Log.i("Area =","${3.14 * (radius * radius)}")
}

该函数使用radius变量,该变量在调用该函数时被初始化,以使用公式3.14 * (radius * radius)计算圆的面积。

然后,代码继续调用第一个函数两次,调用第二个函数一次。这在下面的代码片段中再次显示(为了突出重点,删除了有用的注释):

val firstName = "Gabe"
val secondName = "Newell"

printName("Sid","Meier")
printName(firstName, secondName)

printAreaCircle(3f)

请注意,我们可以使用文字值或变量调用函数,前提是它们的类型与声明的参数相匹配。

明确地说,函数声明在任何其他函数之外,但是在类的左括号和右括号内。函数调用在onCreate函数内部。随着我们的应用变得越来越复杂,我们将从所有代码(甚至其他代码文件)中调用函数。onCreate功能只是讨论这些话题时方便使用的地方。

类型

如果您想更仔细地检查代码结构,包含该代码的文件在Chapter09/Functions Demo文件夹中。创建一个新的空活动项目,您可以复制并粘贴代码来处理它。

另一点可能很明显,但值得一提的是,当我们为真正的应用编写函数时,它们可以包含尽可能多的实用代码;它们不会像这些例子一样只是一行代码。我们在前面章节中学习的任何代码都可以进入我们的函数。

现在,让我们继续讨论另一个与函数相关的主题,它为我们提供了更多的选择。

返回类型和返回关键字

很多时候,我们需要从函数中得到一个结果。仅仅让函数知道结果是不够的。函数可以声明为具有返回类型。请看下一个函数声明:

fun getSum(a: Int, b: Int): Int { 
  return a + b 
}

在前面的代码中,查看参数列表右括号后的高亮部分。:Int代码意味着函数可以并且必须向调用它的代码返回一个Int类型的值。函数体内部的代码行使用return关键字来实现这一点。return a + b代码返回ab的总和。

我们可以像调用没有返回类型的函数一样调用getSum函数:

getSum(10, 10)

前面的代码行可以工作,但是有点无意义,因为我们对返回值不做任何事情。下面这段代码显示了对getSum函数的更可能的调用:

val answer = getSum(10, 10)

在前面的函数中,函数返回的值用于初始化answer变量。由于返回类型为Int,Kotlin 推断answer也属于Int类型。

我们还可以以其他方式使用getSum——下面显示了一个这样的例子:

// Print out the returned value
Log.i("Returned value =","${getSum(10, 10)}")

前面的代码以另一种方式使用getSum函数,通过使用字符串模板打印返回值来打印到 logcat 窗口。

任何类型都可以从函数中返回。这里举几个例子;首先是声明,然后是一些我们可以称之为声明的方式:

// Return the area of the circle to the calling code
fun getAreaCircle(radius: Float): Float{
  return 3.14f * (radius * radius)
}

// Return the joined-up String to the calling code
fun getName(first: String, second: String): String{
  return "$first $second"
}

// Now we can call them from elsewhere in the code
Log.i("Returned area =","${getAreaCircle(3f)}")
Log.i("Returned name =","${getName("Alan","Turing")}")

这是这两个函数调用将产生的输出:

Returned area =: 28.26
Returned name =: Alan Turing

我们可以看到,一个圆的面积被检索和打印,连接在一起的名字和姓氏被检索和打印。

作为一个快速的健全性检查,值得指出的是,我们实际上不需要仅仅为了将数字加在一起或连接字符串而编写函数。这只是演示各种功能的一种有用方式。

还值得注意的是return关键字即使在函数没有返回类型时也有它的用途。

例如,我们可以使用return关键字提前从函数中返回。当主体中的最后一行代码执行完毕时,我们前面的所有函数示例(没有返回类型)都会自动返回到调用代码。下面是一个我们使用return关键字的例子:

fun printUpTo3(aNumber: Int){ // No return type!
  if(aNumber > 3){
    Log.i("aNumber is","TOO BIG! - Didn't you read my name")
    return // Going back to the calling code
  }

  Log.i("aNumber is","$aNumber")
}

// And now we call it with a few different values
printUpTo3(1)
printUpTo3(2)
printUpTo3(3)
printUpTo3(4)

看看我们运行前面代码时的输出,然后我们将讨论它是如何工作的:

aNumber is: 1
aNumber is: 2
aNumber is: 3
aNumber is: TOO BIG! - Didn't you read my name

在函数体中,if表达式检查aNumber是否大于三,如果大于三,则打印一条不满的注释,并使用return关键字返回调用代码,避免将值打印到 logcat。从程序输出中,我们可以看到当aNUmber为一、二或三时,它是由printUpTo3函数尽职尽责地打印出来的,但是当我们一传入值 4 时,我们就得到替代结果。

功能体和单表达式功能

身体的功能可以是我们需要的复杂或简单。到目前为止,我展示的所有例子都故意过于简单,这样我们就可以将注意力集中在特定的函数上,而不是函数中的代码上。随着本书通过更多真实世界的例子,我们将看到函数体中的代码变得更长、更复杂。然而,职能机构应该坚持执行一项具体任务。如果你在 Android Studio 中有一个占据整个屏幕的功能,很可能是应该拆分成多个功能的标志。

当你有一个非常简单的函数体只包含一个表达式时,Kotlin 允许我们通过使用单表达式语法来缩短代码。例如,getSum功能可以更改为以下代码:

fun getSum(a: Int, b: Int) = a + b

在前面的示例中,我们去掉了通常将代码包装在正文中的花括号,并推断出了返回类型,因为将a添加到b只能得到一个Int变量,因为ab本身就是Int变量。

使功能灵活

由于函数是我们代码的构建块,它们需要是通用的,以满足我们可能需要做的任何事情。我们已经看到了如何创建各种各样的参数列表和返回类型,以及在代码中决定何时返回到调用代码。随着我们的进步,你会发现我们需要更多的选择。接下来是我们现在将介绍的更多 Kotlin 函数选项的快速浏览,然后在整本书的不同地方开始真正使用。

默认和命名参数

一个默认参数是我们程序员为一个参数提供一个值(默认值),如果调用函数的代码没有提供这个值,那么这个参数就会被使用。一个命名参数是当调用函数的代码指定一个名称和一个值。请注意,提供值是可选的。仅仅因为给定了参数的默认值,并不妨碍调用代码通过提供它来重写它。请看下面的例子:

fun orderProduct(giftWrap: Boolean = false,
                product: String,
                postalService: String = "Standard") {

   var details: String = ""

   if (giftWrap) {
       details += "Gift wrapped "
   }

   details += "$product "
   details += "by $postalService postage"

   Log.i("Product details",details)
}

// Here are some ways we can call this function
orderProduct(product = "Beer")
orderProduct(true, product = "Porsche")
orderProduct(true, product = "Barbie (Jet-Set Edition)", postalService = "Next Day")

orderProduct(product = "Flat-pack bookcase", 
   postalService = "Carrier Pigeon")

在前面的代码中,我们首先声明一个名为orderProduct的函数。请注意,在参数列表中,我们已经声明了两个默认值,为了清楚起见,下面将重新打印并突出显示:

fun orderProduct(giftWrap: Boolean = false,
       product: String,
       postalService: String = "Standard") {

当我们调用函数时,我们可以在不指定giftwrap和/或postalService值的情况下这样做。下面代码中的第一个函数调用清楚地表明了这一点:

orderProduct(product = "Beer")

请注意,当这样做时,我们需要指定参数的名称,该名称必须与参数列表中的名称以及类型相匹配。在第二个函数调用中,我们为giftwrapproduct指定一个值:

orderProduct(true, product = "Porsche")

在第三种情况下,我们为所有三个参数指定一个值,如下面的代码所示:

orderProduct(true, product = "Barbie (Jet-Set Edition)",
   postalService = "Next Day")

最后,在第四部分,我们指定最后两个参数:

orderProduct(product = "Flat-pack bookcase", 
   postalService = "Carrier Pigeon")

函数内部的代码首先声明一个名为detailsvar变量,这是一个String值。如果giftwrap的值为真,则Gift Wrapped被追加到Product details中。接下来将product的值附加到details上,最后将postalService的值附加到任一侧的文字String值上。

如果我们运行代码,这是 logcat 窗口中的输出:

Product details: Beer by Standard postage
Product details: Gift wrapped Porsche by Standard postage
Product details: Gift wrapped Barbie (Jet-Set Edition) 
 by Next Day postage
Product details: Flat-pack bookcase by Carrier Pigeon postage

我们可以调用函数的各种方法非常有用。在其他编程语言中,当您希望能够以不同的方式调用相同的命名函数时,您必须提供该函数的多个版本。虽然学习命名参数和默认参数可能会增加一点复杂性,但这肯定比必须编写四个版本的orderProduct函数要好。这一点,以及类型推断,只是你经常会听到程序员赞美 Kotlin 简洁本质的两个原因。

使用命名参数和默认参数,我们可以选择提供函数允许的尽可能多或尽可能少的数据。简单地说,如果我们为所有没有默认值的参数提供值,它就会工作。

类型

如果你想玩这个代码,那么本章的所有例子都在Chapter09文件夹中。创建一个空活动项目,然后将函数复制并粘贴到MainActivity类中,函数调用到onCreate函数中。

当我们这样做的时候,有一些警告,当我们在整本书中使用一些更真实的例子时,我们会看到它们。

更多功能

函数还有更多,比如顶级函数、局部函数、变量变元函数,以及函数访问级别,但这些最好在类和面向对象编程的主题旁边或之后讨论。

总结

在本章中,我们在学习函数方面取得了良好的进展。虽然从第一章开始,函数就潜伏在我们的代码中,但我们终于可以正式学习和理解它们了。我们了解了函数的不同部分:名称、参数和返回类型。我们已经看到,函数实际上做的事情进入了开括号和闭括号内,称为函数体。

我们还看到,通过使用return关键字,我们可以随时从函数中返回,并且我们还可以将返回类型与return 关键字结合使用,以使函数中的数据可供最初调用该函数的代码使用。

我们学习了如何使用默认和命名参数来提供同一个函数的不同版本,而无需编写多个函数。这使得我们的代码更加简洁和易于管理。

我们还发现,函数的内容甚至比我们在本章中介绍的还要多,但是最好了解这些主题,因为它们出现在贯穿本书的各种项目中。

接下来,我们将进入最落后的一章。我一直参考并遵从第 10 章面向对象编程。最后,就在这里,我们将看到类和对象结合 Kotlin 的真正力量。在接下来的几章中,我们将很快看到类和对象是释放安卓应用编程接口能力的关键。我们将很快能够让我们的用户界面变得生动起来,并将构建一些真实、可用的应用,我们可以将其发布到 Play 商店。