建议阅读:第6.1-6.6节

方法基础

我们已经看到,Java Math类包含一些有用的静态方法,用于计算数学函数(如Math)。罪,数学。√6等。在第6章中,我们将学习如何构造我们自己的静态方法,以及什么时候这样做是有用和合适的。

我们从课本中的一个基本例子开始。这个例子从几个循环开始,它们计算类似的东西,特定范围内的整数和:

公共类RangeSums{公共静态void main(String[] args) {int sum = 0;For (int I = 1;I <= 10;I ++) sum += I;system . out。println(“从1到10的和是”+ Sum);Sum = 0;For (int I = 20;I <= 37;I ++) sum += I;system . out。println(“从20到37的总和是”+ Sum);Sum = 0;For (int I = 35;I <= 49;I ++) sum += I;system . out。println(“从35到49的总和是”+ Sum ");}}

这段代码包含一些明显的冗余—每次我们想要计算特定范围的和时,我们都必须构造一个循环来完成此操作,并且上面示例中的赢博体育三个循环除了涉及范围的细节之外都是相似的。

方法是消除这种冗余的极好方法。我们要做的是构造一个方法来进行计算,我们通过每次计算时变化的量来参数化这个方法。在本例中,每次计算求和时变化的数量是范围的起始值和结束值。

下面是为了使用一个方法而重写的代码:

公共类RangeSums{公共静态int sum(int i1,int i2) {int result = 0;For (int I = 1; I <= 2;I ++) result += I;返回结果;}公共静态void main(String[] args) {System.out。println("Sum from 1 to 10 is "+ Sum (1,10));system . out。println("Sum from 20 to 37 is "+ Sum (20,37));system . out。println("Sum from 35 to 49 is "+ Sum (35,49));}}

这个类现在包含两个方法定义:一个是sum方法的定义,我们将调用它来帮助我们计算范围和,另一个是main方法的定义。

方法定义遵循一组结构规则。

一旦你写了一个方法定义,你就可以在你的代码中按照下面的语法调用这个方法:

mySum = sum(1,100);

请注意,当您调用一个方法时,您必须提供与方法定义在其参数列表中设置的参数一样多的参数。sum方法使用两个参数来指示要求和的整数范围,因此在调用该方法时必须提供这两个值。

注意,方法需要作为输入的值也可以来自变量:

Int a = 1;Int b = 50;Int total = sum(a,b);

在调用方法时提供的变量称为实际参数。在方法定义的参数列表中出现的变量称为形式参数。当您调用函数时,Java将通过将每个实际参数的值复制到形式参数中来自动地将实际参数与形式参数关联起来。在上面的例子中,a的值1被复制到参数i1中,b的值50被复制到参数i2中。一旦参数有了值,方法定义主体中的代码就可以开始工作并执行所需的计算。

将程序转换为使用方法

在关于文件的讲座中,我展示了这个例子。这是一个计算并输出从1到1000的赢博体育质数的程序。

public static void main(String[] args) {printwwriter pw = null;try {pw = new printwwriter (new File("prime .txt"));} catch(异常ex) {System.out。println(“无法打开文件进行写入”);system . exit (0);} int n = 3;当(n < 1000) {int d = n - 1;While (n % d == 0) {break;} d——;} if (d == 1) {pww .println(n);} n = n + 2;} pw.close ();}

这个程序比较复杂,因为它需要一个嵌套循环结构。我们需要一个外部循环来遍历从1到1000的赢博体育整数,以及一个内部循环来检查给定的整数是否为素数。嵌套循环结构对于新手程序员来说很难理解,所以这段代码可以简化。

简化嵌套循环的最佳策略之一是编写一个方法来完成内部循环逻辑的工作。通过重写代码来调用这个方法,可以简化嵌套循环的结构。下面是按照这个策略重写的示例。

公共静态布尔isPrime(int n) {int d = n - 1;While (n % d == 0) {break;} d——;} if (d == 1)返回true;否则返回false;}公共静态void main(String[] args) {printwwriter pw = null;try {pw = new printwwriter (new File("prime .txt"));} catch(异常ex) {System.out。println(“无法打开文件进行写入”);system . exit (0);} for (int n = 3;N < 1000;n = n + 2) {if (isPrime(n)) {pww .println(n);}} pw.close();}

这段代码使用了一个叫做布尔函数的函数。布尔函数是设计用来回答真/假问题的函数。布尔函数通常用于if语句或循环的测试。布尔函数的返回类型为boolean,这是用于存储真/假值的Java类型。

这里是另一个示例程序,它可以受益于使用方法来简化和清理代码。我在关于if-else语句的课上展示了这个例子。该程序实现了一个中等复杂的纸牌游戏的规则。

public class CardGame {public static void main(String[] args) {int dealerCard1, dealerCard2, dealerCard3;字符串dealerCardName1, dealerCardName2, dealerCardName3;int playerCard1, playerCard2, playerCard3;字符串playerCardName1, playerCardName2, playerCardName3;扫描器输入=新的扫描器(System.in);//初始化int dealerPoints = 0;int playerPoints = 0;//第一轮-发给每个玩家两张牌。dealerCard1 = (int) Math.floor(Math.random() * 13) + 1;if (dealerCard1 >= 10) {dealerPoints = dealerPoints + 10;} else {dealerPoints = dealerPoints + dealerCard1;} if (dealerCard1 == 1) {dealerCardName1 = "Ace";} else if (dealerCard1 == 11) {dealerCardName1 = "Jack";} else if (dealerCard1 == 12) {dealerCardName1 = "Queen";} else if (dealerCard1 == 13) {dealerCardName1 = "King";} else {dealerCardName1 = Integer.toString(dealerCard1);} dealerCard2 = (int) Math.floor(Math.random() * 13) + 1;if (dealerCard2 >= 10) {dealerPoints = dealerPoints + 10;} else {dealerPoints = dealerPoints + dealerCard2;} if (dealerCard2 == 1) {dealerCardName2 = "Ace";} else if (dealerCard2 == 11) {dealerCardName2 = "Jack";} else if (dealerCard2 == 12) {dealerCardName2 = “皇后”;} else if (dealerCard2 == 13) {dealerCardName2 = "King";} else {dealerCardName2 = Integer.toString(dealerCard2);} playerCard1 = (int) Math.floor(Math.random() * 13) + 1;if (playerCard1 >= 10) {playerPoints = playerPoints + 10;} else {playerPoints = playerPoints + playerCard1;} if (playerCard1 == 1) {playerCardName1 = "Ace";} else if (playerCard1 == 11) {playerCardName1 = "Jack";} else if (playerCard1 == 12) {playerCardName1 = "Queen";} else if (playerCard1 == 13) {playerCardName1 = "King";} else {playerCardName1 = Integer.toString(playerCard1);} playerCard2 = (int) Math.floor(Math.random() * 13) + 1;if (playerCard2 >= 10) {playerPoints = playerPoints + 10;} else {playerPoints = playerPoints + playerCard2;} if (playerCard2 == 1) {playerCardName2 = "Ace";} else if (playerCard2 == 11) {playerCardName2 = "Jack";} else if (playerCard2 == 12) {playerCardName2 = "Queen";} else if (playerCard2 == 13) {playerCardName2 = "King";} else {playerCardName2 = Integer.toString(playerCard2);} //要删除下面的杂乱,继续生成潜在的第二轮卡牌。playerCard3 = (int) Math.floor(Math.random() * 13) + 1;if (playerCard3 == 1) {playerCardName3 = "Ace";} else if (playerCard3 == 11) {playerCardName3 = "Jack";} else if (playerCard3 == 12) {playerCardName3 = "Queen";} else if (playerCard3 == 13) {playerCardName3 = "King";} else {playerCardName3 = Integer.toString(playerCard3);} dealerCard3 = (int) Math.floor(Math.random() * 13) + 1;if (dealerCard3 == 1) {dealerCardName3 = "Ace";} else if (dealerCard3 == 11) {dealerCardName3 = "Jack";} else if (dealerCard3 == 12) {dealerCardName3 = "Queen";} else if (dealerCard3 == 13) {dealerCardName3 = "King";} else {dealerCardName3 = Integer.toString(dealerCard3);} //打印第一轮的结果System.out。println(“经销商有“+ dealerCardName1 +”和“+ dealerCardName2 +”,共计“+ dealerPoints +“points”);system . out。println(“你有” + playerCardName1 + “和” + playerCardName2 + “,总共” + playerPoints + " points.");如果(dealerPoints > 17 && playerPoints > 17){系统。两家公司都破产了。比赛以平局告终。”} else if (dealerPoints bbb17) {System.out。println(“经销商萧条。你赢了。”);}如果(playerPoints > 17){系统。你已经破产了。你输了。”);} else{//第二轮的逻辑。println(“游戏进入第二轮”);system . out。你想做什么,打还是留下?”);字符串playerChoice = input.next();if (playerChoice.equalsIgnoreCase("hit")) {if (playerCard3 >= 10) {playerPoints = playerPoints + 10;} else {playerPoints = playerPoints + playerCard3;} system . out。println(“你的下一张牌是” + playerCardName3 + ".");system . out。println(“你现在有” + playerPoints + " points.");} if ((playerPoints <= 17) && (dealerPoints < 14 || dealerPoints < playerPoints)) {System.out。println(“发牌者再拿一张牌”);if (dealerCard3 >= 10) {dealerPoints = dealerPoints + 10;} else {dealerPoints = dealerPoints + dealerCard3;} system . out。println(“经销商的下一张牌是” + dealerCardName3 + ".");system . out。println(“经销商现在有+ dealerPoints +”);} else if (playerPoints <= 17) {System.out。println(“经销商保持。”);} //打印结果如果(playerPoints > 17) {System.out。你已经破产了。你输了。”);} else if (dealerPoints bbb17) {System.out。println(“经销商萧条。你赢了。”);} else {System.out。println(“发牌者有“+ dealerPoints +”,你有“+ playerPoints +”);如果(dealerPoints > playerPoints){系统。println(“发牌者以点数取胜”);} else if (dealerPoints == playerPoints) {System.out。println(“游戏以点数平手”);} else {System.out。println(“你赢在分数上。”);} } } } }

对这段代码的快速检查应该会使您相信这里有相当程度的冗余。特别是,计算一张牌值多少分所需的逻辑和确定一张牌的名称所需的逻辑在程序中被反复使用。这表明我们应该构造一些方法来完成这些计算:

公共类CardGame{公共静态int cardPoints(int cardNumber) {int points = cardNumber;If (points > 10) points = 10;返回点;}公共静态字符串cardName(int cardNumber){字符串名称;if(cardNumber == 1) name = "Ace";if(cardNumber == 11) name = "Jack";else if(cardNumber == 12) name = “皇后”;if(cardNumber == 13) name = "King";else name = Integer.toString(cardNumber);返回名称;}公共静态void main(String[] args) {int dealerCard1, dealerCard2, dealerCard3;字符串dealerCardName1, dealerCardName2, dealerCardName3;int playerCard1, playerCard2, playerCard3;字符串playerCardName1, playerCardName2, playerCardName3;扫描器输入=新的扫描器(System.in);//初始化int dealerPoints = 0;int playerPoints = 0;//第一轮-发给每个玩家两张牌。dealerCard1 = (int) Math.floor(Math.random() * 13) + 1;dealerPoints = cardPoints(dealerCard1);dealerCardName1 = cardName(dealerCard1);dealerCard2 = (int) Math.floor(Math.random() * 13) + 1;dealerPoints = dealerPoints + cardPoints(dealerCard2);dealerCardName2 = cardName(dealerCard2);playerCard1 = (int) Math.floor(Math.random() * 13) + 1;playerPoints = cardPoints(playerCard1);playerCardName1 = cardName(playerCard1);playerCard2 = (int) Math.floor(Math.random() * 13) + 1;playerPoints = playerPoints + cardPoints(playerCard2);playerCardName2 = cardName(playerCard2);//要清除下面的杂乱,继续生成潜在的//第二轮卡片。playerCard3 = (int) Math.floor(Math.random() * 13) + 1;playerCardName3 = cardName(playerCard3);dealerCard3 = (int) Math.floor(Math.random() * 13) + 1;dealerCardName3 = cardName(dealerCard3);//输出第一轮的结果。println(“经销商有“+ dealerCardName1 +”和“+ dealerCardName2 +”,共计“+ dealerPoints +“points”);system . out。println(“你有” + playerCardName1 + “和” + playerCardName2 + “,总共” + playerPoints + " points.");如果(dealerPoints > 17 && playerPoints > 17){系统。两家公司都破产了。比赛以平局告终。”} else if (dealerPoints bbb17) {System.out。println(“经销商萧条。你赢了。”);}如果(playerPoints > 17){系统。你已经破产了。你输了。”);} else{//第二轮的逻辑。println(“游戏进入第二轮”);system . out。你想做什么,打还是留下?”);字符串playerChoice = input.next();if (playerChoice.equalsIgnoreCase("hit")) {playerPoints = playerPoints + cardPoints(playerCard3);system . out。println(“你的下一张牌是” + playerCardName3 + ".");system . out。println(“你现在有” + playerPoints + " points.");} if ((playerPoints <= 17) && (dealerPoints < 14 || dealerPoints < playerPoints)) {System.out。println(“发牌者再拿一张牌”);dealerPoints = dealerPoints + cardPoints(dealerCard3);system . out。println(“经销商的下一张牌是” + dealerCardName3 + ".");system . out。println(“经销商现在有+ dealerPoints +”);} else if (playerPoints <= 17) {System.out。println(“经销商保持。”);} //打印结果如果(playerPoints > 17) {System.out。你已经破产了。你输了。”);} else if (dealerPoints bbb17) {System.out。println(“经销商萧条。你赢了。”);} else {System.out。println(“发牌者有“+ dealerPoints +”,你有“+ playerPoints +”);如果(dealerPoints > playerPoints){系统。println(“发牌者以点数取胜”);} else if (dealerPoints == playerPoints) {System.out。println(“游戏以点数平手”);} else {System.out。println(“你赢在分数上。”);} } } } }

这段代码仍然相当冗长,但比原来的代码少了冗长和冗余。原来的程序有177行代码,新版本有115行。

扩展示例:计算文本的复杂度

下面是一个程序的扩展示例,它演示了如何使用方法来帮助我们构建中等复杂问题的解决方案。所讨论的问题是基于Cay Horstmann在《Big Java, Second Edition》中出现的一个编程问题。

这个问题

Flesch可读性指数是一个简单的计算方法,可以衡量一些英语文本的复杂程度。

Np为文本中的句子数,Nw为单词总数,Ns为文本中的音节总数。对于典型的英语文本,索引介于0到100之间。该指数大致相当于一个年级水平。

指数 年级水平
91 - 100 五年级
81 - 90 六年级
71 - 80 7年级
66 - 70 8年级
61 - 65 9年级
51 - 60 高中
31 - 50 大学
0 - 30 大学毕业生
小于0 法学院毕业生

我们将编写一个程序,它可以获取存储在文件中的文本样本,并计算文本中有多少句子、单词和音节。然后我们将使用上面的公式来计算文本的可读性索引。

从文本文件中读取单词的基本机制

我们已经看到可以使用Scanner从文件读取输入数据。下一个示例演示如何从文本文件中一次读取一个单词。

包testScanner;进口java.io.File;进口java.util.Scanner;公共类EchoWords{公共静态void main(String[] args){扫描器输入= null;try {input = new Scanner(new File("text.txt"));} catch (Exception ex) {ex. printstacktrace ();system . exit (0);} while (input.hasNext()){字符串字= input.next();System.out.println(词);}}

在Scanner上调用next()方法将返回文本中的下一个单词。上面的示例使用该技术从文本文件中读取单个单词,然后将它们回显到输出窗口。

在使用这种方法时,我们必须解决一个小问题。Scanner通过寻找明显的分隔符(称为分隔符)将文件中的文本分解为单词。默认情况下,Scanner类只将空格、制表符和换行符作为分隔符。在大多数情况下,这会导致正确的行为,但在这种情况下,它会产生不想要的副作用。该代码最终打印的一些“单词”包括附加的标点符号,如逗号、引号和破折号。

如果我们想要打印文本中的赢博体育单词,同时过滤掉标点符号和数字,我们必须告诉Scanner更改它用作分隔符的字符集。为此,我们调用Scanner的useDelimiter方法,并向它传递一个我们想要用作分隔符的字符列表:

输入。useDelimiter(“[\ t \ n \ r0-9 ,.\"\\-]");

除此之外,我们还必须放入一些不回显长度为0的字的额外逻辑。当扫描器遇到一个分隔符序列时,它偶尔会产生0长度的单词。

现在有一个版本的程序可以成功地将文本分解成单个单词,同时剥离标点符号和其他不需要的字符。

包testScanner;进口java.io.File;进口java.util.Scanner;公共类EchoOnlyWords{公共静态void main(String[] args){扫描器输入= null;try {input = new Scanner(new File("text.txt"));} catch (Exception ex) {ex. printstacktrace ();system . exit (0);}输入。useDelimiter(“[\ t \ n \ r0-9 ,.\"\\-]");while (input.hasNext()){字符串字= input.next();if (word.length() > 0) {System.out.println(word);} } } }

学习如何去掉不需要的字符的一个有用的副作用是,我们现在也知道如何解决在文本样本中计算句子的问题。我们所要做的就是将分隔符设置更改为那些通常出现在句子末尾的字符:

input.useDelimiter ("[.!?]");

这样做将导致Scanner的next()方法一次返回一个完整的句子,而不是通常的一次返回一个单词。

计数音节

这个问题最难的部分是数音节。下面是计算单词音节的策略大纲

  1. 每一组相邻的元音算作一个音节。(注意,“y”在上下文中被认为是元音。)
  2. 如果一个单词有多个音节,单词末尾的一个“e”不算作一个独立的音节。(例如,“time”和“sale”都是单音节单词。)
  3. 规则2的一个例外是结尾的“le”。当“le”跟在一个单词的辅音后面时,它总是算作一个音节。(例如,单词“able”和“eagle”都是双音节单词。)

为了实现这个策略,我们必须知道如何访问单词中的单个字符。我们将在string中存储单个单词,string是一种用于存储文本的对象类型。Java String类包含一个名为charAt()的方法,该方法可用于从String中提取单个字符。charAt()使用一个索引系统,其中字符串的第一个字母是索引0。要确定String的长度,可以使用String类的length()方法。因此,例如,要检查单词的最后一个字母是否为“e”,我们可以使用以下代码:

Int index = word.length()-1;//最后一个字母的索引if(Word . charat (index) == 'e') // Word以e结尾否则// Word不以e结尾。

我们需要的另一个有用的方法是toLowerCase方法,它将String中的赢博体育字母转换为小写。

溶液的结构

下一步是为解决我们的问题勾画出一个适当的结构。这样做的一种方法是遵循自顶向下的编程模型。在这个模型中,我们首先为赢博体育程序的main方法编写代码。这将帮助我们确定要执行的最重要的任务。我们将把这些工作委托给稍后编写的方法,而不是深入研究这些任务的细节。

这里是我们的文本分析程序的主要方法的代码。

public static void main(String[] args)抛出Exception {int sentenceCount;int wordCount;int syllableCount;//设置扫描器try {input = new Scanner(new File("text.txt"));} catch (Exception ex) {ex. printstacktrace ();system . exit (0);} input.useDelimiter ("[.!?]");//初始化计数器sentenceCount = 0;wordCount = 0;音节计数= 0;//扫描输入文件while (input. hasnext ()) {String sentence = input.next();sentenceCount + +;wordCount = wordCount + countWordsInSentence(句子);音节计数=音节计数+ count音节计数(句子);} //计算肉可读性指数double fleschIndex = 206.835 -(84.6 *字数)/ wordCount -(1.015 *字数)/ sentenceCount;//打印结果System.outprintln(“此文本的索引为” + fleschIndex);system . out。println(“平均每个单词的音节数”+(1.0 *音节数)/单词数);system . out。println(“句子平均字数”+ (1.0 * wordCount) / sentenceCount);}

这段代码建立了计算的基本结构。它将计算句子中的单词和音节这一比较困难的工作委托给另外两个方法,countWordsInSentence和countsyllable insentence。

编写其他方法

下一步是编写单词和音节计数方法的代码。

公共静态int countWordsInSentence(字符串文本){int wordCount = 0;扫描器s =新的扫描器(文本);s.useDelimiter(“[\ t \ n \ r0-9 ,.;\"\\-]");while (s.hasNext()){字符串word = s.next();if (word.length() > 0) {wordCount++;}}返回wordCount;}公共静态int countSyllablesInSentence(字符串文本){int音节计数= 0;扫描器s =新的扫描器(文本);s.useDelimiter(“[\ t \ n \ r0-9 ,.;\"\\-]");while (s.hasNext()){字符串word = s.next();if (word.length() > 0) {word = word. tolowercase ();音节计数=音节计数+ countsyllable inword(单词);}}返回音节计数;}

这两种方法使用相同的基本策略。在这两种情况下,我们打开一个扫描器,它将扫描我们给出的句子字符串的内容。我们为该Scanner配备了一个分隔符集,以保证每次调用next()时都能给出一个单词,然后建立一个循环,扫描句子中的单词。

countsyllable insentence方法又将它需要做的一些工作委托给另一个方法countsyllable inword。下面是该方法的代码。

public static int countsyllable inword (String word) {int count = 0;int指数;//计算元音组if (isVowel(word.charAt(0))) {Count ++;} for (index = 1;索引< word.length();index++){//首先过滤掉一个特例if(word.charAt(index) == 'a' && word.charAt(index-1) == 'i') count++;if (isVowel(word. charat (index)) && !charAt(index - 1)) {count++;}} if (count > 1){//检查终端‘e’异常int lastIndex = word.length() - 1;if (word. charat (lastIndex) == ‘e‘){//检查’le’异常if (word. charat) == 'e')charAt(lastIndex - 1) != 'l' || isVowel(word)。charAt(lastIndex - 2)) {count——;}}}返回计数;}

这个方法又需要最后一个方法isVowel的进一步帮助,isVowel检查单个字符并确定它是否是元音。

公共静态布尔isVowel (char ch){如果(ch = = ' a ' | | ch = =“e”| | ch = =“我”| | ch = = ' o ' | | ch = = ' u ' | | ch = = y){返回true;}返回false;}

isVowel是布尔函数的一个例子。这种类型的函数被设计用来为测试中使用的问题提供一个是非答案。countsyllable inword方法广泛地使用isVowel来询问有关组成我们正在使用的单词的字符的问题。

单击下面的按钮下载包含本示例完整代码的存档文件。

项目文件夹