第六讲 关于方法
1 关于程序设计的理念
关于程序设计:
我们在学习程序设计的时候,该以什么样的心态和角色去学习?
- 程序设计,核心是设计,我们应该以设计者的角色去学习
- 何为设计?设计的主体是使用者,也就是说我们是设计者,我们要给用户设计软件,满足用户的需求。
- 用户对需求有什么样的要求?或者是说怎样叫做满足用户需求?简单来讲,用户希望我们做出来的东西能够让他们满意。
- 因此,我们时时刻刻都要想着,怎样让用户满意。那么我们在设计软件的时候、设计程序的时候,要考虑很多因素。我们的代码要漂亮,要简洁高效,同时我们的软件要能够让用户使用起来方便、快捷、稳定.. .关键是要实现用户想要的功能。
- 我们在学习的过程中要建立起设计者的角色定位,要在学习的过程中保持以用户需求为目标,我们在设计、实现我们的程序的时候,要实时的考虑功能性、健壮性、安全性等等。而不是简单地实现了事。
- 题外话,关于设计。我们本身就应该要保持设计的理念来处理日常的生活、工作、学习,这不仅在程序设计中,我们的人生也是如此。我们要学会做规划,这是一个对于未来的设计。另外,每天做的事情,我们也要做设计,这些设计都是为了人生的厚度做准备的。没有设计,就浑浑噩噩的,一塌糊涂。
2 关于方法
1 方法是什么?
方法是处理业务逻辑的。我们刚谈了设计,我们是设计者,为用户设计软件,那么用户的需求或者是说用户需要使用一些功能,我们进行实现,这些功能或者叫做业务,有一定的逻辑,这些东西叫做业务逻辑。方法就是处理这些业务逻辑的。
比如println()方法,用来向控制台输出用户想要输出的结果。这就是一个方法,它用来处理业务逻辑。
2 定义方法的原则
一个方法应该尽可能的单一,也就是说一个方法尽可能的只处理一个业务逻辑。不要很多业务逻辑在同一个方法中实现。
比如:println()方法中,除了打印的业务逻辑之外,还有计算+-*/的业务逻辑,你让用户怎么用?
sum(int x, int y) {
// 这里有没有必要自己再重新写一套关于如何输出的业务逻辑?
// 我们只希望在这里做求和运算而已,如果要用到print这样的业务逻辑的时候
// 我们可以调用print相关的方法。
}
3 关于方法的调用
我们知道main()方法是入口,它可以调用其他的方法,这些方法包括了java中已写好的一些方法,这些方法是SUN公司java团队写好的,提供给广大开发者用的。也可以包括程序员自己定义的方法。
自己定义的方法也可以调用库中的方法,也可以调用自己写的其他的方法。C语言中也是这么做的。
int printSum() {
printf(sum());
}
4 关于方法的返回值
我们知道方法是用来处理业务逻辑的,也可以理解为处理某些数据从而得到程序员/用户想要得到的结果的。这种结果分为两种,第一种不需要给出去的,也就是说在该方法中处理完了就了事了,不需要给其他的方法,或者是没有其他的用途。另一种是需要给出去的,需要给到其他的方法或者是其他的地方,供它们来使用。
我们知道方法返回结果的时候,使用的是return,执行遇到了return该方法结束,并将值返回。不用return,有没有其他的办法返回结果呢?在C语言中,全局变量能够保存返回值。在java中我们说了没有全局这个概念了,因为java是面向对象的,那么在java中也可以用某种变量来保存方法的返回值。
关于有返回值的方法:返回值的类型一定要与方法名前的返回值类型相匹配,否则编译报错。一个方法返回了结果,调用该方法的时候,我们可以接收也可以不接收这个返回值。
5 关于方法的执行
方法在什么时候执行?方法在调用的时候被执行,没有被调用的方法,是不会被执行的。我们在定义方法的时候,在其他方法中没有调用该方法,该方法永远不会被执行。有一个方法不需要被调用也会被执行,这个方法是main方法,因为他是应用程序的入口,jvm在加载完程序后会自动寻找main方法,如果找不到,会直接报错。编译时,没有main方法是可以的。因为编译只要符合词法、语法的规则。运行则不然,一个程序要运行起来,一定要有一个入口,该入口就是main()。main方法,在java中没有返回值,也就是说main中一般情况下是没有return的,那么什么时候main方法运行结束呢?
当main方法中的代码执行完比之后,就会自动结束。因为java中代码是顺序执行的。当最后一行代码执行完毕之后,下一条是}的时候,就自动结束了。
那么,main方法运行结束,程序是否结束了?不是。这涉及到多线程。多线程是未来要讲的重点。这里先不说。
6 再次理解什么是方法
首先我们要理解什么是程序?程序包括哪些部分?
程序包括了两部分:数据、处理数据的方法。数据结构是组织数据的,算法是处理数据的,怎样处理的?靠的是组织指令。如,查找算法,可以暴力查找,也可以用二分查找,这些东西无非是如何使用for循环,说白了就是如何编排语句。
方法就是对语句的编排。也就是对指令的组织。所以说,方法是用来组织处理数据的指令的。
方法只针对某个具体的数据吗?比如sum(100,200)?当然不是,方法可以看成是提供某一类功能的一个公式。它具有通用性。
3 关于jvm的内存结构(重点)
栈是一种数据结构( stack) ,栈是一种先进后出( FILO) ,或者是说后进先出( LIFO) 的数据结构。也就是说数据存入栈中,先存入的只能是最后才能取到。最后存入的,最先被取到。栈只有一个入口,压栈、弹栈都从这个口子进行。因此有了LIFO.栈是有大小的,如果满了,就不能再往栈中放入数据。超出了之后,就会溢出。stack over flow
如果我们用C语言去实现,无非是一个一维的数组,数组存入数据的时候从0开始,到n-1;取的时候,我们可以从最后一个开始取。往栈中存入数据的时候,叫做压栈push,从栈中取出数据的时候,叫弹栈pop。
在jvm中,存在内存空间,最主要的内存空间有三块
其中一块叫做栈内存空间,这个内存空间存入和取出数据遵循先进后出(后进先出)。是栈的特性。因此,我们将这块空间取名栈内存空间。这一部分要深入理解,就要讲到汇编。后面有机会给你们讲汇编。另一个叫做堆内存空间,还有一个叫做方法区内存空间。
注意:
栈内存空间采用的是栈的操作,先进后出,后进先出。但是堆内存空间,不是堆的操作,我们只是将这块空间叫做堆内存空间。同样方法区内存空间,不是用来存放方法的,与方法无关,只不过我们叫方法区内存空间。
这三块内存空间有什么用?都是用来存放什么东东的?( 超级重点)
1、方法区内存空间:存放代码片段,存放常量、静态( 被static修饰) 的数据( 这个后面会详细说) 。
解释:我们知道xxx.java文件是开发者写好的源文件,这个无法在JVM上运行,JVM运行的是编译好的文件,即.class文件。
我们知道,我们的源文件在硬盘上,就是说xxx.java文件写好之后,就存在硬盘上。编译后生成的.class文件存放在哪里?也在硬盘上。
硬盘上的东西能直接跑起来吗?不能。需要加载到内存中。我们以前就说过,只有将代码加载到内存中,才能够将这些代码的执行交给CPU。
我们知道,只有将.class文件加载到JVM中,程序才能运行,.class文件中保存的代码的二进制形式,.class文件中的内容要放到某个内存中才能让程序执行。
我们又知道,java程序只能跑在JVM上,JVM就有一块内存空间用来存放代码。存放的是.class文件中的内容。只不过我们没有做什么操作,只是使用java命令,让.class文件进行了加载和运行。这个加载的过程是有指令帮我们做了。不是由我们双击鼠标或者是其他的方式。那么存放这些.class文件的内存空间叫做方法区内存空间。这个加载的过程叫做类加载( 这是一种机制,未来再说,很有用) 。这个空间存放的代码的二进制形式。只有将代码加载到内存中,CPU才有可能执行,否则没法执行。
我们在桌面上双击一个.exe文件,实际是将程序的代码加载到内存的过程,只有这样该代码才能执行。否则,在硬盘上存放的程序,都无法执行。CPU是大脑,但是它能处理的数据来自于寄存器,只跟寄存器通信,寄存器呢只跟内存和CPU交互。不会直接去硬盘上取。因为内存很有限,而硬盘很大,存取速度很慢。内存的存取速度快一些,寄存器存取速度非常快,但寄存器很小。
除了存放.class文件中的代码之外,这部分内存还存放了常量、静态的数据等等( 这个以后我们再详细讲解) 。
2 栈内存空间
这是一个栈结构的内存空间,它先进后出或者说是后进先出。栈内存空间的作用是,当方法被执行的时候,是在栈内存空间中分配内存用以执行方法。那么,方法中的参数、方法中定义的变量及数据,在这里分配空间存储( 当前就这么理解。不完全是这样。讲面向对象的时候我们还会完善) 。
当方法被调用且执行的时候,方法会压栈,方法执行结束后,会弹栈。压栈就是进入栈内存空间,弹栈就是退出栈内存空间。
3 堆内存空间,这个我们暂时用不到,我们后面再说。它的作用很重要。相当于C语言中用malloc、new申请到的空间都出自这里。
public class JVMTest02
{
public static void main ( String[ ] args)
{
int x = 10 ;
int y = 20 ;
int res = 0 ;
res = sum ( x, y) ;
System. out. println ( res) ;
}
static int sum ( int x, int y) {
int res = x + y;
return res;
}
}
static 修饰的变量以及常量是放在方法区内存中的。
栈内存空间中,只存放局部变量,局部变量包括:方法的参数,方法内定义的非静态变量,以及循环条件中的参数。
for (int i = 0; ;)// 这种方式,循环执行完毕,i不存在了
// 下面的方式,循环结束i还在栈中存在。
int i = 0;
for (i;;)
记住,方法在栈中执行,方法中的局部变量在栈中分配空间。方法运行完毕后,出栈,该方法所使用到的栈内存空间都会释放。
public class JVMTest03
{
public static void main ( String[ ] args)
{
System. out. println ( "main start!" ) ;
m1 ( ) ;
System. out. println ( "main over!" ) ;
}
static void m1 ( ) {
System. out. println ( "----m1----start!" ) ;
m1 ( ) ;
System. out. println ( "----m1----over!" ) ;
}
}
4 方法的重载
重载(overload)
方法名是可以同名的,因为java中有这样的机制,叫做方法重载。
方法重载机制发生的条件:(这三个条件一定要同时满足)
1. 在同一个类中
2. 方法名相同
3. 方法的参数列表不相同。
参数类型不同
参数个数不同
参数位置不同
public class OverloadTest01
{
public static void main ( String[ ] args)
{
System. out. println ( "Hello World!" ) ;
System. out. println ( 'a' ) ;
System. out. println ( 100 ) ;
System. out. println ( 3.14F ) ;
}
}
println()方法怎么做到能打印各种数据类型的内容?
我们知道,一个方法要么有参数列表,要么没有参数列表。只有这两种情况。
如果一个方法有参数,给该方法传入实参的时候,一定要对应个数和类型。
如:sum(int x, int y)------------>sum(3, 2)-------X--??--> sum(33.0, 2.0)、sum(3)、sum(3, 2, 1)
在上面的代码中,println()方法只接收一个参数。那么我们可以猜测println方法一定有以下几种形式,而且这些形式是并存的:
println(int arg)
println(String arg)
println(char arg)
println(float arg)
方法名能重复吗?可以
如果方法名不能重复,会出现什么问题?
用户需求:张三是用户,他说,请你帮我开发一个程序,该软件能够计算两个数的和。张三说他希望简单,一个窗口 __ + __ = __就够了。
我是设计者,我的思考如下:
首先,拆解需求:两个数,和,这里可以确定,和就是相加,我用加法就可以了。
两个数可以有哪些形式?两个整数相加、两个小数相加,一个整数+一个小数,一个小数+一个整数
我来做设计:定义一个方法,名字叫做add()
两个整数:int addInt(int x, int y)
两个小数:double addDouble(double x, double y)
一个整数一个小数: double addIntAndDouble(int x, double y)
一个小数一个整数: double addIntAndDouble(double x, int y)
这样写代码感觉很多冗余。
我的想法是这样的:对于张三来说,它只要看到一个窗口,那么我只给他提供一个方法叫做add(),它传入什么样的参数,我就匹配什么样的参数。这样的话,用户使用起来很舒服。
public class Add
{
static int add ( int x, int y) {
return x + y;
}
static double add ( double x, double y) {
return x + y;
}
static double add ( int x, double y) {
return x + y;
}
static double add ( double x, int y) {
return x + y;
}
}
像这样没有main方法的类,里面定义了一些方法,功能相似,它不能单独运行,这样的做法叫做封装。
public class OverloadTest01
{
public static void main ( String[ ] args)
{
System. out. println ( Add. add ( 100 , 199 ) ) ;
System. out. println ( Add. add ( 1.0 , 100.8 ) ) ;
System. out. println ( Add. add ( 100 , 199.5 ) ) ;
System. out. println ( Add. add ( 1.0 , 100 ) ) ;
}
}
这也解释了方法重载的好处。
解释一下:
这两个文件在同一个文件夹下,Add. class , 一个是OverloadTest01. class ,一定要在同一个文件夹下。
Add. add ( ) 这种调用方法,一定是静态的方法也就是被static 修饰的方法才能直接用类名去调用。
非静态的不能这样做。这样做编译报错。为什么不能,我们后面再详细讲。
不能是Add. java源文件,这个东西不会被加载到内存中,只有Add. class 文件才会被加载到内存中。所以一定要编译,两个文件都要编译,才能正常执行。
作业:
写递归,画内存图,测试,图、代码、分析思路放到博客上,不要照抄。