方法和递归
方法
不使用“方法”写计算和结果:
public class MethodTest{
public static void main(String[] args){
//需求一:请编写计算10+20的结果,并输出结果【功能:计算两个int类型的数据之和】
int a = 10;
int b = 20;
int c = a + b;
System.out.println(c);
//需求二:请编写计算66+88的结果,并输出结果【功能:计算两个int类型的数据之和】
int d = 66;
int e = 88;
int f = d + e;
System.out.println(f);
需求三:请编写计算11+22的结果,并输出结果【功能:计算两个int类型的数据之和】
int x = 11;
int y = 22;
int z = x + y;
System.out.println(z);
//以上三个需求是一个功能:计算两个int类型的数据之和
//只是每次参与计算具体数据不同
//上面的代码写了三个计算和的代码,代码没有得到重复使用。
}
}
方法:某个功能代码只需要写一遍,要使用这个功能,仅需要传递具体的数据,功能完成后返回最终的结果。这样代码可以重复利用,提高代码复用性。
使用方法我们称为" 调用/invoke"。方法定义在一个类体中,一个类中可以定义多个方法,方法编写位置没有先后顺序。方法体中不能再定义方法。
方法的本质就是一段代码片段,这段片段能够完成特定的功能,且可以重复使用。
注:方法在C语言当中称为“函数/Function”。
使用方法的机制把上面的代码重新编写:
public class MethodTest{
public static void main(String[] args){
MethodTest.sumInt(10,20); //30
MethodTest.sumInt(66,88); //154
MethodTest.sumInt(11,22); //33
}
//单独定义一个方法,完成两个int数据类型的和,将结果输出
public static void sumInt(int a,int b){
int c = a + b;
System.out.println(a + "+" + b + "=" + c);
}
}
1.方法的语法:
//返回值类型是int是java中任意一种类型
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
return 返回值;
}
//返回值是void
[修饰符列表] void 方法名(形式参数列表){
方法体;
}
1.1关于修饰符列表
*不是必须的
*目前学习到这里,统一写:public static【后面有】
*方法中有static关键字,调用方式:类名 . 方法名(实际参数列表);
1.2返回值类型
*返回值:方法执行结果后返回的数据就是返回值。
*返回值类型:返回值是一个具体存在的数据,数据都是有类型的。此处需要指定返回值的具体类型。
java中的任意一种类型都可以是返回值类型(包括基本数据类型和引用数据类型)。
*有的方法可以没有返回值类型:方法执行结束后不返回任何结果,java规定,当一个方法执行结束后不返回任何数据的话,返回值类型位置写“void”。
当方法需要返回一个值,需要在方法体写” return 值; “ 。反过来不需要返回值,即返回值类型位置是"void",则不需要return。
*可以编写 ” return ; ”,且在放回类型是"void"方法中写这样的语句,只要带有return关键字的语句执行,return所在的方法结束(强行结束所在的方法,不是JVM)。
1.3方法名
*只要是合法标识符就行。
*方法名最好是动词,见名知意。
*首字母要求小写,后面每个字母大写。
1.4形式参数列表(形参)
*形参是局部变量:int a ; double b ; float c ; ……
*形参间用逗号隔开。
*形参起决定作用的是数据类型。如:
//方法定义
public static int sum(int a ,int b){} //(int a, int b)是形参列表
//方法调用
sum("abc","def"); //编译器报错
sum(10,20); //编译通过(10,20)是实参列表
*方法调用的时候,实际给这个方法传递真实数据被称为:实际参数(实参)
*实参列表和形参列表必须满足:数量相同,类型对应相同。
1.5方法体
方法体由大括号括起来,方法体中代码有顺序。
1.6方法调用
方法不调用时不执行。
语法规则(方法修饰符中有static): 类名.方法名(实参列表);
例子:
//public表示公开的
//class 表示定义类
//MethodTest是一个类名
public class MethodTest{ //表示一个公开的类,起名为MethodTest,由于是公开的类,所以源文件必须:MethodTest.java
//类体
//类体不能直接编写java语句,除声明变量外
//方法出现在类体当中
//方法
//public表示公开的
//static表示静态的
//void表示方法执行结束后不返回任何数据
//main是方法名:主方法
//(String[] args):形式参数列表,其中String[]是一种引用数据类型,args是一个局部变量的变量名。
//所以以下只有args这个局部变量的变量名是随意的。
//而主方法就需要这样固定编写,这是程序的入口。【SUN规定必须这样写主方法】
public static void main(String[] args){
//这里的程序时一定会执行的
//main方法是JVM负责调用的,是一个入口的位置
//从这里作为起点开始执行程序
//我们就可以在这里写java语句来调用其他方法
MethodTest.sum(20,50);//这句语句的意思是调用MethodTest的sum方法,传递两个实参。(10,20)表示实参列表
//一个方法可以重复被调用
int a = 88;
MethodTest.sum(a,88);//(a,88)是参列表
//再次调用
int j = 50;
int k = 60;
MethodTest.sun(j,k);
}
//自定义方法
//方法作用:计算两个int类型数据的和,不要求返回结果,但是要求直接输出到控制台。
//修饰符列表:public static
//返回值类型:void
//方法名:sum
//形式参数列表:(int x,int y)
//方法体:主要任务是求和之后输出计算结果
public static void sum(int x,int y){ //(int x, int y )形参列表
System.out.println(x + "+" + y "=" + (x+y));
}
}
方法调用不一定在main方法当中,可以再其他方法中。
public class MethodTest{
public static void sum(int a, int b){
System.out.println(a + "+" + b "=" +(a + b));
//调用doSome方法
MethodTest.doSome();
}
public static void main(String[] args){
MethodTest.sum(1,2);
System.out.println("Hello");
}
public static void doSome(){
System.out.println("do some!");
}
}
方法调用时,形参列表和实参列表必须一致。
public class MethodTest{
public static void main(String[] args){
//编译错误,参数数量不同
//MethodTest.sum();
//编译错误,实参形参类型不是对应相同
//MethodTest.sum(true,false);
MethodTest.sum(10L,20L);//可以
//存在自动类型转换int->long
MethodTest.sum(10,20);
}
public static void sum(long a,long b){
System.out.println(a + "+" + b "=" + (a + b));
}
}
正规调用的时候,完整调用方式:类名.方法名(实参列表); 。而在当前类当中,方法含有static关键字,"类名."可以省略不写。
规范:在一个java源文件中只定义一个class,比较清晰。
public class MethodTest{
public static void main(String[] args){
//完整调用方式:MethodTest.m();
//由于当前是在同个类中,类名可以省略
m();
m2();//报错:这里直接调用m2方法会报错
//这个时候需要添加"类名."
A.m2();
}
//写一个方法,类名为m。带了static修饰符
public static void m(){
}
}
public class A{
//这里在另一个类中写了方法。
public static void m2(){
}
}
例子:分析程序输出结果
public class MethodTest{
public static void main(String[] args){
System.out.println("main begin");
m1();
System.out.println("main over");
}
public static void m1(String[] args){
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
public static void m2(String[] args){
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
public static void m3(String[] args){
System.out.println("m3 begin");
System.out.println("m3 over");
}
}
这个程序输出结果如下
main begin
m1 begin
m2 begin
m3 begin
m3 over
m2 over
m1 over
main begin
1.7方法类型不是void时
当方法类型不是void时,一定要有" return 值; "语句的执行。且返回值的类型要与方法类型相同。
例子:
//需求:定义一个方法并实现,方法可以计算两个int类型数据的商。将最终计算结果返回给调用者。
public class MethodTest{
public static void main(String[] args){
//调用方法
subtract(30,15);//这里没有接收这个方法返回的数据
//这里采用变量接收方法返回的数据。变量的数据类型与返回的数据类型相同,或者可以自动类型转换。
int sub = subtract(20,10);
System.out.println(sub); //10
}
public staitic int subtract(int a,int b){
int c = a - b;
return c;
}
}
1.8return语句
*带有return关键字的java语句只要执行,所在方法执行结束。
*在同一个作用域中,return下面(物理上)不能编写任何代码,因为这些代码执行不到,所以导致编译报错。
public class MethodTest{
public static void main(String[] args){
}
/*这个方法会编译报错:缺少返回语句
因为编译器认为 ”return 1;” 无法百分百保证会执行
public static int m(){
int a = 10;
if(a > 3){
return 1;
}
}
*/
//下面这个方法有了else就可以
public static int m2(){
int a = 10;
if(a > 3){
return 1;
}else{
return 0;
}
}
//或者
public static int m3(){
int a = 10;
if(a > 3){
return 1;
//下面的语句会编译错误:因为return语句下面不能编写任何代码
//System.out.println("Hello");
//注意:这里是在if的作用域里边
}
//注意:这里是在m3()的作用域里边,if语句的作用域外边
System.out.println("Hello");//可以编译
return 0;
//下面的语句会编译错误:因为return语句下面不能编写任何代码
//System.out.println("Hello");
}
}
1.9在返回值类型是void方法当中使用"return ; "语句
*"return;"语句出现在void方法当中主要是为了结束当前方法。
例子1:
public class MethodTest{
public static void main(String[] args){
}
/*编译错误:对于结果为void类型为空的方法无法返回值
public static void m(){
return 10;
}
*/
//这个方法可以
public static void m1(){
return;
}
}
例子2:
public class MethodTest{
public static void main(String[] args){
m();
}
public static void m(){
for(int i =0;i<5;i++){
if(i == 5){
//"return;"终止的不是for循环,是m()方法
//所以下面的打印"The end"执行不到。
return;
}
System.out.println(i);
}
System.out.println("The end");
}
}
2.方法执行过程中的内存分配
2.1方法内存分配
*方法只定义不调用时,是不会执行的,并且在JVM中也不会给该方法分配“运行所属”的内存空间。
*在“ JVM内存 ”划分上有三个“主要”内存空间:方法区内存,堆内存,栈内存。
*关于“ 栈 ”数据结构:
(1)栈:stack,是一种数据结构
(2)数据结构反应的是数据存储形态,数据结构是独立学科,不属于任何编程语言的范畴。大部分编程语言都要使用到数据结构。
(3)作为程序员需要精通:数据结构 + 算法【计算机专业必修】
*常见的数据结构:数组、队列、栈、链表、二叉树……
2.2方法执行内存
1.方法代码片段
*方法代码片段数据.class字节码文件的一部分,字节码文件在类加载的时候,将其放到方法区中。所以JVM中三个主要的内存空间方法区内存最先有数据。
*代码片段虽然在方法区内存当中只有一份,但是可以重复调用。每次调用这个方法需要给方法分配独立的活动产所,在栈内存中分配。【栈内存中分配方法运行所属内存空间】
*方法调用时,给该方法分配独立的内存空间,在栈中分配,此时发生“ 压栈 ”动作。方法执行结束后,方法分配的内存空间全部释放,此时发生“ 弹栈 ”动作。
(1)压栈:给方法分配内存
(2)弹栈:释放该方法的内存空间
*局部变量在"方法体"中声明,运行阶段内存在栈中分配。
3.方法的重载机制
以下代码不使用“方法重载机制”,不使用overload。
缺点:sumInt,sumDouble,SumLong方法虽然功能不同,但是功能是相似的:都是求和。分别起了三个不同名字,调用时不方便。
public class OverloadTest{
//main函数,程序入口
public static void main(String[] args){
//调用方法
int result1 = sumInt(1,2);
System.out.println(result1);
double result2 = sumDouble(1.0,2.0);
System.out.println(result2);
long result3 = sumLong(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;
}
}
方法重载机制/Overload:在不同的但"功能相似"的方法中,可以使用同一个方法名。
优点:调用时方便,虽然是调用不同的方法,但是感觉在使用一个方法一样。
前提:功能相似时,方法名可以相同。但是功能不同时,尽可能让两个方法名不同。
注:Java支持这种机制,但有些语言不支持。例如javascript。
public class OverloadTest{
public static void main(String[] args){
//调用方法的时候,就像在使用一个方法一样
//参数类型不同,对应调用方法不同。此时区分方法不再依靠方法名,而是依靠参数的数据类型。
System.out.println(sum(1,2)); //3
System.out.println(sum(1.0,2.0)); //3.0
System.out.println(sum(1L,2L)); //3
}
//以下三个方法构成了方法重载机制
public static int sum(int a ,int b){
return a + b;
}
public static double sum(double a ,double b){
return a + b;
}
public static long sum(long a ,long b){
return a + b;
}
}
方法重载:
1.方法重载又称:overload
2.使用方法重载:功能相似,尽可能让方法名相同。(但功能不同/不相似的时候,尽可能让方法名不同)
3.满足方法重载条件:在同个类中;方法名相同;参数列表不同(数量,类型,顺序三者有一个不同)。
4.*方法重载和方法名+参数列表有关系。
*方法重载与返回值类型、修饰符列表无关。
方法重载应用:
public class OverloadTest{
public static void main(String[] args){
/*使用方法重载把下列代码简化
System.out.println("Coffee add ice");
System.out.println(10);
System.out.println("true);
*/
U.p("coffee add ice"); //coffee add ice
U.p(10); //10
U.p(true); //true
}
}
//自定义类
class U{
public static void p(byte b){
System.out.println(b);
}
public static void p(int b){
System.out.println(b);
}
public static void p(String b){
System.out.println(b);
}
public static void p(char b){
System.out.println(b);
}
public static void p(double b){
System.out.println(b);
}
public static void p(boolean b){
System.out.println(b);
}
}
4.方法的递归
1.方法递归:方法自身调用自身。
*语法规则:
a(){
a();
}
*递归是很耗费栈内存,可以不用时尽量不用。
*程序运行时发生一个错误:java.lang.StackOverFlowError。这个不是异常,是一个栈内存溢出错误。错误发生时无法挽回,JVM停止工作。
*递归一定要有结束条件,不然必定发生栈溢出错误。【有条件,如果递归太深也可能发生栈溢出错误】
注意:有些情况下必须依靠递归方式。例如:目录拷贝。
例子1:不使用递归,计算1~N的和
public class RecoursionTest{
public static void main(String[] args){
int n = 4;
int retValue = sum(n);
System.out.println(retValue);
}
public static int sum(int n){
int result = 0;
for(int i=1;i<=n;i++){
result += i;
}
return result;
}
}
例子2:使用递归,计算1~N的求和
public class RecoursionTest{
public static void main(String[] rags){
int a = sum(3);
System.out.println(a); //6
}
public static void sum(int n){
if(n == 1){
return 1;
}
return n + sum(n-1);
}
}
例子3:不使用递归,计算N的阶乘(N!)。
注:N! = N * (N-1) *…… *2 *1
public static RecoursionTest{
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;
}
}
例子4:使用递归,计算N的阶乘(N!)。
public static RecoursionTest{
public static void main(String[] args){
int n =5;
int retValue = method(n);
System.out.println(retValue); //120
}
public static int method(int n){
if(n == 1){
return 1;
}
return n * method(n-1);
}
}
——本章节为个人学习笔记。学习视频为动力节点Java零基础教程视频:动力节点—JAVA零基础教程视频