当前位置: 凤凰彩票登陆 > 编程知识 > 正文

Java开拓笔记,方法的输入参数

时间:2019-09-26 07:45来源:编程知识
前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述。 一个方法可以有输入参数,也可以没有输

前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述。
一个方法可以有输入参数,也可以没有输入参数,倘若无需输入参数,则方法定义的圆括号内部直接留空。以打印当前时间为例,下面的showTime方法没有输入参数也能正常实现:

Scala名称是scalable language,表明可扩展性较强

前面介绍了匿名内部类的简单用法,通过在sort方法中运用匿名内部类,不但能够简化代码数量,还能保持业务代码的连续性。只是匿名内部类的结构仍显啰嗦,虽然它省去了内部类的名称,但是花括号里面的方法定义代码一字不落,依然生生占据了好几行代码。比如下面排序方法的调用代码例子:

// 没有输入参数,则方法名称后面的圆括号内部留空。// showTime方法的用途是显示当前时间private static void showTime() {Date date = new Date(); // 创建一个时间对象int hour = date.getHours(); // 获取当前时钟int minute = date.getMinutes(); // 获取当前分钟int second = date.getSeconds(); // 获取当前秒钟System.out.println("当前时间是"+hour+"时"+minute+"分"+second+"秒");}
  • 面向对象编程和函数式编程的结合
  • Scala代码被编译成Java字节码,运行在JVM上,可以调用已有的Java代码
  • 方便进行并发和同步处理
Integer[] intArray = { 89, 3, 67, 12, 45 };// 匿名内部类无需专门定义形态完整的类,只需指明新创建的实例从哪个接口扩展而来Arrays.sort(intArray, new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare; // 倒过来的参数顺序变成了降序}});

在main方法里面只要以下简简单单的一行代码,即可调用showTime方法,并成功运行showTime内部的时间打印代码:

面向对象

任何值都是一个对象,任何操作都是一个方法调用

1 + 2//实际上是:Int类调用+方法

尽管这种匿名内部类的代码有点别扭,然而在早期的Java编程中也只能如此了,毕竟还得按照面向对象的代码规矩来,否则缺胳膊断腿的匿名内部类,编译器怎知它是什么玩意?直到Java8推出了Lambda表达式,才迎来了匿名内部类代码优化的曙光。
Lambda表达式其实是一个匿名方法,所谓匿名方法指的是:它是个没有名字的方法,但方法体的内部代码是完整的。可是常规的方法调用都必须指定方法名称,假如匿名方法不存在方法名称,那么别的地方要怎样才能调用它呢?为了保证编译器能够识别匿名方法的真身,Java对它的调用时机规定了以下限制条件:
1、调用匿名方法的地方,本身必须知晓该位置的参数类型。举个例子,Math库的对数函数log,根据方法定义可知,它的输入参数是双精度类型,则程序员书写“Math.log”的时候,虽然这个1看不出数值类型,编译器也会自动将它转换为双精度数。
2、参数类型必须是某个接口,并且该接口仅声明了一个抽象方法。由于Java体系里的方法参数要么是基本变量类型如int、double,要么是某个类或某个接口,就是不支持把方法作为参数类型,因此需要借助接口把某个方法单独包装一下,这样每当给这个接口创建匿名内部类的时候,编译器便知道接下来只能且必定调用该接口的唯一方法。
根据以上的两个行规,对比排序方法sort可知该方法满足第一项条件,同时排序比较器Comparator也满足第二项条件,于是调用sort方法出现的匿名内部类完全支持改写为Lambda表达式。一方面,因为拥有两个参数的sort方法早已声明第二个参数是Comparator类型,所以匿名内部类当中的该接口名称允许略去;另一方面,因为比较器Comparator只有唯一的抽象方法compare,所以匿名内部类里面的方法名称也允许略去。如此一来,既省略接口名又省略方法名的Lambda排序代码示例如下:

showTime();

函数式语言

  • 函数是 first-class values,就像int和String类型,可用作为其他函数的参数,返回值,可以赋值存储在变量中

