第07部分:方法

方法是有名称的 Java 语句序列,可被其他 Java 代码调用。调用方法时,可以传入零个或多个值,这些值叫参数。方法执行一些计算,还可以返回一个值。前面介绍过,方法调用是 Java 解释器计算的表达式。不过,因为方法调用可以有副作用,因此,也能作为表达式语句使用。这里不讨论方法调用,只说明如何定义方法。

定义方法

方法主体就是放在花括号里的任意语句序列。更有趣的是方法的签名。签名指定下述内容:

1   方法的名称;

2   方法所用参数的数量、顺序、类型和名称;

3   方法的返回值类型;

4   方法能抛出的已检异常(签名还能列出未检异常,不过不是必需的);• 提供方法额外信息的多个方法修饰符。

方法签名定义了调用方法之前需要知道的一切信息,是方法的规范,而且定义了方法的API。若想使用 Java 平台的在线 API 参考指南,需要知道如何阅读方法签名。若想编写Java 程序,需要知道如何定义自己的方法。方法都以方法签名开头。方法签名的格式如下:

modifiers   type   name (   params   ) [   throws   exceptions   ]

签名(方法规范)后面是方法主体(方法的实现),即放在花括号里的 Java 语句序列。抽象方法没有实现部分,方法主体使用一个分号表示。

方法签名中可能包含类型变量声明,这种方法叫泛型方法(generic method)。下面是一些方法定义示例,都以签名开头,后面跟着方法主体:

// 这个方法传入的是字符串数组,没有返回值

// 所有Java程序的入口都是这个名称和签名

public static void main(String[] args) {

                if (args.length > 0)    System.out.println("Hello " + args[0]);

                else    System.out.println("Hello world");

}


// 这个方法传入两个double类型的参数,返回一个double类型的数字

static double distanceFromOrigin(double x, double y) {

                return Math.sqrt(x*x + y*y);

}

// 这是抽象方法,没有主体

// 注意,调用这个方法时可能会抛出异常

protected abstract String readText(File f, String encoding) throws FileNotFoundException, UnsupportedEncodingException;


modifiers 是零个或多个特殊的修饰符关键字,之间使用空格分开。例如,声明方法时可以使用 public 和 static 修饰符。

方法签名中的 type 指明方法返回值的类型。如果方法没有返回值,type 必须是 void。如果声明方法时指定了返回类型,就必须包含一个 return 语句,返回一个符合(或能转换为)所声明类型的值。


构造方法是一段类似方法的代码,用于初始化新建的对象。构造方法的定义方式和方法类似,不过签名中没有 type 部分。


方法的修饰符和返回值类型后面是 name,即方法名。方法名和变量名一样,也是 Java 标识符。和所有 Java 标识符一样,方法名可以包含 Unicode 字符集能表示的任何语言的字母。定义多个同名方法是合法的,往往也很有用,只要各方法的参数列表不同就行。定义多个同名方法叫方法重载(method overloading)。


和某些其他语言不同,Java 没有匿名方法。不过,Java 8 引入了 lambda 表达式,作用类似于匿名方法,但是 Java 运行时会自动把 lambda 表达式转换成适当的具名方法。


例如,我们见过的 System.out.println() 方法就是重载方法。具有这个名字的某个方法打印字符串,而具有这个名字的其他方法打印各种基本类型的值。Java 编译器根据传入这个方法的参数类型决定调用哪个方法。


定义方法时,方法名后一定是方法的形参列表(parameters list),而且必须放在括号里。形参列表定义零个或多个传入方法的实参(argument)。如果有形参的话,每个形参都包含类型和名称,(如果有多个形参)形参之间使用逗号分开。调用方法时,传入的实参值必须和该方法签名中定义的形参数量、类型和顺序匹配。传入的值不一定要和签名中指定的类型一样,但是必须能不经校正转换为对应的类型。


如果 Java 方法没有实参,其形参列表是 (),而不是 (void)。C 和 C++ 程序员要特别注意,Java 不把 void 当作一种类型。


