课时10:this调用本类方法

简介: 本次分享的主题是 This 调用本类方法。主要分为两个部分:1.普通方法的调用2.构造方法的调用

课时10:this调用本类方法

摘要:本次分享的主题是 This 调用本类方法。主要分为两个部分:

1.普通方法调用

2.构造方法的调用

 

除了使用属性之外,这次还可以实现方法的调用。但是在调用方法时必须区分构造方法和普通方法。为什么要这样考虑呢?首先关于构造方法大家都有所了解如果要调用构造方法,通常会用到 New 关键字后跟上一对圆括号。但这里需要注意一点:构造方法是在使用 New 关键字实例化对象时才会被调用的

 

01.普通方法的调用

 

普通方法的调用过程涉及四个步骤:首先是跟上方法名称,然后是找到并添加圆括号。那么普通方法会在什么情况下出现呢?很简单实例化对象产生之后就可以调用普通方法了,所以这两种调用的形式是不同的。比如说先来看一下如何在类中调用一个普通方法。


以这个程序为例,在这个地方添加一些代码先调用String Name 方法,跟上 String Name ,然后添加圆括号并传入参数 Name 。接着再调用 String Age 方法,跟上 String Age ,并传入参数 Age 。最后还有一个 GetName 方法,它的返回值是String 类型。


现在把这些方法都完整地写出来: Return this . name ;,这是一个标准的写法,大家严格遵守后就能理解了同样的 GetAge 方法可以写成 Return this . age ;。代码写到这里应该很清晰了。


写完之后回到这里,看看 Person 类的代码。你说这个构造方法是不是要用来复制或设置值呢?是的确实有 String 方法。那如果有String Name  String Age 这些方法的话,你直接调用它们不就可以设置值了吗?如果你想更明确地指出是在当前对象上调用这些方法,可以加上 This .前缀,比如This . String Name 。

image.png

