今天主要学习如下内容:
1.方法定义,方法调用,方法重载等内容
2. 数组相关内容,以及有数组初始化引出的
3. Jvm的运行时数据区的内存模型相关知识
一、方法
- 引入:
文件上传是做一个电商网站所必不可少的功能,比如每一个商品都需要上传若干商品图片,网站上各种品牌都需要上传其logo,用户在注册时可以上传头像…
假设你现在已经写了100行代码完成了文件上传功能,但问题是网站的很多地方都要使用这一功能,比如管理商品数据的商品服务,管理用户数据的用户信息服务等等。
问题来了,这些地方都要使用文件上传的功能,怎么办呢?
方案1
在每个用到文件上传功能的地方重写这些100行代码 但是方案1会引发一系列的问题!!
方案2
复用这100行代码,将这些代码放在一个{}中,并给它们起个名字,通过名字来复用这段代码
方法定义:方法就是完成特定功能的代码块(在有些其他语 言中,也被成为函数)
- 方法定义的格式:
修饰符 方法返回值类型 方法名(参数1类型 参数1名称, 参数2类型 参数2名称, ……) {
方法体语句;
return 返回值;
}
方法定义的格式说明:
修饰符:现在先认为是固定的 public static
返回值类型:方法体中的代码执行结果的数据类型
方法名 :标识符
参数 :类比数学中函数的自变量 z = f(x, y)
方法体:实现具体功能的语句结合
return:跳转控制关键字
返回值:方法执行的到的最终结果 注意: return 返回值;这句并非必须
如何写一个方法呢?
两个明确 :
- 返回值类型----- 明确功能结果的数据类型
- 参数列表 --- -- 明确有几个参数,以及参数的类型
学习了方法的定义之后,如何使用方法,完成特定的功能呢? 此时,我们就需要学习,方法调用了。 对于方法的调用,又分了两种情况:
- 有返回值的方法调用
- 没有返回值的方法调用
方法调用注意事项:
- 方法不调用不执行
- 方法与方法是平级关系,不能嵌套定义
- 方法定义的时候参数之间用逗号隔开
- 方法调用的时候不用再传递数据类型
- 如果方法有明确的返回值,一定要有return带回一个值
图:一次方法调用的过程
/** * 方法定义:方法就是完成特定功能的代码块(在有些其他语 言中,也被成为函数function) 方法定义的格式 修饰符 方法返回值类型 方法名(参数1类型 参数1名称,参数2类型 参数2名称, ……) { 方法体语句; return 返回值; } 方法定义的格式说明: 修饰符:现在先认为是固定的 public static 返回值类型:方法体中的代码执行结果的数据类型 方法名 :标识符 参数 :类比数学中函数的自变量 z = f(x, y) 方法体:实现具体功能的语句结合 return:跳转控制关键字 返回值:方法执行的到的最终结果 定义方法:两数求和(int) 如何写一个方法呢?两个明确 返回值类型 明确功能结果的数据类型 参数列表 明确有几个参数,以及参数的类型 对于方法的调用,又分了两种情况: 1.有返回值的方法调用 2.没有返回值的方法调用 方法调用注意事项: 1.方法不调用不执行 2.方法与方法是平级关系,不能嵌套定义 3.方法定义的时候参数之间用逗号隔开 4.方法调用的时候不用在传递数据类型 5.如果方法有明确的返回值,一定要有return带回一个值 * * */ public class MethodDemo1 { public static void main(String[] args) { //用变量接收有返回值的方法调用 int result = add(3, 4); System.out.println(result); //输出调用 System.out.println(add(3,4)); //直接调用 有返回值的方法调用没有意义 add( 3, 4); //调用方法没有返回值的方法, 只能使用直接调用 print("void 你好"); } //定义方法 执行 3 + 4 //public static int add1 () { // int result = 3 + 4; // return result; //} // 定义实现两数求和的加法运算 public static int add (int x, int y) { int result = x + y; return result; } //定义没有返回值的方法 void表示方法没有返回值 public static void print (String s) { System.out.println("我自己来输出" + s); //return; //可以有可以没有 } }
练习: 键盘录入一个数据n(1<=n<=9),输出对应的nn乘法表
import java.util.Scanner; /** * * 练习: 键盘录入一个数据n(1<=n<=9),输出对应的nn乘法表 */ public class Exercise { public static void main(String[] args) { //调用方法 // 2 x 2乘法表 Scanner sc = new Scanner(System.in); int n = sc.nextInt(); printMulti(n); } //定义一个方法来完成输出n x n乘法表 public static void printMulti(int n) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { System.out.print(j + "x" + i + "=" + j * i + " "); } System.out.println(); } } }
方法重载(overload)
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数参数类型不同参数顺序不同)
为什么呢?1. 二义性 2. 方法签名
package com.method.overloads; /** * * 方法重载(overload) 在同一个类中,允许存在一个以上的同名方法, * a.只要它们的参数个数 * b.参数类型不同 * c.参数顺序不同: 不是指形式参数的变量名顺序不同,而是指形式参数类型顺序 * * 编译器如何秋分方法呢? 通过方法签名 方法签名: * 方法名 + 参数列表 比如 add(int, int) * add(double, dobule) */ public class OverLoadDemo { public static void main(String[] args) { int a = 1; int b = 2; //int add = add(a, b, 10); //double c = 1.1; //double d = 2.2; //double doubleAdd = add(c, d); // //add(1,2); //int call = add(a, b); //add(a, b); } //两数相加 public static int add(int x, int y) { System.out.println("int add"); return x + y; } //public static double add(int x, int y) { // return x + y; //} //实现3数相加 public static int add(int x, int y, int z) { return add(x, y) + z; } //参数类型不同 public static double add(double x, double y) { System.out.println("double add"); return x + y; } //参数顺序不同 public static int add(int y, byte x) { return x + y; } public static int add(byte y, int x) { return x + y; } }
注意:
与返回方法重载与返回值类型无关,只看方法名和参数列表。(为什么呢?思考一下)
package com.method.overloads; /** * * 比较两个数据是否相等。 * 参数类型分别为 两个byte类型, * 两个short类型, * 两个int类型 * 两个long类型 * 并在main方法中进行测试 */ public class Exercise { public static void main(String[] args) { byte b = 1; short c = 2; long a = 1L; long d = 100L; equals(b,c);//equals(byte,short)调用equals(short,short),不需要完全一样,JVM会自己选择; equals(a,d); } public static boolean equals(byte a, byte b) { System.out.println("byte equals"); return a == b; } public static boolean equals(short a, short b) { System.out.println("short equals"); return a == b; } public static boolean equals(int a, int b) { System.out.println("int equals"); return a == b; } public static boolean equals(long a, long b) { System.out.println("long equals"); return a == b; } }
方法重载练习
比较两个数据是否相等。参数类型分别为两个byte类型,两个short类型,两个int类型,两个long类型,并在main方法中进行测试
二、数组
假设一个班有80个人,我们现在需要统计,其某一门课程的平均成绩,思路很简单,先求和,在平均,想一想,如果使用我们之前所学的知识,该怎么做呢?
大致想一想都会觉得真的好麻烦!!!
为了应对以上的使用场景,我们就需要学习数组。
package com.array; /** * * *假设一个班有80个人,我们现在需要统计,其某一门课程的平均成绩 */ public class Demo1Intruduction { public static void main(String[] args) { // 1.定义80个变量 // 2.用这80个变量存储80个同学的成绩 // 3. 求和表达式 80变量的求和 a1 + a2 + ... + a80 } }
数组的概念(一组数据) 相同数据类型的数据元素的有序集合
存储多个数据元素 这多个数据元素的数据类型必须一致(为什么呢?)
数组的定义格式
格式1:
数据类型[ ] 数组名;
格式2:
数据类型 数组名[ ];
注意,这只是数组的定义,如何给定义好的数组“赋值”呢
图:数组的概念
package com.array; /** * * 数组中究竟可以存储哪些类型的数据呢? 基本类型数据 byte short int char double float… 引用类型数据 对象 对象数组 数组的定义格式 格式1:数据类型[] 数组名; 有点变量定义 int a = 1; int a = 1; 格式2:数据类型 数组名[]; 数组的初始化 1.Java中的数组必须先初始化,然后才能使用。 2.所谓初始化:就是为数组中的数组元素分配内存空间,并为每个数组元素赋初值 动态初始化 初始化时程序猿只指定数组长度,由jvm为数组分配初始值。 静态初始化 初始化时指定每个数组元素的初始值,由系统决定数组长度。 int[] a = {1, 2, 3} 数组的动态初始化 格式: 数据类型[] 数组名 = new 数据类型[数组长度]; 数组长度其实就是数组中元素的个数。 int[] arr = new int[3]; */ public class Demo2Array { public static void main(String[] args) { //数组定义的格式 存放int类型值的数组 //格式1 int[] arr; //官方推荐的写法 //格式2 声明int类型的数组 int arr1[]; // 声明了数组,使用之前必须赋予初值 //System.out.println(arr); { int a; } //System.out.println(a); //数组的动态初始化 //格式: 数据类型[] 数组名 = new 数据类型[数组长度]; int[] array = new int[3]; } }
数组的初始化
Java中的数组必须先初始化,然后才能使用。
所谓初始化:就是为数组中的数组元素分配内存空间,并为每个数组元素赋初值
数组的初始化方式
- 动态初始化-------初始化时程序猿只指定数组长度,由系统为数组分配初始值。
- 静态初始化-------初始化时指定每个数组元素的初始值,由系统决定数组长度。
数组的动态初始化
格式: 数据类型[] 数组名 = new 数据类型[数组长度];
数组长度其实就是数组中元素的个数。
int[] arr = new int[3];
如果清楚地了解数组的初始化过程,那我们就必须知道,数组初始化过程中,究竟在Java虚拟机内存中做了哪些工作。
Java虚拟机内存模型
一个Java程序在虚拟机运行的过程中,
- 在内存中需要保存很多中类型的数据。比如局部变量,数组等等。局部变量:定义在方法体中的变量
- 不同类型的数据,其使用方式和生命周期,都不相同。
- 为了更好的管理这些不同类型的数据,jvm将自己的内存空间划分为不同的内存区域,各个区域针对不同类型的数据,其内存空间有不同的管理方式。
- 栈 存储局部变量 迄今为止我们代码中几乎所有的变量都在栈(Stack)
- 堆 存储new出来的东西 数组,对应的存储空间在堆中(Heap)
- 方法区 (后面讲)
- 本地方法栈 (系统相关) java语言 可以调用c/c++
- 程序计数器 指明代码执行的位置
图:栈上变量和堆上变量的不同
图:JVM内存模型
图:数组的动态初始化
Java中数组的内存图解
图解1: 定义一个数组,输出数组名及元素。然后给数组中的元素赋值,再次输出数组名及元素。
package com.array; /** * * 图解1: 定义一个数组,输出数组名及元素。然后给数组中的元素赋值,再次输出数组名及元素。 */ public class Demo3ArrayMap { public static void main(String[] args) { //定义一个数组,输出数组名及元素。然后给数组中的元素赋值,再次输出数组名及元素。 int[] a = new int[3]; //输出数组名 System.out.println(a); // [I@4554617c [表示一维数组 I整数类型 4554617c数组实际存储的首地址 //输出 System.out.println(a[0]); System.out.println(a[1]); System.out.println(a[2]); //给数组元素赋值 a[0] = 10; a[1] = 100; System.out.println(a[0]); System.out.println(a[1]); System.out.println(a[2]); } }
图:单个数组
图解2: 定义两个数组,分别输出数组名及元素。然后分别给数组中的元素赋值,分别再次输出数组名及元素。
1 package com.array; 2 3 /** 6 * 图解2: 7 定义两个数组,分别输出数组名及元素。 8 然后分别给数组中的元素赋值,分别再次输出数组名及元素。 9 */ 10 public class Demo4ArrayMap { 11 12 public static void main(String[] args) { 13 14 int[] arr1 = new int[3]; 15 System.out.println(arr1); //[I@4554617c 16 17 System.out.println(arr1[0]); 18 System.out.println(arr1[1]); 19 System.out.println(arr1[2]); 20 System.out.println("--------------------------------------"); 21 22 int[] arr2 = new int[2]; //[I@74a14482 23 System.out.println(arr2); 24 System.out.println(arr2[0]); 25 System.out.println(arr2[1]); 26 27 arr1[0] = -100; 28 System.out.println(arr1); //[I@4554617c 29 System.out.println(arr1[0]); 30 System.out.println(arr1[1]); 31 System.out.println(arr1[2]); 32 33 arr2[1] = 30; 34 System.out.println(arr2); //[I@74a14482 35 System.out.println(arr2[0]); 36 System.out.println(arr2[1]); 37 38 39 } 40 41 }
图:多个数组
图解3: 定义两个数组,先定义一个数组,赋值,输出。然后定义第二个数组的时候把第一个数组的地址赋值给第二个数组。然后给第二个数组赋值,再次输出两个数组的数组名及元素。
1 package com.array; 2 3 /** 6 * 7 * 图解3: 8 定义两个数组,先定义一个数组,赋值,输出。 9 然后定义第二个数组的时候把第一个数组的地址赋值给第二个数组。 10 然后给第二个数组赋值,再次输出两个数组的名及元素。 11 */ 12 public class Demo5ArrayMap { 13 14 public static void main(String[] args) { 15 16 byte[] bytes1 = new byte[2]; 17 bytes1[0] = 10; 18 bytes1[1] = 20; 19 System.out.println(bytes1); //[B@4554617c 20 System.out.println(bytes1[0]); 21 System.out.println(bytes1[1]); 22 23 //然后定义第二个数组的时候把第一个数组的地址赋值给第二个数组。 24 byte[] bytes2 = bytes1; 25 bytes2[0] = -10; 26 bytes2[1] = 110; 27 System.out.println("------------------第一个数组--------------------"); 28 System.out.println(bytes1); //[B@4554617c 29 System.out.println(bytes1[0]); 30 System.out.println(bytes1[1]); 31 System.out.println("-------------------第一个数组-------------------"); 32 33 System.out.println("------------------第二个数组--------------------"); 34 System.out.println(bytes2); //[B@4554617c 35 System.out.println(bytes2[0]); 36 System.out.println(bytes2[1]); 37 System.out.println("-------------------第二个数组-------------------"); 38 39 } 40 41 }
图:多个引用变量指向同一个数组
数组的静态初始化:
初始化时指定每个数组元素的初始值,由系统决定数组长度。
格式:数据类型[] 数组名 = new 数据类型[]{元素1,元素2,…};
int[] arr = new int[]{1,2,3};
简化写法:
int[] arr = {1,2,3};
注意: 简化写法只在数组定义时有效!
package com.array; /** * * 数组的静态初始化: 初始化时指定每个数组元素的初始值,由系统决定数组长度。 格式:数据类型[] 数组名 = new 数据类型[]{元素1,元素2,…}; int[] arr = new int[]{1,2,3}; 简化写法: int[] arr = {1,2,3}; //只有在声明数组的时候才能使用 */ public class Demo6SaticInit { public static void main(String[] args) { //静态初始化,可以指定数组的元素初值,无法指定数组长度 int[] arr = new int[]{1,2,3}; // 0 1 2 int[] arr1 = {10, 21, 53}; System.out.println(arr1[0]); System.out.println(arr1[1]); System.out.println(arr1[2]); //只有在声明数组的时候才能使用 //arr = {10, 21, 53}; } }
图:静态初始化
数组操作中,常见的两个问题:
-
- 数组索引越界 ArrayIndexOutOfBoundsException
- 空指针异常 NullPointerException
图:空指针异常
1 package com.array; 2 3 /** 4 * 5 * 数组常见异常: 6 * 数组索引越界 ArrayIndexOutOfBoundsException 7 空指针异常 NullPointerException 8 9 */ 10 public class Demo7ArrayException { 11 12 public static void main(String[] args) { 13 14 //数组索引越界 15 //java.lang.ArrayIndexOutOfBoundsException: 3 16 int[] arr = new int[3]; //包含的元素 位置 0 1 2 17 //访问了数组中并不存在的索引对应的元素 18 //System.out.println(arr[3]); 19 20 //空指针异常 NullPointerException 21 arr = null; // null空常量 22 System.out.println(arr[0]); 23 } 24 25 }
数组遍历(依次输出数组中的每一个元素)
数组元素逆序
对取值范围在1~100的数据集合排序
数组获取最值(获取数组中的最大值最小值)
数组查表法(根据键盘录入索引,查找对应星期)
数组元素查找(查找指定元素第一次在数组中出现的索引)