下一篇持续更新中…
一、方法的基础语法
1. 举例体验、分析并理解方法使用原理
A. 不使用方法的代码缺点-方法来源
/*
以下程序不使用“方法”,分析程序存在那些缺点?
* 以下代码都是完成两个int类型数据的和,相同的代码写了三遍(只不过每一次参与求和的数据不同)代码没有得到重复使用。
* 应该在java语言当中有这样的一种机制:
- 某个功能代码只需要写一遍
- 要使用这个功能,只需要给这个功能传递具体的数据
- 这个功能完成之后返回一个最终的结果
这样代码就可以重复利用了,提高代码复用性
* 使用这个方法我们称为“调用 / invoke”
*/
public class MethodTest01{
public static void main(String[] args){
//需求1:请编写程序计算10+20的和
//并将结果输出【功能:计算两个int类型数据的和】
int a = 10;
int b = 20;
int c = a + b;
System.out.println(a + "+" + b + "=" + c);
//需求2:请编写程序计算666 + 888的和
/并将结果输出【功能:计算两个int类型数据的和】
int x = 666;
int y = 888;
int z = x + y;
System.out.println(x + "+" + y + "=" + z );
//需求3:请编写程序计算111 + 222的和
//并将结果输出【功能:计算两个int类型数据的和】
int m = 111;
int n = 222;
int e = m + n;
System.out.println(m + "+" + n + "=" + e );
//以上的三个需求其实就是一个需求
//计算两个int类型数据的和。
//功能相同,只不过每一次参与计算的具体数据不同
}
}
B. 方法的优点、使用注意事项
-
以下直接使用方法这种机制【这个栗子不讲方法的语法】,分析程序的优点:代码得到了重复使用
-
方法的本质是什么?
方法就是一段代码片段,并且这段代码片段可以完成某个特定的功能,并且可以被重复的使用 -
方法,对应的单词:Method
方法在c语言中叫做函数/Function -
方法定义在类体当中,在一个类当中可以定义多个方法,方法编写的位置没有先后顺序,可以随意
-
方法体当中不能再定义方法!!!!
-
方法体由Java语句构成,方法体当中的代码遵守自上而下的顺序依次执行
public class MethodTest02{
//类体
public static void main(String[] args){
//计算两个int类型数据的和
MethodTest02.sumInt(10 + 20);
MethodTest02.sumInt(666 + 888);
MethodTest02.sumInt(111 + 222);
}
//方法
//单独的定义一个方法,该方法完成计算两个int类型数据的和,并且将结果输出
public static void sumInt(int a ,int b){
int c = a + b;
System.out.println(a + "+" + b + "="+ c);
}
}
2. 方法定义、语法结构:
[修饰符列表] 返回值类型 方法名 (形式参数列表){
方法体;
}
3. 对以上语法结构解释说明:
3.1. 修饰符列表
- 可选项,不是必须的
- 目前统一写成public static 【以后出文章写】
- 方法的修饰符列表当中 “ 有static关键字 ” 的话,怎么调用这个方法?
- 类名.方法名(实际参数列表);
3.2. 返回值类型
什么是返回值?
一个方法是可以完成某个特定功能的,这个功能结束之后大多数都是需要返回最终执行结果的,执行结果可能是一个具体存在的数据。而这个具体存在的数据就是返回值
返回值类型
返回值是一个具体存在的数据,数据都是有类型的,此处需要指定返回值的具体类型
返回值类型都可以指定哪些类型?
java任意一种类型都可以,包括基本数据类型和所有的引用数据类型。它可以是:byte,short,int,long,float,double,char,boolean,String,void…
【非void返回值类型】练习
- 返回值类型不是void时,要求方法必须保证百分百的执行 " return 值; " 这样的语句来完成值的返回。没有这个语句编译器会报错
- 当一个方法有返回值的时候,我们调用这个方法时,方法回了一个值。对于调用者来说,这个返回值可以选择接受,也可以选择不接收。但是大部分情况下,我们都是选择接收的。
/*
需求:
请定义并实现一个方法。
该方法可以计算两个int类型数据的商,并要求将最终的计算结果返回给调用者
注意:
返回语句、返回值要写
返回值要和返回值类型一致
具体方法编写中的代码要能够满足当前需求
方法执行结束之后的返回值可以采用 变量接收 或者直接 打印输出接收
*/
public class MethodTest08{
public static void main(String[] args){
//调用方法
divide();//这里并没有接收这个方法的返回数据
//接收一下方法执行结束之后的返回值
//采用变量接收
//变量的数据类型需要和返回值的数据类型相同,或者可以自动类型转换
int i = divide( 10 / 3);
System.out.println(i);//3
long x = divide( 10 / 3);
System.out.println(x);//3
System.out.println(divide(10/3));
}
public static int divide(int a,int b){
return a / b;
//int c = a / b;
//return c;
}
}
【return语句】练习
/*
深入return语句
* 带有return关键字的java语句只要执行,所在的方法执行结束。
* 在“同一个作用域”当中,return语句下面不能编写任何代码,因为这些代码永远都执行不到。所以编译报错
*/
public class MethodTest09{
public static void main(String[] args){
}
//编译报错,缺少返回语句。以下程序编译器认为无法百分百保证"return 1;"会执行。
/*
public static int m(){
int a = 10;
if(a > 3){
return 1;
*/
//以下程序可以保证执行"return 1;"或"return 0;",编译通过。
public static int m(){
int a = 10;
if(a > 3){
return 1;
}else{
return 0;
/*
或者表示为
public static int m(){
int a = 10;
if(a > 3){
return 1;
}
return 0;
` */
public static int m(){
return 10 > 3 ? 1 : 0;
}
}
}
}
返回值代码怎么写?
“ return 值; ”,并且要求返回的 “ 值 ” 的数据类型必须和“方法体的 ‘ 返回值类型 ’ ”一致。不然编译器报错。
只要带有return关键字的语句执行,return语句所在的方法结束。【不是JVM结束,是return语句所在的方法结束】
方法执行结束之后也可以不返回任何数据
java中规定,当一个方法执行结束之后不返回任何数据的话,返回值类型位置必须编写:void关键字。
----返回值类型若不是void,表示这个方法执行结束之后必须返回一个具体的数值,若没有返回任何数据的话,编译报错。
----返回值若是void,方法体中不能编写 “ return 值; ” 这样的语句,但是可以写 “ renturn; ” 这样的语句。
【void方法中使用return语句】练习
“ renturn; ” 出现在返回值为void的方法中,主要是为了终止方法的执行
/*
ps: 对于结果类型为空的方法无法返回值
*/
public class MethodTest10{
public static void main(Sring[] args){
m();
for(int i = 10;i > 0;i--){
if(i == 2){
return;//结束的是main方法
}
System.out.println("data: -->" + i);//10 9 8 7 6 5 4 3
}
System.out.println("Execute Here!");
}
public static void m(){
for(int i = 0 ; i < 0 ; i++){
if(i == 5){
return;//不是终止for循环,终止的是m方法
//break;//终止的for循环
}
System.out.println("i-->" + i);//0 1 2 3 4
}
System.out.println("HelloWorld!");
}
}
3.3. 方法名
- 只要是合法的标志符就行
- 方法名最好见名知意
- 方法名最好是动词
- 方法名首字母要求小写,后面每个单词首字母大写
3.4. 形式参数列表
- 形参是局部变量:int a; double b; String s; float c;…
- 形参的个数可以是:0~N个
- 多个形参之间用 “ 逗号 ” 隔开
- 形参中起决定性作用的是形参的数据类型,形参的名字就是局部变量的名字
- 方法在调用的时候,实际给这个方法传递的真实数据被称为实际参数,简称实参。
- 实参列表和形参列表必须满足:
a.数量相同。
b.
例如:- 方法定义
public static int sum(int 变量名,int 合法的标识符就行){ //(int a,int b)是形式参数列表
} - 方法调用
sum(“abc” , “def”); 编译器报错
sum(10,20);//是实参列表
- 方法定义
3.5. 方法体
方法体必须由大括号括起来,方法体当中的代码有顺序,遵循自上而下的顺序依次执行,并且方法体由Java语句构成每一个Java语句以 “ ;” 结尾
4. 方法调用
语法规则:【方法的修饰符列表当中有static的前提下】
类名.方法名(实参列表);【这是一条Java语句表示调用某个类的某个方法时,传递这样的实参】
4.1.方法调用时才会执行
方法,只定义不去调用时不会执行。只有在调用的时候才会执行。
// Public表示公开的
// Class表示定义类
//MethodTest03是一个类名
//表示定义一个公开的类,起名MethodTest03,由于是公开的类,所以源文件名必须为MethodTest03
public class MethodTest03{//汽车工厂
//类体
//类体中不能直接编写Java语句,除声明变量之外。
//方法出现在类体当中
//方法
//public表示公开的
//static表示静态的
//void表示方法执行结束之后不返回任何数据
//main是方法名,表示主方法。
//(String[] args):形式参数列表。其中String是一种引用数据类型,args是一个局部变量的变量名。
//所以一下只有args这个局部变量的变量名是随意的。
//主方法就需要这样固定编写,这是程序的入口【sun公司规定的必须这样写】
public static void main(String args){
//调用这个方法
//这里的程序一定会执行
//main方法是JVM负责调用的,是入口位置。
//从这里作为起点开始执行程序。我们可以在这里编写Java语句来调用其他的方法。
//调用MethodTest03的sum方法,传递两个实参
MethodTest03.sum(10,20);//(10,20)实参列表【实际上执行到这里,main方法暂停了,进入sum方法执行,sum方法执行结束之后表示main方法中调用sum方法所在行,执行结束】
//注意:方法体当中的代码是有顺序的,遵循自上而下的顺序依次执行。第110行的程序执行不结束。无法执行第111行的程序。
//一个方法可以被重复使用,重复调用
int a = 100;
MethodTest03.sum(a,500);//(a,500)实参列表
//再次调用方法
int k = 90;
int f = 10;
MethodTest03.sum(k,f);//(k,f)实参列表
}
//定义一个方法
//一个车间
//自定义方法,不是程序的入口
//方法作用是计算两个int类型数据的和,不要求返回结果,但是要求将结果直接输出到控制台
//修饰符列表:public static
//返回值类型:void
//方法名:sum
//形式参数列表:(int i,int y)
//方法体主要任务:求和之后输出计算结果
public static int sunInt(int i,int j){
System.out.println(i + "+" + j + "=" + (i + j));
}
}
4.2.方法调用不一定在main中
方法的调用不一定在main方法当中,可以在其他方法当中,只要是程序可以执行到的位置,都可以去调用其他方法。
public class MethodTest04{
public static void sum(int a,int b){
System.out.println(a + "+" + b + "=" + (a+b));
//调用doSome方法
MethodTest04.doSome();
}
//主方法
public static void main(String[] args){
//调用sum方法
MethodTest04.sum(1,2);
System.out.println("HelloWorld!");
}
public static void doSome(){
System.out.println("doSome!");
}
}
4.3.实参和形参:个数、数据类型相同
类型不同的时候,要求能够进行相应的自动类型转换
public class MethodTest05{
public static void main(String[] args){
//编译错误,参数数量不同
//MethodTest05.sum();
//编译错误:实参和形参的类型不是对应相同的
//MethodTest05.sum(true,false);
//可以
MethodTest05.sum(10L,20L);
//存在自动类型转换:int-->long
MethodTest05.sum(10,20);
//编译错误,参数类型不是对应相同的
//MethodTest05.sum(3.0,10);
//可以
MethodTest05.sum((long)3.0,10)
}
public static void sum(long a,long b){
System.out.println(a + "+" + b + "=" + (a+b));
}
}
4.4.类名省略
方法的修饰符列表当中有static关键字,完整的调用方式是:“ 类名.方法名( 实参列表 ); ”。
m1(){
类名.m2()
}
但当m1方法和m2方法在同一个类体当中的时候,类名可以省略不写
ps:建议在一个Java源文件当中只定义一个class,比较清晰。这里只是为了叙述方便才在一个Java源文件当中编写了多个class这种方式不要模仿。
public class MethodTest06{
public static void main(String[] args){
//调用方法
MethodTest06.m()
//对于方法的修饰符列表当中有static关键字的“类名.”可以省略不写
m();
//调用其他类【不是本类中的】方法
A.doOther();
//省略“类名.”编译报错,省略之后默认从当前类中找do Other方法。在当前类中该方法不存在
//doOther();
}
public static void m(){
System.out.println("m method execute!");
//完整的方式
MethodTest06.m2();
//省略的方式
m2();
//不想调用当前本类当中的m2方法,就必须添加“类名.”
}
}
class A
{
public static void doOther(){
System.out.println("A's doOther method invoke!");
}
}
4.5.小练习
/*
分析以下程序的输出结果
main begin
m1 begin
m2 begin
m3 begin
m3 over
m2 over
m1 over
main over
对于当前的程序来说:
main方法最先被调用,所以也是最后一个结束
最后调用的方法是m3方法,该方法最先结束。
main方法结束之后,整个程序结束了【这句话只适合于当前所讲的内容】
不用刻意去记忆,只要记住方法中的代码是自上而下的顺序,依次执行即可,当前行的程序在没有结束的时候下一行代码是无法执行的。
5. 方法执行–内存分析
- 方法只定义,不调用是不会执行的,并且在JVM中也不会给该方法分配 “ 运行所属 ” 的内存空间。只有在调用这个方法的时候,才会动态的给这个方法分配所属的内存空间。
- 在JM内存划分上有这样三块主要的内存空间。(当然除了这三块之外,还有其他的内存空间):
- 方法区内存
- 堆内存
- 栈内存
- 关于 ” 栈 “ 数据结构:
- 栈,stack,是一种数据结构
- 数据结构反映的是数据的存储形态。
- 数据结构是独立的学科,不属于任何编程语言的范畴,只不过在大多数编程语言当中要使用数据结构。
- 作为程序员需要提前精通:数据结构 + 算法【计算机专业必修的一门课程】
- java程序员在不精通数据结构和算法的前提下,也可能进行Java开发,因为Java有一套庞大的类库支撑。别人写好了,直接用。 【Java SE当中的集合章节,使用了大量的数据结构】
- 常见的数据结构:
- 数组
- 队列
- 栈
- 链表
- 二叉树
- 哈希表 / 散列表
… …
-
方法代码片段存放在哪里?方法执行的时候执行过程的内存在哪里分配?
-
方法代码片段属于 “ . class ” 字节码文件的一部分。字节码文件在类加载的时候,将其放到了方法区当中。所以JVM中的三块主要的内存空间中,方法区内存最先有数据。它存放了代码片段。
-
代码片段虽然在方法区内存当中只有一份,但是可以被重复调用。每一次调用这个方法的时候,需要给该方法分配独立的活动场所,在栈内存中分配。【栈内存中分配方法运行的所属内存空间】
-
-
方法在调用的时候会给该方法分配独立的内存空间,在栈中分配,会在栈中发生压栈动作。方法执行结束之后,给该方法分配的内存空间全部释放,此时发生弹栈动作。
- 压栈:给方法分配内存
- 弹栈:释放该方法的内存空间
- 局部变量在方法体中声明;局部变量运行阶段内存,在栈中分配
画图练习 1:
//注意: 在EditPlus当中,字体颜色为红色的表示一个类的名字,并且这个类是Java SE类库中自带的
//D:\jdk8\jre\lib\rt解压缩\java\lang(源码路径)
//我们自定义的类MethodTest01字体颜色是黑色,是标识符。其实Java SE中自带的类,例如 " string.class\system.class " 这些类的类名也是标识符
//只要是类名,就一定是标识符。
public class MethodTest01{
public static void main(String[] args){
int a = 10;
int b = 20;
int retValue = sunInt(a,b);
System.out.println("retValue = " + retValue);
}
public static int sumInt(int i, int j){
int result = i + j;
int num = 3;
int retValue = divide(result,num);
return retValue;
}
public static int divide(int x,int y){
int z = x / y;
return z;
}
}
画图练习 2 :
public class MethodTest02{
public static void main(String[] args){
int i = 10;
method(i);
System.out.println("main-->" + i);//10
}
public static int method(int i){
i++;
System.out.println("method-->" + i);//11
}
}
注意:没有 “ return;” 语句,所以值没有返回
二、方法的重载机制 Overload
1. 举例体验、分析并理解方法使用原理
A. 不使用Overload的代码缺点-方法来源
/*
以下代码不使用“方法重载机制”,不使用overload,分析程序存在的缺点
1. sumInt sumLong sumDouble方法虽然功能不同,但是功能是相似的,都是求和.在以下程序当中,功能相似的方法,分别起了三个不同的名字。这对于程序员来说,调用方法的时候不方便,程序员需要记忆更多的方法才能完成调用【不方便】
2. 代码不美观。
有没有这样的一种机制?
功能虽然不同,但是功能相似的时候可以让程序员使用这些方法的时候,就像在使用同一个方法一样。这样程序员以后编写代码比较方便,也不需要记忆更多的方法名,代码也会很美观。
有这种机制:方法重载机制/Overload
*/
public class OverLoadTest01{
//入口
public static void main(String[] args){
//调用方法
int result1 = sumInt(1,2);
System.out.println(result1);
double result1 = sumInt(1.0,2.0);
System.out.println(result2);
long result1 = sumInt(1L,2L);
System.out.println(result3);
}
//定义一个方法,可以计算两个int类型数据的和
public static int sumInt(int a,int b){
return a + b;
//定义一个方法,可以计算两个double类型数据的和
public static double sumDouble(double a,double b){
return a + b;
//定义一个方法,可以计算两个long类型数据的和
public static long sumLong(long a,long b){
return a + b;
}
//最终希望达到的效果是程序员在使用上面三个相似的方法的时候,就像在用一个方法一样。Java支持这种机制【有些语言不支持,例如以后要学习的Javascript】
}
B. 方法的优点、使用注意事项
/*
该程序还是一个体验程序,体验一下方法重载的优点:
* 程序员调用方法的时候比较方便,虽然调用的是不同的方法,但是就感觉在使用一个方法一样,不需要记忆更多的方法名
* 代码美观
前提:功能相似的时候,方法名可以相同
但是功能不同的时候,尽可能让这两个方法的名字不同
*/
public class OverloadTest02{
public static void main(String[] args){
//调用方法的时候,就像在使用一个方法一样
//参数的类型不同,对应调用的方法不同
//此时区分方法不再依靠方法名了,依靠的是参数的数据类型
System.out.println(sum(1,2));
System.out.println(sum(1.0,2.0));
System.out.println(sum(1L,2L));
}
//以下三个方法构成了方法重载机制
public static int sum(int a,int b){
System.out.println("sum(int,int)");
return a + b;
}
public static double sum(double a,double b){
System.out.println("sum(double,double)");
return a + b;
}
public static long sum(long a,long b){
System.out.println("sum(long,long)");
return a + b;
}
}
2. 定义
-
方法重载又被称为:Overload
-
什么时候考虑使用方法重载?
功能相似时,尽可能让方法名相同;
功能不同 / 不相似时,尽可能让方法名不同。 -
什么条件满足之后构成了方法重载?
- 在同一个类当中
- 方法名相同
- 参数列表不同:
- 数量不同
- 顺序不同
- 类型不同
- 方法重载和什么有关系,和什么没有关系?
- 方法重载和 “ 方法名 + 参数列表 ” 有关系
- 方法重载和返回值类型、修饰符列表无关
public class OverloadTest03{
public static void main(String[] args){
m1();
m1(10);
m2(1,2.0);
m2(2.0,1);
m3(10);
m3(3.0);
}
//以下两个方法构成重载
public static void m1(){}
public static void m1(int a){}
//以下两个方法构成重载
public static void m2(double a,int b){}
public static void m2(int a,double b){}
//以下两个方法构成重载
public static void m3(double x){}
public static void m3(int x){}
//编译错误,以下不是方法重载,是发生了方法重复
public static void m4(int a,int b){}
public static void m4(int b,int a){}
}
3. 具体应用
//自定义类
public class T
{
public static void pl(byte data){
System.out.println(data);
}
public static void pl(short data){
System.out.println(data);
}
public static void pl(int data){
System.out.println(data);
}
public static void pl(long data){
System.out.println(data);
}
public static void pl(float data){
System.out.println(data);
}
public static void pl(double data){
System.out.println(data);
}
public static void pl(char data){
System.out.println(data);
}
public static void pl(String data){
System.out.println(data);
}
}
三、方法递归调用
- 什么是递归方法?
自身调用自身
a(){
a();
}
- 递归是很耗费栈内存的。递归算法,可以不用的时候尽量别用
- 以下程序运行的时候发生了这样的一个错误【不是异常,是错误Error】:
java.lang.StackOverflowError
栈内存溢出错误。错误发生无法挽回,只有一个结果就是JVM停止工作。 - 递归必须有结束条件。没有结束条件,一定会发生栈内存溢出错误递归,即使有了结束条件,且即使结束条件是正确的,也可能会发生栈内存溢出错误,因为递归的太深了。
- 注意递归可以不使用,尽量别用。但是有些情况下,该功能的实现必须依靠递归方式
public class RecursionTest01{
public static void main(String[] args){
System.out.println("main begin");
//调用doSome方法
doSome();
System.out.println("main over");
}
//以下的代码片段虽然只有一份,但是可以被重复的调用,并且只要调用doSome方法,就会在栈内存中新分配一块所属的内存空间
public static void doSome(){
System.out.println("doSome begin");
doSome();//这行代码不结束,下一行程序是不能执行的
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
doSome();//这行代码不结束,下一行程序是不能执行的
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
doSome();//这行代码不结束,下一行程序是不能执行的
System.out.println("doSome over");
}
public static void doSome(){
return;
}
}
练习 1 :
/*
不使用递归,计算1~N的和【可以不用递归,尽量不用递归】
*/
public class RecursionTest01{
public static void main(String[] args){
/*
//1~4的和
int n = 4;
int sum = 0;
for(int i = 1;int i <= 4;i++){
sum += 1;
}
System.out.println("sum-->" + sum);
*/
//直接调用方法即可
int n = 4;
int retValue = sum(n);
System.out.println(retValue);
}
//单独的定义一个方法,这是一个独立的功能,可以完成1~N的求和
public static int sum(int n){
int result = 0;
for(int i = 1;i<=n;i++){
result += i;
}
return result;
}
}
练习 1 递归(附图解) :
/*
使用递归计算1~N的求和
-->1 + 2 + 3 + 4
-->4 + 3 + 2 + 1
*/
public class RecursionTest03{
public static void main(String[] args){
//1~4的和
int n = 4;
int retValue = sum(n);
System.out.println(retValue);
}
public staitc int sum(int n){
//4 + 3 + 2 + 1
if(n == 1){
return 1;
}
return n + sum(n-1);
}
}
//n + sum(n-1)
//4 + sum(3)
//4 + 3 + sum(2)
//4 + 3 + 2 + sum(1)
//4 + 3 + 2 + 1
练习 2:
/*
不使用递归,计算N的阶乘
5的阶乘:
5 * 4 * 3 * 2 * 1
*/
public class RecursionTest04{
public static void main(String[] args){
int n = 5
int retValue = method(n);
System.out.println(retValue);//120
}
public static int method(int n){
int result = 1;
for(int i = n;i>0;i--){
result *= i;
}
return result;
}
}
练习 2 递归:
//不懂递归的同学,最好将以下程序记住
public class RecursionTest04{
public static void main(String[] args){
int n = 5;
int retValue = method(n);
System.out.println(retValue);
}
public staitc int method(int n){
if(n == 1){
return 1;
}
return n * sum(n-1);
}
}