private String name ;
private int age ;
public Person(String name,int age) {
this.name = name ; 
this.age = age ;
}
public void tell(){
System.out.println("姓名:"+ this.name +"、年龄:"+ this.a
}
public void setName(String name){
this.name = name ;
}
public void setAge(int age){
this.age = age ;
}
public string getName(){
return this.name ;
}

但是你一定要记住如果不加 This 关键字,它也依然表示调用当前对象的方法。加与不加 This 在功能上都是表示调用本位方法。从标准的角度来看加上 This 是更为明确的做法,但如果你就是不想加那也没办法,可以不加。不过不加的话可能会让代码的可读性稍微差一些。


虽然强烈建议加上 This 是最佳实践,这样能帮助理解代码的含义。那么先将这段代码留在这里,接下来看一看这个过程是否成功地将值设置了上去。这就是调用本类的方法。这个过程非常容易理解,没有什么难点,加与不加 This 在功能上是等价的。


这里面最复杂的是构造方法的处理,除了普通方法的调用之外还需要进行构造方法的调用。对于构造方法的调用它自然是在构造方法内部执行的。现在来举个例子说明一下。假设类中定义了三个构造方法,但有一个要求:无论调用哪个构造方法,都需要执行一行输出语句。


比如输出“一个新的 Person 类的对象已实例化”。大家能理解这个意思吗?那么应该如何编写代码来实现这个要求呢?


现在请大家仔细看好我的代码。接下来将采用两种形式来编写。第一种是传统做法。什么是传统做法呢?简单来说就是按照最直接的方式来实现需求,不考虑太多的设计原则或结构化思想。这种方法适合那些没有太多设计经验,只关注于实现功能的人。


02.构造方法的调用

 

那这位同学是这样写的,为了简化起见,在这里省略了 Setter 和getter 方法的实现,这样写是可以的吧?接下来按照传统做法开始编写代码。


现在假设有三个构造方法, Person 之后在这个地方再跟上 String Name 之后在跟上 Out.print 跟上 *** 接下来告诉了一个新的 Person 为对象,继续再往底下再跟上,随后这里面也需要设置,那么这个设置为了简化代码在构造方法内部只进行简单的赋值操作,比如

 This . name  =  name ;和 This . age  =  age ;。这样代码就编写完成了。


代码已经编写完成,同学们请看这里。你们会发现在每个构造方法中都重复了相同的输出语句。这是正常的做法,因为要确保无论调用哪个构造方法,都能输出提示信息。


接下来进行编译和执行。当调用构造方法时,提示信息被成功输出了。如果现在调用的是单参数构造方法,再次编译和执行,同样会输出提示信息。无参数构造方法也是如此。


image.png

D:\mldnjava>

当前的需求是否得到了满足?答案是肯定的,同学们一定觉得已经满足了需求。确实从功能上来看这样做无可厚非,已经满足了需求。但是要深入思考一下,如果把这句重复的输出语句扩展成十行代码的重复,那么这个程序中就会存在什么问题呢?代码重复。那么如何去评价一个代码的好坏呢?其实只有一个标准,那就是无重复且高可用。


如果要评价一个代码的好坏,可以从以下几个方面来考虑。首先代码结构应该具备可重用性。这意味着代码应该像组件一样,可以独立地使用和替换。就像电脑中的 CPU 一样,如果某个 CPU性能不足,那么就可以更换一个更强大的 CPU ,从而提升整体性能。同样的道理代码中的组件也应该具备这样的可替换性和可升级性。


第二个重要的目标是消除重复代码。现在来观察这个程序会发现存在重复的代码,那么面对重复代码应该怎么办呢?此时可以利用构造方法之间的调用来进行优化。具体来说可以在一个构造方法中调用另一个构造方法,以避免重复的代码。


对它进行优化,那么该如何优化呢?回到这里来看一下,这个地方是不是代表无参?那么调用的是哪个方法来表示去了无参呢?那么在调用本类构造的方法时,无参的构造方法应该放在这里,那么这里应该调用无参方法吗?在这里发现可能存在重复,这两个部分是不是重复了?那么调用单参数构造方法是否可以呢?可以的,同学们可以使用单层构造方法


现在来看看能否实现预期的效果。先来编译并执行一下单参数构造方法,看看结果是否一致。那么如果在这里调用一个多参数构造方法,再次编译并执行,是否能成功呢?结果也成功了。

image.png

image.png

class Person {
private String name; 
private int age ;
public Person(){
System,out .println("***一个新的Person类对象实例化了。");
}
public Person(String name){
this() ;  // 调用本类无参构造 
this.name = name ;
}
public Person(String name,int age){
this (name); // 调用单参构造 
this.age =age;
}
public void tell(){
System.out.println("姓名:"+ this .name+"、年龄:"+this.a
}
// setter、getter略
}
public class JavaDemo{//主类
public static fid main(String args[]){
Person per new Person("王五") ;
per.tell() ;
 }
}

根据刚才所讨论的大家认为这段代码是否可以被重用呢?因此当程序具备这样的设计时才认为它的结构是合理的。不过从代码长度上看,这个程序是不是显得有些简短?


但如果将那一行代码想象成十行代码,那么这个过程就减少了不少代码的使用。所以这就是构造方法之间的互相调用。不过在这里,关于本类构造方法之间的互相调用,同学们需要注意以下几个重要问题。


image.png

class Person {
private String name; 
private int age ;
public Person(){
System,out .println("***一个新的Person类对象实例化了。");
}
public Person(String name){
this() ;  // 调用本类无参构造 
this.name = name ;
}
public Person(String name,int age){
this (name); // 调用单参构造
this.age =age;
}
public void tell(){
System.out.println("姓名:"+ this .name+"、年龄:"+this.a
}
// setter、getter略

第一点构造方法必须在实例化新对象时被调用。首次实例化对象时构造方法的调用是必须的。因此构造方法中的 This() 语句只允许放在构造方法的首行。这里说的“只允许放在构造方法的首行”是什么意思呢?大家请看这里,这是构造方法,而 This() 语句应该位于首行。如果将它换个位置,虽然它仍然是构造方法的一部分,但不再是首行了。


此时如果再次编译代码,编译器会给出什么提示呢?它会提示“这次的调用必须是构造器中的第一个语句”,这是明确的语法要求,相信大家都能够理解。

image.png

D:\mldnjava>java JavaDemo
***一个新的Person类对象实例化了。
姓名:王五、年龄:0
 
D:\mldnjava>javac JavaDemo.java
 
D:\mldnjava>java JavaDemo
***一个新的Person类对象实例化了。
姓名:王五、年龄:10
 
D:\mldnjava>javac JavaDemo.java
JavaDemo.java:10:错误:对this的调用必须是构造器中的第一个语句
this() :4 // 调用本类无参构造 
1个错误
 
D:\mldnjava>_

第二点来看这里。现在给大家展示的是什么内容呢?这是关于普通方法与构造方法的区别。那么当再次编译代码时,编译器会告诉我们这是不允许的。这样的用法在构造方法中是不适用的。

image.png

public Person(String name,int age){
this (name); // 调用单参构造 
this.age =age;
}
public void tell(){
this(); // 调用本类无参构造 
System.out.println("姓名:"+ this .name + "、年龄:"+this.a} 
}
// setter、getter略
}
public class JavaDemo{//主类
public static void main(String args[]){
Person per = new Person("王五",10);  
per.tel1();
 }
}

image.png

D:\mldnjava>javac JavaDemo.java
JavaDemo.java:10:错误:对this的调用必须是构造器中的第一个语句
this():     //调用本类无参构造 
 
1个错误
 
D:\mldnjava>java JavaDemo
***一个新的Person类对象实例化了。
姓名:王五、年龄:10
 
D:\mldnjava>javac JavaDemo.java
JavaDemo.java:15:错误:对this的调用必须是构造器中的第一个语句
this()      // 调用本类无参构造 
1个错误 
 
D:\mldnjava>

因此一定要牢记的逻辑关系是:在构造方法中,你可以调用普通方法。但是,反过来,在普通方法中是无法调用构造方法的。也就是说 This() 这样的调用必须并且只能放在构造方法的首行,这是毫无疑问的。


点是最重要的一个操作要点:在构造方法互相调用时,请确保程序有出口。也就是说要避免形成死循环。那么这是什么意思呢?来看一个例子。这个过程这里有一个双参数构造方法调用单参数构造方法的情况,单参数构造方法又做了某些操作。


但是如果不小心在单参数构造方法中又调用了双参数构造方法,而且调用条件没有适当限制,那么就可能形成一个无限循环。

image.png

image.png

class Person {
private String name; 
private int age ;
public Person(){
System,out .println("***一个新的Person类对象实例化了。");
}
public Person(String name){
this() ;  // 调用本类无参构造 
this.name = name ;
}
public Person(String name,int age){
this (name); // 调用单参构造
this.age =age;
}
public void tell(){
System.out.println("姓名:"+ this .name+"、年龄:"+this.a
}
// setter、getter略

这里涉及到了构造方法的首行问题,并且我是明明白白、正正经经地在讨论构造方法的首行问题。请你们用心思考,而不是随意臆想。不是让你们用脑袋空想,而是希望你们能真正去理解这个问题。就好比现在让你们去咬自己的屁股,那种不可能的感觉,这里是想强调在构造方法中不当地互相调用,可能会导致类似“咬自己屁股”这样的逻辑错误。


现在想象一下如果构造方法调用不当会出现什么问题?一定会有问题的所以就像之前提到的当编写代码并尝试编译时,如果构造方法之间存在不当的递归调用,编译器会直接给出错误提示。它会明确地告诉我们程序中出现了构造方法的递归调用问题。因此这种情况下是不能继续使用的,这就是关于构造方法调用的讨论。


关于构造方法来看一个具体的案例,一个关于构造方法互相调用的案例。假设现在需要定义一个描述员工信息的类,这个类中包含员工的编号、姓名、部门和工资等信息。在这个类中提供三到四个构造方法。


System.out.println("***个新的 Person类对象实例化了。")
}
public Person(String name){
thisO; //调用本类无参构造
this.name = name;
}
public Person(String name,int age){
this(name); //调用单参构造
this.age=age;

image.png

D:\mldnjava>java JavaDemo
***一个新的Person类对象实例化了。
姓名:王五、年龄:10
 
D:\mldnjava>javac JavaDemo.java
JavaDemo.java:15:错误:对this的调用必须是构造器中的第一个语句
this():    //调用本类无参构造 
 
1个错误
 
D:\mldnjava>javac JavaDemo.java
JevaDemo.java:12:错误:递归构造器调用
public Person (String name, int age){
1个错误 
 
D:\mldnjava>

第一个构造方法称之为参构造,四参构造是指所有的属性全部进行传递。关于无参,其中编号默认为 1000姓名默认设置为“无名氏”。接下来是一个单参构造,只传递编号。姓名定义为“新员工”,部门默认为“未定”,工资默认为 0 。此外还有一个三参构造方法,传递的是编号、姓名和部门,此时工资默认为 2500 。最后还有一个四参构造方法,需要传递所有的属性:编号、姓名、部门和工资。这就是现在所写的案例。


对于这样的一个操作案例应该如何去思考呢?首先刚开始的时候不要过多地纠结于设计,对于你们来说现在谈论设计可能还为时过早。首先对于代码应该先做什么呢?应该先进行代码的初期实现。什么叫代码的初期实现呢?如果现在要编写一个程序类应该怎么做呢?首先可以先清空之前的代码,开始新的编写。


在清空之前的代码后,在这里开始编写一个新的类,命名为 Empno 。接下来定义类的属性。首先定义一个私有属性 Empno ,类型为 Int ,用于存储员工编号。然后定义一个私有属性 Ename ,类型为 String ,用于存储员工的姓名。接着再定义一个私有属性 DEPT ,类型也为 String ,用于存储部门名称。最后定义一个私有属性 Sal ,类型为 Double ,用于存储基本工资。


image.png

dlass EMP
{
}
 
public class JavaDemo{ //主类
public static void main(String args[]){
}
}

在当前的流程中,首先会在这里给它一个提示信息,并与上一个值相关联,称之为 GetInfo 。首先返回一个数据,这个数据称之为“雇员编号”,并在其后加上 Empno 。接下来在这里再加上一个信息,称之为“雇员姓名”,并在其后加上 Ename 。继续向下找到一个叫做“所在部门”的字段,并在其后加上 DEPT 。最后再添加一个字段,叫做“基本工资”,并在其后加上 Sal 


写完后先放在这里,现在所展示的就是当前的一个基本的雇员信息集合。按照刚才所说的,记住这里面一定包含了很多的 Setter  Getter方法, Setter  Getter 方法要根据上个月的进展继续完善。


那么这些信息完成之后接下来要在这个基础上定义一些基本的结构。那么程序现在应该有几个构造方法呢?四个构造方法。第一个是无参构造方法,在无参构造方法中, Empno 等于某个默认值,比如1000  Ename 设置为一个占位符,比如“无名氏”。


接下来还需要一个单参构造方法,这个单参构造方法传入的是 Empno ,在这个地方如果没用 Int 类型的话,用 String 类型来表示编号也是可以的,比如 Long ,但通常情况下还是用 Int 类型来表示编号更为合适。

image.png

class Emp {
private long empno ; // 员工编号 
private String ename ; // 员工姓名 
private String dept ; // 部门名称 
private double salary; //基本工资 
public Emp() {
this.empno =1000;
this.ename ="无名氏”;
 }
public Emp (long empno){
}
// setter、getter略
public String getInfo() {
return "雇员编号:"+ this.empno +
"、雇员姓名:"+ this.ename +
"、所在部门:"+ this.dept +
"、基本工资:"+ this.sal;

首先使用 Long 作为示例值,然后现在来设置这些属性。 EMPNO 等于 This.empno 。接下来 Ename 应该被赋予一个新员工的默认姓名。


继续向下再添加一个属性,其值设置为表示“未定工资”的默认值0。然后考虑一个包含三个参数的构造方法,其中一个参数是 Empno ,之后找到 Long Empno 再跟上 Ename 同时还有一个字符串类型的参数 String DEPT 设置到当前对象的 DEPT 属性中。按照这样的流程明确了几个设置This.empno 等于传入的 EMPO ; This.ename 等于传入的 ename ; This.demp 等于传入的 DEPT值。


image.png

image.png

class Emp {
private long empno ; // 员工编号 
private String ename ; // 员工姓名 
private String dept ; // 部门名称 
private double salary; //基本工资 
public Emp() {
this.empno =1000;
this.ename ="无名氏”;
 }
public Emp (long empno){
}
// setter、getter略
public String getInfo() {
return "雇员编号:"+ this.empno +
"、雇员姓名:"+ this.ename +
"、所在部门:"+ this.dept +
"、基本工资:"+ this.sal;

在当前的代码中可以添加一个特定的示例值,比如“四川”。同时会使用以下参数: EMPNO  String 类型的 Ename  String 类型的 DEPT ,以及一个 DEPT 类型的属性和 Sal 。完成这些设置后程序可以包含以下四个关键点的设置: This.empno 等于传入的Empno ; This.ename 等于传入的 Ename ; This.demp 等于传入的 DEPT ; This.sal 等于传入的 Sal 值。


现在所有代码都已经编写完成,接下来使用一下这段代码。将按照以下步骤进行操作:首先创建一个 EMP 对象在创建对象时可以使用一个有参构造方法,并传入相应的参数。例如可以传入 7369L 作为雇员编号,"史密斯"作为雇员姓名,以及Accounting作为部门名称。


接下来为 EMP 对象的薪资属性设置一个值,比如 6500.00 ,表示该雇员的月工资为 6500 元。最后调用 System.out.print 方法来打印 EMP 对象的信息。为了打印出详细的信息可以假设 EMP类中有一个名为 GetInfo 的方法,该方法能够返回雇员的详细信息


完成代码编写后现在要在这块编写一个 Java 程序来执行它。不过请注意之前提到的地方有一个小错误,即分号的使用。现在将编写一个 Java 程序,并在中文环境下编译和执行它。


接下来看一下结果是否如预期。如果按照正常流程实现的话,现在应该已经实现了所需的功能。但是也需要关注潜在的问题,特别是重复代码的问题。这是在编程过程中需要时刻警惕的。


在检查代码时可能会发现一些赤裸裸的重复代码段。这些重复代码不仅会增加程序的复杂度,还会降低代码的可读性和可维护性。因此需要仔细审查代码,找出并消除这些重复部分。

image.png

}
// setter、getter略
public String getInfo() {
return "雇员编号:"+ this.empno +
"、雇员姓名:"+ this.ename +
"、所在部门:"+ this.dept +
"、基本工资:"+ this.sal;
  }
}
public class JavaDemo{//主类
public static void main(String args[]) {
Emp emp = newEmp(7369L,"史密斯","财务部",6500.00); System.out.println (emp.getInfo());}
 }
}

同学们都在进行赋值操作,这时可以考虑对代码进行简化。那么如何进行简化呢?接下来仔细探讨一下。在审视代码的过程中可能会发现数据存在重复,即代码中有冗余的部分。为了优化代码结构可以考虑对 EMP 类进行简化定义。


通过减少重复代码可以提高代码的可读性和可维护性。那该如何定义呢?来看整个程序代码,首先同学们是不是都在赋值四参并且是不是足以乘以所有的赋值?如果是这样就可以这样写。那么大家是否已经理解了类型定义?

image.png

class Emp {
private long empno ; // 员工编号 
private String ename ; // 员工姓名 
private String dept ; // 部门名称 
private double salary; //基本工资 
public Emp(){
this(1000,"无名氏",); 
this.empno =1000;
this.ename ="无名氏";
 }
public Emp(long empno){
this.empno = empno;
this.ename ="新员工"; 
this.dept ="未定";
 }
public Emp(long empno,string ename, string dept){
this.empno =empno; 
this .ename = ename ;

那么默认值是空值吗?先假定它为 0.0 ,然后再利用四层构造函数。这是括号 Empno 直接传递的参数。然后跟上的是新员工的信息,再跟上一个部门信息,并将未定义的工资设置为 0.0 放在这里。


这样又写完了一个部分,但是这里还差一个东西,就是 Salary 等于多少呢?是2500,而这个默认值不影响理解。接下来再往下添加Empno 外部传递的参数,以及 Ename  Dept 外部传递的值。


但是注意 2500 是设定的一个固定值,同时也传递了其他参数值,这样简化了很多。接下来先看看现在调用的三参数构造函数,并观察其结果如何。编译并执行后,结果显示2500从这一点可以看出我们对整个代码进行了优化。而这个优化正是通过构造方法之间的相互调用来实现的。


因此在代码中重复的代码可能出现在任何位置。所以消除重复的代码是在初期学习中最需要优先考虑的部分,其他方面的学习相对会容易一些。

相关文章
|
缓存 安全 网络协议
socket是并发安全的吗 2
socket是并发安全的吗
996 0
|
7月前
|
安全 架构师 Java
90% Java新手踩坑!彻底搞懂这4个权限修饰符
Java权限修饰符看似简单,却暗藏致命风险:`public`暴露数据、`protected`跨包失控、默认权限成地雷。本文通过3大真实案例+1张神图,深度解析`private`、`default`、`protected`、`public`的作用域与内存可见性,揭示Spring Bean、继承陷阱、包级漏洞的根源,并奉上防御性编程5大原则,助你筑牢代码第一道防线。
314 1
|
存储 缓存 JavaScript
NPM怎样清空缓存
NPM怎样清空缓存
|
XML Java 开发工具
Android Studio开发Android TV
【6月更文挑战第19天】
765 5
|
程序员 编译器 C语言
C中的 malloc 和C++中的 new 有什么区别
在C语言中,`malloc`函数用于在运行时分配内存,返回指向所分配内存的指针,需显式包含头文件 `<stdlib.h>`。而在C++中,`new`不仅分配内存,还对其进行构造初始化,且直接使用类型声明即可,无需额外包含头文件。`new`还支持数组初始化,能更好地融入C++的面向对象特性,而`malloc`仅作为内存分配工具。使用完毕后,`free`和`delete`分别用于释放`malloc`和`new`分配的内存。
540 21
|
Java Windows
IDEA不使用lombok,如何快速生成get和set方法
【11月更文挑战第10天】在 IntelliJ IDEA 中生成 `get` 和 `set` 方法有多种方式:通过菜单操作、使用快捷键或自定义模板。菜单操作包括选择“Code”菜单中的“Generate...”,快捷键为“Alt + Insert”。自定义模板可在“File”->“Settings”->“Editor”->“Code Style”->“Java”中设置。批量生成时,可多选变量一次性生成。
3990 2
|
并行计算 Serverless 应用服务中间件
函数计算操作报错合集之部署Stable Diffusion启动失败,是什么导致的
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
770 7
|
SQL 分布式计算 大数据
大数据平台的毕业设计01:Hadoop与离线分析
大数据平台的毕业设计01:Hadoop与离线分析
682 0
|
Linux
配置时间同步服务
在Redhat 9.2上,Chrony被用来同步系统时间与NTP服务器。默认情况下,它已在RHEL7/CentOS7中预装。要安装,首先通过`yum -y remove chrony`卸载,然后用`yum -y install chrony`进行安装。安装后,使用`systemctl restart chronyd`重启服务,并用`systemctl enable chronyd`设置开机启动。编辑`/etc/chrony.conf`添加阿里云NTP服务器如`ntp1.aliyun.com`和`ntp2.aliyun.com`以同步时间。使用`chronyc sources -n`确认时间源。
1590 4
|
存储 C语言 C++
全网最详细用c语言实现植物大战僵尸游戏(上)-1
全网最详细用c语言实现植物大战僵尸游戏(上)
1786 0

热门文章

最新文章

下一篇
开通oss服务