Java-Day-8(方法重载 + 可变参数 + 作用域 + 构造方法 + this 关键字 )
Java-Day-8
方法重载
( Overload )
-
java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致
- 在调用方法时,通过所给的参数来选择执行的是哪个方法
-
重载好处
- 减轻了起名的麻烦
- 减轻了记名的麻烦
-
注意细节
- 方法名必须相同
- 参数列表必须不同
- 形参类型或个数或顺序,至少有一样不同,参数名无要求
- 若只是多个方法的形参名更改,其余不变就只是多个方法的重复定义
- 方法名前面的返回类型无要求
-
使用时要考虑类型的自动转换
-
如果在调用方法时,输入参数 ( 10.0, 2.0, 3 ),但类中的方法只有 ( double x, double y, double z ),就将类型进行自动转换 ( int —> double ) 使用此方法。
// main 中,调用以下类中的方法:max(10.0, 2.0, 30) public double max(double n1, double n2, int n3){ double max = n1 > n2 ? n1 : n2; return n3 > max ? n3 : max; } // 优先级第一 public double max(double n1, double n2, double n3){ ... } // 优先级第二 // 输出的最大值都是 30.0
-
可变参数
( Variable parameters )
-
java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法 ——> 通过可变参数来实现
-
基本语法
- 访问修饰符 返回类型 方法名 ( 数据类型... 形参名 )
-
注意细节
-
可变参数的实参可以为零个或任意多个
// main 中使用此 sum 方法时,参数可以随便输入 public int sum(int... nums){ int res = 0; for (int i = 0; i < nums.length; i++){ res += nums[i]; } return res; }
-
可变参数的实参可以为数组
// main 中定义任意数组,只论长度,可不赋初值,将数组名作为方法的形参 public void sum(int... nums){ System.out.println("长为" + nums.length); }
-
可变参数的本质就是数组
- 例如在求所有的可变参数之和的时,就用 for 循环 .length 遍历
-
可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数放在最后
-
一个形参列表中只能出现一个可变参数
-
作用域
( Scope )
- 在 java 编程中,主要的变量就是属性 ( 成员变量 ) 和局部变量
- java 中作用域的分类
- 全局变量:也就是属性,作用域为整个类体 ( 于类中,方法外 )
- 局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中 ( 一般就是指在成员方法中定义的变量,不绝对 )
- 全局变量可以不赋值直接使用,因为有默认值 ( 属性是有默认值的 ),但局部变量不可以,因为局部无默认值必须初始化
- 注意细节
- 属性和局部变量可以重名,访问时遵循的是就近原则
- 在同一个作用域中不可重名,但属性和局部之间没关系
- 属性的生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁 ( ... = new 类(); 而产生的对象 ) 。局部变量的生命周期较短,伴随着其代码块的执行而创建,伴随着代码块的结束而销毁 ( 即类中方法调用完便销毁 )。
- 作用域范围不同
- 全局变量 ( 属性 ):可以被本类使用,也可以在其他类方法中通过创建对象调用而被使用,或者是在 main 里传一个创建的对象到另一个类方法里进行调用 ( 即把一个对象放进形参列表中 )
- 局部变量:只能被本类中对应的方法中使用
- 修饰符不同
- 全局变量可以加修饰符
- 局部变量不可以加修饰符
构造方法/构造器
( Constructor )
-
构造方法又叫构造器,石磊的一种特殊的方法,它的主要作用是完成对新对象的初始化 ( 并非创建 )
- 方法名和类名必须一致
- 没有返回值
- 在创建对象时,系统会自动调用该类的构造器完成对对象的初始化
-
基本语法
-
[修饰符] 方法名 (形参列表)
-
构造器的修饰符可以默认
-
形参列和成员方法一样的规则
// main 里 Person p1 = new Person("zhuyazhu",1); System.out.println("名为" + p1.name + " " + p1.age); // 编写构造器,使 p1 创建后紧接着对其进行初始化赋值 class Person{ String name; int age; // 构造器 public Person(String pName, int pAge){ System.out.println("已调用构造器,完成属性的初始化"); name = pName; age = pAge; } // 利用无参构造器将所有人的年龄定为18 // main 中创建就无需传值于形参列表中:Person p1 = new Person(); public Person(){ age = 18; } }
-
-
使用细节
-
一个类可以有多个构造器,即构造器重载
-
构造器名都要与类名相同且无返回值
-
对象是在 new 时创建,在使用构造器时,其对象已经存在,所以是对其初始化
-
构造器是不能自行调用的,只能是系统自动执行使用
-
若程序员没有定义构造器,系统就会自动给类生成一个默认无参构造器
-
即默认构造器,可在 Dos 里 javap Person.class 进行反编译看源文件
Person() { }
-
-
拓展 javap
- javap 是 JDK 提供的一个命令行工具,可以对指定的 class 文件提供的字节代码进行反编译
- 从而对照源代码和字节码,能了解更深内容
- -v ( -verbose ):输出附加信息 -c:对代码进行反汇编 -p ( -private ):显示所有类和成员
-
一旦定义了自己的构造器,就是覆盖了默认的无参构造器,除非自行定义无参构造器
- 是非无参构造器的话,在创建类的时候 Person p1 = new Person(); 就会报错
-
-
对象创建流程分析
-
例:
// main 内: Person p = new Person("zhuyazhu",20); class Person{ String name; int age = 90; Person(String n, int a){ name = n; age = a; } }
-
流程分析 ( 加载 Person 类信息,只加载此一次;堆内分配空间;完成对象初始化;堆中地址返回给对象名 )
- 创建时,先加载 person 类于方法区内
- new 了,就在堆内开辟了一个地址空间,有存放 name 与 age 两个空间大小,其内值都是先默认初始化
- 然后显式的初始化把 age 改为 90
- 执行构造器初始化,把 main 内形参列表的值传给构造方法
- 常量池一个带地址的空间存放 " zhuyazhu ",把地址放进堆内 name 的空间里,形成指向
- 再把 20 替换掉原 90
- 此时才把堆内的地址给栈内的自定义的对象名,使此对象的引用 ( 对象名 ) 指向着堆内的地址空间 ( 真正的对象 )
-
this 关键字
-
java 虚拟机会给每个对象分配 this,代表当前对象
- 简单来说就是,哪个对象调用,this 就代表哪个对象
- 活用 get 和 set 来获取和更改属性
class Person{ String name; int age; // Person(String n, int a){ // name = n; // age = a; // } public Person(String name, int age){ this.name = name; this.age = age; // 不加this的话,根据就近原则,name 就都是局部变量,和属性一点关系都没有了 // this 的话就是当前对象的属性 } }
-
jvm 中,于堆中真正的对象都有个隐藏的 this 地址指向堆内真正的对象自己
- 可以借用 this.hashCode() 简单的看作对象地址,但注意并不是对象的实际地址 ( 而是把真正地址转成了一个整数,借由此来观察地址所在 )
-
注意细节
-
this 可以访问本类的属性、方法、构造器
- 方法内若是直接调用某变量名是按就近原则,但 this 就一定是指向属性
- 若方法里更改了属性的值,则同对象的其他方法再调用此属性就是更改后的属性值,但是再 new 的对象调用的属性值就是没有改变的值
-
this 用于区分当前类的属性和局部变量
-
访问成员方法的语法:this.方法名 ( 参数列表 )
class Person{ public void f1(){ System.out.println("f1()"); } public void f2(){ System.out.println("f2()"); // F1:同一类中可以直接用 f1(); // F2: this.f1(); } }
-
访问构造器语法,this (参数列表);注意只能在构造器内使用 ( 即只能在构造器中访问另一个构造器 )
- this 调用必须是构造器中的第一个语句
class Person{ public Person(){ this(10); System.out.println("构造器1"); // this(10); 非第一句就会报错 } public Person(int a){ System.out.println("构造器2," + "值为" +a); } } // 走构造器1时,输出先"构造器2,值为10",再输出"构造器1"
-
this 不能在类定义的外部使用,只能在类定义的方法中使用
-
-
本章小练习
-
查询 double 类型数组的最大值
// 常用写法:(缺少健壮性,若是传来的是空数组就会报错) class Test{ public double max(double[] arr){ double max = arr[0]; // for循环判断最大值并return } } // 优化,改double为包装类Double,可以返回空null class Test{ public Double max(double[] arr){ if (arr != null && arr.length > 0){ double max = arr[0]; // for循环判断最大值并return } else { return null; // 在main调用时用Double接收判断是否为空 } } }
-
一个对象 new 创建后,再赋给新的对象,则此两个对象指向同一个堆内地址,在任意一个对象的方法里修改属性,则两个对象的属性值都改变 ( 勿忘,记不清了就画 jvm 内的存储图 )
-
复用构造器( 只能在一个方法里使用一个构造器,由于 this 只能放在第一条语句,所以无法同时调用两个 this 构造器 )
-
人与电脑剪刀石头布
package com.qut.test; import java.util.Random; import java.util.Scanner; public class java_test { public static void main(String[] args) { finger_guessing fg = new finger_guessing(); // 二维接收各自出拳状况 int[][] arr1 = new int[3][3]; int j = 0; // 输赢情况 String[] arr2 = new String[3]; Scanner scanner = new Scanner(System.in); for (int i = 0; i < 3; i++){ System.out.println("请输入(0:石头 1:剪刀 2:布)"); // 获取玩家出拳 int num = scanner.nextInt(); fg.setPerGuess(num); arr1[i][j + 1] = num; // 获取电脑出拳 int comnum = fg.computerGuess(); fg.setComGuess(comnum); arr1[i][j + 2] = comnum; // 比较出拳大小 String isWin = fg.vsComputer(); arr2[i] = isWin; arr1[i][j] = i; // 战况输出 System.out.println("————————————"); System.out.println("局数\t玩家出拳\t电脑出拳\t输赢情况"); System.out.println(i + "\t" + num + "\t\t" + comnum + "\t\t" + isWin); System.out.println("————————————"); } } } class finger_guessing{ int perGuess; // 人猜拳 int comGuess; // 人猜拳 int perWinGuess; // 人猜拳赢的次数 int count = 1; // 总次数 public void showInfo(){ } public int computerGuess(){ Random r = new Random(); comGuess = r.nextInt(3); // 返回0~2的随机数 return comGuess; } public void setPerGuess(int perGuess) { if (perGuess > 2 || perGuess < 0){ throw new IllegalArgumentException("数字输入不在范围内"); } this.perGuess = perGuess; } public void setComGuess(int comGuess) { this.comGuess = comGuess; } public String vsComputer(){ if (perGuess == 0 && comGuess == 1){ return "玩家获胜"; } else if (perGuess == 1 && comGuess == 2) { return "玩家获胜"; } else if (perGuess == 2 && comGuess == 0){ return "玩家获胜"; } else if (perGuess == comGuess) { return "平局"; } else { return "电脑获胜,玩家输咧"; } } }
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义