方法:创建可以重用的代码,然后通过给定的不同的条件(参数)执行得出为同的结果
定义方法:
修饰符返回值类型 方法名(参数列表){
方法体;
}
方法头:指定方法的和修饰符、返回值类型、方法名和方法参数
方法以返回一个值则在修饰符后加上返回值的类型,如果这个方法只是完成某个任务而不返回任何值的话则写上void
定义在方法应中的变量称为形式参数,也叫形参,形参也就是一个占位符,当调用方法的时候会给其传一个值,这个值就是实际参数,参数列表指定了方法中参数的类型及顺序和个数
方法名和参数列表一起就构成了方法签名,对于方法来说参数是可选的,可以是无参的方法,方法体则定义了这个方法做什么的语句集合,如果一个方法有返回值的话则在方法体当中一定要带上return语句返回方法的返回值,return语句也意味着方的结束
注意:在其它的一些语言中把方法称为函数和过程有返回值的称为函数,无返回值的称为过程
在方法应中需要对参数列表中的每一个参数进行独立的声明而不能一起声明
比如:int max(int n1,int n2)这个是对的而不能写成 int max(int n1,n2)
方法调用:
在创建方法的时候,定义方法要做什么工作,而在使用方法的时候则要调用它
调用方法分为两种:一种是有返回值的
int larger = max(3,4);
这里把方法当作一个值处理并赋给相应的变量还有一种调用就是
System.out.println(max(3,4));直接在某些语句中调用方法
如果方法是没有返回值的则对方法的调用一定是一条语句
如println方法没有返回值则调用它一定是一条语句如下:
System.out.println(“Hello!”);
当程序调用一个方法时,程序控制就转移到被调用的方法当执行到return语句或是执行到方以结束的右括号时,被调用的方法把程序的控制还给调用者
publicclass TestMax {
publicstaticvoid main(String[] args) {
// 定义两个变量
int i = 5;
int j = 2;
int k = max(i, j);
System.out.println(i + " 与 " + j + " 之间的最大值是:" + k);
}
privatestaticint max(int num1, int num2) {
/*
* int result;
* if(num1>num2)
* result = num1;
* else
* result = num2;
* return result;
*/
// 上面的过程可做如下的改进
int result = num1;
if (num1 < num2)
result = num2;
return result;
}
}
上面的这个程序中包括了main方法和一个max方法main方法与其它方法的唯一区别在于它是由Java虚拟机调用的,main方法的方法头永远都是一样的 public static void main(String[] args)
main中的语句可以调用main方法扣在类中定义的方法也可以调用其它类中定义的方法,调用的时候如果有参数会把参数值传到被调用的方法并且流程会进入到被调用方法中去执行,当执行完被调用函数或是遇到return后流程会继续调用法后的语句继续执行
方法可以带来的好处可以简化流程,共享和重用,比如现在我们写语句时我们可以在main中一直写下去把一个大的逻辑处写上一个方法先假定这个方法是main中流程需要的最后在外面把方法补全了。
调用堆栈:
每当调用一个方法的时候,系统都会把参数、局部变量存储在一个称为堆栈的内存区域当中然后用后进先出的方法来存储数据,当一个方法调用另一个方法的时候调用者的堆栈空间保持不变新开辟的空间处理新方法的调用,当一个方法结束返回到调用者时,其相应的空间也就释放了。
关于void方法,这个有的语言中又称为过程
import java.util.Scanner;
publicclass TestVoidMethod {
publicstaticvoid main(String[] args) {
// 提示用户进行输入
System.out.print("请输入你的分数(0~100):");
Scanner input = new Scanner(System.in);
double score = input.nextDouble();
// 判断得到一个什么级别
System.out.print("你的分数级别是:");
getGrade(score);
}
privatestaticvoid getGrade(double score) {
if (score >= 90.0) {
System.out.println('A');
} elseif (score >= 80.0) {
System.out.println('B');
} elseif (score >= 70.0) {
System.out.println('C');
} elseif (score >= 60.0) {
System.out.println('D');
} else {
System.out.println('E');
}
}
}
getGrade(double score)这个方法是一个void方法不返回任何的值到调用它的地方只是在自己的内部完成判断并进行打印
在void方法中不需要return语句因为它是为了执行其内部的语句当然也可以在其中加上return语句来提前结束方法的执行其语法是return;
privatestaticvoid getGrade(double score) {
if(score<0.0 || score>=100.0){
System.out.println("输入有误!");
return;
}
if (score >= 90.0) {
System.out.println('A');
} elseif (score >= 80.0) {
System.out.println('B');
} elseif (score >= 70.0) {
System.out.println('C');
} elseif (score >= 60.0) {
System.out.println('D');
} else {
System.out.println('E');
}
}
这是对上面的方法的改进,在一定条件下不会执方法后面的语句直接return
参数值传递:
方法的威力在于它处理参数的能力,如果定义的方法有形参则在调用方法的时候需要提供相应的实参,而且它们必须要与方法签名中对应的形参次序相同,这个就叫做参数顺序匹配
注意:实参必须要与形参中对应的参数在次序和数量上匹配在类型上要兼容,这里的类型兼容是指不需进行显示的类型转换,实参的值就可以传给形参。
在调用带参方法的时候把实参的值传给形参这个过程称为值传递pass-by-value,如果实参是一个变量则会把变量的值传给形参,如论在被调用的方法中形参如何改变传给它的实参都是不会有任何影响的
publicclass Increment {
publicstaticvoid main(String[] args) {
int x = 1;
System.out.println("在调用之前x的值是:"+x);
//方法调用
increment(x);
System.out.println("在调用之后x的值是:"+x);
}
privatestaticvoid increment(int x) {
x++;
System.out.println("传过来的x的值进行自加1后的结果:"+x);
}
}
上面程序的运行结果是
这里就说明了对于形参来说它在被调用方法体内不管怎么改变都不会影响实参,方法返回后原来的实参还是原来的实参并未因形参的改变而改变
在Java中常常在这种调用过程中会说把实参x传给形参x,其实上是把实参x的值传给形参x
模块代码:
使用方法可以减少冗余代码,提高代码的复用性。方法也可以用来模块化代码来提高程序的质量
比如下面的求最大公约数的过程就可以把取两个数最大公约数的过程进行模块化
import java.util.Scanner;
publicclass GetGys {
publicstaticvoid main(String[] args) {
// 提示用户输入两个整数
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数: ");
int n1 = input.nextInt();
System.out.print("请输入一个整数: ");
int n2 = input.nextInt();
// 输出显示这个最大公约数
System.out.println(n1 + " 与 " + n2 + " 的最大公约数是: " + gys(n1, n2));
}
/**
* 计算最大公约数的方法
*
* @param n1
* 整数
* @param n2
* 整数
* @return最大公约数
*/
privatestaticint gys(int n1, int n2) {
int gys = 1;// 初始化最大公约数是1
int k = 2;// 可能的最大公约数初始化为2
while (k <= n1 && k <= n2) {
if (n1 % k == 0 && n2 % k == 0)
gys = k;
k++;
}
return gys;
}
}
如上程序把计算最大公约数的过程封装在一个方法当中这样做的优点是:
1, 它把计算最大公约数的过程与main方法分隔开来这样的话则使程序更加的清晰可读性更强
2, 计算最大公约数的错误就限定在方法当中从而缩小了查找错误的范围
3, 其它的程序可以重复使用这个方法
开发一个小的程序只要从main开始把语句一步一步写下去就可以了,特别是在某些IDE当中比如eclipse当中当遇到认为需要一堆逻辑的地方直接写上一个方法名,最后这个地方报错则把相应的方法填上就可以一层一层写下去就可以
import java.util.Scanner;
publicclass PrimeNumberMethod {
publicstaticvoid main(String[] args) {
//在主函数中我们需要完成的是打印多个素数
//得到一个控制台扫描器对象以便接受输入
Scanner input = getInput();
//提示用户输入一个整数
System.out.print("请输入需要多少个素数:");
//显示素数的打印结果
//得到素数个数
int number = input.nextInt();
System.out.print("请输入每一行显示多少个素数:");
int numberOfLine = input.nextInt();
System.out.println(number+" 个素数打印结果是:");
//显示素数
printPeimes(number,numberOfLine);
}
//按要求显示素数
privatestaticvoid printPeimes(int number, int numberOfLine) {
//创建一个变量用来记录显示了多少素数
int count = 0;
//素数的测试数
int testNumber = 2;
while(count<number){
//判断是不是素数
if(isPrime(testNumber)){
count++;//累加计数器
//在这里控制换行打印的结果
if(count%numberOfLine == 0){
//打印结果并且要换行
System.out.printf("%-5s\n",testNumber);
}else
//打印结果不需换行
System.out.printf("%-5s", testNumber);
}
testNumber++;//累加要测试的数字,计算下一个是否为素数
}
}
privatestaticboolean isPrime(int testNumber) {
for(int divisor = 2;divisor<=testNumber/2;divisor++){
if(testNumber % divisor == 0)
returnfalse;
}
returntrue;
}
//得到一个Scanner对象返回
privatestatic Scanner getInput() {
returnnew Scanner(System.in);
}
}
读上面的程序则在main中只是做了相应的逻辑说明,具体的逻辑由相应的得方法得到这样看上去更加的清晰
计算机系统中程序设计时常常会用到十六进制数,把十进数转为十六进制可以不断除以16得到的余数来取得
import java.util.Scanner;
publicclass Decimal2HexConversion {
publicstaticvoid main(String[] args) {
//得到一个Sanner
Scanner input = getInput();
//提示用户输入一个十进制的整数
System.out.print("请输入一个十进制的整数:");
int decimal = input.nextInt();
//把这个十进制整数转为十六进制,返回一个字符串
String hex = decimalToHex(decimal);
//显示处理后的结果:
System.out.println(decimal+" 的十六进制形式是:"+(decimal<0?"-":"")+hex);
}
/**把一个十进制的整数转为一个十六进制的字符串*/
privatestatic String decimalToHex(int decimal) {
//声明一个字符串
String hex = "";
while(decimal !=0){
if(decimal<0)
decimal = -decimal;
int hexValue = decimal%16;
//把得到的这个整数转为一个字符
hex = toHexChar(hexValue)+hex;
//处理完一次后把十进制数除16
decimal = decimal/16;
}
return hex;
}
/**把取余得到的数值传成一个十六进制表示的字符*/
privatestaticchar toHexChar(int hexValue) {
if(hexValue <=9 && hexValue>=0)
return (char)(hexValue+'0');
else
return (char)(hexValue-10+'A');
}
privatestatic Scanner getInput() {
returnnew Scanner(System.in);
}
}
方法的重载:
比如有如下的方法max(int n1,int n2)
这时如果要比较两个double类型的值的大小该怎么办呢?
这个时候则可以再创建一个方法max(double n1,double n2)
那么在调用方法的时候根据传递的实参的类型的不同会调用不同的方法,这就是方法重载
也就是说在一个类中有两个方法它们的方法名是相同的,但是它们的参数列表不相同,Java编译器是根据方法的签名来判断使用哪一个方法的,那么方法锁签名就包括方法名,方法的形参列表,如果这些有不同则可以进行区分,形参列表的不同包括形参的类型或个数不同
publicclass TestMethodOverloading {
publicstaticvoid main(String[] args) {
// 显示两个整数之间的大者
System.out.println("两个整数中的大者是:" + max(3, 4));
// 显示两个double值之间的大者
System.out.println("两个double值之间的大者是:" + max(3.0, 4.1));
// 显示三个double值之间的大者
System.out.println("三个double值之间的大者是:" + max(3.0, 4.1, 5.2));
}
privatestaticdouble max(double d, double e, double f) {
return (max(d, e) >= f) ? max(d, e) : f;
}
privatestaticdouble max(double d, double e) {
return (d >= e) ? d : e;
}
privatestaticint max(int i, int j) {
return (i >= j) ? i : j;
}
}
对于上面的我们可能会发现,当调用max(3,4)的时候为什么不选择max(double d,double e)因为int值可以默认的转为double类型,这是因为Java编译器在遇到这种情况的时候会选择最精确的匹配的方法因而会选择max(int i,int j)
对于被重载的方法一定是要有不同的参数列表而不能是基于不同的修饰符可是返回值来重载方法因为在调用的时候编译器无法唯一的判断出要调用哪个方法则会出错
变量作用域:
这就是指变量在程序中可以引用的范围,在一个方法中定义的变量称之为局部变量,局部变量的作用域从声明变量开始到这声明它的这个块结束为止,局部变量必须要使用之前进行声明并且赋值。
一个方法的参数的作用域变涵盖了整个方法,for循环头中声明的变量其作域就是整个for循环而在for循环内部声明的变量的作用域则是从声明它开始到声明它所在的块结束为止为这个变量的作用域
可以在方法中不同块当中声明同名的局部变量但是不能在嵌套块中或是同一块中两次声明同一局部变量名
如果在一个块内声明了一个变量不要企图在块外再去使用它,这个时候这个变量的引用已不存在了,这样使用会报错
Math类:
Math类包含了完成基本数学函数所需要的方法,Math类中的方法可以分为三类:三角函数、指数函数、服务函数
三角函数:
public static double sin(double radians);
public static double cos(double radians);
public static double tan(double radians);
public static double toRadians(double degree);
public static double toDegrees(double radians);
public static double asin(double a);
public static double acos(double a);
public static double atan(double a);
指数函数:
public static double exp(double x);
public static double log(double x);
public static double log10(double x);
public static double pow(double a,double b);
public static double sqrt(double x);
取整数方法:
public static double ceil(double x); 这里取的是大于它的最小整数,如x为2.1则结果是3.0
public staitc double floor(double x);这里取的是小于它的最大整数,如x这2.1则结果是2.0
public static double rint(double x);这里取的是x最接近的整数
public static int round(float x);这个四舍五入返回的是一个int型
public static long round(double x);这个四舍五入返回的是一个long型
min,max这两个都有重载方法返回的是传入的两个数中的最小或最大值
abs根据传入的类型也有重载方法返回的是这个值的绝对值
random()这个方法返回的是0.0~1.0但不包括1.0的一个随机小数
在这里Math类中是没有main方法的它只是为了给其它的类使用的而不会给Java虚拟机去调用它,它是为其它类完成相应的功能而提供的服务
随机字符的生成:
每一个字符都有一个唯一对应的十六进制的统一码(0~FFFF)也就是0~65535,那么要生成一个随机字符则就是产生一个随机的0~65535之间的数字
(int)(Math.random()*(65535+1))
那么对于小写字母,它们的统一码是连续的从’a’开始一直到’z’
(int)((int)’a’+Math.random()*((int)’z’-(int)’a’+1)
所有的数字操作符可以用到字符上则上面的运算可简化为
‘a’+Math.random()*(‘z’-‘a’+1)
最后再把得到的随机字符做一个转型就得到了字符
char(‘a’+Math.random()*(‘z’-‘a’+1))
对上面的进行推广则可以得出要得到任意两个字符之间的随机字符可以如下(ch2>ch1)
(char)(‘ch1’+Math.random()*(ch2-ch1+1))
如下类中定义的方法则用到上面的推论:
publicclass RandomCharacter {
/**产生一个随机字符[ch1,ch2]*/
publicstaticchar getRandomCharacter(char ch1,char ch2){
return (char)(ch1+Math.random()*(ch2-ch1+1));
}
/**随机产生一个小写字母*/
publicstaticchar getRandomLowerCaseLetter(){
return getRandomCharacter('a', 'z');
}
/**随机产生一个大写字母*/
publicstaticchar getRandomUpperCaseLetter(){
return getRandomCharacter('A', 'Z');
}
/**随机产生一个字符*/
publicstaticchar getRandomCharacter(){
return getRandomCharacter('\u0000','\uFFFF');
}
}
publicclass TesRandomCharacter {
publicstaticvoid main(String[] args) {
finalint NUMBER_OF_CHARS = 175;
finalint CHARS_OF_LINE = 25;
for (int i = 0; i < NUMBER_OF_CHARS; i++) {
// 产生一个随机小写字母
char ch = RandomCharacter.getRandomLowerCaseLetter();
if ((i + 1) % CHARS_OF_LINE == 0)
System.out.println(ch);
else
System.out.print(ch);
}
}
}
方法的抽象与逐步求精:
开发软件的关键在于应用抽象的概念
方法的抽象是通过把方法的使用和它的实现分离来实现的,用户在不需知道方法是如何实现的情况下就可以使用方法,方法的实现细节封装在方法内,对使用这个方法的用户来说是隐藏的,这就是信息的隐藏或封装,如果要改变方法的实现,如果方法的签名没有变的话对用户的程序来说是没有任何影响的
对于一个大型的程序可以使用“分治”也就是逐步求精的方法来处理把大问题分解为小问题,小问题又分解为更小的问题
在开始编写一个程序的时候应该是先把方法的抽象来把细节与设计分离,在最后才实现这些细节
比如有如下的要求用户输入一个年份输入一个月份数显示出现应的月历
打印月历我们可以分解为两个部分:
一个是打印月历的标题
一个是打印月历的主体
月历的标题由三行组成年月、虚线、星期名,需要把表示月份的数字转为大写的月份数(getMonthName())
这个时候我们分治得到的结果就是
mainàprintCalendar()à
readInput、printMonth
printMonthà
printMonthTitle()、printMonthBody()
printMonthTitle()àgetMonthName
接下来就是打印月历的主体,首先要知道这个月的第一天是星期几(getStartDay)及这个月有多少天(getNumberOfDaysInMonth)
如何知道一个月的第一天是星期几?
假定知道1800年1月1号是星期三,然后计算1800年1月1号与日历月份的第一天之间相差的总天数(totalNumberOfDays),因为每个星期有7天则日历的第一天是(totalNumberOfDay+3)%7,要计算出总天数则要知道这年是否是闰年及每个月的天数
对于对上面的实现我们可以把每一个过程放在一个函数当中当然对于过于简单的可以一并写在某一个方法内这取决于可读性,比如readInput则可以在main中完成
实现细节:
对于闰年:
return (year % 400 == 0 || (year % 4 ==0 && year % 100 !=0))
1,1,3,5,7,8,10,12这几个月是31天
4,6,9,11这几个月是30天
2月通常是28天如果是闰年则是29天所以如果是闰年则全年366年,非闰年则是365天
import java.util.Scanner;
publicclass PrintCalendar {
publicstaticvoid main(String[] args) {
// 提示用户输入相应的年数和月份数
Scanner input = new Scanner(System.in);
int year; // 四位年数
int month; // 1~12月份数
do {
System.out.print("请输入四位年数:");
year = input.nextInt();
System.out.print("请输入月份数(1~12):");
month = input.nextInt();
} while (year < 1800 || year > 9999 || month < 1 || month > 12);
// 打印这个月历
printMonth(year, month);
}
privatestaticvoid printMonth(int year, int month) {
// 打印月历头
ptintMonthTitle(year, month);
// 打印月历体
printMonthBody(year, month);
}
privatestaticvoid printMonthBody(int year, int month) {
// 得到第一天是星期几
int startDay = getStartDay(year, month);
// 得到这个月共有多少天
int numberOfDaysInMonth = getNumberOfDaysInMonth(year, month);
// 显示的时候把第一天的前面的几个空开
int i = 0;
for (i = 1; i < startDay; i++)
System.out.printf("%4s", " ");
for (i = 1; i <= numberOfDaysInMonth; i++) {
System.out.printf("%4d", i);
if ((i + startDay - 1) % 7 == 0)
System.out.println();
}
}
privatestaticint getStartDay(int year, int month) {
finalint START_DAY_FOR_JAN_1_1800 = 3;
int totalNumberOfDays = getTotalNumberOfDays(year, month);
return (totalNumberOfDays + START_DAY_FOR_JAN_1_1800) % 7;
}
privatestaticint getTotalNumberOfDays(int year, int month) {
int total = 0;
for (int i = 1800; i < year; i++)
if (isLeapYear(i))
total = total + 366;
else
total = total + 365;
for (int i = 1; i < month; i++)
total = total + getNumberOfDaysInMonth(year, i);
return total;
}
privatestaticint getNumberOfDaysInMonth(int year, int i) {
if (i == 1 || i == 3 || i == 5 || i == 7 || i == 8 || i == 10
|| i == 12)
return 31;
if (i == 4 || i == 6 || i == 9 || i == 11)
return 30;
if (i == 2)
return isLeapYear(year) ? 29 : 28;
return 0;
}
// 判断是否是闰年
privatestaticboolean isLeapYear(int i) {
return i % 400 == 0 || (i % 4 == 0 && i % 100 != 0);
}
privatestaticvoid ptintMonthTitle(int year, int month) {
System.out.println(" " + year + "年 " + getMonthName(month));
System.out.println("------------------------------");
System.out.println(" 一 二 三 四 五 六 日 ");
}
privatestatic String getMonthName(int month) {
String monthName = "";
switch (month) {
case 1:
monthName = "一月";
break;
case 2:
monthName = "二月";
break;
case 3:
monthName = "三月";
break;
case 4:
monthName = "四月";
break;
case 5:
monthName = "五月";
break;
case 6:
monthName = "六月";
break;
case 7:
monthName = "七月";
break;
case 8:
monthName = "八月";
break;
case 9:
monthName = "九月";
break;
case 10:
monthName = "十月";
break;
case 11:
monthName = "十一月";
break;
case 12:
monthName = "十二月";
break;
}
return monthName;
}
}