目录
前言
- 本文分享了方法重载、可变参数、变量的作用域、构造器这些知识,是博主在学习了网课后,结合自己的理解总结整理处理的。
- 本文是面向对象编程(基础)的第2部分,如果有些小伙伴对本文中的一些内容,如,内存分配机制的示意图…不是很理解的话,可以去第1部分的博文看看,那里写的很详细。
一、方法重载
- java 中允许同一个类中,多个同名方法的存在,但要求这些同名方法的形参列表不一致。 比如:System.out.println()方法,可以传入各种数据类型的实参,并且传入实参个数没有限制,如果不使用方法重载,我们就需要创建多个方法才能实现这些功能。
- 重载的好处:
- 减轻了起名的麻烦;
- 减轻了记名的麻烦;
1. 快速入门
- 举例入门:
在MyCalculator 类中,实现用相同的方法名calculate,可以计算:
- 两个整数的和
- 一个整数,一个double的和
- 一个double ,一个Int和
- 三个int的和
- 代码如下:
public class OverLoad01 {
//编写一个main方法
public static void main(String[] args) {
MyCalculator mc = new MyCalculator();
System.out.println(mc.calculate(1, 2));
System.out.println(mc.calculate(1.1, 2));
System.out.println(mc.calculate(1, 2.1));
System.out.println(mc.calculate(1, 2, 3));
}
}
class MyCalculator {
//下面的四个 calculate方法构成了重载
//两个整数的和
public int calculate(int n1, int n2) {
System.out.println("calculate(int n1, int n2) 被调用");
return n1 + n2;
}
//一个整数,一个double的和
public double calculate(int n1, double n2) {
return n1 + n2;
}
//一个double ,一个Int和
public double calculate(double n1, int n2) {
System.out.println("calculate(double n1, int n2) 被调用..");
return n1 + n2;
}
//三个int的和
public int calculate(int n1, int n2,int n3) {
return n1 + n2 + n2;
}
}
- 说明:若是没有使用方法重载,则要分别给这四个方法重新命名,而重载则可以减少命名的麻烦。
2. 注意事项和使用细节
- 方法名必须相同:构成重载的方法它们的方法名必须相同。
- 形参列表必须不同:构成重载的方法,它们的形参类型、个数、顺序,这三者中必须至少有一种和其他的不同;
- 参数名无影响:形参的命名可以相同也可以不同,不是决定方法重载的因素;
- 方法返回类型无影响:方法的返回类型可以相同也可以不同,不是决定方法重载的因素;
- 举例如下:
二、可变参数
- 引入可变参数:在上面的方法重载中,我们举例了分别计算两个int 类型和三个int 类型变量和的方法 calculate ;这两个方法除了形参个数不同外其他都是相同的。那我们是否可以采用一种机制,将同一个类中多个同名同功能但参数个数不同的方法,封装成为一个方法呢?这时可变参数便出现了。
- java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。 这个机制通过可变参数实现。
1. 基本语法
- 访问修饰符 返回类型 方法名(数据类型… 形参名) { 方法体 }
- 注意:数据类型后面是有(…)跟着的。
看一个案例: 创建一个类 HspMethod,创建方法 sum,【可以计算 2 个数的和,3 个数的和,…,n个数的和】。
- 代码如下:
public class VarParameter01 {
//编写一个main方法
public static void main(String[] args) {
HspMethod m = new HspMethod();
System.out.println(m.sum(1, 5, 100)); //106
System.out.println(m.sum(1,19)); //20
}
}
class HspMethod {
//计算 2个数的和,3个数的和,...,n个数的和。
/*
可以使用方法重载,但是要重复创建很多个重构方法;
public int sum(int n1, int n2) {
//2个数的和
return n1 + n2;
}
public int sum(int n1, int n2, int n3) {
//3个数的和
return n1 + n2 + n3;
}
public int sum(int n1, int n2, int n3, int n4) {
//4个数的和
return n1 + n2 + n3 + n4;
}
上面的三个方法名称相同,功能相同, 参数个数不同-> 使用可变参数优化
*/
// 可变参数:
//1. int... 表示接受的是可变参数,类型是int ,即可以接收多个int(0-多) ;
//2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组;
//3. 遍历 nums, 求和即可。
public int sum(int... nums) {
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
2. 注意事项和使用细节
- 细节如下:
- 解释:
- 可变参数,其实int… == int[],可变参数就是数组,传参机制为引用传递;
- 传参给可变参数时,实参传入一个对应数据类型的数组或者是多个对应数据类型的变量都可以;
- 举例说明:
有三个重载方法,分别实现返回姓名和两门课成绩(总分), 返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分)。将它们封装成一个可变参数的方法。
public class VarParameterExercise {
public static void main(String[] args) {
HspMethod hm = new HspMethod();
System.out.println(hm.showScore("milan" , 90.1, 80.0 ));
System.out.println(hm.showScore("terry" , 90.1, 80.0,10,30.5,70 ));
}
}
class HspMethod {
//分析 1. 方法名 showScore; 2. 形参(String ,double... ); 3. 返回类型 String
public String showScore(String name ,double... scores) {
double totalScore = 0;
for(int i = 0; i < scores.length; i++) {
totalScore += scores[i];
}
return name + " 有 " + scores.length + "门课的成绩总分为=" + totalScore;
}
}
三、变量作用域
1. 基本概念
- 基本概念:
- 代码说明:
public class VarScope {
public static void main(String[] args) {
}
}
class Cat {
// 全局变量:也就是属性,作用域为整个类体 即Cat类,cry、eat 等方法可以使用属性;
// 属性在定义时,可以直接赋值;
int age = 10; //指定的值是 10
//全局变量(属性)可以不赋值,直接使用,因为有默认值;
double weight; // 默认值是0.0
public void hi() {
//局部变量必须赋值后,才能使用,因为没有默认值;
int num = 1;
String address = "北京的猫";
System.out.println("num=" + num);//局部变量
System.out.println("weight=" + weight);//属性
}
public void cry() {
//1. 局部变量一般是指在成员方法中定义的变量;
//2. n 和 name 就是局部变量;
//3. n 和 name的作用域在 cry方法中;
int n = 10;
String name = "jack";
System.out.println("在cry中使用属性 age=" + age);
}
public void eat() {
System.out.println("在eat中使用属性 age=" + age);
//System.out.println("在eat中使用 cry的变量 name=" + name);//错误
}
}
2. 注意事项和使用细节
- 细节如下:
- 生命周期不同:
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。 局部变量,生命周期较短,伴随着它的代码块的执行而创建, 伴随着代码块的结束而销毁。即在一次方法调用过程中;- 作用域范围不同:
全局变量/属性:可以被本类使用,或其他类使用(通过对象调用);
局部变量:只能在本类中对应的方法中使用;- 修饰符不同:
属性可以加修饰符(public protected private…);局部变量不能加修饰符;- 命名规则:
属性和局部变量可以重名,访问同名的属性和局部变量时遵循就近原则;在同一个作用域中,比如在同一个成员方法中,两个局部变量不能重名。
- 代码说明:
public class VarScopeDetail {
public static void main(String[] args) {
Person p1 = new Person();
/* 细节1:
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。
局部变量生命周期较短,伴随着它的代码块的执行而创建,
伴随着代码块的结束而销毁。即在一次方法调用过程中
*/
// p1.say(); 当执行say方法时,say方法的局部变量比如name,会创建;
// 当say执行完毕后,name局部变量就销毁,但是p1的属性(全局变量)仍可以使用;
// 细节2:属性:可以被本类使用,或其他类使用(通过对象调用);
T t1 = new T();
t1.test(); // 第1种跨类访问对象属性的方式
t1.test2(p1);//第2种跨类访问对象属性的方式
}
}
class T {
// 细节2:属性:可以被本类使用,或其他类使用(通过对象调用);
public void test() {
// 创建一个Person 类的对象p1,可以使用p1调用Person类的属性
Person p1 = new Person();
System.out.println(p1.name);// 输出 jack
}
// 传入一个Person类对象的实参给方法test2 ,
// 当调用test2 方法时,便可以访问Person 类的属性;
public void test2(Person p) {
System.out.println(p.name);// 输出 jack
}
}
class Person {
// 细节3: 属性可以加修饰符(public protected private..);
// 局部变量不能加修饰符;
public int age = 20;
String name = "jack";
public void say() {
// 细节4: 属性和局部变量可以重名,访问时遵循就近原则;
String name = "king";
System.out.println("say() name=" + name);// 输出的是最近的name的值
}
public void hi() {
// 细节4:在同一个作用域中,两个局部变量不能重名;
String address = "北京";
// String address = "上海";// 错误,重复命名
String name = "hsp";// 可以
}
}
四、构造器/构造方法
- 我们来看一个需求:前面我们在创建人类的对象时,是先把一个对象创建好后,再给他的年龄和姓名属性赋值;
- 如果现在要求,在创建人类的对象时,就直接指定这个对象的年龄和姓名,该怎么做? 这时就可以使用构造器。
1. 基本概念
- 构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新创建对象的初始化。它有几个特点:
- 方法名和类名相同;
- 没有返回值;
- 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
- 基本语法:[修饰符] 方法名(形参列表) { 方法体; }
- 说明:
1)构造器的修饰符可以默认, 也可以是 public ,protected ,private ;
2)构造器一定没有返回值 ;
3)方法名 和 类名字必须一样 ;
4)参数列表 和 成员方法使用一样的规则;
5)构造器的调用, 由系统自动完成,使用者不能主动调用构造器。
现在我们用构造方法来完成刚才提出的问题:在创建人类的对象时,就直接指定这个对象的年龄和姓名。
- 代码如下:
public class Constructor01 {
public static void main(String[] args) {
//当我们new 一个对象时,直接通过构造器指定名字和年龄
Person p1 = new Person("smith", 80);
System.out.println("p1的信息如下");
System.out.println("p1对象name=" + p1.name);//smith
System.out.println("p1对象age=" + p1.age);//80
}
}
//在创建人类的对象时,就直接指定这个对象的年龄和姓名
//
class Person {
String name;
int age;
//构造器
//1. 构造器没有返回值, 也不能写void
//2. 构造器的名称和类Person一样
//3. (String pName, int pAge) 是构造器形参列表,规则和成员方法一样
public Person(String pName, int pAge) {
System.out.println("构造器被调用~~ 完成对象的属性初始化");
name = pName;
age = pAge;
}
}
2. 注意事项和使用细节
- 如下图所示:
- 代码说明:
public class ConstructorDetail {
public static void main(String[] args) {
Person p1 = new Person("king", 40);//第1个构造器
Person p2 = new Person("tom");//第2个构造器
//Person p2 = new Person(); 不能使用默认的无参构造器,因为被覆盖了
Dog dog1 = new Dog();// 可以使用默认的无参构造器,因为显式地重新定义了
}
}
class Dog {
//如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)
/*
默认构造器
Dog() {
}
*/
//一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,
//除非显式的定义一下,即: Dog(){} 写 (这点很重要)
public Dog(String dName) {
//...
}
//显式的定义无参构造器,才能使用;
Dog() {}
}
class Person {
String name;
int age;//默认0
//第1个构造器
public Person(String pName, int pAge) {
name = pName;
age = pAge;
}
//第2个构造器, 只指定人名,不需要指定年龄
public Person(String pName) {
name = pName;
}
}
五、对象创建的流程分析(重要!)
- 举例分析:
- 解释:
- new Person(“小倩”, 20); 首先在方法区中加载了Person类的信息,只会加载一次;
- 其次在堆内存中开辟了一个内存空间(地址),这个内存空间是真正的对象;
- 接着进行对象的初始化,分为3步:
(1)默认初始化,此时对象的各个属性的值都是默认值,上例中 age = 0,name = null;
(2)显式初始化,此时给对象的各个属性赋值,赋的值为类中声明属性时的初始值,上例中 age = 90,name = null;
(3)构造器初始化,此时调用构造器,给对象的各属性赋值,赋的值为创建对象时传入的参数,上例中 age = 20,name = “小倩”。- 完成对象的初始化后,最后将对象的堆空间的地址,返回给栈空间中的对象名 p,此时 p 就可以引用该对象(堆内存空间)了。
- 流程示意图如下:(有些潦草)
六、this 关键字
1. 基本概念
- 什么是this :Java虚拟机会给每个对象分配this,用来代表当前对象。
假设我们创建了一个 Dog 类,并在里面定义构造器和一些方法,看下图案例,分析this 的内存机制。
- 内存分析示意图:
- 解释:
- this 关键字,本质是对象在其堆内存空间中隐式地创建了一个引用类型变量 this,然后将自身的内存地址赋值给了 this;简而言之,this 就是该对象的内存空间内的对象名。
- 举例:有一个孩子,他的父母给他取名为小明,而这个孩子在和别人交流时,会自称“我”;这个孩子就是一个人类对象,小明就是对象名,而“我” 就相当于 this。
2. 注意事项和使用细节
- 细节如下:
- this 关键字只能用来访问本类的属性、方法、构造器;
- this 用于区分本类的属性和局部变量;
- 访问本类成员方法的语法:this.方法名(参数列表);
- 访问本类构造器语法:this(参数列表);注意该语句只能在构造器中使用(即只能在构造器中访问另外一个构造器,而且必须放在第一条语句),不能在普通方法中访问构造器;作用是在一个构造器中调用另一个构造器去初始化对象。
- this 不能在类定义的外部使用,只能在类定义的方法中使用。
总结
本文是小白博主在学习B站韩顺平老师的Java网课时整理的学习笔记,在这里感谢韩顺平老师的网课,如有有兴趣的小伙伴也可以去看看。