java面试题网

普通会员

483

帖子

9

回复

187

积分

楼主
发表于 2019-08-26 15:24:39 | 查看: 5105| 回复: 0

java方法

java方法

方法的本质以及作用(理解)

我们先不讲方法是什么,先来看一段代码,分析以下程序存在哪些缺点,应该如何去改进:

public static void main(String[] args) {
	//请计算10和20的和
	int a = 10;
	int b = 20;
	int c = a + b;
	System.out.println(a + "+" + b + "=" + c);
	//请计算666和888的和
	int x = 666;
	int y = 888;
	int z = x + y;
	System.out.println(x + "+" + y + "=" + z);
	//请计算888和999的和
	int m = 888;
	int n = 999;
	int e = m + n;
	System.out.println(m + "+" + n + "=" + e);
}

以上代码完成了三个求和的功能,每一次求和的时候都把代码重新写了一遍,显然代码没有得到“重复利用”,表面上看是三个功能,但实际上只是“一个”求和功能,只不过每一次参与求和的实际数值不同。java中有没有一种方式或者语法,能让我们把功能性代码写一次,然后给这个功能性代码传递不同的数据,来完成对应的功能呢?答案是:当然有。这就需要我们掌握java语言中的方法机制,接下来大家看看改进之后的代码(这里先不需要掌握方法是怎么定义的以及怎么调用的,只要看以下代码就行,此小节是为了让大家理解方法的本质以及作用):

public static void main(String[] args) {
	//调用求和方法计算10和20的和
	sumInt(10 , 20);
	//调用求和方法计算666和888的和
	sumInt(666 , 888);
	//调用求和方法计算888和999的和
	sumInt(888 , 999);
}
//专门负责求和的方法
public static void sumInt(int a , int b){
	int c = a + b;
	System.out.println(a + "+" + b + "=" + c);
}

运行结果如下图所示:

java方法_www.wityx.com

图7-1:求和结果

通过以上程序我们可以看出,其实方法也没什么神秘的,方法其实就是一段普通的代码片段,并且这段代码可以完成某个特定的功能,而且可以被重复的调用/使用。java中的方法又叫做method,在C语言中叫做函数。

从现在开始大家以后在写代码的时候就要有定义方法的意识了,只要是可以独立出来的功能,我们都可以定义为单独的一个方法来完成,如果以后需要使用此功能时直接调用这个方法就行了,不要把所有的代码都扔到main方法当中,这样会导致程序的“复用性”很差。

方法的定义以及调用(掌握)

通过以上内容的学习,可以看出方法是一段可以完成某个特定功能的并且可以被重复利用的代码片段。接下来我们来学习一下方法应该怎么定义以及怎么调用。

定义/声明方法的语法格式如下所示:

[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}

例如代码:
public static void sumInt(int a , int b){
	int c = a + b;
	System.out.println(a + "+" + b + "=" + c);
}
public static是修饰符列表;
void是返回值类型;
sumInt是方法名;
(int a , int b)是形式参数列表,简称形参,每一个形参都是局部变量;
形参后面使用一对儿大括号括起来的是方法体,方法体是完成功能的核心代码,方法体中的代码有执行顺序的要求,遵循自上而下的顺序依次逐行执行,不存在跳行执行的情况。
再如代码:
public static int sumInt(int a , int b){
	int c = a + b;
    return c;
}
以上程序中sumInt之前的int是返回值类型。

