5.1 类和对象
5.1.1 定义类
- Java语言定义类的方法如下:
(1)在上面的语法格式中,修饰符可以是public、final、abstract,或者完全省略这三个修饰符。
(2)类名:每个单词首字母大写,其他字母小写。 - 常见的三种成员:构造器、成员变量、方法。
(1)各成员可以相互调用,但需要指出的是,static修饰的成员变量不能访问没有static修饰的成员。
(2)成员变量用于定义该类或该类的实例所包含的状态数据
(3)方法用于定义该类或该类的实例的行为特征和功能实现。
(4)构造器用于构造该类的实例;Java通过new关键字来调用构造器,返回该类的实例。
构造器是类创建对象的根本途径,如果一个类没有构造器,系统会为这个类提供一个默认的构造器。 - 定义成员变量的语法:
[修饰符] 类型 成员变量名[=默认值]
(1)修饰符:public、protected、private、static、final,其中public、protected、private三个中只能出现其中之一,可以与static、final组合起来修饰成员变量。
(2)类型:可以是Java的任何类型。
(3)成员变量名:第一个单词首字母小写,其他单词首字母大写。 - 定义方法
[修饰符] 方法返回值类型 方法名(形参列表){
//方法体
}
(1)修饰符:public、protected、private、static、final、abstract,其中,public、protected、private三个中最多只能出现一个,final,abstract最多只能出现一个,他们可与static组合起来修饰方法。
(2)返回值:可以是Java语言允许的任何类型。必须有一个return语句,返回返回值。
(3)方法名:与成员变量规则相同,建议以英文动词开头。
static真正作用是用于区分成员变量、方法、内部类、初始化模块这四种成员到底属于类本身还是属于实例;有static的就属于类本深,没有static的就属于该类的实例。
5. 构造器
[修饰符] 构造器名(形参列表){
//构造器执行体
}
(1)修饰符:public、protected、private其中之一。
(2)构造器名:必须和类同名
构造器既不能有返回值,也不能使用void来声明构造器没有返回值,因为如果定义了返回值或使用void之后该构造器就变成了方法。
5.1.2 对象的产生与使用
- 创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例。
- static修饰的方法和成员变量,即可通过类来调用,可以通过实例来调用;没有static修饰的类普通方法和成员变量,只能通过该实例来调用。
5.1.3 对象、引用和指针
- 类也是一种引用数据类型,它被存储在栈内存中,指向实际的对象(堆内存),对把Person值赋给一个引用变量时,只需要将此引用变量指向该对象即可。
PS:Java中的引用就是C里的指针,只是Java语言把这个指针封装起来,避免开发者进行繁琐的指针操作。 - 同理,如果堆内存中的对象没有任何变量指向该对象,那么程序将无法访问该对象,这个对象就成为了垃圾,Java垃圾回收机制,将回收这个对象,释放内存区。
如果希望回收某个对象,只需切断该对象的所有引用变量和它之间的关系即可(将该引用变量赋值为null)。
5.1.4 对象的this引用
- this总是指向调用该方法的对象,this作为对象的默认引用有两种情形:
(1)构造器中引用该构造器正在初始化的对象。
(2)在方法中引用调用该方法的对象。 - this最大的作用就是:让类中的一个方法,访问该类中的另一个方法或者实例变量。
- Java允许对象的一个成员直接调用另一个成员,可以省略this前缀。
省略this只是一种假象,虽然程序员省略了调用方法之前的this,但实际上这个this依然是存在的。 - 对Java语言来说,调用成员变量、方法时,主调是必不可少的,及时代码中省略了主调,但实际的主调依然存在:
(1)如果调用static修饰的成员时,省略了前面的主调,那么默认使用该类作为主调。
(2)如果调用没有static修饰的成员时省略了主调,那么默认使用this作为主调。 - 对于static修饰的方法,可以使用类来直接调用该方法,而不能使用this引用(static修饰的方法使用this关键字,则这个关键字就无法指向合适的对象),进而,static修饰的方法只能访问static修饰的普通成员,即静态成员只能访问静态成员。
- 大部分时候,普通方法访问其他方法、成员变量时无须使用this前缀,但如果方法里有个局部变量和成员变量同名,但程序有需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。
- 如果构造器有一个与成员变量同名的局部变量,有必须在构造器中使用这个被覆盖的成员变量,则必须用this。
- this引用也可以用于构造器中作为默认的引用。this在构造器中代表该构造器正在初始化的对象。
如果构造器中有一个与成员变量同名的局部变量,又必须在构造其中访问这个被覆盖的成员变量,则必须使用this。
5.2 方法详解
5.2.1 方法的所属性
- 方法不能独立存在,必须属于类或对象。
- 如果这个方法使用了static修饰,则这个方法属于这个类,否则这个方法属于这个类的实例。
- 方法执行必须使用类(类.方法)或对象(对象.方法)。同一个类的方法调用另一个方法时,如果被调用的是普通方法,则默认使用this作为调用者;如果被调用的方法时静态方法,则默认使用类作为调用者。
- 使用static修饰的方法属于这个类本身,使用static修饰的方法既可以使用类作为调用者,也可以使用对象作为调用者,但因为使用static修饰的方法还是属于类本身,因此使用该类的任何对象调用这个方法,都会得到相同的执行结果。
- 没有static修饰的方法,只能通过对象来调用。
5.2.2 方法的参数传递机制
Java里方法的参数传递方式只有一种:值传递。就是将实际参数值的副本(复制品)传入方法,而参数本身不会受到任何影响。
5.2.3 形参可变个数的方法
在方法定义时,在最后一个形参的类型后增加三点(…),表名,该形参可以接受多个参数值,这多个参数值被当成数组传入。
这两种形式都包含了一个名为books的形参,在两个方法的方法体内都可以把books当数组处理;区别在于:调用这两个方法时,对于以可变形参的形式定义的方法,调用方法是更简洁。
5.2.4 递归方法
- 递归:一个方法体内调用它本身。
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这段代码无须循环控制。
5.2.5 方法重载
- 同一个类里定义多个同名的方法,只要形参列表不同就行。
- 两同一不同:同一类,方法名相同;形参列表不同。
5.3 成员变量和局部变量
5.3.1 成员变量和局部变量是什么
- 类变量的作用域比实例变量的作用域更大。实例也可以访问类变量,同一个类的所有实例访问类变量时,实际上访问的是该类的同一个变量,即同一片内存区。
- 与成员变量不同的是,局部变量除了形参外,都必须显示初始化。
- 一个类里不能定义两个同名的成员变量,即使一个是类变量,一个是实例变量也不行。
- Java允许局部变量和成员变量同名,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。
5.3.2 成员变量初始化和内存中的运行机制
5.3.3 局部变量初始化和内存中的运行机制
5.3.4 变量的使用规则
5.4 隐藏与封装
Java推荐对类和对象的成员变量进行封装。
5.4.1 理解封装
将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息。
如果每个Java类的实例变量都被private修饰,并为每个实例变量都提供了public修饰的setter和getter方法,那么这个类就符合JavaBean规范的类,因此,JavaBean总是一个封装良好的类。
5.4.2 使用访问控制符
-
Java的访问控制级别有小到大如图:
如果一个Java源文件里定义的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法文件名,但如果一个Java文件里定义了一个public修饰的类,则这个原文件的文件名必须与public修饰的类同名。 -
访问控制符的使用,存在以下几条规则:
5.5 深入构造器
构造器是一个特殊的方法,用于创建实例时的初始化。
5.5.1 使用构造器执行初始化
- 使用构造器初始化的原因:
当创建一个对象时,系统为这个对象的实例变量进行默认初始化,即将数值类型的实例变量设为0,对布尔型实例变量设为false,把引用型实例变量设为null,如果想改变这种默认的初始化,就需要通过构造器来实现。 - 一旦提供了自定义的构造器,系统就不再使用默认的构造器。
- 因为构造器主要被其他方法调用,因此通常把构造器设置成public访问权限。
5.5.2 构造器重载
- 同一个类里具有多个构造器,多个构造器的列表参数不同,就构成了构造器重载。
- 如果系统中包含了多个构造器,其中一个构造器的执行体里完全包含了另一个构造器的执行体,可以使用this关键字来调用相应的构造器。
PS:使用this调用另一个构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句。
5.6 类的继承
5.6.1 继承的特点
- Java的继承具有单继承的特点,即每个子类只有一个直接父类。
- 子类继承父类的语法如下:
- 如果定义一个Java类时并未显式指定这个类的直接父亲,则这个类默认扩展java.lang.Object类。
5.6.2 重写父类的方法
- 子类包含父类同名方法的现象被称为方法重写(Override),也被称为方法覆盖。
- 重写父类方法的原因
鸵鸟继承了鸟类飞翔的方法,但是鸵鸟又是一种特殊的鸟,不能飞,只能在地上跑,因此需要重写父类“飞”的方法。 - 方法覆盖需要遵循“两同两小一大”:
(1)方法名同,形参列表同;
(2)子类方法返回值类型比父类方法返回值类型更小或相等;子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
(3)子类方法的访问全向应该比父类大。
PS:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不用一个是实例方法,一个是类方法。 - 子类覆盖了父类方法之后,子类对象将无法访问父类中被覆盖的方法,但可以在子类方法中使用super或者父类名调用父类中被覆盖的方法。
- 如果父类方法访问权限是private,则该方法对其子类是隐藏的,子类无法访问,也就无法重写该方法,如果子类中定义了一个与父类private方法同名,同参,同返回值的方法,依然不是重写,而是定义了一个新的方法。
**PS:**重载发生在同一个类的多个同名方法之间,重写发生在子类和父类之间。
5.6.3 super限定
- super用于限定该对象调用它从父类继承的实例变量或方法,正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法(因为该方法的调用者可能是一个类,而不是对象,因而super限定就失去了意义)。
- 如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量。
- 如果子类定义了和父类同名的实例变量,则会发生子类实例变量隐藏父类实例变量的情形,
- 如果子类里没有包含和父类同名的成员变量,那么在子类实例方法中访问该成员变量是,则无需显式使用super或父类名作为调用者。
如果在某个方法中访问名为a的成员变量,但没有显式的指出调用者,系统的查找顺序为:
(1)查找该方法是否含有名为a的局部变量。
(2)查找当前类中是否包含有名为a的成员变量。
(3)查找a的直接父类是否包含名为a的成员变量,依次上溯到Object类。 - 当程序创建一个子类对象时,系统不仅为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存。即使子类定义了和父类中同名的实例变量。
5.6.4 调用父类构造器
- 子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码,类似于一个构造器调用另一个重载的构造器。
- 一个构造器中调用另一个重载的构造器使用this,子类构造器调用父类构造器使用super。
- super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this和super调用不能同时出现。
子类构造器执行体中既没有使用super,也没有使用this,系统将会在执行子类构造器之前,隐式的调用父类无参构造器。
5.7 多态
Java引用变量有两个类型:编译时类型、运行时类型。
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋值给该变量的对象决定。如果编译时类型与运行时类型不一致,就可能出现所谓的多态。
5.7.1 多态性
- 引用变量在编译阶段只能调用其编译时类型所具备的方法,但运行时则执行它运行时类型所具备的方法,因此,编写Java时,引用变量只能调用声明该变量时所用类里包含的方法。
- 通过引用变量访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。
5.7.2 引用变量的强制类型转换
类型转换运算符的用法:(type)variable。
这种类型转换还可以将一个引用变量成其子类类型。需要注意的是:
(1)基本类型之间的转换只能在数值类型之间进行(整数型,字符型,浮点型)。
(2)引用类型之间的转换只能在具有继承关系的两个类型之间进行。
5.7 3 instanceof运算符
- instanceof运算符前一个操作数是一个引用变量,后一个操作数通常是一个类,用于判断前面的对象是否是后面的类、子类或者实现类的实例。
- instanceof运算符前面操作数在编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系。否则会引起编译错误。
- instanceof和(type)是Java体用的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)进行强制转换。
5.8 继承和组合
继承的坏处:继承的子类可以直接访问父类成员变量(内部信息)和方法,从而造成子类和父类的严重耦合,破坏封装。
组合也是实现类复用的重要方式,而采用组合方式来实现复用则能提供更好的封装性。
5.8.1 使用继承的注意点
- 为保证父类有良好的封装性,父类设计通常应遵循如下规则:
(1)尽量隐藏附列的内部数据。设成private 访问类型。
(2)不要让子类直接随意访问,修改父类方法。
(3)尽量不要在父类构造器中调用将被子类重写的方法。
- 何时需要派生子类?
(1)子类需要额外增加属性,而不仅仅是属性值的改变。
(2)子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)。
5.8.2 利用组合实现复用
- 组合:将旧类对象作为新类的成员变量组合起来,用以实现新类的功能。用户看到的是新类的方法,而不是被组合对象的方法,因此通常需要在新类里使用private修饰被组合的旧类对象。
- 组合的系统开销
- 继承是一种“is a”的关系,组合是“has a”的关系。
5.9 初始化块
5.9.1 使用初始化块
- 初始化块和构造器一样,也可以完成整个Java对象的状态初始化,初始化块的语法
PS:初始化块的修饰符只能是static - 当创建Java对象时,系统总是先调用该类里定义的初始化块,初始化块在Java中是隐式执行,而且在构造器之前,因此可以将初始化块看做是构造器的补充。
5.9.3 静态初始化块
- 静态初始化块:用static修饰的初始化块,也被称为类初始化块。
- 系统将在初始化阶段执行静态初始化块,而不是创建对象时才执行。因此静态初始化块总比普通初始化块先执行。
- 静态初始化块和声明静态成员变量是所指定的初始值都是该类的初始化代码,他们的执行顺序与源程序中的排列顺序相同。