课时28:数组与方法
摘要:本次分享的主题是数组与方法。主要分为四个部分:
1.数组与方法之间的交互
2.定义返回数组的方法
3.内存关系的分析
4.数组与方法的关系
01.数组与方法之间的交互
接下来看一下数组与方法之间的交互。引用数据类型的主要特点是它可以与方法进行引用传递,而数组本身也属于引用类型。因此它自然也可以通过方法实现引用传递的操作。那么该怎么做?比如来看一下下面的例子,先来实现一个数组的引用传递。比如现在编写一个简单的程序。
代码可以这样写:首先声明一个名为 Int Date 的变量,并给它赋一个值称之为 New Int ,并随意给它赋一个值,比如 12345 。随后编写一个方法命名为 Public 。很明显这个 Public 方法需要接收一个 Int 类型的数组作为参数,那么在代码中是否可以直接将 Date 变量作为单个元素传递到这个数组中?这个数组在这个过程中的作用很简单,它只是想要进行打印操作。
具体来说就是遍历数组,对于每个元素 X ,如果 X 小于 Temp的某个特定值,则X++ 。
完成遍历后使用 System.out.print 方法打印数组 Temp 。
public classArrayDemo{ public static voidmain(String args[]) { int.data [] = new int[] {1,2,3,4,5}; } public static void printArray(int temp[]) {} }
现在来观察程序代码。在 Java 中创建一个名为 ArrayDemo 的 Java 文件,并执行 ArrayDemo 程序。可以看到程序正常完成了执行,并输出了 12345 。因此已经实现了一个最为简单的引用传递示例。那么具体的内存关系还是需要绘制一下,下面就来绘制它,因为这个例子比较简单,那么就以这个程序为例,即刚才提到的 NewInt = 12345 。
按照之前的绘制方法应该准备一个数据区域。这个数据区域在编写时很明显,而为了示例定义了五个位置。那么这里面是不是还需要记录一些其他信息?比如数值 12345 。而具体内容是怎样的?1 跟上, 2 再跟上, 3 再跟上, 4 再跟上,最后是 5 ,这样就实现了速度定义。
Int data[]=new int[]{1,2,3,4,4};
当执行一个名为 Print 的函数,并传入一个名为 Date 的参数进行处理时,实际上在代码层面程序中会有一个变量 Temper 。这个Tmper 变量将会指向与 Date 相同的内存空间引用。因此最终的结果是不是相当于 Temper 也会输出这块内存中的数据?这是一个最为简单,也是最为标准的引用传递的操作示例。
现在按照这个过程继续分析,现在已经实现了方法参数接收一个数组的功能,那么反过来思考既然方法可以接收一个数组作为参数,那么同样地方法也可以通过返回值返回一个数组对象。此时只需要在方法的返回值类型上进行相应的设置即可。
02.定义返回数组的方法
在这个程序中代码可以这样写。为了简洁明了直接定义一个 Public Static 方法,返回类型为 Int[] ,并给这个方法命名为 InitArray 。这里的 Int[] 表示该方法将返回一个整型数组。比如创建一个名为 Int Arr 的数组,这个数组是通过一个方法返回的。但是在声明数组时这里必须加上什么?即用大括号表示的是数组的定义。
那么在程序中代码可以这样写: Int[] Arr = New int[]{1, 2, 3, 4, 5} ;,而后 Return Arr ;。很明显这个地方返回的是一个数组,那么能够接收它的也必然是一个数组。
所以初始化数组是可以的。那么这就意味着通过我们的方法可以获取数组的内容。现在来查看一下这个结果,它应该与之前的结果没有差别。如果现在的代码是这样写的,那么这种效果是否被称为匿名数组?但是在提到匿名数组时,首先编译并执行一下代码,看看是否正常工作。
然而如果你尝试使用简化的写法,那是不对的,这是不正确的简化方式。当这样执行时它是不是会报错?因此之所以建议大家不要去依赖简化写法,是因为在编写代码时如果使用匿名数组就必须提供完整的类型信息。
public class ArrayDemo{ public static void main(String args[]){ int data []=initArray();//通过方法可以获得数组内容 printArray(data); //传递数组 } public static int [] initArray(){ int arr [] = new int[]{1,2,3,4,5); return arr ; // 返回一个数组 } // 要求接收一个int型的数组 public static void printArray(int temp []){ for (intx=0;x< temp.length ;x++){ System.out.println(temp[x]); } } }
03.内存关系的分析
接下来将针对这个程序进行内存关系的分析。首先把程序直接展示出来。按照之前的程序做法将在这里进行数组与方法的分析。这是第一步。在第一步中应该关注的是哪个方法进行了初始化。以及在初始化这个方法里它执行了什么操作?有一个变量 Arr ,所以在这段代码中实际上这个方法在这里实现的操作就相当于我们定义了一个名为 Arr 的数组。
接下来进行第二步,返回之后,当执行这句话时其本质就相当于什么?即 Da ,这个 Da 将指向与 Arr 相同的操作空间,然后第三步当执行 PrintArray 之后,或者当直接调用 PrintArray 之后,这个地方描述的是什么概念?实际上与刚才相比,这里又多了一个名为 Temp 的指向。这并不复杂。只不过与之前的代码相比现在最大的特征在于是通过一个方法来获得了所需的实例化对象。再往下看除了能够通过方法获得对象之外,还可以做一件什么事情?接下来看一下程序。比如可以通过方法来修改数组的内容。
现在有这样一段程序代码,为了简单起见暂时不考虑返回操作。在这里希望定义一个方法称之为 Y 。这个方法会接收一个名为 Arr 的整数数组作为参数。在方法的代码中使用一个 For 循环,定义一个整数变量 X ,从 0 开始,直到 X 小于 Arr 的长度,每次循环 X 自增1 。在循环体内将 Arr 数组中索引为 X 的元素乘以 2 ,即 Arr[x] * = 2 。这句话的含义是将数组中的每个元素的内容乘以 2 并保存。现在完成了一个操作即将数组中的每个元素都乘以 2 并保存。
那么现在在这里调用这个方法,称之为 ChangeArray ,并传入一个数组作为参数,目的是修改这个数组的内容。现在来看一下最终的结果,编译并再次执行程序后,大家可以看到输出结果为 2 、 4 、 6 、8 、 10 ,这是因为原来的数组 1 、 2 、 3 、 4 、 5 中的每个元素都被乘以了 2 。接下来分析一下这道程序的内存关系。这是本程序的内存关系,继续来观察这道程序的内存关系图。
public class ArrayDemo{ public static voidmain(String args[1){ int data [] = new int[] {1,2,3,4,5}; changeArray (data); // 修改数组内容 printArray (data); //传递数组 } public static voidchangeArray (intarr[]){ for (intx=0;x<arr.length;x++){ arr[x] *=2 ; // 每个元素的内容乘2保存 } } // 要求接收一个int型的数组 public static void printArray(int temp []) { for (intx=0;x<temp.length;x++) { System.out.println(temp[x]) ; } } }
04.数组与方法的关系
首先这个程序的第一步操作与之前类似,相当于现在在这个地方准备了一个数组。而后将数组命名为 Data ,并且 Data 这个地方是进行第一步先期保存的位置。紧接着要执行的是第二步,即调用ChangeArray 方法。一旦执行到这一步整个过程中就涉及到了引用传递。
如果要明确体现引用传递的代码形式应该在这里有一个 Arr ,它指向一个内存空间,这个内存空间存储了我们的数组数据。然后调用 ChangeArray 方法,并传入 Arr 作为参数,以修改 Arr 所指向的内容。在修改 Arr 内容的过程中,无论是否采用循环,有一点是确定的:数组中的所有元素都会被乘以 2 ,从而变成 2 、 4 、6 、 8 、 10 。
完成方法之后是否还需要 Arr 继续占用那块内存引用?既然这个方法已经结束,它也就完成了使命。那么 Arr 结束之后会怎样?它会消失。当 Arr 消失后,如果代码中紧接着又出现了一个名为 PrintArray 的函数,并执行 PrintArray 的操作,对于整个程序而言这相当于在这里重新准备了一个东西。就像之前提到的 Temp 变量,现在 Temp 再去指向那块内存空间,并进行内容的输出。所以这就是整个程序执行流程的一个分析。
在这样一个分析过程中无论是否存在方法,引用传递的本质并没有发生改变。始终都是同一块内存地址被不同的引用所指向。接下来通过一个综合案例来进一步说明。比如可以定义一个数组,要求能够计算出这个数组元素的总和。这里以一个 Int 数组为例,其实无论是哪种类型的数组,原理都是一样的。因为不仅要计算出这个数组元素的总和,还要求出它的最大值、最小值,以及平均值。
System.out.println(temp[x]); } } }
现在要定义一个数组,并将其放在代码中。为了简化说明就不添加其他复杂的部分,就用这个数组来举例。请注意除了保留PrintArray 之外,其他部分都先不考虑。这个地方是关于打印速度输出的数据,之后就用这些数字来进行计算,求出总和、最大值、最小值和平均值。首先平均值、总和以及平均值相对容易求解。
关于总和的计算可以这样表示:首先定义一个变量叫 Int sum = 0 。接下来为了计算平均值可以定义一个 Double 类型的变量,不考虑精度问题,也可以先这样声明,即 Double AVG = 0.0 。这两个变量的初始化很简单。如果要直接进行计算通常会遍历一个数据集,比如使用循环 For (int x = 0; x < data.length; x++) ,这里的Data 是假设的数据数组。
public class ArrayDemo { public static void main(String args[]) { int data [] = new int[]{1,2,3,4,5}; int sum =0; I } public static void printArray(int temp [1){ for (intx=0 ; x< temp.length ;x++) { System.out.println(temp[x]); } } }
在 X++ 完后的整个代码里程序中 Sum 变量会等于多少倍的 X 。随后计算 AVG ,它是 Sum 除以 Data.length 的结果。完成这些计算后,使用 System.out.print 来输出结果。在这里输出一个称为“最大值”的变量,这个叫做“总数组和”,然后再输出一个叫做“数组内容平均值”的变量。
那么这个地方还缺少了一个索引,完成这一步之后再来查看一下结果。编译并执行程序到这里,而这个过程求出总和相对比较简单,但还存在问题。那么最大值和最小值是怎么求的?它们的求解方法是这样的:首先假设第一个元素是最大值,同时也是最小值。即假设第一个元素是最大值 Int Max ,并初始化一个变量 Int Min 为 0 ,而后进入循环。
在循环中进行比较:如果当前元素比 Max 大,则更新 Max 为该元素的值;如果当前元素比 Min 小,则更新 Min 为该元素的值。那么这个过程应该怎么写?
public class ArrayDemo { public static void main(String args[]) { int data [] = new int []{1,2,3,4,5}; int sum =0;; double avg =0.0 ; int max=data[0]; int min for (int x =0 ; x< data,length ;x++){ sum += data[x]; } avg = sum 1/ data.length; System.out.println("数组内容总和:"+ sum) ; System.out.println("数组内容平均值:"+ avg) ; } public static void printArray(int temp [1){ for (int x=0 ;x<temp.length ;x++) { system.out.println(temp[x]) ; }
如果现在假设有一个日期值 DateX ,它大于当前的 Max ,这意味着 Max 的地位需要改变了,那应该怎么做?很简单将 Max 更新为DateX 的值。反过来如果 DateX 比之前记录的 Min 还小,那么这里应该先更新Min 的值,将其改为 DateX ?
于是在这个逻辑下可以编写相应的代码。当把这段代码放到程序中,并且再添加一部分代码来分别跟踪数组中的最大值和最小值,之后在某个地方打印出Max 和Min 的值。当程序编译并再次执行时会发现它成功地找到了数组中的最大值和最小值。
但有个问题既然要写这个程序,肯定不能按照刚才那种直观但低效的方式去做。对于程序的最基本实现接下来会展示给大家。但为什么不能按照那种方式去做?首先要清楚第一个问题:主方法。主方法所在的那个类通常称之为主类,既然是主类肯定不希望它涉及到过于复杂的功能。
那么什么叫过于复杂的功能?来举个最简单的例子。就好比电脑开机,我们都知道怎么操作,只需按下一个按键就能启动,这个过程并没有特别复杂的功能。但是如果要按照那种复杂的代码编写方式去开机,好比你是客户端,在开机过程中需要深入到电脑的每一个细节去操作,这样显然过于复杂了。因此现在的最佳做法不是将复杂的代码逻辑直接放在主方法中,而是应该设计一个更简洁、更高效的方案来完成任务。
int sum=0;; double avg =0.0; int max = data[0];1假改第一个是最大值 int min = data[0] ; //假设第一个是最小值 for (int x = 0 ;x< data.length;x++{ if (data[x]> max) { // max地位改变了 max = data[x] } if (data[x] < min){ min = data[x]; } sum += data [x]; } avg = sum / data.length ; System.out.println("数组内容总和:"+sum); System,out.println("数组内容平均值:"+avg); System.out.println("数组内容最大值:"+max); System.out.printin("数组内容最小值:"+min);
那么应该怎么做?在开发过程中主方法本身就相当于一个客户端的角色。对于客户端的代码应该尽量保持其简洁性。因此最好的做法是将这一系列的计算过程交给单独的模块或程序去完成。
接下来看一下设计改善方案。虽然这个设计确实能够解决问题,但在实现过程中发现整个代码结构变得有些复杂了。而现在特别希望能设计一个名为 ArrayUtil 的操作工具类,它绝对不再是一个简单的 Java 类。那么最终需要从这个工具类中获取几个数值?比如说现在需要的是 Int Sum 来保存总和。
接下来再定义一个 Private Double Avg ,这个变量是用来保存平均值。此外还需要一个 Int Max 来保存最大值,以及一个 Int Min 来保存最小值。
class ArrayUtil{ } public classArrayDemo { public static void main(String args[1) { int data [] = new int[] {1,2,3,4,5}; int sum =0;: double avg =0.0; Int max = data[0] ; // 假设第一个是最大值 int min =data[0] ; // 假设第一个是最小值 for (intx=0 ; x<data.length ;x++){ if (data[x]> max) { // max地位改变了 max = data[x] ; } if(data[x]< min){ min = data[x]; } sum += data[x]; }
关于这个过程最终得到的是一个统计结果。那么是不是得到这个统计结果就可以了?严格来说这里应该提供一个 Scatter 方法,而不是使用 setter 方法,因为这些值通常不需要手动设置。这些值并不是通过外部输入计算得来的,如果它们是计算得出的,那么就不需要设置它们了。比如计算出平均值后,紧接着可能会有一个方法叫做 Int Guid Max 。
在这个过程中可以直接返回 This.max 。同样地也可以直接在这里返回 This.mean 。以及最小值可以直接返回 This.min 。这样就完成了这些方法的编写。
class ArrayUtil { //是一个操作工具的类 private int sum ;//保存总和 private double avg ; //保存平均值 private int max ; //保存最大值 private int min ; //保存最小值 } public class ArrayDemo { public static void main(String args[]) { int data [] = new int[] {1, 2,3,4,5}; int sum =0;; double avg = 0.0; int max = data[0] ; // 假设第一个是最大值 int min = data[0]; // 假设第一个是最小值 for (int x =0;x<data.length ; x ++){ if (data[x] > max) { // max地位改变了 max = data[x] ;
写完之后来说一下数组操作的部分,它主要是为了计算而存在的。那么就按照这样的思路来写。艾瑞克提醒我们要在这里传入一个参数。接下来来进行数组的计算。关于这个过程怎么写可以采用最简单的方法,直接对数组进行操作。可以把需要的数据拿过来进行计算。那么能不能把这些操作都集中到这里来做?当然可以。这样代码会更加清晰和有条理。
在这个过程中要确保每一步都跟上,不要遗漏。同时当大家在写代码的时候,要注意这次也要使用 This来引用类的属性,就像之前说过的那样。当然不要忘了处理一个假设的最大值和最小值问题,这是数组操作中常见的一个需求。
class ArrayUtil { //是一个操作工具的类 private int sum ; // 保存总和 private double avg ; //保存平均值 private int max ; // 保存最大值 private int min ; //保存最小值 public ArrayUtil(int data[]) { // 进行数组计算 for (int x= 0 ; x<data.length ; x ++) { if (data[x]> max) {// max地位改变了 max = data[x] ; } if (data[x]< min) { min = data[x] ; } sum += data[x] ; } avg = sum / data.length ; } public int getSum(){
那么应该在这个过程中关注什么?关于 This.max ,需要把这个任务交给一个团队去处理,这样处理之后外部的流程就不会再这么复杂了。在整个流程中的程序都会被替换掉。接下来说明一下 RVUT ,之前提到的 UT 将会被新的 RVUT 所替代,来传递的数据和计算过程。
接着会基于 UT. 找到一个叫做 Sum 的功能点,再基于另一个UT. ,结合 AG 功能,找到一个 UT. 来实现 Get Max 的功能,以及再基于另一个 UT. 来实现 Get Mean 的功能。目前这个方法中暂时还没有用到这些,所以先暂时移除它们,以防混淆。等需要的时候再恢复过来。
} } public class ArrayDemo{ public static void main(String args[]) { int data [] = new int[] {1,2,3,4,5}; int sum =0i; double avg = 0.0; int max = data[0] ; // 假设第一个是最大值 int min = data[0] ; // 假设第一个是最小值 System.out.println("数组内容总和:"+sum) ; System.out.println("数组内容平均值:"+avg) ; System.out.println("数组内容最大值:"+max) ; System.out,println("数组内容最小值:"+ min) ; } public static voidprintArray(int temp [1) { for (intx=0;x<temp.length ; x++) { System,out,println (temp[x]);
} } public class ArrayDemo{ public static void main(String args[]) { int data [] = new int[] {1,2,3,4,5}; ArrayUtil System.out.println("数组内容总和:"+sum) ; System.out.println("数组内容平均值:"+avg) ; System.out.println("数组内容最大值:"+max) ; System.out,println("数组内容最小值:"+ min) ; } public static voidprintArray(int temp [1) { for (intx=0;x<temp.length ; x++) { System,out,println (temp[x]); } } }
接下来编译一下代码,并再次执行。看看结果是否与预期一致。现在对于主类设计是不是就像客户端一样?那么主类的代码量是不是会得到很大程度的减少?
在这个设计下主类的作用就好比使用电脑时一样,只需关心如何进行操作,而不需要深入了解具体的操作过程。这些具体的操作过程已经被封装起来了。通过这样的封装可以更加专注于操作本身,而无需担心底层的实现细节。
} } public class ArrayDemo{ public static void main(String . args[]){ int data [] = new int [] {1,2,3,4,5}; ArrayUtil util = new ArrayUtil (data) ; //数据计算 System.out.println("数组内容总和:"+ util.getSum()) ; System.out.println("数组内容平均值:"+ util.getAvg() ) ; System.out.println("数组内容最大值:"+ util.getMax()) ; System.out.println("数组内容最小值:"+ util.getMin()) ; } }
也就是说现在只要知道如何将值传入,就能得到最大值和最小值。而且这个功能模块就像是一个即插即用的组件,用时直接拿来即可。但一定要记住 RVUT 已经不再是一个简单的概念了,它包含了一系列的计算过程。因此现在已经实现了数组与方法的操作,这一操作与普通方法的构造方式一模一样,只要是方法都可以接收引用类型。
public ArrayUtil(int data[]) {//进行数组计算 this.max = data[0]; //假设第一个是最大值 this.min = data[0]; //假设第一个是最小值 for (int x = 0 ; x<data.length:x++){- if (data[x] >max) {// max 地位改变了. this.max = data[x]: } if (data[x] <min) {