面向对象(上)

来源:互联网 发布:数据质量系统 编辑:程序博客网 时间:2024/05/17 02:41

一、类和对象

    面向对象的程序设计过程中有两个重要概念:类(class)和对象(object,也被称为实例,instance)。对一个类定义而言,可以包含三种最常见的成员:构造器、成员变量和方法。类里各成员之间的定义顺序没有任何影响,各个成员之间可以相互调用,但需要指出的是,static修饰的成员不能访问没有static修饰的成员

    static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,通常把static修饰的成员变量和方法称为类变量、类方法;不使用static修饰的普通方法、成员变量则属于该类的单个实例而不属于该类,通常称之为实例变量、实例方法。

对象的this引用:

    this作为对象的默认引用有两种情况:构造器中引用该构造器正在初始化的对象;在方法中引用调用该方法的对象。

    this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的:它只能代表当前类的实例;只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this就代表谁。

    Java允许对象的一个成员直接调用另一个成员,可以省略this前缀。

    对于static修饰的方法而言,可以使用类来直接调用该方法。如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象,因此static修饰的方法中不能使用this引用

    如果调用static修饰的成员时省略了前面的主调,那么默认使用该类作为主调;如果调用没有static修饰的成员时省略了前面的主调,那么默认使用this作为主调。

    Java编程时不要使用对象去调用static修饰的成员变量、方法,而是应该使用类去调用static修饰的成员变量、方法

    大部分时候,普通方法访问其他方法、成员变量时无须使用this前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。

二、方法详解

    使用static修饰的方法既可以使用类作为调用者来调用,也可以使用对象作为调用者来调用。但使用static修饰的方法还是属于这个类的,因此使用该类的任何对象来调用这个方法时将会得到相同的执行结果。

(1)方法的参数传递机制

    Java中方法的参数传递机制只有一种:值传递。所谓的值传递,就是将实际参数值的副本传入方法内,而参数本身不会受到任何影响。值传递的实质:当系统开始执行方法时,,系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,方法里操作的并不是实际的实参变量。

    注意区分基本类型的参数传递和引用类型的参数传递的最终结果

(2)形参个数可变的方法

    Java允许定义形参个数可变的参数,从而为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入

    例子:public static void test(int a, String ... animals);  该语句与public static void test(int a, String[] animals);   语句效果完全一样。

    对于可变形参的形式定义的方法,调用方法时更加简洁:test(5, "dear" , "wolf" ); 传给animals参数的实参数值无需是一个数组,但如果采用数组形参来声明方法,调用时则必须传给该形参一个数组:test(5, new String[] {"dear" , "wolf"} );  

    个数可变的形参只能处于形参列表的最后,一个方法中最多只能由一个个数可变的形参。

(3)方法重载

    Java中允许一个类中定义多个同名的方法,只要形参列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载

    Java里不能使用方法返回值类型作为区分方法重载的依据

三、成员变量和局部变量

    如果通过一个实例修改了类变量的值,由于这个类变量不属于它,而是属于它对应的类,因此修改的依然是类的类变量,与通过该类来修改类变量的结果完全相同。

    局部变量根据定义形式的不同,可以分为如下三种:形参;方法局部变量;代码块局部变量。与成员变量不同的是,局部变量处理形参之外,都必须显示初始化

     在同一个类中,成员变量的作用范围是整个类内有效,一个类里不能定义两个同名的成员变量,即使一个是类变量,一个是实例变量也不行。Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,可使用this或类名作为调用者来限定访问成员变量。

(1)成员变量的初始化和内存中的运行机制

    当系统加载类或创建该类的实例时,系统自动为成员变量分布内存空间,并在分配内存空间后,自动为成员变量指定初始值。在类的准备阶段,系统将会为该类的类变量分配内存空间,并指定默认初始值

(2)局部变量的初始化和内存中的运行机制

    局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化,这意味着定义局部变量后西,系统未为这个变量分配内存空间,直到等到程序为这个变量赋初值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。

    与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中。栈内存中的变量无需系统垃圾回收,往往随方法或代码块的运行结束而结束

四、隐藏和封装

(1)使用访问控制符

     private:当前类访问权限,这个成员只能在当前类的内部被访问;

    default:包访问权限,这个成员或外部类可以被相同包下的其他类访问;

    protected:子类访问权限,这个成员既可以被相同包下的其他类访问,也可以被不同包中的子类访问;

    public:公共访问权限,这个成员或外部类可以被所有类访问。

    对于外部类而言,它也可以使用访问控制符修饰,但外部类只能有两种访问控制级别:public和默认

    如果一个Java源文件中定义了一个public修饰的类,则这个源文件的文件名必须与public修饰的类的类名相同

    如果一个Java类的每个实例变量都被private修饰,并为每个实例变量都提供了public修饰的setter和getter方法,那么这个类就是一个符合JavaBean规范的封装良好的类

(2)package、import、import static

    Java允许将一组功能相关的类放在同一个package下,从而组成逻辑上的类库单元。如果希望把一个类放在指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:package packageName;

    Java规定:位于包中的类,在文件系统中也必须有与包名层次相同的目录结构;

    在编译Java文件是应该总使用-d选项,在执行编译的.class文件时,应使用语句:java 包名.编译文件名;

    同一个包中的类不必位于相同目录下,只要让CLASSPATH环境变量里包含该类的路径即可,虚拟机会自动搜索CLASSPATH下子路径,把它们当成同一个包下的类来处理。

    同一个下的类可以自由访问,无需添加包前缀。但父包和子包在用法上不存在任何关系,如果父包中的类需要使用子包中的类,则必须使用子包的全名,不能省略父包部分。

    为了简化编程(不输入包的全名),引入import关键字,一个Java源文件可以包含多个import语句,多个import语句用于导入多个包层次下的类。

    使用import语句导入单个类的用法:import package.subpackage ... ClassName;

    使用import语句导入指定包下所有类的用法:import package.subpackage ... *;   但注意该语句只导入类,并不导入子包。

    静态导入使用import static语句,用于导入指定类的某个静态成员变量、方法或全部的静态成员变量、方法。

五、深入构造器

    构造器是创建Java对象的途径,但不能说它完全复制创建Java对象:当系统开始执行构造器的执行提之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回。

    存在这样一种情况:构造器B完全包含了构造器A。对于这种情况,如果是两个方法之间存在这种关系,则可在方法B中调用方法A(其实是使用this引用进行调用,而this 前缀可以省略)(this作为对象的默认引用有两种情况:构造器中引用该构造器正在初始化的对象;在方法中引用调用该方法的对象)。使用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句。

六、类的继承

    Java的继承具有单继承的特点,每个子类只有一个直接父类。Java的继承通过extends关键字来实现。

(1)重写父类的方法

    子类包含于父类同名方法的现象称之为方法重写,也被称为方法覆盖,要求:方法名相同,形参列表相同;子类方法返回值类型比父类方法返回值类型更小或相等,子类方法声明抛出的异常类比父类方法声明抛出的异常类更小或相等;子类方法的访问权限应比父类方法的访问权限更大或相等。

    覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法

    如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。

    如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。如果子类中定义了一个与父类private方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,只是在子类中重新定义了一个新方法。

    当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。

(2)调用父类构造器

    子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码,类似于前面所介绍的一个构造器调用另一个重载的构造器。在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成。

    使用super调用父类构造器也必须出现在子类构造器执行体的第一行,因此this调用和super调用不会同时出现。

    当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;不仅如此,执行父类构造器时,系统会再次上溯执行器父类构造器。因此,在创建任何Java对象时,最先执行总是java.lang.Object类的构造器

    子类构造器执行体既没有super调用,也没有this调用,系统会在执行子类构造器之前,隐式调用父类无参数的构造器

七、多态

     Java 引用变量有两个类型:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定。如果编译时类型和支行时的类型不一致,这就有可能出现所谓的多态。

    Java允许把一个子类对象直接赋给一个父类引用变量,无需任何类型转换,或者被称为向上转型,由系统自动完成。

    把一个子类对象直接赋给父类引用变量时,该引用变量编译时的类型是父类,而运行时的类型是子类。

    引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如:Object p = new Person() ; 代码定义了一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。

    通过引用变量来访问其所包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量

(1)强制类型转换:

    数值类型和布尔类型之间不能进行类型转换。

    引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型则无法进行类型转换。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型为子类类型)

    考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。

(2)instanceof运算符

    instanceof运算符的作用是:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健壮。

    在使用instanceof运算符时注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。

八、继承与组合

    如果父类中的方法需要被外部类调用,则必须以public 修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法;如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则使用protected来修饰该方法。

    尽量不要在父类构造器中调用将要被子类重写的方法,如果父类构造器调用了被其子类重写的方法,则变成调用被子类重写的方法

    对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承到的方法;而组合是把旧类对象作为新类的成员变量组合进来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此,通常需要在新类里使用private修饰被组合的旧类对象。

    继承要表达的是is-a关系,组合表达的是has-a关系

九、初始化块

    一个类里可以由多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先还行,后面定义的初始化块后执行。初始化块的修饰符只能是static,会用static修饰的初始化块被称为是静态初始化块。

     当创建Java对象时,系统总是先调用该类里定义的初始化块。初始化块虽然也是Java类的一种成员,但它没有名字,也没有标识,因此无法通过类、对象来调用初始化块。初始化块只在创建Java对象时隐式执行,且在执行构造器之前执行。    

     当Java创建一个对象时,系统先为该对象的所有实例变量分配内存,接着程序开始对这些实例变量执行初始化,其初始化顺序是:先执行初始化块或声明实例变量时指定的初始值,在执行构造器里指定的初始值。

    如果两个构造器中有相同的初始化代码,且这些初始化代码无需接受参数,就可以把它们放在初始化块中定义。实际上初始化块是一个假象,使用javac命令编译Java类后,该Java类中的初始化块会消失,初始化块中代码会被“还原”到每个构造器中,且位于构造器所有代码的前面。

    静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。

    初始化顺序:类初始化阶段,先执行最顶层父类的静态初始化块,然后依次向下,直到执行当前类的静态初始化块。对象初始化阶段,先执行最顶层父类的初始化块,最顶层父类的构造器,然后依次向下,直到执行当前类的初始化块、当前类的构造器。

    静态初始化块和声明静态成员变量时所指定的初始值都是该类的初始化代码,它们的执行顺序与源程序中的排列顺序相同

0 0