类的继承
继承的概念
在已有类(基类)的基础上创建新的类(派生类)。新类继承了现有类的属性和方法(不必重新编写代码,实现代码复用),同时又添加了自己的新特性,从而在继承的基础上实现扩充。
在继承关系中,被继承的原有类称为父类、基类或超类;
通过继承关系定义出来的新类被称为子类或派生类。
子类除了可以继承父类属性和方法外,还可以通过以下方式产生新的成员:
- 增加新的成员
- 重新定义继承自父类的成员
单继承:一个子类只有一个父类。
多继承:一个子类有一个以上的父类。
Java不支持多重继承,即子类只能有一个直接父类。
Java允许类的继承存在多个层次关系,形成继承链,位于继承链下端的子类可拥有该继承链上所有父类的属性和方法。
也可多个类可以继承一个父类。
子类的声明
子类的定义格式:
class 子类名 extends 父类名
{ 子类中新成员的声明 }
父类名:子类的直接父类,是一个已经定义的类,可以来自系统类库或用户自定义类
子类中新成员的声明:子类继承父类所有成员(构造方法除外),但不能直接访问父类的私有成员。
子类对父类可做扩展和特殊化:
- 创建新的成员:变量和方法
- 重新定义父类中已有的变量:隐藏
- 重新定义父类中已有的方法:覆盖(override),子类中的方法应与父类中的被覆盖的方法有完全相同的:参数列表、返回值
能够实现继承的条件:
父类非final(最终类,不能再派生出新的子类);
只有对一个类具有访问控制权限时,才能以此类为基础创建子类。因此,要求父类是public、或父类与子类同包。
子类的继承性访问控制
| 父类中的成员 | private | protected | public | 默认的 |
|---|---|---|---|---|
| 同包子类 | 不可直接访问,但可通过可访问的方法进行访问 | 可访问,访问权限不变 | 可访问,访问权限不变 | 可访问,访问权限不变 |
| 不同包子类且父类为public | 不可直接访问,但可通过可访问的方法进行访问 | 可访问,访问权限不变 | 可访问,访问权限不变 | 不可直接访问,但可通过可访问的方法进行访问 |
成员变量的隐藏
在子类中定义与父类中同名的成员变量时,子类就隐藏了继承的成员变量,即子类对象以及子类自己声明定义的方法操作与父类中同名的成员变量。
方法的覆盖
方法的覆盖(方法重写,OverWrite或Override)
- 在子类中可以对从父类中继承的成员方法进行重新定义,使之满足子类的具体需要。——同名覆盖
- 子类中重定义的方法特征与父类定义的对应方法的特征完全一样,即方法的名字、返回类型、参数个数和类型与从父类继承的方法完全相同,则子类中的重定义方法覆盖了父类中对应的方法,父类中对应的方法在子类中不起作用。
- 通过子类对象调用一个被重定义过的父类成员方法,被调用的是子类的成员方法,不会执行父类的方法。
方法重写与方法重载(Overload)的区别:
- 重写不能改变参数列表(包括参数个数,参数数据类型和参数的位置);重载必须改变参数列表;
- 重写不能改变返回值类型,重载可以改变返回值类型;
- 重写方法不能有比被重写方法限制更严格的访问修饰符,重载可以改变访问修饰符。 注意:
- 构造方法不能被重写,可以被重载。
- 用final、private、static修饰的方法不能被重写,可以被重载。
- 方法的重载能在同一个类中或者子类中进行。
- 方法的重写能在实现接口的具体类中或者子类中进行。
- 子类里的方法与超类里用final、private、static关键字修饰的方法即使方法名相同,也不是重写,有可能是方法重载,或者是父类没有而子类所有的方法。
super关键字
- super代表当前或者正在创建的实例对象的直接父类,通常可以利用super实现对直接父类成员的访问。
- super关键字不可访问非直接父类的成员,即不可访问父类的父类等。
java.lang.Object类是Java中的根类,即所有类的父类,它是任何一条继承链最顶端的类,所有类都直接或间接的继承该类。- 当某个类没有显式的继承其他类时,则默认继承Object类。
- 由于所有类都是Object子类,所以在定义类时,省略了
extends Object。 - 由于Object类为Java语言的根类,已经没有父类,因此,在Object类中使用了关键字super,将引发编译时错误。 super一般用于以下需求:
- 在子类中使用super访问被子类隐藏的成员变量或方法(同名覆盖)。
- 子类不能继承父类的构造方法,若子类想使用父类的构造方法,必须在子类的构造方法中使用并且必须使用关键字super来表示,且super(参数值列表)必须是子类构造方法中的第1条语句。
子类的构造方法
子类不能继承父类的构造方法。由于子类继承了父类成员,在建立子类的对象时,必须先调用(隐式或显式)父类的构造方法来创建和初始化子类对象的父类成员。
①隐式初始化
如果父类中没有定义任何构造方法,则子类调用父类的默认构造方法
如果父类中定义了包含无参构造方法在内的多个构造方法,且在子类的构造方法中没有显示调用父类的构造方法时,子类就调用父类不带参的构造方法,直到执行完Object的构造方法后才执行子类的构造方法。
②显式初始化
在子类构造方法的第一条语句中通过super()或super(参数列表)调用父类的默认构造方法或带参构造方法。
当父类没有提供默认构造方法时,即父类定义的构造方法都是有参数的,则必须在子类的第一条语句通过super(参数列表)完成父类成员的初始化工作。
构造方法的调用顺序:
①在创建子类对象时,首先调用父类的构造方法,初始化子类中的父类成员;
②调用子类的构造方法,初始化子类中的新增数据成员。
多态
在Java中,允许用一个父类类型的变量引用一个子类类型的对象,根据被引用子类对象特征的不同,得到不同的运行结果,这就是多态,继承是实现多态的基础。
对象之间的类型转换
对象之间的类型转换分为向上转型和向下转型两种情况。
向上转型:
将子类类型的引用赋值给其父类或祖先类类型的引用,赋值操作中包含了隐式的类型转换。
class A{
…
}
class B extends A{
…
}
A a;
B b=new B();
a=b;
对象a是子类对象b的上转型对象,上转型对象会丢失原对象的一些属性和功能。上转型对象不能操作子类声明定义的成员变量和方法,即上转型对象不能操作子类新增的方法和成员变量。
上转型对象可以操作子类继承的成员变量和隐藏的成员变量,也可以使用子类继承的或重写的方法(多态性)。
将对象的上转型对象再强制转换到一个子类对象,该子类对象又具备了子类的所有属性和功能。
由于向上转型是从一个较具体的类到较抽象的类的转换,所以它总是安全的。
向下转型:
将某类类型的引用赋值给其子类类型的引用,赋值操作必须进行显式(强制)的类型转换。向下转型是从较抽象类转换为较具体类,这样的转型通常会出现问题。
class A{
…
}
class B extends A{
…
}
A a=new B();
B b=(B)a;
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前要使用instanceof运算符来判断父类对象是否为子类对象的实例。
格式:
myobject instanceof ExampleClass
- myobject:某个类的对象引用
- ExampleClass:某个类 使用instanceof运算符的表达式返回值为布尔值,若返回为true,说明myobject对象为ExampleClass的实例对象,返回值为false,则不是。
final关键字
final关键字可用于修饰类、变量和方法,有“无法改变”或者“最终”的含义。
final修饰类
Java中的类被final关键字修饰后,该类将不可以被继承,即不能派生子类。因此,称被final修饰的类为最终类。
final class A
{
类体;
}
final修饰方法
final方法:一个方法被修饰为final,则该方法不能被重写,即不允许子类通过重写隐藏继承的final方法。一般,对于一些比较重要且不希望子类进行更改的方法,可声明为final方法。可防止子类对父类关键方法的错误重写,增加代码的安全性和正确性。
final修饰成员变量
常量:在Java中被final修饰的变量称为常量,只能被赋值一次,即final修饰的变量一旦被赋值,其值不能改变。如果再次对改变量进行赋值,则程序会在编译时出错。
抽象类与接口
抽象类
抽象类:用关键字abstract修饰的类。 (不完善的、应该被继承的类。)
声明格式:
访问权限修饰符 abstract class 类名{
类的成员
}
类的成员:
属性成员
方法(构造方法、抽象方法、非抽象方法)
abstract类不能用new运算符创建对象,即抽象类不能创建对象,必须产生其具体子类,由具体子类创建对象。
abstract class A{…}
A a=new A();//错误
abstract class A{…}
class B extends A{…}
B b=new B();//正确
注意:不允许被创建对象,并不意味着它不允许有构造方法,定义抽象类的构造方法的原则与一般的类完全相同,且在创建子类对象时,该构造方法会被执行。
抽象方法:使用abstract关键字修饰的方法,其格式为:访问权限修饰符 abstract 返回值类型 方法名(参数列表);
抽象方法的特征:
- 不允许存在方法体:没有具体实现
- 必须存在于抽象类中
- 不允许使用static和final关键字修饰
- 必须被子类重写,除非子类也是抽象的
- 不允许是私有的
一个抽象类中可以没有抽象方法,但是若一个类中包含一个或多个抽象方法,这个类必须是抽象类。抽象方法只允许声明,而不允许实现,而该类的子类必须实现abstract方法,即重写父类的abstract方法。
如果子类也是抽象的,则不强制实现抽象方法,但抽象的子类应该存在更下级的非抽象子类(具体实现类)实现该抽象方法。
一个abstract类只关心子类是否具有某种功能,不关心功能的具体实现,具体实现由子类负责。
抽象类的唯一目的是为子类提供公有信息,它是用来被继承的。
没有抽象的构造方法,是因为构造方法是在使用new关键字创建对象时系统自动调用的,二者矛盾。
接口
接口是极端的抽象类,即所有方法只有方法的定义,没有方法的实现。
(1)接口的声明
修饰符 interface 接口名 {
接口体
}
接口名的命名规范与类相同,接口也可以指定所属的包
修饰符:
- public:与类的public修饰符相同,绝大多数情况使用public。
- abstract:通常被省略,因为接口中的方法都是抽象的。 接口体:成员主要由常量定义和方法声明组成,其特征:
- 所有的方法都由
public abstract修饰,即方法都为抽象方法,只提供方法的声明,不提供实现。 - 所有的属性成员都由
public static final修饰,即接口中属性都是静态常量,必须显式初始化。 注意: - 接口中不允许声明构造方法,更不能用new 关键字实例化一个接口,接口从本质上是一种特殊的抽象类,不能实例化。
- 接口中的方法默认是public和abstract,因此,接口在声明方法时可以省略方法前面的public和abstract关键字,但是,类在实现接口方法时,一定要用public来修饰。
- 接口体中不可以出现main()主方法。
(2)接口的实现
- 类如果没有实现接口中的全部方法,则这个类是抽象类,必须显示用关键字abstract修饰,因为包含抽象方法的类一定是抽象类。(可考虑在非抽象类中对没有具体实现的方法写空语句。
- 类在实现接口的方法时必须加上关键字public修饰方法。
(3)接口回调
- 接口可以理解成一种数据类型,当声明一个变量是接口类型时,我们知道接口不可以实例化,那如果为之赋值呢?正确的方法是把完全实现接口的类的实例化对象赋值给这个变量。
- 当采取上述方法赋值、调用方法时,方法会在运行时动态的调用具体的被重写的方法。
(4)接口做参数
- 接口不可以实例化,但是接口可以通过接口回调的方法赋值,也就是可以用实现接口的类对象赋值。
- 参数可以是任何一种数据类型。接口既然可以理解为一种新的数据类型,就可以作为参数传递。
(5)接口之间的继承
接口与接口之间使用关键字extends实现继承关系。
声明格式如下:
public interface 子接口 extends 父接口1,父接口2,…,父接口n{
接口体
}
接口支持多继承,即一个接口可以同时继承多个接口,各父级接口之间使用逗号分隔。
(6)接口与抽象类的对比
相同点:
- 都可有抽象方法,且必须在子类中实现这些方法
- 都不能用new关键字创建这两种类型的对象
- 都可以具有继承关系
- 接口和类一样可以具有public属性
不同点:
- 在抽象类中,方法声明时必须加abstract关键字,而在接口中不需要
- 在抽象类中,除包含抽象方法外,还可定义实例变量和非抽象方法,而在接口中,只能定义静态常量和抽象方法
- 接口允许多继承,抽象类支持单继承
- 接口没有构造方法,抽象类有构造方法
内部类
- Java语言支持在一个类中(类内部、方法内部、局部代码块中)声明另一个类,即类中可以嵌套类。
- 嵌套在其他类中的类叫做内部类,而包含内部类的类称为内部类的外嵌类。
例如:
class A{
class B{…}
……
}
- 内部类可以声明自己的方法和成员变量,外嵌类把内部类成员看作是自己的成员。
- 外嵌类的成员变量在内部类中仍然有效,内部类中的方法也可以调用外嵌类的方法。
- 外嵌类可以用内部类声明对象,作为外嵌类的成员。
“非静态”内部类
- 前面没有static关键字。
- 使用时的语法限制:“非静态”内部类中不能定义静态成员,只能定义非静态成员。
- 非静态内部类不能单独创建对象,必须依赖于外部类对象才能存在,即需先创建外部类的对象,然后在内部创建内部类对象,且可创建多个。
class A{
class B{…}
……
}
A a=new A();
B b=a.new B();
“静态”内部类
- 前面有static关键字。
- 属于外部类,不属于外部类对象,其创建不依赖于外部类对象。
class A{
static class B{…}
……
}
B b=new B();
局部内部类
定义在方法代码块或局部代码块中的类,也只能在局部代码块中使用
class A{
void fun(){
class C{
void show(){ System.out.println("内部类"); }
}
C c=new C();
c.show();
}
}
A a=new A();
a.fun();
匿名内部类
没有名字的内部类,定义匿名内部类的地方往往直接创建该类的一个对象。
class A{
…
}
A a=new A(){
…
};
A()后的大括号是匿名类
new是新建匿名类对象
A作为父类,被匿名类继承