JAVA学习笔记之父类与子类.doc_第1页
JAVA学习笔记之父类与子类.doc_第2页
JAVA学习笔记之父类与子类.doc_第3页
JAVA学习笔记之父类与子类.doc_第4页
JAVA学习笔记之父类与子类.doc_第5页
已阅读5页,还剩26页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

this的用法 构造器构造器是为了创建一个类的实例。这个过程也可以在创建一个对象的时候用到:Platypus p1 = new Platypus(); 构造器可以用来在初始化对象时初始化数据成员,一个类可以有多个构造器。一个类的构造器的名称必须与该类的名称一致。要退出构造,可以使用返回语句“return;”。 相反,方法的作用是为了执行java代码。 修饰符,返回值和命名的不同 构造器和方法在下面三个方面的区别:修饰符,返回值,命名。和方法一样,构造器可以有任何访问的修饰: public, protected, private或者没有修饰(通常被package 和 friendly调用). 不同于方法的是,构造器不能有以下非访问性质的修饰: abstract, final, native, static, 或者 synchronized。 返回类型也是非常重要的。方法能返回任何类型的值或者无返回值(void),构造器没有返回值,也不需要void。 最后,谈谈两者的命名。构造器使用和类相同的名字,而方法则不同。按照习惯,方法通常用小写字母开始,而构造器通常用大写字母开始。构造器通常是一个名词,因为它和类名相同;而方法通常更接近动词,因为它说明一个操作。和方法使用关键字this有很大的区别。方法引用this指向正在执行方法的类的实例。静态方法不能使用this关键字,因为静态方法不属于类的实例,所以this也就没有什么东西去指向。构造器的this指向同一个类中,不同参数列表的另外一个构造器,我们看看下面的代码: public class Platypus String name; Platypus(String input) name = input; Platypus() this(John/Mary Doe); public static void main(String args) Platypus p1 = new Platypus(digger); Platypus p2 = new Platypus(); 在上面的代码中,有2个不同参数列表的构造器。第一个构造器,给类的成员name赋值,第二个构造器,调用第一个构造器,给成员变量name一个初始值 John/Mary Doe. 在构造器中,如果要使用关键字this,那么,必须放在第一行,如果不这样,将导致一个编译错误。 super的用法 构造器和方法,都用关键字super指向超类,但是用的方法不一样。方法用这个关键字去执行被重载的超类中的方法。看下面的例子: class Mammal void getBirthInfo() System.out.println(born alive.); class Platypus extends Mammal void getBirthInfo() System.out.println(hatch from eggs); System.out.print(a mammal normally is ); super.getBirthInfo(); 在上面的例子中,使用super.getBirthInfo()去调用超类Mammal中被重载的方法。 构造器使用super去调用超类中的构造器。而且这行代码必须放在第一行,否则编译将出错。看下面的例子: public class SuperClassDemo SuperClassDemo() class Child extends SuperClassDemo Child() super(); 在上面这个没有什么实际意义的例子中,构造器 Child()包含了 super,它的作用就是将超类中的构造器SuperClassDemo实例化,并加到 Child类中。 编译器自动加入代码 编译器自动加入代码到构造器,对于这个,java程序员新手可能比较混淆。当我们写一个没有构造器的类,编译的时候,编译器会自动加上一个不带参数的构造器,例如:public class Example 编译后将如下代码: public class Example Example() 在构造器的第一行,没有使用super,那么编译器也会自动加上,例如: public class TestConstructors TestConstructors() 编译器会加上代码,如下: public class TestConstructors TestConstructors() super; 仔细想一下,就知道下面的代码 public class Example 经过会被编译器加代码形如: public class Example Example() super; 继承 构造器是不能被继承的。子类可以继承超类的任何方法。看看下面的代码: public class Example public void sayHi system.out.println(Hi); Example() public class SubClass extends Example this 指向同一个类中另外一个构造器,在第一行 指向当前类的一个实例,不能用于静态方法 super 调用父类的构造器,在第一行 调用父类中一个重载的方法 继承 构造器不能被继承 方法可以被继承 编译器自动加入一个缺省的构造器 自动加入(如果没有) 不支持 编译器自动加入一个缺省的调用到超类的构造器 自动加入(如果没有) +JAVA技术专题综述之构造方法篇+类的继承机制使得子类可以使用父类的功能(即代码),并且子类也具有父类的类型。下面介绍类在继承关系上的初始化的顺序问题。 示例1: class SuperClass SuperClass() System.out.println(SuperClass constructor); public class SubClass extends SuperClass SubClass() System.out.println(SubClass constructor); public static void main(String args) SubClass sub = new SubClass(); 输出结果: SuperClass constructor SubClass constructor 在子类中只实例化了一个子类对象。从输出结果上看,程序并不是一开始就运行自己的构造方法,而是先运行其父类的默认构造方法。注意:程序自动调用其父类的默认构造方法。 示例2: class SuperClass SuperClass(String str) System.out.println(Super with a string.); public class SubClass extends SuperClass SubClass(String str) System.out.println(Sub with a string.); public static void main(String args) SubClass sub = new SubClass(sub); 在JDK下编译此程序不能成功。正如上例中说的:程序在初始化子类时先要寻找其父类的默认构造方法,结果没找到,那么编译自然不能通过。 解决这个问题有两个办法: 1在父类中增加一个默认构造方法。 2在子类的构造方法中增加一条语句:super(str); 且必须在第一句。 这两种方法都能使此程序通过编译,但就本程序来说运行结果却不相同。 第1种方法的运行结果是:Sub with a string. 第2种方法的运行结果是: Super with a string. Sub with a string. 第2种解决方法实际上是指定编译器不要寻找父类的默认构造方法,而是去寻找带一个字符串为参数的构造方法。 下面介绍对象的初始化顺序问题。 示例3: class One One(String str) System.out.println(str); class Two One one_1 = new One(one-1); One one_2 = new One(one-2); One one_3 = new One(one-3); Two(String str) System.out.println(str); public class Test public static void main(String args) System.out.println(Test main() start.); Two two = new Two(two); 输出结果: Test main() start. one-1 one-2 one-3 two 在main()方法中实例化了一个Two类的对象。但程序在初始化Two类的对象时,并非先调用Two类的构造方法,而是先初始化Two类的成员变量。这里Two类有3个成员变量,它们都是One类的对象,所以要先调用3次One类的相应的构造方法。最后在初始化Two 类的对象。 示例4: class One One(String str) System.out.println(str); class Two One one_1 = new One(one-1); One one_2 = new One(one-2); static One one_3 = new One(one-3); Two(String str) System.out.println(str); public class Test public static void main(String args) System.out.println(Test main() start.); Two two_1 = new Two(two-1); System.out.println(-); Two two_2 = new Two(two-2); 输出结果: Test main() start. one-3 one-1 one-2 two-1 - one-1 one-2 two-2 如果一个类中有静态对象,那么它会在非静态对象前初始化,但只初始化一次。非静态对象每次调用时都要初始化。 示例5: class One One(String str) System.out.println(str); class Two One one_1 = new One(one-1); One one_2 = new One(one-2); static One one_3 = new One(one-3); Two(String str) System.out.println(str); 3 public class Test static Two two_3 = new Two(two-3); public static void main(String args) System.out.println(Test main() start.); Two two_1 = new Two(two-1); System.out.println(-); Two two_2 = new Two(two-2); 输出结果: one-3 one-1 one-2 two-3 Test main() start. one-1 one-2 two-1 - one-1 one-2 two-2 程序中主类的静态变量会在main()方法执行前初始化。结果中只输出了一次one-3,这也说明:如果一个类中有静态对象,那么它会在非静态对象前初始化,但只初始化一次。非静态对象每次调用时都要初始化。 示例6: class One One(String str) System.out.println(str); class Two static int i = 0; One one_1 = new One(one-1); static One one_2 = new One(one-2); static One one_3 = new One(one-3); Two(String str) System.out.println(str); public class Test public static void main(String args) System.out.println(Test main() start.); System.out.println(Two.i = + Two.i); 4 输出结果: Test main() start. one-2 one-3 Two.i = 0 不仅第1次创建对象时,类中所有的静态变量要初始化,第1次访问类中的静态变量(没有创建对象)时,该类中所有的静态变量也要按照它们在类中排列的顺序初始化。 综上所述:在创建对象时,对象所在类的所有数据成员会首先进行初始化,如果其中的成员变量有对象,那么它们也会按照顺序执行初始化工作。在所有类成员初始化完成后,才调用对象所在类的构造方法创建对象。构造方法作用就是初始化。 静态对象(变量)在非静态对象前初始化。静态对象(变量)只初始化一次,再次调用就不初始化了,但非静态对象在每次调用时都要初始化。 程序中的主类的静态变量会在main()方法执行前进行初始化工作。 不仅第1次创建对象时,类中所有的静态变量要初始化,第1次访问类中的静态变量(没有创建对象)时,该类中所有的静态变量也要按照它们在类中排列的顺序初始化。 初始化的顺序包括构造方法调用的顺序如下: 1主类的静态成员首先初始化。 2主类的超类的构造方法按照从最高到最低的顺序被调用。 3主类的非静态对象(变量)初始化。 4调用主类的构造方法。 在一个构造方法中只能调用一次其它的构造方法,并且调用构造方法的语句必须是第一条语句。+深入剖析java类的构造方式+概要:本文通过查看一个精心构造的类结构的运行输出和使用javap工具查看实际生成的java字节码(bytecode)向java程序员展示了一个类在运行时是如何构造生成的。关键字: java 构造 javap 字节码 bytecode 按照java规范,一个类实例的构造过程是遵循以下顺序的:1.如果构造方法(constructor,也有翻译为构造器和构造函数的)是有参数的则进行参数绑定。2.内存分配将非静态成员赋予初始值(原始类型的成员的值为规定值,例如int型为0,float型为0.0f,boolean型为false;对象类型的初始值为null),静态成员是属于类对象而非类实例,所以类实例的生成不进行静态成员的构造或者初始化,后面将讲述静态成员的生成时间。3.如果构造方法中存在this()调用(可以是其它带参数的this()调用)则执行之,执行完毕后进入第6步继续执行,如果没有this调用则进行下一步。4.执行显式的super()调用(可以是其它带参数的super()调用)或者隐式的super()调用(缺省构造方法),此步骤又进入一个父类的构造过程并一直上推至Object对象的构造。5.执行类申明中的成员赋值和初始化块。6.执行构造方法中的其它语句。现在来看看精心构造的一个实例: class Parentint pm1;int pm2=10;int pm3=pmethod();System.out.println(Parents instance initialize block); public static int spm1=10;staticSystem.out.println(Parents static initialize block);Parent()System.out.println(Parents default constructor);static void staticmethod()System.out.println(Parents staticmethod); int pmethod()System.out.println(Parents method);return 3;class Child extends Parentint cm1;int cm2=10;int cm3=cmethod();Other co;public static int scm1=10;System.out.println(Childs instance initialize block); staticSystem.out.println(Childs static initialize block);Child()co=new Other();System.out.println(Childs default constructor);Child(int m)this();cm1=m;System.out.println(Childs self-define constructor);static void staticmethod()System.out.println(Childs staticmethod);int cmethod()System.out.println(Childs method);return 3;class Otherint om1;Other() System.out.println(Others default constructor); public class InitializationTestpublic static void main(String args)Child c;System.out.println(program start);System.out.println(Child.scm1);c= new Child(10);System.out.println(program end); 进入此文件所在的目录,然后编译此文件:javac InitializationTest.java运行此程序:java ?classpath . InitializationTest得到的结果是: program startParents static initialize blockChilds static initialize block10Parents methodParents instance initialize blockParents default constructorChilds methodChilds instance initialize blockOthers default constructorChilds default constructorChilds self-define constructorprogram end 如果没有看过上面的关于类的构造的说明,很容易让人误解为类的构造顺序是如下的结果(忽略参数绑定、内存分配和非静态成员的缺省值赋值):1.完成父类的非静态成员初始化赋值以及执行初始化块(这个的先后顺序取决于源文件中的书写顺序,可以将初始化块置于成员声明前,那么先执行的将是初始化块,将上面的代码稍稍变动一下就可以验证这一点。)2.调用父类的构造方法完成父类构造。3.完成非静态成员的初始化赋值以及执行初始化块。4.调用构造方法完成对象的构造,执行构造方法体中的其它内容。如果根据以上java规范中给出的顺序也可以合理的解释程序的输出结果,那么如何亲眼看到是规范中的顺序而不是以上根据程序的输出推断的顺序呢?下面就使用JDK自带的javap工具看看实际的顺序,这个工具是一个根据编译后的字节码生成一份字节码的助记符格式的文档的工具,就像根据机器码生成汇编代码那样。反编译:javap -c -classpath . Child输出的结果是(已经经过标记,交替使用黑体和斜体表示要讲解的每一块): Compiled from InitializationTest.javaclass Child extends Parent int cm1;int cm2;int cm3;Other co;public static int scm1;static ;Child();Child(int);int cmethod();static void staticmethod();Method static 0 bipush 102 putstatic #22 5 getstatic #20 8 ldc #5 10 invokevirtual #21 13 returnMethod Child()0 aload_01 invokespecial #14 4 aload_05 bipush 107 putfield #16 10 aload_011 aload_012 invokevirtual #18 15 putfield #17 18 getstatic #20 21 ldc #2 23 invokevirtual #21 26 aload_027 new #8 30 dup31 invokespecial #13 34 putfield #19 37 getstatic #20 40 ldc #1 42 invokevirtual #21 45 returnMethod Child(int)0 aload_01 invokespecial #12 4 aload_05 iload_1 6 putfield #15 9 getstatic #20 12 ldc #4 14 invokevirtual #21 17 returnMethod int cmethod()0 getstatic #20 3 ldc #3 5 invokevirtual #21 8 iconst_39 ireturnMethod void staticmethod()0 getstatic #20 3 ldc #6 5 invokevirtual #21 8 return 请仔细浏览一下这个输出并和源代码比较一下。下面解释如何根据这个输出得到类实例的实际的构造顺序,在开始说明前先解释一下输出的语句的格式,语句中最前面的一个数字是指令的偏移值,这个我们在此可以不管,第二项是指令助记符,可以从字面上大致看出指令的意思。例如 getstatic 指令将一个静态成员压入一个称为操作数堆栈(后续的指令就可以引用这个数据结构中的成员)的数据结构,而 invokevirtual 指令是调用java虚拟机方法,第三项是操作数(#号后面跟一个数字,实际上是类的成员的标记),有些指令没有这一项,因为有些指令如同汇编指令中的某些指令一样是不需要操作数的(可能是操作数是隐含的或者根本就不需要),这是java中的一个特色。如果你直接检查字节码,你会看到成员信息没有直接嵌入指令而是像所有由java类使用的常量那样存储在一个共享池中,将成员信息存储在一个常量池中可以减小字节码指令的大小,因为指令只需要存储常量池中的一个索引而不是整个常量。需要说明的是常量池中的项目的顺序是和编译器相关的,因此在你的环境中看到的可能和我上面给出的输出不完全一样,第四项是对前面的操作数的说明,实际的字节码中也是没有的,根据这个你能很清楚的得到实际上使用的是哪个成员或者调用的是哪个方法,这也是javap为我们提供的便利。说完上面这些你现在应该很容易看懂上面的结果和下面将要叙述的内容了。其它更进一步的有关java字节码的信息请自己查找资料。先看看最开始的部分,很像一个标准的c+类的声明,确实如此。成员声明的后面没有了成员初始化赋值语句和初始化块,那么这些语句何时执行的呢?先不要急,继续往下看。第二块,是一个Method static ,对比看看第一部分,它被处理为一个静态的方法(从前面的Method可以看出),这就是源代码中的静态初始化块,从后面的语句可以看出它执行的就是 System.out.println(Childs static initialize block)语句,由于这个方法是没有方法名的,所以它不能被显式的调用,它在何处调用后面会有叙述。第三块,缺省构造方法的实现,这是本文的重点,下面详细讲解。由于源代码中的缺省构造方法没有显式调用this 方法,因此没有this调用(对比看看下一块的有参的构造方法的前两句),同时也没有显式的super调用,那么隐式调用父类的缺省构造方法,也就是前两条语句(主要是语句invokespecial #14 ),它调用父类的构造方法,和这个类的构造相似(你可以使用javap ?c ?classpath . Parent反编译父类的字节码看看这个类的构造过程);紧接着的是执行源代码中的第一条初始化赋值语句cm2=10(即接下来的三条语句,主要是 bipush 10和putfield #15 ,此处回答了第一块中的疑问,即初始化赋值语句到哪儿去了。);接下来是执行cm3=cmethod()(接下来的四条语句);然后是执行初始化块中的内容System.out.println(Childs instance initialize block)(接下来的三条语句);java规范内部约定的内容至此执行完毕,开始执行构造方法的方法体中的内容,即co=new Other()(接下来的五条语句)和System.out.println(Childs default constructor)(接下来的三条语句),最后方法执行完毕返回(最后一条语句return)。剩下的几块相信应该不用解释了吧,有参构造方法调用无参构造方法然后执行自己的方法体,成员方法cmethod执行一条打印语句然后返回一个常量3,静态方法staticmethod执行一条打印语句。另外需要说明一下的是你可以将有参构造方法中的this调用去掉,然后看看反编译的结果,你会发现两个构造方法非常的类似,如果你将两个构造方法的内容改为一样的,那么反编译后的生成也将是同样的。从这个可以说明本文开始的构造顺序的说明中构造方法中this调用的判断是在编译阶段就完成的,而不是在运行阶段(说明中的意思好像是这个判断是在运行时进行的)。对构造过程的另一个细节你可能还不相信,就是顺序中的第二条关于非静态成员的赋予缺省初始值(内存分配部分无法考证,这是java虚拟机自动完成的),这个你可以通过在子类Child的cmethod方法的最开始用 System.out.println(cm3)打印cm3的值(输出为0,其它类型成员的值可以通过类似的方法得到)。下面来讲解另一个还没有解决的问题:静态成员初始化和静态初始化块的执行是在何时完成的?这个可以通过一个小小的试验推断得到:是在第一次使用该类对象时进行的(注意是类对象而不是类实例,对于类的公有静态成员可以直接通过类名进行访问,并不需要生成一个类实例,这就是一次类对象的使用而非类实例的使用,如果在生成第一个类实例前没有使用过该类对象,那么在构造第一个类实例前先完成类对象的构造(即完成静态成员初始化以及执行静态初始化块),然后再执行以上类实例的构造过程),试验的步骤如下:1.修改main方法,将其中的System.out.println(Child.scm1)和c= new Child(10)都注释掉(不要删除,后面还需要用到这两个语句),编译运行程序,输出将只有program start和program end,这说明没有使用类对象也没有生成类实例时不进行静态成员的构造。2.将System.out.println(Child.scm1)的注释取消,编译运行后输出多了父类和子类的静态初始化块部分的执行输出(使用子类的类对象将导致生成父类的类对象,父类先于子类构造)。3.将System.out.println(Child.scm1)注释掉并取消c= new Child(10)的注释,编译运行后输出只比最开始没有注释任何语句时少了一条(输出Child.scm1的值10)从以上的试验中我们可以得到前面的结论。本文至此可以说结束了,由于本人的java功底并不很扎实,java规范看得也不完整,因此文中可能有错误,如果您觉得某些地方有错误的话,欢迎通过mail联系。+Java构造函数运行解析+源代码如下:import java.util.*;class super1 System.out.println(super1 ok); super1() System.out.println(3); class Employee extends super1 private String name; private double salary=1500.00; private Date birthday; public Employee(String n,Date DoB) System.out.println(2); name=n; birthday=DoB; public Employee(String n) this(n,null); System.out.println(4); class Manager extends Employee System.out.println(Manager ok); private String department; public Manager(String n,String d) super(n); department=d; public class Test1 public static void main(String args) new Manager(Smith,sales); 程序运行结果:super1 ok324Manager ok生成成功(总时间:1 秒)new Manager(Smith,sales)调用过程:(1)绑定构造函数参数。其实就是传递参数的过程(2)查看是否有this()语句。没有。虽然没有使用this()语句调用构造函数,但是该步骤不能省略(3)调用super()语句,此时,程序跳转到Public Employee(String n)。(4)绑定构造函数参数String n(5)查看是否有this()。有,则执行构造函数public Employee(String n,Date DoB)(6)绑定构造函数参数String n,Date DoB(7)查看是否有this()语句。没有(8)执行有系统自动插入的super()语句:执行super1()(9)执行显式初始化语句System.out.println(super1 ok);(10)执行构造函数语句System.out.println(3);(11)执行显式初始化语句private double salary=1500.00;(12)执行构造函数语句System.out.println(2);同时执行name=n;birthday=DoB;(13)执行构造函数语句System.out.println(4);(14)执行显式初始化语句System.out.println(Manager ok);(15)执行构造函数语句department=d;几点总结:(1)对象是由new运算符创建的,且在任何构造函数执行之前就已经创建完毕了(2)构造函数的执行总是“向上”的:而且总是先执行完父类的构造函数(3)在构造函数中,没有this()语句则由super()语句。没有this()时,或者自己编写super(),或者由系统自动调 用 super() (4)显式初始化语句总是先于构造函数语句,但后于super()或this()+java 构造函数的执行过程+类初始化时构造函数调用顺序: (1)初始化对象的存储空间为零或null值; (2)调用父类构造函数; (3)按顺序分别调用类成员变量和实例成员变量的初始化表达式; (4)调用本身构造函数。例子:public class Dollar extends Money Rmb r=new Rmb(); public Dollar() System.out.println(Dollar is construct!); public static void main(String args) new Dollar(); class Moneypublic Money() System.out.println(Money is construct);class Rmbpublic Rmb() System.out.println(RMB is construct);输出结果:Money is constructRMB is constructDollar is construct!+深度理解JAVA本身的构造器及子父类构造方法的初始化顺序+我们说构造器是一种方法,就象讲澳大利亚的鸭嘴兽是一种哺育动物。(按:老外喜欢打比喻,我也就照着翻译)。要理解鸭嘴兽,那么先必须理解它和其他哺育动物的区别。同样地,要理解构造器,那么就要了解构造器和方法的区别。所有学习java的人,尤其是对那些要认证考试的,理解构造器是非常重要的。下面将简单介绍一下 ,最后用一个表作了些简单的总结。 功能和作用的不同 构造器是为了创建一个类的实例。这个过程也可以在创建一个对象的时候用到:Platypus p1 = new Platypus(); 相反,方法的作用是为了执行java代码。 修饰符,返回值和命名的不同 构造器和方法在下面三个方便的区别:修饰符,返回值,命名。和方法一样,构造器可以有任何访问的修饰: public, protected, private或者没有修饰(通常被package 和 friendly调用). 不同于方法的是,构造器不能有以下非访问性质的修饰: abstract, final, native, static, 或者 synchronized。 返回类型也是非常重要的。方法能返回任何类型的值或者无返回值(void),构造器没有返回值,也不需要void。 最后,谈谈两者的命名。构造器使用和类相同的名字,而方法则不同。按照习惯,方法通常用小写字母开始,而构造器通常用大写字母开始。构造器通常是一个名词,因为它和类名相同;而方法通常

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论