数据,函数,类等的类型分为三类
First Class 该类型的值可以作为函数的参数和返回值,也可以赋给变量。
Second Class 该类型的值可以作为函数的参数,但不能从函数返回,也不能赋给变量。(C/C++ 中的函数指针)
Third Class 该类型的值作为函数参数也不行

  • 输入值处理后映射到新的输出值,不改变原数据,即不可变数据结构(immutable data structure),或者说方法不产生副作用,即引用透明(reference transparent

副作用:side effect,改变方法外部的的变量状态或者执行I/O操作,返回值为空的函数都有副作用,副作用是不可避免的,但是可以尽可能的减少此类函数


// Lambda表达式第一招。去掉了new、接口名称、方法名称Arrays.sort(intArray, (Integer o1, Integer o2) -> {return Integer.compare; // 按照降序排列});

当然,方法定义的多数情况是存在输入参数的,并且参数格式为“参数类型 参数名称”。像闹钟的设置操作,就必须输入闹钟提醒的时分秒,或者设定闹钟在当前时刻之后的某个时间触发。于是形成了下述的setAlarm方法,该方法允许延迟若干小时后打印日志:

特点

  • 兼容性
    Scala可以和Java无缝对接,调用Java的方法,访问字段,继承类,实现接口等,不需要其他的语法,Java代码也可以调用Scala代码
    重用了很多Java的类型,也对一些类型进行了增强,通过隐式转换来实现扩展的高级操作和Java原有操作的兼容
  • 简洁
    Scala是同样的Java代码行数的一半左右
    避免了一些样板式的语法,类型推断减少了重复的类型信息,大量的库代码可以直接使用,特别是traits,便于构建一个类
// Java 类定义
  class MyClass {

      private int index;
      private String name;

      public MyClass(int index, String name) {
          this.index = index;
          this.name = name;
      }
  }

//等同的Scala类定义
  class MyClass(index: Int, name: String)
  • 高阶 high-level
//name中是否包含大写字母
val nameHasUpperCase = name.exists(_.isUpper)//_.isUpper是函数字面量,或者叫做匿名函数

//Java 8中的写法
boolean nameHasUpperCase =
    name.chars().anyMatch(
        (int ch) -> Character.isUpperCase((char) ch)
    );

可以使用其他函数作为参数,轻量,容易理解和重构,引用透明

s.exists(p) || s.exists(q) 等价 s.exists(x => p(x) || q(x)) 
  • 静态类型
    指在编译时进行类型检查
    Scala的类型系统通过类型推断避免冗长,通过模式匹配以及其他几种新方法保证灵活性
    属性验证:静态系统通过类型检查,避免某些运行错误,减少需要类型检查的次数
    安全重构:修改代码,正确率更高,例如更改函数名称,修改参数等
    文档:编译器和IDE可以提供更多的帮助,例如根据类型进行代码提示

仔细观察上述的Lambda表达式,发现compare方法的参数列表与方法体之间多了箭头标志“->”,这正是Lambda表达式的特征标记,箭头左边为匿名方法的参数列表,箭头右边为匿名方法的方法体。注意到参数列表中仍然保留了每个参数的类型名称,其实依据compare方法的定义,对于整型数组而言,此处的两个输入参数一定是Integer类型,故而参数列表里的类型名称可以统统去掉。这样进一步简化后的Lambda表达式变成了下面代码:

// 只有一个输入参数,参数格式为“参数类型 参数名称”// setAlarm方法的用途是设置指定时刻的闹钟,其中时钟为在当前时间上增加若干小时private static void setAlarm(int addedHour) {Date date = new Date();int hour = date.getHours()+addedHour;int minute = date.getMinutes();int second = date.getSeconds();System.out.println("设定的闹钟时间是"+hour+"时"+minute+"分"+second+"秒");}

起源

Scala采用了大量的Java和C#语法,他们大部分又来自C和C ++的语法。 表达式,语句,块,类,包和导入等语法大都类似Java,同时Scala也采用了Java的基本类型,库及其执行模型等。此外也借鉴了许多其他的语言,例如Ruby,Algol,Simula,ML等
同时Scala也有自己的创新,例如抽象类型abstract types,特质traits,提取器extractors等


// Lambda表达式第二招。去掉了输入参数的变量类型Arrays.sort(intArray,  -> {return Integer.compare; // 按照降序排列});

如需设定闹钟在一个小时后触发,则调用setAlarm方法时可填写参数1,正如下面这行代码:

语法概览

尽管上面的Lambda表达式已经足够简洁了,但对于这种内部只有一行代码的方法体来说,还能用点劲继续压缩代码。首先,只有一行代码的话,包裹方法体的花括号赶紧去掉;其次,compare方法需要一个整型返回值,刚好“Integer.compare”返回的正是整型数,因而这行代码前面的return也可去掉,顺便把末尾的分号一块扔了。于是经过三次精简的Lambda排序代码如下所示:

setAlarm;

定义变量

有两种类型的变量valvarval类似于Java中的final变量, 一旦初始化,不能被重新赋值,
var可以重新赋值

Scala的解释器或编译器可以推断类型

scala> val msg = "Hello, world!"
msg: String = Hello, world!

scala> val msg2: java.lang.String = "Hello again, world!"//显式指定类型
msg2: String = Hello again, world!
// Lambda表达式第三招。去掉了方法体的花括号,以及方法返回的return和分号Arrays.sort(intArray,  -> Integer.compare;

若想输入多个参数,那么在圆括号内通过逗号来分隔参数列表。例如下面的setAlarm方法,支持同时输入小时数和分钟数:

定义函数

函数开头是def关键字,之后是函数名,圆括号内是通过逗号分隔的参数列表,参数的类型必须指定(不会进行类型推断),冒号后跟返回类型,之后是等号后跟一对花括号,内部是函数体(函数相当于返回一个数值的表达式块)

def max(x: Int, y: Int): Int = {
    //Scala的 if 语句可以返回一个值
    if (x > y) 
        x
    else 
        y
}

图片 1

基本的函数定义形式

函数参数类型是val,不能重新赋值
返回类型可以推断时,可以省略,没有返回值对应Unit类型(Java中的void类型)
函数只有一条语句,花括号可以省略

这下终于把Lambda表达式压缩到了极致,连同sort方法在内都只有短短一行,比起匿名内部类的实现代码又前进了一大步。
再来一个字符串数组的排序练练手,有利于加深大家对Lambda表达式的理解。在上一篇文章中,对字符串数组按照长度排序的功能,通过匿名内部类的实现代码是下面这样的:

// 有两个输入参数,参数格式为“参数1类型 参数1名称, 参数2类型 参数2名称”// 下面的setAlarm方法与上面的setAlarm方法名称相同,但参数个数不同,该情况被称作方法重载。// 虽然两个方法的方法名称一样,但是编译器仍然能够根据参数个数和参数类型来判断当前要调用的是哪个方法private static void setAlarm(int addedHour, int addedMinute) {Date date = new Date();int hour = date.getHours()+addedHour;int minute = date.getMinutes()+addedMinute;int second = date.getSeconds();System.out.println("设定的闹钟时间是"+hour+"时"+minute+"分"+second+"秒");}

匿名函数

也叫作函数字面量(function literal),通常作为函数的参数进行传递

图片 2

匿名函数语法

//功能相同的遍历打印语句
args.foreach(arg => println(arg))
args.foreach((arg: String) => println(arg))
args.foreach(println) //对应函数为带一个参数的一条语句

for (arg <- args)
    println(arg)
// 通过匿名内部类对字符串数组按照字符串长度进行排序private static void sortStrArrayByLength() {String[] strArray = { "说曹操曹操就到", "东道主", "风马牛不相及", "亡羊补牢", "无巧不成书","冰冻三尺非一日之寒", "同窗", "青出于蓝而胜于蓝" };// 字符串数组的默认排序方式为根据首字母的拼写顺序,// 下面的匿名内部类把排序方式改成了按照字符串长度进行排序Arrays.sort(strArray, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {// 比较前后两个数组元素的字符串长度大小return o1.length() < o2.length() ? -1 : 1;}});String desc = "字符串数组比较字符串长度的升序结果为:";for (String item : strArray) {desc = desc + item + ", ";}System.out.println;}

注意到带两个参数的setAlarm方法和带一个参数的setAlarm方法居然同名,为啥变量不能重名,方法却能重名呢?这是因为这两个方法的参数个数不一样,即使代码里的方法名称看起来相同,其实编译器会偷偷给它俩改名。比如只带一个参数的setAlarm方法,编译器给它的编号可能是“setAlarm_1”;而带两个参数的setAlarm方法,编译器可能给它分配编号“setAlarm_2”。所以只要参数个数不同,或者参数类型不同,代码中的同名方法都会被编译器当作不同的方法,这种情况也称作“方法重载”。
有了方法重载,再来第三个、第四个参数,也能通过重载同名方法来实现。可是如此一来,方法数量就多了许多,有没有一种机制能够动态调整参数的个数呢?该机制在Java中叫做可变参数,意思是参数的个数是允许变化的,只要这些参数的类型保持一致即可。仍旧以闹钟为例,提醒的时间单位时、分、秒分别对应三个整形参数,那么完全可以定义整型的可变参数,参数的数量可多可少,有几个参数就用几个参数。Java的参数“可变”符号利用变量类型后面的三点号“...”来表示,比如“int...”表示整型的可变参数,而“double...”表示双精度型的可变参数。于是采用了可变参数的setAlarm方法便改写成下面这样:

数组

访问数组中的元素,索引放在圆括号内val类型的数组本身的内容是可变的

val greetStrings = new Array[String](3)
//初始化每个元素
greetStrings(0) = "Hello" 
greetStrings(1) = ", "
greetStrings(2) = "world!n"
for (i <- 0 to 2)
    print(greetStrings(i))

//等价于 
greetStrings.update(0, "Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!n")
for (i <- 0.to(2))
    print(greetStrings.apply(i))

0 to 2 对应方法调用 (0).to(2)如果方法只有一个参数,则可以不带圆点或圆括号,但是必须指定方法调用的调用方

Scala没有运算符重载,因为它实际上并没有传统意义上的运算符,但是可以在方法名称中使用+, -, *, /等字符
访问:将包含一个或多个值的括号应用于变量时,Scala会将代码转换为调用该变量名为apply的方法
赋值:当对包含圆括号的变量进行赋值时,编译器会将其转换为update的方法,该方法将括号中的参数以及等号右侧的对象作为参数

更简单的方法,等同调用伴生对象的apply工厂方法

val numNames = Array("zero", "one", "two")//等价于Array.apply("zero", "one", "two")

现在把排序器的匿名内部类代码改写为匿名方法,则精兵简政之后的Lambda表达式缩短到了如下一行代码:

// 参数类型后面添加三个点号“...”,表示这里的参数数量并不固定,可以有一个、两个,也可以有三个,也可以没有参数。// 故而此时的输入参数被称为可变参数,意思是参数的数量允许变化,“...”可以看作是方法参数的省略号。private static void setAlarm(int... addedNumber) {Date date = new Date();int hour = date.getHours();int minute = date.getMinutes();int second = date.getSeconds();// 可变参数的数量也是通过“.length”获得if (addedNumber.length > 0) {// 获取指定位置的可变参数,依然通过下标“[数字]”实现,就像是访问数组元素一般hour += addedNumber[0];}if (addedNumber.length > 1) {minute += addedNumber[1];}if (addedNumber.length > 2) {second += addedNumber[2];}System.out.println("可变参数设定的闹钟时间是"+hour+"时"+minute+"分"+second+"秒");}

链表 List

类型相同的不可变对象序列scala.List

// ::: 是连接两个链表 concatenation
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour

//空链表用List() 或者 Nil 表示
val oneTwoThree = 1 :: 2 :: 3 :: Nil

运算符函数,通常是在左操作数上调用该方法,例如a.*(b), 但是如果方法名以冒号结尾,则在右操作数上调用该方法,例如threeFour.:::(oneTwo)

// 下面的Lambda表达式把排序方式改成了按照字符串长度进行排序Arrays.sort(strArray,  -> o1.length() < o2.length() ? -1 : 1);

外部调用带可变参数的方法之时,既允许不输入任何参数,也允许输入多个参数。以下即为拥有可变参数的setAlarm方法的调用代码例子:

元组 Tuple

类型可以不同的不可变对象容器
要实例化一个包含某些对象的新元组,只需将对象放在括号中,并用逗号分隔,之后可以用._N(从1开始索引)访问

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

别看Lambda代码如此精炼,该做什么编译器一个都没落下。运行包含Lambda表达式的测试代码,输出的日志结果明明白白,可见字符串数组果然按照升序排列了。

setAlarm();setAlarm(1, -10, 3);

Set/Map

集合库分为可变集合和不可变集合两种类型。 例如,数组总是可变的,列表总是不变的。 Scala还为Set/Map提供了可变和不可变的库,并且简单名称相同,完全限定名称不同,因为每个名称都位于不同的包中

var jetSet = Set("Boeing", "Airbus")  //本质上调用apply方法,默认是immutable类型
jetSet += "Lear" //等同jetSet = jetSet + "Lear"
println(jetSet.contains("Cessna"))

如果使用mutable类型

import scala.collection.mutable
val movieSet = mutable.Set("Hitch", "Poltergeist")
movieSet += "Shrek" //调用方法 += : movieSet.+=("Shrek")
println(movieSet)

明确使用HashSet

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")

Map的情况:

import scala.collection.mutable
val treasureMap = mutable.Map[Int, String]()
treasureMap += (1 -> "Go to island.")
treasureMap += (2 -> "Find big X on ground.")
treasureMap += (3 -> "Dig.")
println(treasureMap(2))

Scala程序中的任何对象上调用->方法,返回一个包含键和值的双元素元组

默认immutable的情况

val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V")
println(romanNumeral(4))
字符串数组比较字符串长度的升序结果为:同窗, 东道主, 亡羊补牢, 无巧不成书, 风马牛不相及, 说曹操曹操就到, 青出于蓝而胜于蓝, 冰冻三尺非一日之寒, 

注意,如果已经存在同名且参数个数确定的方法,则编译器优先调用参数个数确定的方法。只有不存在参数个数确定的同名方法,编译器才会调用定义了可变参数的方法。
输入参数的类型还可以是数组,例如整型数组的参数定义格式为“int[] 参数名称”,这样方法内部就能将该参数当作数组一样来操作。使用了数组参数的闹钟设置方法setAlarmByArray代码示例如下:

  

// 编译器认为“int...”与“int[] ”类型相同,// 所以不允许定义参数分别为“int...”和“int[] ”的同名方法private static void setAlarmByArray(int[] addedNumber) {Date date = new Date();int hour = date.getHours();int minute = date.getMinutes();int second = date.getSeconds();if (addedNumber.length > 0) {hour += addedNumber[0];}if (addedNumber.length > 1) {minute += addedNumber[1];}if (addedNumber.length > 2) {second += addedNumber[2];}System.out.println("设定的闹钟时间是"+hour+"时"+minute+"分"+second+"秒");}

更多Java技术文章参见《Java开发笔记章节目录》

可见该方法的内部代码竟然与采用可变参数的setAlarm代码是一样的,这缘于编译器把“int...”和“int[]”看作是同一种类型,既然是同一种类型,那么这两个办法就不能叫一样的名称,只能换别的名称才行。此外,二者被外部调用时也有差别,带可变参数的方法,调用时输入的参数列表以逗号分隔;而带数组参数的方法,它的输入参数必须是数组类型,就像下面的调用代码例子那样:

// 下面setAlarmByArray方法的输入参数为数组类型int[] addedArray = {1, -10, 3};setAlarmByArray(addedArray);

至此终于把方法的几种输入参数变化讲完了,最后总结一下与输入参数有关的几个要点:
1、两个名称相同,但是参数个数与参数类型不同的方法,是通过方法重载机制区分开的。
2、输入参数在变量类型后面添加三点号“...”,表示这个输入参数是可变参数,调用时填写的参数数量可多可少。
3、可变参数与数组参数在方法内部的处理代码基本没有区别,但在外部调用时书写的参数形式是不一样的。

 

更多Java技术文章参见《Java开发笔记章节目录》

编辑:编程知识 本文来源:Java开拓笔记,方法的输入参数

关键词: