类和对象
定义类
面向对象的程序设计过程中有两个重要概念:类(class)和对象(object,也被称为实例,instance),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。
[修饰符] class 类名
{
零个到多个构造器定义...
零个到多个成员变量...
零个到多个方法...
}
修饰符可以是public、final、abstract或者完全忽略。
如果从程序的可读性方面来看,Java类名必须由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。
构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例。如果程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器。一旦程序员为一个类提供了构造器,系统将不再为该类提供构造器。
定义成员变量
static修饰的成员不能访问没有static修饰的成员。
成员变量用于定义该类或该类的实例所包含的状态数据,方法则用于定义该类或该类的实例的行为特征或者功能实现。构造器用于构造该类的实例,Java语言通过new关键字来调用构造器,从而返回该类的实例。
[修饰符] 类型 成员变量名 [=默认值]
修饰符:public、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰成员变量。
类型:基本类型和引用类型
成员变量名:如果从程序的可读性方面来看,Java类名必须由一个或多个有意义的单词连缀而成的,第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。
默认值:定义成员变量还可以指定一个可选的默认值。
定义方法
[修饰符] 方法返回值类型 方法名(形参列表)
{
//由零条到多条可执行性语句组成的方法体
}
修饰符:public、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰成员变量。
方法返回值类型:基本类型和引用类型如果声明了方法返回值类型,则方法体内必须与此处声明的类型匹配。如果一个方法没有返回值,则必须使用void来声明没有返回值。
形参列表:形参列表用于定义该方法可以接受的参数,形参列表由零组到多组“参数类型 形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开。
static
static是一个特殊的关键字,可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,因为通常把static修饰的成员变量和方法也称为类变量、类方法。不使用static修饰的普通方法、成员变量则属于该类的单个实例,而不属于该类。因为通常把不使用static修饰的成员变量和方法也称为实例变量、实例方法。
static修饰的成员变量和方法称为静态变量和静态方法,把不使用static修饰的成员变量和方法称为非静态变量和非静态方法。静态方法不能直接访问非静态成员。
有static修饰的成员属于类本身,没有static修饰的成员属于该类的实例。
构造器
构造器是一个特殊的方法,定义构造器的语法格式与定义方法的语法格式很像,定义构造器的语法格式如下:
[修饰符] 构造器名(形参列表)
{
//由零条到多条可执行性语句组成的构造器执行体
}
修饰符:public、protected、private
构造器名:构造器名必须和类同名
形参列表:和定义方法形参列表的格式完全相同。
构造器既不能定义返回值类型,也不能使用void声明构造器没有返回值。
对象的产生和使用
static修饰的方法和成员变量,既可通过类来调用,也可通过实例来调用;没有使用static修饰的普通方法和成员变量,只可通过实例来调用。
对象、引用和指针
变量实例实际上是一个引用,它被存放在栈内存里,指向实际的类对象;而真正的类对象则存放在堆内存中。栈内存里的引用变量并未真正存储对象的成员变量,对象的成员变量数据实际存放在堆内存里;而引用变量只是指向该堆内存里的对象。
对象的this引用
this关键字总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认引用有两种情形。
构造器中引用该构造器正在初始化的对象。
在方法中引用调用该方法的对象。
对于static修饰的方法而言,则可以使用类来直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以,static修饰的方法中不能使用this引用。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员。
普通方法访问其他方法、成员变量时无须使用this前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。this引用也可以用于构造器中作为默认引用,由于构造器是直接使用new关键字来调用,而不是使用对象来调用的,所以this在构造器中代表该构造器正在初始化的对象。
方法详解
方法的所属性
Java语言里方法的所属性主要体现在如下几个方面:
方法不能独立完成,方法只能在类体里定义。
从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象。
永远不能独立执行方法,执行方法必须使用类或对象作为调用者。
同一个类的一个方法调用另一个方法时,如果被调用方法是普通方法,则默认使用this作为调用者;如果被调方法是静态方法,则默认方法类作为调用者。
方法的参数传递机制
声明方法时包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际传给形参的参数值也被称为实参。
形参个数可变的方法
Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。
public statci void test(int a, String ... books)
数组形式的形参可以处于形参列表的任意位置,但个数可变的形参只能处于形参列表的最后。也就是说一个方法中最多只能有一个长度可变的形参。
递归方法
一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无限循环控制。
希望遍历某个路径下的所有文件,但这个路径下文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个方法,该方法接受一个文件路径作为参数,该方法可遍历当前路径下的所有文件和文件路径——该方法中再次调用该方法本身来处理该路径下的所有文件路径。
方法重载
Java允许同一个类里定义多个同名方法,只有形参列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
方法重载三个因素:
调用者,也就是方法的所属者,既可以是类,也可以是对象。
方法名,方法的标识。
形参列表,当调用方法时,系统将会根据传入的实参列表匹配。
两同一不同:同一个类中方法名相同,参数列表不同。方法返回值类型、修饰符等,与方法重载没有任何关系。
成员变量和局部变量
成员变量
成员变量指的是类里定义的变量,也就是前面所介绍的field;局部变量指的是方法里定义的变量。
成员变量:一个类不能定义两个同名的成员变量。
static修饰的类变量。类变量从该类的准备阶段起开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同;而实例变量则从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例变量的作用域与对应实例的生存范围相同。
成员变量无须显式初始化,只要为一个类定义了类变量或实例变量,系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化,成员变量默认初始化时的赋值规则与数组动态初始化时数组元素的赋值规则完全相同。
局部变量:一个方法里不能定义两个同名的方法局部变量,方法局部变量与形参也不能同名;同一个方法中不同代码块内的代码块局部变量可以同名;如果先定义代码块局部变量,后定义方法局部变量,前面定义的代码块局部变量与后面定义的方法局部变量也可以同名。
形参(方法签名中定义的变量) 作用域在整个方法内有效 必须显式初始化
方法局部变量(在方法内定义) 作用域从定义该变量的地方生效,到该方法结束时失效 必须显式初始化
代码块局部变量(在代码块内定义) 作用域从定义该变量的地方生效,到该代码块结束时失效 无须显式初始化
Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。
成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。
局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
变量的使用规则
定义一个成员变量时,成员变量将被放置到堆内存中,成员变量的作用域将扩大到类存在范围或者对象存在范围,这种范围的扩大有两个害处。
增大了变量的生存时间,这将导致更大的内存开销。
-
扩大了变量的作用域,这不利于提供程序的内聚性。
应该考虑使用成员变量的情况:
如果需要定义的变量是用于描述某个类或某个对象的固有信息。
如果在某个类中需要以一个变量来保存该类或者实例运行时的状态信息。
如果某个信息需要在某个类的多个方法之间进行共享,则这个信息应该使用成员变量来保存。
即使在程序中使用局部变量,也应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间就越短,程序运行性能就越好。因此,能用代码块局部变量的地方,就坚决不要使用方法局部变量。
隐藏和封装
理解封装
封装指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,二世通过该类所提供的方法来实现对内部信息的操作和访问。
对一个类或对象实现良好的封装,可以实现以下目的。
隐藏类的实现细节
让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对成员变量的不合理访问。
可进行数据检查,从而有利于保证对象信息的完整性。
便于修改,提高代码的可维护性。
为了实现良好的封装,需要从两个方面考虑:
将对象的成员变量和实现细节隐藏起来,不允许外部直接访问。
把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作。
使用访问控制符
private → default → protected → public
访问控制级别由小到大
private(当前类访问权限):使用它来修饰成员变量就可以把成员变量隐藏在该类的内部。
default(包访问权限):defaulte访问控制的成员或外部类可以被相同包下的其他类访问。
protected(子类访问权限):成员既可以被同一包中的其他类访问,也可以被不同包中的子类访问。通常,如果使用protected来修饰一个方法,是希望其子类来重写这个方法。
public(公共访问权限):成员或外部类可以被所以类访问,不管访问类和被访问类是否处于同一包中,是否具有父子继承关系。
private default protected public
同一个类中 √ √ √ √
同一个包中 √ √ √
子类中 √ √
全局范围内 √
外部类只能有两种访问控制级别:public和default,外部类不能用private和protected修饰,因为外部类没有处于任何类内部,也就没有其所在类的内部、所在类的子类的两个范围。
如果一个Java类的每个实例变量都被使用private修饰,并为每个实例变量都提供了public修饰setter和getter方法,那么这个类就是一个符号JavaBean规范的类。因此,JavaBean总是一个封装良好的类。
进行程序设计时,应尽量避免一个模块直接操作和访问另一个模块的数据,模块设计追求高内聚(尽可能把模块的内部数据、功能实现细节隐藏在模块内部独立完成,不允许外部直接干预)、低耦合(仅暴露少量的方法给外部使用)。
访问控制符的使用的基本原则:
类里的绝大部分成员变量都应该使用private,只有一些static修饰的、类似全局变量的成员变量,才可能考虑使用public修饰。除此之外,有些方法只用于辅助实现该类的其他方法,这些方法被称为工具方法,工具方法也应该使用private修饰。
如果某个类主要用作其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰这些方法。
希望暴露出来给其他类自由调用的方法应该使用public修饰。因此,类的构造器通过public修饰,从而允许在其他地方创建该类的实例。因为外部类通常都希望被其他类自由使用,所以大部分外部类都使用public修饰。
package、import和import static
如果希望把一个类放到指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:
package packageName;
一旦在Java源文件中使用了这个package语句,就意味着该源文件里定义的所有类都属于这个包。位于包中的每个类的完整类名都应该是包名和类名的组合,如果其他人需要使用该包下的类,也应该使用包名加类名的组合。
同一个包中的类不必位于相同的目录下,例如有leePerson和LeePersonTest两个类,它们完全可以一个位于C盘下某个位置,一个位于D盘下某个位置,只要让CLASSPATH环境变量里包含这两个路径即可。虚拟机会自动搜索CLASSPATH下的子路径,把它们当成同一个包下的类来处理。也应该把Java源文件放在与包名一致的目录结构下。通常建议将源文件和class文件分开存放,以便管理。
从可读性规范角度来看,包名应该全部是小写字母,而且应该由一个或多个有意义的单词连缀而成。
package语句必须作为源文件的第一条非注释性语句,一个源文件只能指定一个包,即只能包含一条package语句,该源文件中可以定义多个类,则这些类将全部位于该包下。如果没有显式指定package语言,则处于默认包下。
同一个包下的类可以自由访问,无须添加包前缀。
父包和子包之间确实表示了某种内在的逻辑关系。但父包和子包在用法上不存在任何关系,如果父包中的类需要使用子包中的类,则必须使用子包的全名,而不能省略父包部分。
//调用构造器时需要在构造器前添加包前缀
lee.sub.Apple a = new lee.sub.Apple();
import关键字
import可以向某个Java文件中导入指定包层次下某个类或全部类,import语句应该出现在package语句之后(如果有)、类定义之前。一个Java源文件只能包含一个package语句,但可以包含多个import语句,多个import语句用于导入多个包层次下的类。java.lang包下的所有类默认导入。
//使用import语句导入单个类
import package.subpackage...ClassName;
//使用import语句导入指定包下全部
import package.subpackage.*
星号()只能代表类,不能代表包。因此使用import.lee.;语句时,它表明导入lee包下的所有类,而lee包下sub子包内的类则不会被导入。如需导入lee.sub.Apple类,则可以使用impor.lee.sub.*;语句来导入lee.sub包下的所有类。
一旦在Java源文件中使用import语句来导入指定类,在该源文件中使用这些类时就可以省略包前缀,不再需要使用类全名。
静态导入import static
import static package.subpackage..ClassName.fieldName|methodName;
field:静态成员变量;methodName:静态方法。
使用import可以省略写包名;使用import static则可以连类名都省略。
Java源文件的大致结构:
package语句 //0个或1个,必须放在文件开始
import | import static 语句 //0个或多个,必须放在所有类定义之前
public classDefinition | interfaceDefinition | enumDefinition //0个或1个public类、接口或枚举定义
classDefinition | interfaceDefinition | enumDefinition //0个或多个普通类、接口或枚举定义
Java的常用包
Java.lang:核心类,如String、Math、System和thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。
java.util:Java的大量工具类/接口和集合框架类/接口,例如Arrays和List、Set等
java.net:一些Java网络编程相关的类/接口
java.io:一些Java输入/输出编程相关的类/接口
java.text:一些Java格式化相关的类
java.sql:Java进行JDBC数据库编程的想相关类/接口
java.awt:抽象窗口工具集的相关类/接口,这些类主要用于构建图形用户界面GUI程序
java.swing:Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序
深入构造器
构造器是一个特殊的方法,用于创建实例时执行初始化。
使用构造器执行
当创建一个对象时,系统为这个对象的实例变量进行默认初始化,这种默认的初始化把所有基本类型的实例变量设为0或false,把所有引用类型的实例变量设为Null。
如果程序员没有为Java类提供任何构造器,则系统会为这个类提供一个无参数的构造器,这个构造器的执行体为空,不做任何事情。无论如何,Java类至少包含一个构造器。
通常把构造器设置为public访问权限,从而允许系统中任何位置的类来创建该类的对象。
构造器重载
同一个类里具有多个构造器,多个构造器的形参列表不同,即被称为构造器重载,构造器重载允许Java类里包含多个初始化逻辑,从而允许使用不同的构造器来初始化Java对象。
类的继承
继承的特点
Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类。父类与子类的关系,是一种一般和特殊的关系。Java类只能有一个直接父类。
修饰符 class SubClass extends SuperClass
{
//类定义部分
}
java.lang.Object类是所有类的父类,要么是直接父类,要么是其间接父类。因此所有的java对象都可以调用java.lang.Object类定义的实例方法。
重写父类的方法
子类包含与父类同名方法你的现象被称为方法重写(Override),也称为方法覆盖。
方法的重写遵循“两同两小一大”规则
“两同”:方法名、形参列表相同;
“两小”:子类方法返回值类型应比父类返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
“一大”:子类方法的访问权限应比父类方法的访问权限更大或相等。
覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个类方法,一个是实例方法。
当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类方法最后调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。
如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。如果子类中定义了一个与父类private方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,只是在子类中重新定义了一个新方法。
super限定
如果需要在子类方法中调用父类被覆盖的实例方法,则可使用super限定来调用父类被覆盖的实例方法。super用于限定该对象调用它从父类继承得到的实例变量或方法。this和super均不能出现在static修饰的方法中。
如果在某个方法中访问名为a的成员变量,但没有显式指定调用者,则系统查找a的顺序为:
查找该方法中是否有名为a的局部变量。
查找当前类中是否包括名为a的成员变量。
查找a的直接父类中是否包含名为a的成员变量,依次上溯a的所有父类,直到java.lang.Object类,如果最终不能找到名为a的成员变量,则系统出现编译错误。
如果被覆盖的是类变量,在子类的方法中则可以通过父类名作为调用者来访问被覆盖的类变量。
当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。
调用父类构造器
在一个构造器中调用另一个重载的构造器使用this来完成,在子类构造器使用super调用来完成。this和super调用构造器必须出现在构造器执行体的第一行。
子类构造器调用父类构造器分如下几种情况:
子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
子类构造器执行体的第一行代码使用this显式调用本类中重载构造器,系统将根据this调用里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时即会调用父类构造器。
子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
创建任何java对象,最先执行的总是java.lang.Object类的构造器。即,创建任何对象总是从该类所在继承树最顶层类的构造器开始执行,然后依次向下执行,最后才执行本类的构造器。如果某个父类通过this调用了同类中重载的构造器,就会依次执行此父类的多个构造器。
多态
多态性
Java引用变量有两个类型:编译时类型,运行时类型。
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
BaseClass bc = new BaseClass();
SubClass sc = new SubClass();
BaseClass polymorphicBc = new SubClass();
上面程序显式创建了三个引用变量,对于前两个引用变量bc和sc,它们编译时类型和运行时类型完全相同,因此调用它们的成员变量和方法非常正常,完全没有任何问题。但第三个引用变量polymorphic则比较特殊,它的编译时类型是BaseClass,而运行时类型是SubClass,当调用该引用变量的test()方法(BaseClass类中定义了该方法,子类SubClass覆盖了父类的该方法)时,实际执行的是SubClass类中覆盖后的test()方法,这就可能出现多态了。
因为子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型(upcasting),向上转型由系统自动完成。
相同类型的变量、调用同一方法时呈现出多种不同的行为特征,这就是多态。
polymorphicBc.sub();这行代码会在编译时引发错误。虽然polymorphicBc引用变量实际上确实包含sub()方法,但因为它的编译时类型为BaseClass,因此编译时无法调用sub方法。
与方法不同的是,对象的实例变量则不具备多态性。比如上面的polymorphicBc引用变量,程序中输出它的book实例变量时,并不是输出SubClass类里定义的实例变量,而是输出BaseClass类的实例变量。
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如,通过Object p = new Person()代码定义了一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。
通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。
引用变量的强制类型转换
编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象却是包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型转换运算符。
当进行强制类型转换时需要注意:
基本类型之间的转换只能在数值类型之间进行,这里所说的数组类型包括整数性、字符型和浮点型。但数值型和布尔类型直接不能进行类型转换。
-
引用类型直接的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类实例,则这个对象必须实际上是子类实例才行(即编译时类型为福类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。
进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换,从而避免出现ClassCastException异常,这样可以保证程序更加健壮。
instanceof运算符
instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回true,否则返回false。
instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。
instanceof和(type)是Java提供的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)运算符进行强制类型转换,从而保证程序不会出现错误。
继承与组合
使用继承的注意点
为了保证父类有良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下规则。
尽量隐藏父类的内部结构。尽量把父类的所有变量都设置成private访问类型,不要让子类直接访问父类的成员变量。
不要让子类可以随意访问、修改父类的方法。父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法;如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符(该修饰符后面会有更详细的介绍)来修饰该方法。如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用protected来修饰该方法。
尽量不要在父类构造器中调用将要被子类重写的方法。 将引发空指针异常。
如果想把某些类设置成最终类,既不能被当成父类,则可以使用final修饰这个类;使用private修饰这个类的所有构造器,从而保证子类无法调用该类的构造器,也就无法继承该类的实例。
何时需要从父类派生新的子类:
子类需要额外增加属性,而不仅仅是属性值的改变。
子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)。
利用组合实现复用
对于继承而已,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承到的方法;而组合则是把旧类对象作为新类的成员变量组合进来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。
继承要表达的是一种“是(is-a)”的关系,而组合表达的是“有(has-a)”的关系。
初始化块
使用初始化块
初始化块是Java类里可出现的第4种成员(前面依次有成员变量、方法和构造器),一个类里可以有多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行。
[修饰符]
{
//初始化块的可执行性代码
}
初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。初始化块里的代码可以包含任何可执行性语句,包括定义局部变量、调用其他对象的方法,以及使用分支、循环语句等。
当创建Java对象时,系统总是先调用该类里定义的初始化块,如果一个类里定义了2个普通初始化块,则前面定义的初始化块先执行,后面定义的初始化块后执行。初始化块只在创建Java对象时隐式执行,而且在执行构造器之前执行。
初始化块和构造器
与构造器不同的是,初始化块是一段固定执行的代码,它不能接收任何参数。
静态初始化块
如果定义初始化块时使用了static修饰符,则这个初始化块就变成了静态初始化块,也被称为类初始化块(普通初始化块负责对对象执行初始化,类初始化块则负责对类进行初始化)。静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。
静态初始化块也被称为类初始化块,也属于类的静态成员,同样需要遵循静态成员不能访问非静态成员的规则,因此静态初始化块不能访问非静态成员,包括不能访问实例变量和实例方法。
系统在类初始化阶段执行静态初始化块时,不仅会执行本类的静态初始化块,而且还会一直上溯到java.lang.Object类(如果它包含静态初始化块),先执行java.lang.Object类的静态初始化块(如果有),然后执行其父类的静态初始化块······最后才执行该类的静态初始化块。
系统在创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,而且系统会一直上溯到java.lang.Object类,先执行java.lang.Object类的初始化块,开始执行java.lang.Object的构造器,一次向下执行其父类的初始化块,开始执行其父类的构造器······最后才执行该类的初始化块和构造器,返回该类的对象。
当JVM第一次主动使用某个类时,系统会在类准备阶段为该类的所有静态成员变量分配内存;在初始化阶段则负责初始化这些静态成员变量,初始化静态成员变量就是执行类初始化代码或者声明类成员变量时指定的初始值,它们的执行顺序与源代码中的排列顺序相同。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。