Java 允许程序员定义和调用参数数量不定的方法,使用的句法叫变长参数(varargs),后面会详细介绍。


方法签名的最后一部分是 throws 子句,列出方法能抛出的已检异常(checked exception)。已检异常是一系列异常类,必须在能抛出它们的方法中使用 throws 子句列出。如果方法使用 throw 语句抛出一个已检异常,或者调用的其他方法抛出一个没有捕获或处理的已检异常,声明这个方法时就必须指明能抛出这个异常。如果方法能抛出一个或多个已检异常,要在参数列表后面使用 throws 关键字指明能抛出的异常类。如果方法不会抛出异常,无需使用 throws 关键字。如果方法抛出的异常类型不止一个,要使用逗号分隔异常类的名称。






方法修饰符

方法的修饰符包含零个或多个修饰符关键字,例如 public、static 或 abstract。下面列出允许使用的修饰符及其意义。

abstract

使用 abstract 修饰的方法没有实现主体。组成普通方法主体的花括号和 Java 语句使用一个分号代替。如果类中有使用 abstract 修饰的方法,类本身也必须使用 abstract 声明。这种类不完整,不能实例化。


final

使用 final 修饰的方法不能被子类覆盖或隐藏,能获得普通方法无法得到的编译器优化。所有使用 private 修饰的方法都隐式添加了 final 修饰符;使用 final 声明的任何类,其中的所有方法也都隐式添加 final 修饰符。


native

native 修饰符表明方法的实现使用某种“本地”语言编写,例如 C 语言,并且开放给Java 程序使用。native 修饰的方法和 abstract 修饰的方法一样,没有主体:花括号使用一个分号代替。

Java 刚出现时,使用 native 修饰方法有时是为了提高效率。现在几乎不需要这么做了。现在,使用 native 修饰方法的目的是,把 Java 代码集成到现有的 C 或 C++ 库中。native 修饰的方法和所在平台无关,如何把实现和方法声明所在的 Java 类链接起来,取决于 Java 虚拟机的实现方式。


public、protected、private

这些访问修饰符指定方法是否能在定义它的类之外使用,或者能在何处使用。


static

使用 static 声明的方法是类方法,关联在类自己身上,而不是类的实例身上。


strictfp

在这个很少使用的奇怪修饰符中,fp 的意思是“浮点”(floating point)。一般情况下,Java 会利用运行时所在平台的浮点硬件提供的可用扩展精度。添加这个关键字后,运行strictfp 修饰的方法时,Java 会严格遵守标准,而且就算结果不精确,也只使用 32 位或 64 位浮点数格式进行浮点运算。


synchronized

synchronized 修饰符的作用是实现线程安全的方法。线程调用 synchronized 修饰的方法之前,必须先为方法所在的类(针对 static 修饰的方法)或对应的类实例(针对没使用 static 修饰的方法)获取一个锁,避免两个线程同时执行该方法。synchronized 修饰符是实现的细节(因为方法可以通过其他方式实现线程安全),不是方法规范或 API 的正式组成部分。好的文档应该明确说明方法是否线程安全,使用多线程程序时不能依赖于是否有 synchronized 关键字。注解是特例,注解可以看作方法修饰符和额外补充信息的折中方案。






已检异常和未检异常

Java 的异常处理机制会区分两种不同的异常类型:已检异常和未检异常。

已检异常和未检异常之间的区别在于异常在什么情况下抛出。已检异常在明确的特定情况下抛出,经常是应用能部分或完全恢复的情况。

例如,某段代码要在多个可能的目录中寻找配置文件。如果试图打开的文件不在某个目录中,就会抛出 FileNotFoundException 异常。在这个例子中,我们想捕获这个异常,然后在文件可能出现的下一个位置继续尝试。也就是说,虽然文件不存在是异常状况,但可以从中恢复,这是意料之中的失败。


