面向对象编程的三大特征:封装、继承和多态。
类的继承
继承是面向对象三大特征之一,也是实现软件复用的重要手段。
(1)继承的特点
<1>通过继承,在已有类型基础之上进行扩充或改造,得到新的数据类型。
已有的类型称为父类或超类。
得到的新数据类型,称为子类或派生类
类的继承提高了程序代码的重用性和可扩充性,缩短了软件开发的周期。
<2>继承的分类:
单继承---------子类只能有一个直接父类
多重继承------子类可以有多个直接父类(Java不支持多重继承)
<3>子类继承父类的语法格式
[修饰符] class 子类名 extends 父类名{
//子类代码部分
}
(2)重写父类的方法
子类扩展父类--总是以父类为基础,额外增加新的属性和方法。但有一种情况例外:子类需要重写父类的方法。
<1>方法重写时要遵循的规则:“三同一小一大”规则
“两同”即方法名相同,形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法
声明抛出的异常类更小或相等;
“一大”指的子类方法的访问权限应比父类方法更大或相等;
覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。
<2>方法重写和方法重载的区别
覆盖是子类和父类之间的关系;而重载是同一类内部多个方法之间的关系。
覆盖一般是两个方法间的,而重载可能有多个重载的方法。
覆盖的方法有相同的方法名和形参表;而重载的方法只能有相同的方法名,不能有相同的形参表。
覆盖时根据调用它的对象来区分方法;而重载是根据形参表来决定调用的是哪个方法。
(3)父类实例的super引用:如果需要在子类方法中调用父类被覆盖的实例方法,可以用super作为调用者来调用父
类被覆盖的实例方法。
注意:
uper是Java提供的一个关键字,它是直接父类对象的默认引用。
正如this不能出现在static修饰的方法中一样,super也不能出现在static的方法中
如果子类定义了和父类同名的属性,也会发生子类属性覆盖父类属性的情形。子类的方法直接访问该属性时,
都会访问到覆盖属性,无法访问父类被覆盖的属性---通过super来访问父类被覆盖的属性
如果我们在某个方法中访问名为a的属性,但没有显示指定调用者,系统查找a的顺序为:
查找该方法中是否有名为a的局部变量
查找当前类中是否包含名为a的属性
查找a 的直接父类中是否包含名为a的属性,依次上溯a的父类,直到java.lang.Object类,如果最终不能找到名为a的属性,则系统出现编译错误。
(4)调用父类构造方法:子类不会继承父类的构造方法,但有的时候子类构造方法里需要调用父类构造方法的初始化代码
(当通过子类构造方法创建子类对象时,默认的先执行父类不含参数的构造方法,再执行子类类构造方法。)
要在子类中显式调用直接父类带参数的构造方法,可通过super()调用来实现。
注意:super调用和this调用很像,区别在于super调用的是其父类的构造方法,而this调用的是同一个类中重载的构造方法。
因此,使用super调用父类构造也必须出现在子类构造执行体的第一行,所以this调用和super调用不会同时出现。
(5)子类构造方法调用父类构造方法分如下几种情况:
<1>子类构造方法执行体的第一行代码使用super显示调用父类构造方法,系统将根据super调用里传入的实参列表调用父类对应的构造方法。
<2>子类构造方法执行体的第一行代码使用this显示调用本类中重载的构造方法,系统将根据this调用里传入的实参列表调用本类另一个构造方法。
执行本类中另一个构造方法时即会调用父类的构造方法。
<3>子类构造方法执行体中既没有super调用,也没有this调用,系统将会在执行子类构造方法之前,隐式调用父类无参数的构造方法。
多态性
多态机制是面向对象技术的精华之一,它是建立在继承基础之上的。所谓多态(polymorphism),子类的对象可以代替父类的对象使用。
(1)思想基础
在类的继承中,子类在父类的基础上进行扩充和改造,父类拥有的成员子类中都有,因而可以认为子类比父类的功能强,
或者说子类的对象应该比父类的对象功能强,因而子类的对象应可以替代父类的对象被使用。
<1>一个对象只能属于一种确定的数据类型,该类型自对象创建直至销毁不能改变。
<2>一个引用类型变量可能引用(指向)多种不同类型的对象—既可以引用其声明类型的对象,也可以引用其声明类型的子类的对象。
(2)在多态情况下,一个引用类型的变量如果声明为父类的类型,但实际引用的是子类对象,则该变量就不能访问子类中添加的属性和方法。
(3) 对象造型(强制类型转换)
在多态的情况下,由于对象以其父类的身份出现,对子类中新添加成员的访问受到限制,
有时我们可能需要恢复一个对象的本来面目—造型(Casting),以发挥其全部潜力。
注意:
从子类到父类的类型转换可以自动进行;
在多态的情况下,从父类到子类转换必须通过造型(强制类型转换)实现;
无继承关系的引用类型间的转换是非法的。
特别强调:从父类到子类的造型也不是都能成功—只有当对象的真正类型本就是子类类型,
只是在多态的情况下,被一个声明为父类类型的变量所引用,才可以进行造型处理,
即再恢复该对象的本来面目,而一个对象如果其真正类型就是父类类型,是不能被造型为子类类型的。
(4)instanceof运算符
调用方法时如果实参是子类Student的对象,则造型是正确的,若实参是父类Person的对象,则造型失败!
例如:if(p instanceof Student){
Student st=(Student)p; st.getSchool();
}
运算符instanceof用于检测一个对象的真正类型。
格式:<变量名> instanceof <类型>
功能:如果instanceof操作符左侧的变量当前时刻所引用对象的真正类型是其右侧给出的类型、或是其子类,则整
个表达式结果为true,否则结果为false。
静态初始化块
Java使用构造方法来对单个对象进行初始化操作。与构造方法作用非常类似的是初始化块,它也可以对对象进行初始化操作。
(1)使用初始化块
初始化块是Java类里可以出现的第四种成员。语法格式:
class 类名{
[修饰符]{
//初始化块的可执行代码
}
…
} (一个类里可以有多个初始化块;相同类型的初始化块之间有顺序,前面定义的初始化块先执行,后面定义的初始化块后执行)
(2)分析:
当创建Java对象时,系统总是先调用该类里定义的初始化块;
如果一个类里定义了两个普通初始化块,则前面定义的初始化块先执行,后面定义的初始化块后执行。
初始化块虽然也是Java类里的一种成员,但它没有名字,也就没有标识,因此无法通过类、对象来调用初始化块;
初始化块只能在创建对象时自动执行而且在执行构造方法之前执行。
(3)注意:初始块和声明实例属性时所指定的初始值都是该实例的初始化代码,它们的执行顺序与源程序中排列顺序相同。
初始化块和构造方法的不同:初始化块是一段固定的执行代码,它不能接受任何参数。因此初始化块对同一个类内的属性所进行的初始化处理完全相同。
用法:如果多个构造方法里有相同的初始化代码,这些代码无需接受参数,那就可以把他们放在初始化块中定义。能更好的提高初始化块的复用,提高整个应用的可维护性。
创建一个Java对象时,不仅会执行该类的初始化块和构造方法,系统会先执行其父类的初始化块和构造方法。
(4)静态初始化块
如果定义初始化块时使用了static修饰符,则这个初始化块就变成了静态初始化块,也被称为类初始化块。
静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行,因此静态初始块总是比普通初始化块先执行。
静态初始块属于类的静态成员,用于对类属性执行初始化处理,不能对实例属性进行初始化处理。
系统在类初始化阶段执行静态初始化时,不仅会执行本类的静态初始化块,还会一直上溯到Object类(如果它包含静态初始化块)。经过这个过程,才完成了对类的初始化过程。
注意:静态初始块和声明静态属性时所指定的初始值都是该类的初始化代码,它们的执行顺序与源程序中排列顺序相同
final修饰符
final关键字可用于修饰类、变量和方法,用于表示它修饰的类、变量和方法不可改变。
(1)final变量
final修饰变量时,表示该变量一旦获得了初始值之后就不可被改变,final既可修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。
<1>final修饰成员变量
类变量:当类初始化时,系统会为类变量分配内存,并赋默认值。
成员变量
实例变量:当创建对象时,系统会为该对象的实例属性分配内存,并赋默认值。
<2>final修饰的类变量、实例变量能指定初始值的地方如下:
类变量:静态初始化块或声明该属性时指定初始值。
实例变量:非静态初始化块、声明该属性时或构造方法中指定初始值。
注意:实例属性不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例属性—非静态成员;
类属性不能在普通初始化块中指定初始值,因为类属性在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。
另外:注意:如果打算在构造方法、初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值
(2)final修饰局部变量
使用final修饰局部变量:
可以在定义时指定默认值,则后面代码中不能再对该变量赋值。
如果在定义时没有指定默认值,则可在后面代码中对该final变量赋初始值,但只能一次,不能重复赋值
<1>final修饰基本类型和引用类型变量的区别
区别:
当用final修饰基本类型变量时,不能对基本类型变量重新赋值,即基本类型变量的值不能被改变;
引用类型变量保存的是一个引用,final只保证这个引用(地址)不会改变,即一直引用同一个对象,但这个对象可以发生改变。
(3)final方法
final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法
注意:final修饰的方法仅仅是不能被重写,并不是不能被重载。
(4)final类
final修饰的类不可有子类。例如,java.lang.Math类就是一个final类,它不可以有子类。
抽象类
(1)抽象方法
基类中定义的方法,有时候只有在派生类中才能写出方法体。
<1>Java中,这种没有方法体的方法称为抽象方法。
<2>抽象方法声明格式:[修饰符] abstract 返回值类型 方法名([形式参数表]);
<3>抽象方法的特点
抽象方法的返回值类型前有关键字abstract;
抽象方法没有方法体;
抽象方法的定义是一行单独语句,以分号结束;
在抽象方法声明中使用static修饰符是错误的。
(2)抽象类
类中如果定义了抽象方法,这个类必须定义为抽象类。
[public] abstract class 类名{
//类体(属性、非抽象方法、抽象方法、构造方法)
//类体(初始化块、内部类、枚举类)
}
<1>抽象类不能创建自己的对象,使用new创建抽象类对象将产生错误。
<2>子类继承抽象类时,应该覆盖抽象类中的所有抽象方法,否则子类也必须定义为抽象类。
<3>注意
含有抽象方法的类(包括直接定义了一个抽象方法;继承了一个抽象父类,但没有完全实现父类包含的抽象方法)
只能被定义成抽象类。但抽象类中却并一定包含抽象方法。
抽象方法和空方法体的方法不是同一个概念
public abstract void test();是一个抽象方法,它根本没有方法体,即方法定义后面没有一对花括号
public void test(){}一个普通方法,定义了方法体,只是方法体为空,因此这个方法不可以使用abstract来修饰
<4>注意
final和abstract永远不能同时使用。
abstract不能用于修饰属性,不能用于修饰局部变量,即没有抽象变量、没有抽象属性等说法;abstract也不能用于修饰构造方法,没有抽象构造方法。
抽象类里定义的构造方法只能是普通构造方法。
static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。
abstract关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会有方法体,因此abstract方法不能定义为private访问权限。
(3)抽象类的作用
<1>代码重用--子类可以重用抽象父类中的属性和非抽象方法;
<2>规划--抽象类中通过定义抽象方法规划了其所有子类必须要实现的功能,或者说指定了其子类对象与外界的交互界面,因为抽象方法的方法头部分
已经规定了该方法将来被子类对象调用的格式
<3>模板模式:抽象类作为多个子类的通用模板,子类在抽象类的基础上扩展、改造,但子类总体上会保留抽象类的行为方式。
<4>抽象类不能实例化,但抽象类可作为变量的类型和方法形参类型,可将抽象类子类的对象赋给该变量或做方法的实参。
例如,Shape s=new Rectangle();public static String ShowShapinfo(Shape item){if(item instanceof Rectangle){ Rectangle r=(Rectangle)item; //其他代码}
接口
(1) 接口的概念
接口是另一种定义数据类型的方式。它和类非常相似的。
相同之处:都有成员变量和成员方法也可以形成继承关系
不同之处:接口中的属性都是常量(final)
接口中的方法是抽象方法(没有方法体)
引入接口的原因:Java只支持单重继承,每个类只能有一个超类。但实际应用中有时候需要多重继承--使用接口,一个类可以实现多个接口。
(2)接口的定义
[public](表示可以被不同包中的类或接口使用,缺省时具有包内可访问性) interface(interface关键字表示接口
) 接口名(任意有效标识符 ) [extends 父接口1,父接口2,…(接口支持多继承,多个父接口之间用逗号分隔 )]{
[public][static][final] 数据类型 属性名=常量值;
[public abstract] 返回值类型 方法名([形参表]);
}
<1>接口的属性必须用public static final修饰,是系统默认的,可部分省略或全部省略,但一般都写出final。
int MAX_SIZE = 50; 等价于public static final int MAX_SIZE = 50;
<2>接口的方法默认为public abstract,一般不写修饰符,可省略--接口中的方法都是抽象方法。
(3)实现接口
接口定义后不能直接创建对象,必须由类实现接口后创建类的对象。每个类只能继承一个基类,但可以实现多个接口。类定义形式如下:
public] class 类名 extends 基类 implements(可实现多个接口,多个接口之间用逗号分隔)接口1,…{
//类体(注意:类实现接口,就继承了接口中的所有成员变量和成员方法。由于接口中的方法都是抽象的,因此实现接口的类必须重写这些方法。)
}
(4)注意:
实现接口中的抽象方法时,除去掉关键字abstract外,方法头必须和接口定义中的方法头完全相同,并且public修饰符不能省略。
如果类实现了多个接口,必须重写这些接口中的所有方法。
接口不是类,不能使用new实例化接口,但可以声明接口变量。接口变量可以指向一个实现了该接口的类的对象,
可以使用instanceof来判断对象是否实现了某个接口。
虽然可以使用接口变量来引用实现接口类的对象,但这种引用只能引用接口的成员,否则会发生编译错误。
强调:一个接口定义一个协定,实现接口的类必须遵守其协定。接口最适合为不相关的类提供通用功能,利用接口提供的方法,程序可以多态地处理这些完全不同的类的对象。
(5)接口的继承
接口具有多重继承的特点,即一个接口可以有多个父接口。新的子接口继承所有父接口的全部方法和常量。接口继承的格式为:
[public] interface 子接口 extends 父接口1,父接口2,…{
//静态属性;
//抽象方法;
}
(6)接口和抽象类
<1> 接口和抽象类很像,它们都具有如下特征:
接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
注意:接口体现的是一种规范,类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准 。因此一个系统中的接口不应该经常改变
抽象类作为多个子类的共同父类,体现的是模板式设计。抽象类可以被当成系统实现过程中的中间产品,这个中间产品已经实现了部分功能,
但这个产品还不能当成最终产品,必须更进一步的完善,这种完善可能有几种不同方式。
<2>区别
设计目的不同;用法不同
(1)接口里只能包含抽象方法
(2)抽象类可以定义静态方法
(3)抽象类既包含普通属性,也可以包含静态常量属性
(4)抽象类里构造方法不用于创建对象,是让子类调用
(5)抽象类可以包含初始化块
(6)一个只能继承一个父类
(1)抽象类可以包含普通方法。
(2)接口里不能定义静态方法
(3)接口里只能定义静态常量属性
(4)接口不包含构造方法
(5)接口里不能包含初始化块
(6)一个类可以实现多个接口
内部类
即:在一个类中定义其它类
例如:class EnclosingClass{ ……
class ANestedClass{ ……
}
}
内部类在外部类中有特权:内部类可以毫无限制地访问外部类的所有成员。
编译之后,内部类也可产生单独的.class文件,文件名为:外部类名$内部类名.class
使用内部类在编写事件处理驱动的程序时用起来很方便。
只有内部类才能够是私有的--安全机制,只有其外部类的方法才能够创建内部类对象。
匿名类
如果只需要为内部类建立一个对象,甚至不必为该类指定一个名字,这种类被称为匿名内部类。
如果满足下面的一些条件,使用匿名内部类是比较合适的:
只用到类的一个实例
类在定义后马上用到?
类非常小(推荐在4行代码以下)?
给类命名并不会导致你的代码更容易被理解
注意:由于匿名内部类没有名称,因此创建匿名内部类对象时,new运算符后是超类或接口的名称,其后的{}内部为匿名类的定义
(匿名类对象的创建和匿名类的声明是在一起的)。
匿名类不能是抽象类,因为系统在创建匿名类的时候,会立即创建匿名类的对象。因此不允许将匿名类定义成抽象类。
匿名类不能有构造方法,因为匿名类没有类名,无法定义构造方法,但匿名类可以定义实例初始化块,通过实例初始化块来完成构造方法需要完成的事情。?
匿名类不能定义任何静态成员、方法和类;?
匿名类不能是public、protected、private、static;
只能创建匿名类的一个实例。