打怪升级之小白的大数据之旅(十)
Java基础语法之面向对象的方法
上次回顾:
上一章,对面向对象的基本概念,定义以及使用等知识点进行介绍,本章对面向对象中的方法进行分享,个人理解,方法就可以理解为面向过程~~
方法
方法的概念
- 方法也叫函数,是一个独立功能的定义,是一个类中最基本的共功能单元
- 把一个功能封装为方法的目的是,可以实现代码重用,从而减少代码量
- 就像上一节末尾我炒土豆的例子来说,当我将整个过程都封装进函数中,下一次我再想炒土豆丝的时候,直接调用这个函数,我就可以吃土豆丝了~~
成员方法的分类
根据修饰不同的方法,主要分为两大类:
- 实例方法:
– 没有static修饰的方法,必须通过实例对象调用
– 这句话的意思就是,我想要切土豆丝,我需要先拿出一个土豆来,如果没有static修饰这个方法,也没有拿出一个土豆,那我只能切空气 - 类(静态方法)
– 有static修饰的方法,也叫类方法
– 可以使用实例名.方法名()来调用,也可以使用类名.方法名()调用
– 推荐使用类名.方法名()调用
方法的声明
- 方法声明的位置必须在类中,并且在类的成员位置
- 方法中不能嵌套方法
- 语法格式:
【修饰符】 返回值类型 方法名(【参数列表:参数类型1 参数名1,参数类型2 参数名, ...... 】){
方法体;
【return 返回值;】
}
-
格式说明:
- [权限修饰符]: 修饰符后面详细讲,例如:public,static等都是修饰符
- 返回值类型: 表示方法运行的结果的数据类型,与”return 返回值“搭配使用
- 无返回值:void
- 有返回值:可以是任意基本数据类型和引用数据类型
- 方法名:给方法起一个名字,要符合标识符的命名规则,尽量见名知意,能准确代表该方法功能的名字
- 参数列表:方法内部需要用到其他方法中的数据,需要通过参数传递的形式将数据传递过来,可以是基本数据类型、引用数据类型、也可以没有参数,什么都不写
- 方法体:特定功能的代码
- return:结束方法,可以返回方法的运行结果
- 可以返回不同类型的数据,对应匹配的返回值类型。
- 如果方法无返回值,可以省去return,并且返回值类型为void
-
示例代码1
public class Person { public void hello(){ System.out.println("hello world"); } }
-
示例代码2
/*
定义一个圆的图形类:
属性(成员变量):半径,
功能(成员方法):计算圆的面积,并返回面积
在测试类的main中,创建圆的2个对象,为半径属性赋值,调用两个方法进行测试
提示:圆周率为Math.PI
*/
class Circle{
double radius;
double area() {
return Math.PI * radius * radius;
}
}
Circle不同的对象,半径值不同,那么面积也不同,所以这里area()是非静态的
- 示例代码3
/*定义一个计算工具类CountTools:
方法1:求两个整数的最大值*/
class CountTools{
static int max(int a, int b) {
return a > b ? a : b;
}
}
CountTools只是一个工具类,求两个整数最大值的功能,和CountTools对象无关,所以这里max方法声明为静态的更好,当然也可以声明为非静态的,就是调用的时候需要创建CountTools对象而已
方法的调用
注: 方法必须先声明后使用,不调用不执行,调用一次,执行一次
-
实例方法的调用
对象名.实例方法(【实参列表】)
示例代码public class TestCircle { public static void main(String[] args) { Circle c1 = new Circle(); c1.radius = 1.2; System.out.println("c1的面积:" + c1.area()); //非静态方法只能通过"对象."进行访问 // System.out.println("c1的面积:" + Circle.area()); Circle c2 = new Circle(); c2.radius = 2.5; System.out.println("c2的面积:" + c2.area()); } } class Circle{ double radius; public double area() { return Math.PI * radius * radius; } }
-
类方法(静态方法)的调用
格式1
类名.类方法(【实参列表】)
格式二
对象名.类方法(【实参列表】) (不推荐)
-
示例代码:
public class TestCount { public static void main(String[] args) { System.out.println(CountTools.max(4, 1)); //静态方法也可以通过“对象.”访问,就是麻烦点 CountTools c = new CountTools(); System.out.println(c.max(2, 5)); } } class CountTools{ static int max(int a, int b) { return a > b ? a : b; } }
-
形参与实参
- 形参:在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
- 实参:调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
- 形参实际上就是方法声明上的参数列表,调用方法时传入的任意匹配的数据
- 实参就是调用方法时实际传入的数据
- 调用方法时,实参列表必须根据形参列表完全匹配 ,参数的个数,类型,顺序都必须完全一致
- 返回值类型必须与实际的返回数据类型匹配
-
方法调用的注意事项
- 调用方法时,实参的个数、类型、顺序必须要与形参列表一一对应
- 调用方法时,如果方法有返回值,可以用与返回值类型相同的变量接受或直接处理返回值结果,如果方法的返回值类型是void,不需要也不能接收和处理返回值结果。
本类内的成员变量和方法访问
- 在一个类的内部,调用本类的方法或成员变量时,直接用,不需要加“对象名.“和"类名.”
- 例外情况:静态方法中不能直接访问本类的非静态的成员变量和成员方法
** 静态不能调用非静态
** 非静态可以调用静态 - 示例代码1
class Circle{ double radius; //写一个方法,可以返回“圆对象”的详细信息 String getDetailInfo(){ return "半径:" + radius + ",面积:" + area() +",周长:" + perimeter(); } //写一个方法,可以返回“圆对象”的面积 double area(){ return Math.PI*radius*radius; } //写一个方法,可以返回“圆对象”的周长 double perimeter(){ return 2*Math.PI*radius; } }
- 示例代码2
class Test{ static void test(){ System.out.println(""); } void method(){ test(); } public static void main(String[] args){ method();//错误 test();//正确 } }
方法调用内存分析
栈结构特点:先进后出,后进先出。
又到了内存分析了,方法的内存过程如下:
-
方法不调用不执行,调用一次执行一次,
-
每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,
-
当方法执行结束后,会释放该内存,称为出栈,
-
如果方法有返回值,就会把结果返回调用处,
-
如果没有返回值,就直接结束,回到调用处继续执行下一条指令
-
示例代码一:
public class TestCount { public static void main(String[] args) { int a = 4; int b = 2; int m = CountTools.max(a, b)); } } class CountTools{ static int max(int a, int b) { return a > b ? a : b; } }
-
示例代码二:
public class TestCircle { public static void main(String[] args) { Circle c1 = new Circle(); c1.radius = 1.2; int area1 = c1.area(); Circle c2 = new Circle(); c2.radius = 2.5; int area2 = c2.area(); } } class Circle{ double radius; public double area() { return Math.PI * radius * radius; } }
-
方法的内存分析(实例方法)
-
方法的内存分析(静态方法)
-
示例代码三
public class Test { public static void main(String[] args) { int[] arr = {2,4,1,5,3}; ArrayUtil.sort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } class ArrayUtil{ public static void sort(int[] arr){ for (int i = 1; i < arr.length; i++) { for (int j = 0; j < arr.length - i; j++) { if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } }
方法参数的值传递机制
方法的参数传递机制:实参给形参赋值
-
方法的形参是基本数据类型时,形参值的改变不会影响实参;
-
方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
-
Java中,方法传参只有值传递
- 方法参数是基本数据类型,形参的改变不会影响实参
- 方法参数是引用数据类型,形参的改变会影响实参
-
注意:String、Integer等特殊类型容易错
-
方法参数传递的内存分析
-
示例代码一
class Test{ public static void swap(int a, int b){ int temp = a; a = b; b = temp; } public static void main(String[] args){ int x = 1; int y = 2; swap(x,y);//调用完之后,x与y的值不变 } }
-
示例代码二
class Test{ public static void change(MyData my){ my.num *= 2; } public static void main(String[] args){ MyData m = new MyData(); m.num = 1; change(m);//调用完之后,m对象的num属性值就变为2 } } class MyData{ int num; }
-
示例代码三
public class Test { public static void main(String[] args) { int[] arr = {2,4,1,5,3}; ArrayUtil.sort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } class ArrayUtil{ public static void sort(int[] arr){ for (int i = 1; i < arr.length; i++) { for (int j = 0; j < arr.length - i; j++) { if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } }
-
陷阱一 实参形参陷阱
/* 陷阱1:在方法中,形参 = 新new对象,那么就和实参无关了 */ class Test{ public static void change(MyData my){ my = new MyData();//形参指向了新对象 my.num *= 2; } public static void main(String[] args){ MyData m = new MyData(); m.num = 1; change(m);//调用完之后,m对象的num属性值仍然为1 } } class MyData{ int num; }
-
陷阱二 字符串和包装类陷阱
public class Test { public static void main(String[] args) { StringUtil util = new StringUtil(); String str = "尚硅谷"; util.change(str); System.out.println(str);// } } class StringUtil{ public void change(String str){ str += "你好";//String对象不可变,一旦修改就会产生新对象 } }
方法重载
-
在同一个类中,只要它们的参数列表不同,就可以可以存在多个同名的方法
-
方法重载与修饰符和返回值类型无关
-
参数列表不同:指的是参数个数不同,数据类型不同,数据类型顺序不同
-
示例代码一 比较两个数据是否相等
/* 比较两个数据是否相等。参数类型分别为两个byte类型,两个short类型,两个int类型,两个long类型,并在main方法中进行测试。 */ public class Method_Demo6 { public static void main(String[] args) { //定义不同数据类型的变量 byte a = 10; byte b = 20; short c = 10; short d = 20; int e = 10; int f = 10; long g = 10; long h = 20; // 调用 System.out.println(compare(a, b)); System.out.println(compare(c, d)); System.out.println(compare(e, f)); System.out.println(compare(g, h)); } // 两个byte类型的 public static boolean compare(byte a, byte b) { System.out.println("byte"); return a == b; } // 两个short类型的 public static boolean compare(short a, short b) { System.out.println("short"); return a == b; } // 两个int类型的 public static boolean compare(int a, int b) { System.out.println("int"); return a == b; } // 两个long类型的 public static boolean compare(long a, long b) { System.out.println("long"); return a == b; } }
-
示例代码二 求各种最大值
/* 用重载实现: 定义方法求两个整数的最大值 定义方法求三个整数的最大值 定义方法求两个小数的最大值 */ //求两个整数的最大值 public int max(int a,int b){ return a>b?a:b; } //求三个整数的最大值 public int max(int a, int b, int c){ return max(max(a,b),c); } //求两个小数的最大值 public double max(double a, double b){ return a>b?a:b; }
-
示例代码三 判断两个方法是否是合理的重载方法
//判断如下两个方法是否构成重载:是 class StringUtil{ public static String concat(char seperator, String... args){ String str = ""; for (int i = 0; i < args.length; i++) { if(i==0){ str += args[i]; }else{ str += seperator + args[i]; } } return str; } public static String concat(String[] args){ String str = ""; for (int i = 0; i < args.length; i++) { str += args[i]; } return str; } } //判断如下两个方法是否构成重载:不是 class Count{ public static int getSum(int... nums){ int sum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; } return sum; } public static int getSum(int[] nums){ int sum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; } return sum; } } class Test06_Overload_Problem2{ public static void main(String[] args){ System.out.println(sum(1,2));//(int a, int b) System.out.println(sum(1,2,3));//(int... args)和(int a, int... args)都兼容,就有问题 } //不调用编译没问题,但是调用时就有问题 public static int sum(int a, int b){ return a+b; } public static int sum(int... args){ int sum = 0; for(int i=0; i<args.length; i++){ sum += args[i]; } return sum; } public static int sum(int a, int... args){ int sum = a; for(int i=0; i<args.length; i++){ sum += args[i]; } return sum; } }
可变参数
-
我们定义一个方法时,此时某个形参的类型可以确定,但是形参的个数不确定,那么我们可以使用可变参数
-
格式
【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){ }
-
注意事项:
1). 一个方法最多只能有一个可变参数
2). 如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个 -
示例代码一 求n个整数的和
public class ChangeArgs { public static void main(String[] args) { int[] arr = { 1, 4, 62, 431, 2 }; int sum1 = getSum1(arr); System.out.println(sum1); int sum2 = getSum2(arr); System.out.println(sum2); int sum3 = getSum2(1, 4, 62, 431, 2); System.out.println(sum3); } // 完成数组 所有元素的求和 // 原始写法 public static int getSum1(int[] arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } // 可变参数写法 public static int getSum2(int... arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } }
-
示例代码二 求1-那个整数中的最大值
public class ChangeArgs_Exer1 { public static void main(String[] args) { System.out.println(max(1)); System.out.println(max(5,3,2,6)); } public static int max(int num, int... others){ int max = num; for (int i = 0; i < others.length; i++) { if(max < others[i]){ max = num; } } return max; } }
-
示例代码三 字符串拼接
// 需求一:返回n个字符串拼接结果,如果没有传入字符串,那么返回空字符串 public class ChangeArgs_Exer2 { public static void main(String[] args) { System.out.println(concat()); System.out.println(concat("hello","world")); } public static String concat(String... args){ String str = ""; for (int i = 0; i < args.length; i++) { str += args[i]; } return str; } } // 需求二:n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串 public class ChangeArgs_Exer4 { public static void main(String[] args) { System.out.println(concat('+')); System.out.println(concat('+',"hello","world")); } public static String concat(char seperator, String... args){ String str = ""; for (int i = 0; i < args.length; i++) { if(i==0){ str += args[i]; }else{ str += seperator + args[i]; } } return str; } }
递归
- 方法自己调用自己
- 递归的分类
- 直接递归: 自己直接调用自己
- 间接递归; 方法A调用B,方法B调用方法A
注意:
1). 递归需要出口,否则会造成死递归,类似死循环,会造成内存溢出
2). 即使有出口,也不宜太多,否则会造成内存溢出(栈内存的溢出), - 理解递归,首先看这张图
相信大家都看过盗梦空间吧,可以把盗梦空间的梦境理解为递归,现实世界就是最开始的方法,当根据需求需要自己调用自己后,就是递归,也就是盗梦空间进入了第一层梦境.在盗梦空间中,反复提到过,他们要醒来需要一个属于自己的物件,并且需要有死亡、坠落感等.如果缺失了出去的条件,就再也出不去了,递归也是如此,如果没有一个出口,就会陷入死循环,造成内存溢出. - 通过示例代码来加深对例如方法实现递归的印象:
-
示例代码一: 计算1~100之间所有的自然数之和
-
利用循环实现
public class RecursionMethod1{ public static void main(String[] args) { int sum = sum(100); System.out.println("1-100的和:" + sum); } public static int sum(int n){ int sum=0; for(int i=1;i<=n;i++){ sum+=i; } return sum; } }
-
利用递归实现
public class RecursionMethod1{ public static void main(String[] args) { int sum = sum(100); System.out.println("1-100的和:" + sum); } public static int sum(int n){ if(n == 1){ return 1; }else{ return n + sum(n-1); } } }
内存图分析:
-
示例代码二: 利用递归实现斐波那契数列(Fibonacci)的第n个值
规律:一个数等于前两个数之和,/* f(0) =1, f(1) = 1, f(2) = f(0) + f(1) =2, f(3) = f(1) + f(2) = 3, f(4) = f(2) + f(3) = 5 ... f(n) = f(n-2) + f(n-1);*/ public class RecursionMethod3{ public static void main(String[] args) { Count c = new Count(); System.out.println("f(10):" + c.f(10)); System.out.println("f方法被调用的总次数:" + c.total); } } class Count{ int total = 0; public int f(int n){ total++; if(n <= 1){ return 1; }else{ return f(n-2) + f(n-1); } } }
-
斐波那契数列递归思路:
-
- 阶乘的内存图分析
对象数组
- 对象数组,就是在以类为数据类型的数组,
- 数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组
- 注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException
- 定义格式,对象数组的定义格式同数组相同,只是数组类型为对象的类:
类名[] = new 类名()
- 对象数组的初始化
- 对象数组的初始化也同数组相同,分类静态初始化和动态初始化,这里就不写了(偷个懒~),下面举个栗子就懂了
- 示例代码:
// 对象数组 public class instanceArray{ public static void main(String[] args) { // 定义对象数组,并动态初始化3个对象 Student[] stus = new Student[3]; // 实例化3个学生对象 Student s1 = new Student() s1.name = "悟空"; s1.age = 1000; Student s2 = new Student() s2.name = "八戒"; s2.age = 800; Student s3= new Student() s3.name = "沙僧"; s3.age = 900; // 将学生对象放入对象数组中 stus[0] = s1; stus[1] = s2; stus[2 = s3; // 遍历数组 for (int i=0; i<stus.length; i++) { Student s = stus[i]; s.showInfo() } } } class Student{ String name; int age; public void play(){ System.out.print("打妖怪...") } public void study(){ System.out.print("念经...") } public void showInfo(){ System.out.print("姓名: ":+name+",年龄:"+age+",正在"+study()) } }
- 对象数组内存图分析:
命令行参数
-
这里提一个小功能,命令行参数,它一般就是可以在命令行(cmd)上传参数,让java可以接收这个参数,一般来说不怎么用到
-
因为我以前做过爬虫,在主从服务器的时候,需要利用它将配置文件的参数传进去,好让服务器根据配置文件做不同的功能,这里了解一下就好,需要问问度娘就可以了
-
命令行传参数,主要利用的是java的程序入口 main函数,还记得我们从开始的第一个程序吗:
当时没有提这里,这里就是命令行传参数用的,举个栗子:public class TestCommandParam{ //形参:String[] args public static void main(String[] args){ System.out.println(args); System.out.println(args.length); for(int i=0; i<args.length; i++){ System.out.println("第" + (i+1) + "个参数的值是:" + args[i]); } } } 运行命令: java TestCommandParam java TestCommandParam 1 2 3 java TestCommandParam hello atguigu
总结
本章对面向对象的方法就介绍完毕啦,方法的细节点很多,多写写代码就好,不要死记硬背,在IT界,技术是通过有效代码的积累慢慢提高的,遇到了问题随时度娘,csdn,博客园等,90%的问题都可以解决。好了,下一章,我会对面向对象的三大特性进行分享,封装–继承–多态。欢迎大家后台吐槽~另外我后面的博客都用makedown写了,富文本编辑器是真的不好用