接下来我将列出方法的相关规则,其中一些规则目前可能需要大家死记硬背,还有一些规则希望大家在理解的前提下进行记忆:

  • [修饰符列表],此项是可选项,不是必须的,目前大家统一写成public static,后面的课程会详细讲解。
  • 返回值类型,此项可以是java语言当中任何一种数据类型,包括基本数据类型,也包括所有的引用数据类型,当然,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写void。返回值类型例如:byte,short,int,long,float,double,boolean,char,String,void等。
  • 方法名,此项需要是合法的标识符,开发规范中要求方法名首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意,例如:login、getUsername、findAllUser等。
  • 形式参数列表(int a, int b),此项又被称为形参,其实每一个形参都是“局部变量”,形参的个数为0~N个,如果是多个参数,则采用半角“,”进行分隔,形参中起决定性作用的是参数的数据类型,参数名就是变量名,变量名是可以修改的,也就是说(int a , int b)也可以写成(int x , int y)。
  • 方法体,由一对儿大括号括起来,在形参的后面,这个大括号当中的是实现功能的核心代码,方法体由java语句构成,方法体当中的代码只能遵循自上而下的顺序依次逐行执行,不能跳行执行,核心代码在执行过程中如果需要外部提供数据,则通过形参进行获取。

整体来说方法的声明语法是很简单的,我相信每个人都能记住,其实我觉得方法的定义难度最大的不是语法,而是方法在定义的时候,返回值类型定为什么类型比较合适?方法的形式参数列表中定义几个参数合适?每个参数的数据类型定义为什么比较合适?以上的一系列问题实际上还是需要和具体的功能结合在一起才能决定,当然,这不是一天两天的事儿,不是说这一章节的内容学完之后就真正的会定义方法了,我们只能说语法会了,还需要后期不断的做项目,写代码才能找到感觉,找到编程思路。到那时,你自然就会定义返回值类型、形式参数列表了。

当一个方法声明之后,我们应该如何去让这个方法执行呢,当然,这个时候就需要亲自去调用这个方法了,调用方法的语法格式是(前提是方法的修饰符列表中带有static关键字):“类名.方法名(实际参数列表);”,例如以下代码:

public class MethodTest {
	public static void main(String[] args) {
		MethodTest.sumInt(100, 200);
		MethodTest.sumDouble(1.0, 2.0);
	}
	public static void sumInt(int x , int y){
		System.out.println(x + "+" + y + "=" + (x + y));
	}
	public static void sumDouble(double a , double b){
		System.out.println(a + "+" + b + "=" + (a + b));
	}
}

运行结果如下图所示:

java方法_www.wityx.com

图7-2:方法如何调用

需要注意的是,方法在调用的时候,实际传给这个方法的数据被称为实际参数列表,简称实参,java语法中有这样的规定:实参和形参必须一一对应,所谓的一一对应就是,个数要一样,数据类型要对应相同。例如:实参(100 , 200)对应的形参(int x , int y),如果不是一一对应则编译器就会报错。当然也可能会存在自动类型转换,例如:实参(100 , 200)也可以传递给这样的形参(long a , long b),这里我们先不谈这个。

实际上方法在调用的时候,有的情况下“类名.”是可以省略的,我们来看看什么情况下它可以省略不写:

public class MethodTest03 {
	public static void main(String[] args) {
		sumInt(100, 200); //“类名.”可以省略
		sumDouble(1.0, 2.0);//“类名.”可以省略
		//doOther(); //编译报错
		Other.doOther(); //“类名.”不能省略
	}
	public static void sumInt(int x , int y){
		System.out.println(x + "+" + y + "=" + (x + y));
	}
	public static void sumDouble(double a , double b){
		System.out.println(a + "+" + b + "=" + (a + b));
	}
}
public class Other{
	public static void doOther(){
		System.out.println("Other doOther...");
	}
}

运行结果如下图所示:

java方法_www.wityx.com

图7-3:“类名.”什么时候可以省略

通过以上程序的分析,我们得知,当在a()方法执行过程中调用b()方法的时候,并且a()方法和b()方法在同一个类当中,此时“类名.”可以省略不写,但如果a()方法和b()方法不在同一个类当中,“类名.”则不能省略。

方法返回值详解(掌握)

每个方法都是为了完成某个特定的功能,例如:登录功能、求和功能等,既然是功能,那么当这个功能完成之后,大多数情况下都会有一个结果的,比如,登录成功了或者失败了(true/false),求和之后最后的结果是100或者200,当然也有极少数的情况下是没有结果的。这个结果本质上就是一个数据,那么既然是一个数据,就一定会有对应的类型,所以在方法定义的时候需要指定该方法的返回值类型。