然而,在 Java 环境中有些失败是无法预料的,这些失败可能是由运行时条件或滥用库代码导致的。例如,无法正确预知 OutOfMemoryError 异常;又如,把无效的 null 传给使用对象或数组的方法,会抛出 NullPointerException 异常。

这些是未检异常。基本上任何方法在任何时候都可能抛出未检异常。这是 Java 环境中的墨菲定律:“会出错的事总会出错。”从未检异常中恢复,虽说不是不可能,但往往很难,因为完全不可预知。

若想区分已检异常和未检异常,记住两点:异常是 Throwable 对象,而且异常主要分为两类,通过 Error 和 Exception 子类标识。只要异常对象是 Error 类,就是未检异常。Exception 类还有一个子类 RuntimeException,RuntimeException 类的所有子类都属于未检异常。除此之外,都是已检异常。

处理已检异常

Java 为已检异常和未检异常制定了不同的规则。如果定义的方法会抛出已检异常,就必须在方法签名的 throws 子句中声明这个异常。Java 编译器会检查方法签名,确保的确声明了;如果没声明,会导致编译出错(所以才叫“已检异常”)。

就算自己从不抛出已检异常,有时也必须使用 throws 子句声明已检异常。如果方法中调用了会抛出已检异常的方法,要么加入异常处理代码处理这个异常,要么使用 throws 子句声明这个方法也能抛出这个异常。

例如,下述方法使用标准库中的 java.net 和 URL 类访问网页,尝试估算网页的大小。所用的方法和构造方法会抛出各种 java.io.IOException 异常对象,所以在 throws 子句中声明了:

public static estimateHomepageSize(String host) throws IOException {

                URL url = new URL("htp://"+ host +"/");

                try (InputStream in = url.openStream()) {

                                return in.available();

                }

}

其实,上述代码有个问题:协议名拼写错了——没有名为 htp:// 的协议。所以,estimate-HomepageSize() 方法会一直失败,抛出 MalformedURLException 异常。

你怎么知道要调用的方法会抛出已检异常呢?可以查看这个方法的签名。如果签名中没有,但又必须处理或声明调用的方法抛出的异常时,Java 编译器会(通过编译错误消息)告诉你。





变长参数列表

方法可以声明为接受数量不定的参数,调用时也可以传入数量不定的参数。这种方法一般叫作变长参数方法。格式化打印方法 System.out.printf() 和 String 类相关的 format() 方法,以及 java.lang.reflect 中反射 API 的一些重要方法,都使用变长参数。

变长参数列表的声明方式为,在方法最后一个参数的类型后面加上省略号(...),指明最后一个参数可以重复零次或多次。例如:

public static int max(int first, int... rest) {

                /* 暂时省略主体 */

}

变长参数方法纯粹由编译器处理,把数量不定的参数转换为一个数组。对 Java 运行时来说,上面的 max() 方法和下面这个没有区别:

public static int max(int first, int[] rest) {

                /* 暂时省略主体 */

}

把变长参数方法的签名转换为真正的签名,只需把 ... 换成 []。记住,参数列表中只能有一个省略号,而且只能出现在最后一个参数中。

下面填充 max() 方法的主体:

public static int max(int first, int... rest) {

                int max = first;

                for(int i : rest) { // 合法,因为rest其实就是数组

                                if (i > max) max = i;

                }

                return max;

}

声明这个 max() 方法时指定了两个参数,第一个是普通的 int 类型值,但是第二个可以重复零次或多次。下面对 max() 方法的调用都是合法的:

max(0)

max(1, 2)

max(16, 8, 4, 2, 1)

因为变长参数方法被编译成接受数组参数的方法,所以在编译对这类方法的调用得到的代码中,包含创建和初始化这个数组的代码。因此,调用 max(1,2,3) 被编译成:

max(1, new int[] { 2, 3 })

其实,如果参数的方法已经存储在数组中,完全可以直接把数组传给变长参数方法,而不用把数组中的元素取出来一个一个传入。... 参数可以看成一个数组。不过,反过来就不行了:只有使用省略号声明为变长参数方法,才能使用变长参数方法调用的句法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容