初识方法
释义
- 方法是组合在一起来执行操作语句的集合。
方法作用
- 使程序变得更简短更清晰
- 有利于程序维护
- 提高程序开发效率
- 提高代码重用性
方法创建与使用
【语法格式】
[修饰符列表] 返回值类型 方法名 (参数列表){ 方法体; }
[] : 加中括号的意思是,可以有,可以没有,可以有多个
修饰符列表 :
权限修饰符 : public private
【方法分类】
根据方法是否带参、是否带返回值,可将方法分为四类
- 静态方法
使用static修饰的方法 - 成员方法
没有static修饰的方法 - 构造方法
先不管,用于创建对象
其他 : static 修饰成员或静态 , 不加static就是成员
返回值类型 : 运行完方法之后,是否需要响应数据
如果不需要,就写void
如果有,就需要写 返回数据对应的类型(11种数据类型)
所以返回值类型的值为
void+11种数据类型
8种基本
3种引用
如果方法有返回值类型,那么方法体内 必须有return语句,并且 return语句后面 需要加上和返回值类型对应的值
比如 返回值为int 那么 必须要有 return 1; 只要是int值就行,不一定非得是1
注意 return 有终止函数运行的作用(return以后的代码不执行,不能出现任何代码)
如果没有返回值类型,方法体内,就可以没有return,当然也可以有,并且return语句后面 不能添加任何数据,只能起到结束函数运行的作用
函数就是方法
Return语句并不是必须要在程序的最后,
方法名
望文知意
命名规范
符合标识符要求 :
大小写字母,美元符号,下划线,数字,数字不能打头,不能单独使用关键字和保留字
参数列表 :
就是使用这个方法,需要传递的数据
形参 :
是指在方法定义处,需要传入的参数类型的定义
实参 :
调用方法的时候,实际传入的值
方法参数列表中的变量,是局部变量
并且参数列表中可以定义多个变量,多个之间用 逗号 , 隔开
也可以没有,即使没有参数列表, () 小括号还是要有的,这是语法问题
方法体 :
代码段,保存代码
public class Method_02 {
public static void main(String[] args) {
// 重复功能的代码只写一遍,想要多次完成某个功能,就多次使用这个方法即可
// 传入的参数不同,函数的返回值也会不同
int a = 1;
sumInt(a, 2);// 1+2=3
sumInt(10, 11);// 10+11=21
sumInt(100, 200);// 100+200=300
sumDouble(0.1, 0.2);// 0.1+0.2=0.3
m1(1);
// 方法返回值是1
System.out.println(m1(1));
}
// a和b是传递过来的
public static int sumInt(int a, int b) {
// 形参不能在方法中重复定义
// long a = 2L ;
int c = a + b;
System.out.println(a + "+" + b + "=" + c);
return c;
}
public static void sumDouble(double a, double b) {
double c = a + b;
System.out.println(a + "+" + b + "=" + c);
}
// 可以使用多的修饰符修饰方法
// 静态 最终 同步锁
public static final synchronized void sss() {
}
/**
* 方法名命名规则: 1.首字母:英文或者$ 2.由英文,$,和数字构成 3.不能使用关键字和保留字 4.首字母小写,驼峰命名法
*/
public static void class1() {
}
/**
* 如果参数大于0 返回1
*
* 否则返回-1
*/
public static int m1(int i) {
// if单分支,有不执行的情况
// 改进方法是用if-else结构,或在结尾加return
if (true) {
return 1;
}
return -1;
}
}
方法调用
- 静态方法
类名.静态方法名(参数);
当前类中类名可以省略 - 成员方法
对象.成员方法名(参数);
为什么静态方法调用需要加类名呢?
如果想要使用一个数据,必须先找到这个数据
如果你想打我, 必须先找到我
方法不调用不执行,调用才执行,并把结果返回到方法调用处
程序是从上往下,从左到右执行,函数的出现,就可以任意顺序的编写,因为编写的时候不执行,调用才执行,所以 就不需要考虑方法的编写位置,但是在方法的内部,依然是 从上往下从左到右执行
方法,定义的时候不执行
特殊的方法 : main
所有方法的调用起点和终点 都是在main中
Main方法由JVM自动调用
public class Method_05 {
public static void main(String[] args) {
m1();
Method_05.m1();
// 调用别的类的静态方法的时候,必须加类名
// m2();应该是A.m2();
A.m2();
// 只要是调用其他类中的静态数据,必须加对应类的类名
A.m1();
// 接收方法的返回值,因为返回值类型的int,所以用int声明的变量就可以接收返回的值
int i = m4(false);
System.out.println(i);
}
// 不调用不执行
public static void m1() {
System.out.println("Method_05 m1 execute");
// 方法m1调用方法m3
m3();
}
public static void m3() {
System.out.println("Method_05 m3 execute");
}
public static int m4(boolean flag) {
if (flag) {
return 1;
} else {
return 0;
}
}
}
class A {
public static void m2() {
System.out.println("m2 execute");
}
public static void m1() {
System.out.println("A m1 execute");
}
}
重载 Overload
唯一性
方法名相同,参数列表不同,叫方法重载
参数列表不同 :
- 个数不同
- 类型不同
一定要注意,重载和返回值 以及修饰符列表 都没有关系
/**
* 重载 Overload 唯一性 方法名相同,参数列表不同,叫方法重载 参数列表不同: 1.个数不同 2.类型不同
*
* 一定要注意,重载和返回值 以及修饰符列表 都没有关系
*
* @author lenovo
* @Date 2020年6月28日
* @Time 下午6:23:02
*/
public class Method_08 {
public static void main(String[] args) {
System.out.println(Colculate.sumInt(1, 2));
System.out.println(Colculate.sumDouble(1.2, 2.3));
System.out.println(Colculate.sumLong(1274684l, 22354641l));
// 上面的这种情况,功能相同,名字不同,代码太多,不好记,太难看
int i = 2;
System.out.println(Colculate.sum(1, 2));
System.out.println(Colculate.sum(1, 52.32));
System.out.println(Colculate.sum(1165l, 2315));
// 分析一下方法是否是重载
System.out.println(1);
System.out.println(1.2);
System.out.println(true);
System.out.println("------");
System.out.println('a');
}
public static void m1() {
}
int m1(int i) {
return 1;
}
int m1(double i) {
return 1;
}
int m1(int i, int a) {
return 1;
}
int m1(long i, int a) {
return 1;
}
int m1(int i, long a) {
return 1;
}
int m1(int i, double a) {
return 1;
}
}
class Colculate {
public void m1() {
System.out.println("---==============---");
}
public static int sumInt(int a, int b) {
return a + b;
}
public static double sumDouble(double a, double b) {
return a + b;
}
public static long sumLong(long a, long b) {
return a + b;
}
public static int sum(int a, int b) {
return a + b;
}
public static long sum(long a, long b) {
return a + b;
}
public static double sum(double a, double b) {
return a + b;
}
}
内存分析
程序 : 可以执行文件就叫程序,是静态概念,保存在硬盘中
进程 : 就是正在执行的文件,是个动态概念
运行起来的程序,就是指载入到内存的可执行文件,这个时候,操作系统会开启一个进程来执行内存中的这个文件对象,如果要关闭某个程序,可以直接杀死某个进程
- Java中的内存划分和管理 :
Java Runtime Data Area : java运行时数据区 我们也可以叫做 JVM内存
内存被划分了5个区域 : 程序计数器,方法区/静态区/静态代码段 , VM栈(统称栈内存) , 本地方法栈 , 堆内存
程序计数器 :
是比较小的一块区域,可以看做是当前线程执行的字节码的位置指示器
方法区 :
是用来存放我们的程序文件,载入内存后的哪个程序文件对象
Java中指的是class文件
包含方法没有被调用之前,还有代码段,都会保存在方法区
方法区内还有运行时常量池
栈内存 :
虚拟机栈/VM栈 也可以叫做栈内存
方法是放到栈内存的,包括局部变量,也是在栈内存
栈内存 : 是一个以栈数据结构为模型的一段内存空间
栈 : 是一种数据结构,像弹夹,薯片盒子一样,先进后出
栈的构成因素 :
栈空间 : 就是指以栈数据结构为模型开辟的一段内存空间
栈帧 : 栈内存中,每一个栈元素都叫栈帧
栈底元素 : 最先方进入的元素
栈顶元素 : 最后方进入的元素
栈操作 :
压栈 : 就是把元素放入栈空间的过程
弹栈 : 就是把元素从栈内存中弹出的过程
栈内存 用来执行方法
方法调用 就是压栈
方法执行结束 就是弹栈
本地栈
和虚拟机栈一样,只不过虚拟机栈用来执行java方法服务,而本地栈是为JVM提供使用native方法的服务
两者结构一致
堆内存
保存对象
每个对象空间分为3大块
数据部分 : 成员变量
头部 : hashCode值
类型 : 是哪个类创建来的,引用对应的类
生命周期
1 java程序的编写
文本编辑器,按照java规定编写代码
2 编译
Javac 原文件名.java
Javac Method_10.java 就能生成对应的Method_10.class
3 运行
Java 程序名
Java Method_10
3.1 开启java虚拟机,然后把程序名对应的 Method_10.class文件载入到jvm划分的空间中,保存在静态区
3.2 jvm自动调用main方法
3.3 main方法被调用,会在栈内存开辟栈帧
3.4 jvm静态方法调用流程 在main方法中没有调用其他方法的时候,就直接执行,完事之后弹栈,程序销毁
如果main方法中有其他方法调用
1 本类方法
如果在main方法中,调用了本类的方法,就会在main方法栈帧的上面,再开辟一个栈帧,用来保存调用 的方法(m1)
如果被调用的方法中还有其他方法调用,同上,再开辟栈帧
一直到被调用的这个方法执行完成,然后弹栈,再接着main方法中的调用处接着向后执行,一直到main执行结束,main栈帧弹栈,程序销毁
如果调用的方法有返回值,会把对应的值放入临时空间中,携带返回调用处
2 其他类方法
先把对应的类,载入静态区
其他流程同上
怎么算方法执行完成呢?
1 最后一条语句执行结束,碰到方法体的大括号
2 碰到return语句
程序的静态加载和动态加载
静态加载 : 是指程序开始执行,就把相关所有的文件一次性载入内存
动态加载 : 程序开始执行,只载入相关的文件,当用到其他文件的时候,再去动态的加载
方法执行原理
方法在调用的时候,才会在内存中开辟空间
不调用的时候,不会再内存中开辟空间
方法在调用的时候,也就是在栈内存开辟栈帧分配空间
方法调用 就是压栈
方法执行结束 就是弹栈
public class Method_10 {
public static void main(String[] args) {
m1(1);
System.out.println("main方法执行");
/**
* m1传参数1调用方法m2()
* m2传参数1调用方法m3()
* m3输出 i=1
* m3输出 m3执行了
* 返回方法m2() 输出 m2执行了
* 返回方法m1() 输出 m1执行了
* 返回主方法 输出 main方法执行了
*/
}
public static void m1(int i) {
m2(i);
System.out.println("m1执行了");
}
public static void m2(int i) {
m3(i);
System.out.println("m2执行了");
}
public static void m3(int i) {
System.out.println("i=" + i);
System.out.println("m3执行了");
}
}
递归
释义
- 程序自身调用自身的编程技巧称为递归
递归四个特性
- 必须有可最终达到的终止条件,否则程序将陷入无穷循环;
- 子问题在规模上比原问题小,或更接近终止条件;
- 子问题可通过再次递归调用求解或因满足终止条件而直接求解;
- 子问题的解应能组合为整个问题的解。
基本思想 :
以此类推是基本思想
循环也是,递归和迭代是等价的(迭代就是循环)
步长、终止条件、初始值
// 因为没有终止条件,所以栈内存溢出
// Exception in thread "main" java.lang.Error: Unresolved compilation problem:
public static void m1() {
m1();
}
应用场景 :
- 一般树状结构的都可以使用递归查询
递归比普通的算法耗内存,运行效率低,谨慎使用
能用循环搞定,尽量不用递归
常见问题 :
- 需要删除一个目录下所有的文件以及子文件,类似的树状结构需求
- 斐波那契数列这种有规律的也可以
- 累加加和
- 阶乘
- 汉诺塔 等
难点 :
不容易理解,可以画栈帧调用链
演示实例 :
使用for完成1-100的累加加和
public static int sum(int a) {
// 使用for完成1-100的累加加和
int sum = 0;
for (int i = 1; i <= a; i++) {
sum += i;
}
return sum;
}
使用递归完成1-100的累加加和
public static int m1(int a) {
if (a == 1) {
return 1;
} else {
// 5 = 5 + 4 + 3 + 2 + 1
// 5 + m1(4)
// 4 + m1(3)
// 3 + m1(2)
// 2 + m1(1)
// 1
return a + m1(--a);
}
}
使用for完成斐波那契数列
/**
* for循环完成斐波那契数列,并打印前n位的值
*/
public static void m1(int i) {
if (i < 1) {
System.out.println("位数不正确!");
return;
}
// 说明i一定不小于1
if (i == 1 || i == 2) {
System.out.println("第" + i + "位的值是 : 1");
return;
}
// 到这里说明一定是大于第二位的
// 先把第一位和第二位打印
System.out.println("第一位的值是 : 1");
System.out.println("第二位的值是 : 1");
// 当前位的前两位的值,默认是第一位
long l1 = 1;
// 当前位的前一位的值,默认是第二位
long l2 = 1;
// 当前默认是第三位
long l3 = l1 + l2;
for (int j = 3; j <= i; j++) {
l3 = l1 + l2;
System.out.println("第" + j + "位的值是:" + l3);
// 把l2赋值给l1这时候l1就表示当前j位的前1位
l1 = l2;
// 把l3赋值给l2这时候l2就表示当前j位的值
l2 = l3;
}
}
使用递归完成斐波那契数列
// 计数,记录递归执行的压栈个数
static int count = 0;
/**
* 使用递归完成
*/
public static int fibonacci(int n) {
// 每次执行方法对计数+1
count++;
// 因为斐波那契数列,最少要1位,所以判断,小于1,返回-1
if (n < 1) {
return -1;
}
// 因为斐波那契数列的前两位都是1,所以判断,n如果是1或者是2,就直接返回1
if (n == 1 || n == 2) {
return 1;
}
// 除了前两位,每一位的值都等于前两位的和,所以第n位=(n-1)位+(n-2)位
return fibonacci(n - 1) + fibonacci(n - 2);
// return fibonacci(4) + fibonacci(3);n=5
// return fibonacci(3) + fibonacci(2);n=4
// return fibonacci(2) + fibonacci(1);n=2
}