java语言中方法的返回值类型可以是任何一种数据类型,包括基本数据类型,也包括引用数据类型,例如:byte,short,int,long,float,double,boolean,char,String,Student(自定义类)等。当然,如果这个方法在执行完之后不需要返回任何数据,返回值类型必须写void关键字,不能空着不写。

关于方法的返回值在使用的时候有哪些注意事项呢,我们来看几段代码:

public static int method1(){
	
}

以上程序在编译的时候,报错了,错误信息是“缺少返回语句”,为什么呢?这是因为该方法在声明的时候指定了方法结束之后返回int类型的数据,但是以上方法体中并没有编写“返回数据”的代码。也就是说当一个方法在声明的时候返回值类型不是void的情况下,要求方法体中必须有负责“返回数据”的语句。这样的语句怎么写呢?答案是:“return 数据;”(大家还记得第六章节控制语句中的返回语句吗?这个就是),并且要求这个“数据”的类型要和声明的返回值类型一致,要不然编译器就会报错了。代码这样写就没问题了:

public static int method1(){
	/*
	return 1;
	return 0;
	return 100;
	*/
	int a = 100;
	int b = 200;
	return a + b;
}

如果代码这样写呢?

public static int method1(){
	return 1;
	System.out.println("hello world!");
}

编译以上程序,我们可以看到编译器报错了,提示的错误信息是:“System.out.println("hello world!");”这行代码是无法访问的语句。这是为什么呢?因为在方法中如果一旦执行了带有“return”关键字的语句,此方法就会结束,所以以上的程序中“System.out.println("hello world!");”这行代码是永远无法执行到的,所以编译报错了。得到的结论是:带有return关键字的语句只要执行,所在的方法则执行结束。

那这样写呢?

public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
	}
}

还是编译报错,错误信息是“缺少返回语句”,为什么呢?实际上方法在声明的时候指定了返回值类型为int类型,java语法则要求方法必须能够“百分百的保证”在结束的时候返回int类型的数据,以上程序“return 1;”出现在if语句的分支当中,对于编译器来说,它只知道“return 1;”有可能执行,也有可能不执行,所以java编译器报错了,不允许程序员这样编写代码。来看以下代码:

public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
	}else{
		return 0;
	}
}

这样就能编译通过了,为什么呢?这是因为编译器能够检测出以上的if..else..语句必然会有一个分支执行,这样就不缺少返回语句了。其实以上代码也可以这样写:

public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
	}
	return 0;
}

以上代码也可以达到同样的效果,因为“return 1;”如果不执行,则一定会执行“return 0;”,当然,如果执行了“return 1;”则方法一定会结束,“return 0;”也就没有执行的机会了。再看下面的代码:

public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
		System.out.println("第1行");
	}
	System.out.println("第2行");
	return 0;
	System.out.println("第3行");
}

以上程序还是编译报错,哪里出错了,为什么呢?其中“第1行”和“第3行”代码没有执行机会,编译报错,但“第2行”是有执行机会的。通过以上程序我们可以得出这样的结论:在同一个“域”中,“return”语句后面是不能写任何代码的,因为它无法执行到。

如果只是单纯的完成以上代码的功能我们也可以这样写:

public static int method1(){
	boolean flag = true;
	return flag ? 1 : 0;
}

所以,一个功能的实现,代码可以写很多种不同的方式,慢慢培养吧同学们,这需要一个过程,千万戒骄戒躁。我们接着看关于返回值还有哪些注意事项:

public static void method2(){
	return 10;
}

以上代码编译报错,为什么呢?这是因为一个方法在声明的时候返回值类型定义为void,这就表示该方法执行结束的时候不能返回任何数据,而以上程序中编写了“return 10;”这样的代码,自然编译器会报错的。也就是说前后说法要一致,声明的时候有返回值,那么代码编写的时候就必须有“return 值;”这样的语句。如果声明的时候没有返回值,那么方法结束的时候就不能编写“return 值;”这样的语句。那么,大家再来看看以下的代码:

