课时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 。
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 ;。这样代码就编写完成了。
代码已经编写完成,同学们请看这里。你们会发现在每个构造方法中都重复了相同的输出语句。这是正常的做法,因为要确保无论调用哪个构造方法,都能输出提示信息。
接下来进行编译和执行。当调用构造方法时,提示信息被成功输出了。如果现在调用的是单参数构造方法,再次编译和执行,同样会输出提示信息。无参数构造方法也是如此。
D:\mldnjava>
当前的需求是否得到了满足?答案是肯定的,同学们一定觉得已经满足了需求。确实从功能上来看这样做无可厚非,已经满足了需求。但是要深入思考一下,如果把这句重复的输出语句扩展成十行代码的重复,那么这个程序中就会存在什么问题呢?代码重复。那么如何去评价一个代码的好坏呢?其实只有一个标准,那就是无重复且高可用。
如果要评价一个代码的好坏,可以从以下几个方面来考虑。首先代码结构应该具备可重用性。这意味着代码应该像组件一样,可以独立地使用和替换。就像电脑中的 CPU 一样,如果某个 CPU性能不足,那么就可以更换一个更强大的 CPU ,从而提升整体性能。同样的道理代码中的组件也应该具备这样的可替换性和可升级性。
第二个重要的目标是消除重复代码。现在来观察这个程序会发现存在重复的代码,那么面对重复代码应该怎么办呢?此时可以利用构造方法之间的调用来进行优化。具体来说可以在一个构造方法中调用另一个构造方法,以避免重复的代码。
即对它进行优化,那么该如何优化呢?回到这里来看一下,这个地方是不是代表无参?那么调用的是哪个方法来表示去了无参呢?那么在调用本类构造的方法时,无参的构造方法应该放在这里,那么这里应该调用无参方法吗?在这里发现可能存在重复,这两个部分是不是重复了?那么调用单参数构造方法是否可以呢?是可以的,同学们可以使用单层构造方法。
现在来看看能否实现预期的效果。先来编译并执行一下单参数构造方法,看看结果是否一致。那么如果在这里调用一个多参数构造方法,再次编译并执行,是否能成功呢?结果也成功了。
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() ; } }
根据刚才所讨论的大家认为这段代码是否可以被重用呢?因此当程序具备这样的设计时才会认为它的结构是合理的。不过从代码长度上看,这个程序是不是显得有些简短?
但如果将那一行代码想象成十行代码,那么这个过程就减少了不少代码的使用。所以这就是构造方法之间的互相调用。不过在这里,关于本类构造方法之间的互相调用,同学们需要注意以下几个重要问题。
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() 语句应该位于首行。如果将它换个位置,虽然它仍然是构造方法的一部分,但不再是首行了。
此时如果再次编译代码,编译器会给出什么提示呢?它会提示“这次的调用必须是构造器中的第一个语句”,这是明确的语法要求,相信大家都能够理解。
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>_
第二点来看这里。现在给大家展示的是什么内容呢?这是关于普通方法与构造方法的区别。那么当再次编译代码时,编译器会告诉我们这是不允许的。这样的用法在构造方法中是不适用的。
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(); } }
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() 这样的调用必须并且只能放在构造方法的首行,这是毫无疑问的。
第二点是最重要的一个操作要点:在构造方法互相调用时,请确保程序有出口。也就是说要避免形成死循环。那么这是什么意思呢?来看一个例子。在这个过程中这里有一个双参数构造方法调用单参数构造方法的情况,单参数构造方法又做了某些操作。
但是如果不小心在单参数构造方法中又调用了双参数构造方法,而且调用条件没有适当限制,那么就可能形成一个无限循环。
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;
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 ,用于存储基本工资。
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 类型来表示编号更为合适。
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值。
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 程序,并在中文环境下编译和执行它。
接下来看一下结果是否如预期。如果按照正常流程实现的话,现在应该已经实现了所需的功能。但是也需要关注潜在的问题,特别是重复代码的问题。这是在编程过程中需要时刻警惕的。
在检查代码时可能会发现一些赤裸裸的重复代码段。这些重复代码不仅会增加程序的复杂度,还会降低代码的可读性和可维护性。因此需要仔细审查代码,找出并消除这些重复部分。
} // 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 类进行简化定义。
通过减少重复代码可以提高代码的可读性和可维护性。那么该如何定义呢?来看整个程序代码,首先同学们是不是都在赋值四参?并且是不是足以乘以所有的赋值?如果是这样就可以这样写。那么大家是否已经理解了类型定义?
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,从这一点可以看出我们对整个代码进行了优化。而这个优化正是通过构造方法之间的相互调用来实现的。
因此在代码中重复的代码可能出现在任何位置。所以消除重复的代码是在初期学习中最需要优先考虑的部分,其他方面的学习相对会容易一些。