public static void method2(){
	return;
}

经过测试,以上的代码编译通过了,也就是说当一个方法的返回值类型是void的时候,方法体当中允许出现“return;”语句(注意:不允许出现“return 值;”),这个语法的作用主要是用来终止方法的执行。当一个方法的返回值类型是void的时候,在方法执行过程中,如果满足了某个条件,则这个方法可能就没必要往下继续执行了,想终止这个方法的执行,此时执行“return;”就行了。有一些同学在最初的学习过程中,对break和return认识的不够清晰,我们来研究一下,请看以下代码:

public static void main(String[] args) {
	for(int i = 1; i <= 10; i++){
		if(i == 5){
			break;
		}
		System.out.println("i = " + i);
	}
	System.out.println("hello world!");
}

运行结果如下图所示:

java方法_www.wityx.com

图7-4:break测试

public static void main(String[] args) {
	for(int i = 1; i <= 10; i++){
		if(i == 5){
			return;
		}
		System.out.println("i = " + i);
	}
	System.out.println("hello world!");
}

运行结果如下图所示:

java方法_www.wityx.com

图7-5:return测试

通过以上的测试,我们可以得出break和return根本不是一个级别的,break用来终止循环,return用来终止一个方法的执行。接下来再看一段代码:

public static int method3(){
	return;
}

经过测试,以上代码编译报错,错误信息是:缺少返回值,为什么呢?这是因为方法在声明的时候规定了返回int类型的值,但是在方法体当中执行了“return;”语句,并没有返回具体的值,所以“return;”只能出现在返回值类型是void的方法中。

接下来我们来看看,当一个方法执行结束之后怎么接收这个返回值呢?请看下面代码:

public static void main(String[] args) {
	//可以编译也可以正常运行
	sumInt(10 , 20);
	int retValue = sumInt(100 , 200);
	System.out.println("计算结果 = " + retValue);
	//编译报错,返回值类型是int,不能采用byte接收
	//byte retValue2 = sumInt(1 , 2);
}
public static int sumInt(int a , int b){
	return a + b;
}

运行结果如下图所示:

java方法_www.wityx.com

图7-6:测试怎么接收返回值

通过以上的测试我们得知,方法执行结束之后的返回值我们可以接收,也可以不接收,不是必须的,但大多数情况下方法的返回值还是需要接收的,要不然就没有意义了,另外方法的返回值在接收的时候采用变量的方式接收,要求这个变量的数据类型和返回值类型一致(当然,也可以遵守自动类型转换规则)。

栈数据结构(理解)

要想理解方法执行过程中内存的分配,我们需要先学习一下栈数据结构,那么什么是数据结构呢?其实数据结构是一门独立的学科,不仅是在java编程中需要使用,在其它编程语言中也会使用,在大学的计算机课程当中,数据结构和算法通常作为必修课出现,而且是在学习任何一门编程语言之前先进行数据结构和算法的学习。数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

常见的数据结构有哪些呢?例如:栈、队列、链表、数组、树、图、堆、散列表等。目前我们先来学习一下栈(stack)数据结构,这是一种非常简单的数据结构。如下图所示:

java方法_www.wityx.com

栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是:仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈(push),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈、退栈或弹栈(pop),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。如下图所示:

java方法_www.wityx.com

通过以上的学习,我们可以得知栈数据结构存储数据有这样的特点:先进后出,或者后进先出原则。也就是说最先进去的元素一定是最后出去,最后进去的元素一定是最先出去,因为一端是开口的,另一端是封闭的。

对于栈数据结构,目前我们了解这么多就可以了,等学完“方法执行的时候内存是如何变化的”,到那个时候大家再思考一个问题,为什么方法执行过程的内存要采用栈这种数据结构呢,为什么不选择其它数据结构呢?

方法执行过程中内存的变化(理解)

以上内容中讲解了方法是什么,怎么定义,怎么调用,目前来说大家实际上掌握这些内容就行了,接下来的内容大家尽量去学,实在是掌握不了,也没有关系,后期的内容会对这一部分的知识点进行不断的讲解,慢慢的大家就会了,其实在学习编程的过程中会遇到很多这样的情况,没事,大家不要心急,学习后面内容的过程中你会对前面的内容豁然开朗。

以下要讲解的是程序的内存,例如:代码片段被存储在什么位置?方法调用的时候,在哪里开辟内存空间等等。所以这一部分内容还是很高端大气上档次的。不过话又说回来,要想真正掌握java,内存的分析是必要的。一旦掌握内存的分配,在程序没有运行之前我们就可以很精准的预测到程序的执行结果。

好了,接下来我们开始学习方法执行过程中内存是如何变化的?我们先来看一张图片:

java方法_www.wityx.com

图7-9:JVM内存结构图

上图是一张标准的java虚拟机内存结构图,目前我们只看其中的“栈”和“方法区”,其它的后期研究,方法区中存储类的信息,或者也可以理解为代码片段,方法在执行过程中需要的内存空间在栈中分配。java程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码(class)文件,然后将其加载到java虚拟机的方法区当中,开始调用main方法,main方法被调用的瞬间,会给main方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main方法的活动空间处于栈底。

也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java虚拟机才会在“栈内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。由于栈的特点是先进后出,所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main方法最先被调用,那么它一定是最后一个结束的。换句话说:main方法结束了,程序也就结束了(目前来说是这样)。

接下来我们来看一段代码,同时画出内存结构图,以及使用文字描述该程序的内存变化:

public class MethodTest {
	public static void main(String[] args) {
		System.out.println("main begin");
		m1();
		System.out.println("main over");
	}
	public static void m1() {
		System.out.println("m1 begin");
		m2();
		System.out.println("m1 over");
	}
	public static void m2() {
		System.out.println("m2 begin");
		System.out.println("m2 over");
	}
}

运行结果如下图所示:

java方法_www.wityx.com

图7-10:方法执行过程中内存变化测试程序

通过上图的执行结果我们了解到,main方法最先被调用,但是它是最后结束的,其中m2方法最后被调用,但它是最先结束的。大家别忘了调用的时候分配内存是压栈,结束的时候是释放内存弹栈哦。为什么会是上图的结果呢,我们来看看它执行的内存变化,请看下图:

java方法_www.wityx.com

通过上图的分析,可以很快明白,为什么输出结果是这样的顺序,接下来我们再采用文字的方式描述它的内存变化:

  • 类加载器将class文件加载到方法区。
  • 开始调用main方法,在栈内存中给main方法分配空间,开始执行main方法,输出”main begin”。
  • 调用m1()方法,在栈内存中给m1()方法分配空间,m1()方法处于栈顶,具备活跃权,输出”m1 begin”。
  • 调用m2()方法,在栈内存中给m2()方法分配空间,m2()方法处于栈顶,具备活跃权,输出”m2 begin”,继续输出”m2 over”。
  • m2()方法执行结束,内存释放,弹栈。
  • m1()方法这时处于栈顶,具备活跃权,输出”m1 over”。
  • m1()方法执行结束,内存释放,弹栈。
  • main()方法这时处于栈顶,具备活跃权,输出”main over”。
  • main()方法执行结束,内存释放,弹栈。
  • 栈空了,程序结束。

大家是否还记得之前的课程中曾经提到方法体当中的代码是有执行顺序的,必须遵循自上而下的顺序依次逐行执行,当前行代码必须执行结束之后,下一行代码才能执行,不能跳行执行,还记得吗?现在再和栈数据结构一起联系起来思考一下,为什么要采用栈数据结构呢?是不是只有采用这种先进后出的栈数据结构才可以保证代码的执行顺序呢!此时,你是不是感觉程序的设计者在此处设计的非常巧妙呢!


文章来自www.wityx.com,转载请注明出处!原文地址http://www.wityx.com/post/893_1_1.html


java面试题交流群:327440556

您需要登录后才可以回帖 登录 | 立即注册

java面试题网www.wuliaokankan.cnjava建站系统提供技术支持V2.1 网站地图 © 2016-2018