特殊 - 一般
的关系,即is-a
关系。从已有类中派生出新的类
,新的类能吸收已有类的属性和方法
,并且能拓展新的属性和行为
。extends
关键字表示继承,语法表示为: class 子类 extends 父类{}
派生类
,父类又被称为超类
。子类是一种特殊的父类
,子类拥有父类的属性和方法,并且子类可以拓展具有父类所没有的一些属性和方法。Java只支持单继承
,不支持多重继承
操作(extends A,B,C..)
class A {}
class B {}
class C extends A,B {} // 错误的,一个子类继承了两个父类,Java中不允许
为什么只支持单继承?
多继承
会存在安全隐患
,因为当继承的多个类都存在相同的属性
或方法名相同方法体不同的方法
,子类进行调用时,就会产生不知道该调用哪一个类中的方法的情况。Java支持多层继承
(继承体系)
class A {}
class B extends A {}
class C extends B {}
如果想用这个继承体系的所有功能,那么就实用对底层的子类创建的对象
如果想看这个体系的共性功能,那么就看最顶层的类的功能
关键字
限定该对象调用它从父类继承得到的实例变量或方法
。不能出现在静态方法中
,因为静态方法属于类的,调用静态方法的可能是个类,而不是对象,而super和this都是限定对象调用。调用父类中被子类隐藏和覆盖的同名实例变量和同名方法
。构造器
中使用super,则super
会用于限定于该构造器初始化的是该对象从父类继承得到的实例变量
,而不是该类自己定义的实例变量。意思就是调用父类的构造器。super限定
:super紧跟一个点 super调用
:super紧跟圆括号
成员变量和方法
只能
继承父类的所有非私有
的成员变量和方法。可以
继承public protected
修饰的成员,不可以
继承private
修饰的。public 的setter和getter方法
进行间接
的访问和操作
private 的属性子类
中出现
了和父类同名的成员变量和成员方法
时,父类
的成员变量会被隐藏
,父类的成员方法会被覆盖
。需要使用父类的成员变量和方法时,就需要使用super
关键字来进行引用
。 不仅会为该类的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存
,即使子类定义了与父类中同名的实例变量。 即依然会为父类中定义的、被隐藏的变量分配内存。子类中的实例变量被私有
了 ,其父类中的同名实例变量没有被私有
,那么子类对象就无法直接调用该变量,但可以通过先将对象变量强制向上转型为父类型
,在通过该对象引用变量来访问那个实例变量,就会得到的是父类中的
那个实例变量。构造器
子类不能继承获得
父类的构造方法
,但是可以通过super
关键字来访问
父类构造方法。
在一个构造器中调用另一个重载构造器
使用this
调用完成,在子类构造器中调用父类构造器
使用super
调用来完成。
super 和 this 的调用都必须是在第一句
,否则会产生编译错误,this和super只能存在一个
。
不能进行递归构造器调用
,即多个构造器之间互相循环调用。
如果父类有无参构造
时,所有
构造方法(包含任意有参构造)自动默认
都会访问父类中的空参构造方法
。(自带super();
)
默认继承Object超类
,所以每一个构造方法的第一条默认语句都是super()
如果父类没有无参构造
,反而有其他的有参构造方法
时,子类继承父类后,子类必须显式的创建构造器,不论子类的构造器是否和父类构造器中参数类型是否一致,都必须在子类的构造器中显式的通过super关键字调用和父类构造器相应参数的构造方法
。否则编译都通不过。代码示例如下:
class Person {
public Person(int age){
System.out.println(age);
}
}
class Student extends Person{
public Student(int age) {
super(age);
}
public Student(){
super(10); //必须调用父类的有参构造
System.out.println("子类可以创建其他类型构造器,但是必须显式的用super调用父类构造器")
}
}
也可以使用this
先调用子类中的构造方法,再间接调用父类中的有参构造方法,实例如下:
public class ExtendTest1 {
public static void main(String[] args) {
new Student();
}
}
class Person {
public Person(int age){
System.out.println("父类有参构造");
}
}
class Student extends Person{
public Student(int age) {
super(age);
System.out.println("子类有参构造");
}
public Student(){
this(10); //可以使用this先调用子类中的有参构造,从而间接调用父类中的有参构造
System.out.println("子类无参构造");
}
}
使用this,执行顺序结果为:先调用了子类中无参构造,此无参构造会接着调用子类中的有参构造,又接着调用父类中的有参构造,此时首先执行完毕了父类有参构造,接着子类有参构造执行完毕,最后子类无参构造才执行完毕。
父类有参构造
子类有参构造
子类无参构造
以下这种是错误的:(因为当父类中没有无参构造器时,父类中没有这种类型的构造方法)
class Student extends Person{
public Student(String name){
super();
} //错误的,因为当父类中没有无参构造器时,父类中没有这种类型的构造方法
public Student(int age) {
super(age);
}
}
class Person {
public Person(String name ,int age){
System.out.println(name+age);
}
public Person(int age){
System.out.println(age);
}
}
以下这种正确:(因为当父类中没有无参构造器时,子类中的构造方法的类型在父类中有)
class Student extends Person{
//因为当父类中没有无参构造器时,子类中的构造方法的类型在父类中有
public Student(int age) {
super(age);
}
}
class Person {
public Person(String name ,int age){
System.out.println(name+age);
}
public Person(int age){
System.out.println(age);
}
}
class Student extends Person{
//因为当父类中没有无参构造器时,子类中的构造方法的类型在父类中有
public Student(String name ,int age){
super(name,age);
}
public Student(int age) {
super(age);
}
}
class Person {
public Person(String name ,int age){
System.out.println(name+age);
}
public Person(int age){
System.out.println(age);
}
}
结论:当父类中没有无参构造器时,子类继承父类,子类中的构造器方法类型可以和父类中的构造器不同,但是必须每个构造器都显式的使用super关键字调用父类中的某个有参构造器,也可以使用this调用子类中的某个有参构造器,但这个有参构造器必须通过super访问父类中的有参构造器。
继承体系中的构造器执行顺序
父类构造器总是在子类构造器之前执行
。继承树最顶层类的构造器开始执行
,然后依次向下
执行,最后才执行本类的构造器。如果父类通过this调用了同类中的重载构造器
,就会依次执行此父类的多个构造器
。继承体系中的静态域
执行顺序
父类优先于子类进行加载到内存
,所以会先执行父类中的静态域
父类和子类中都有静态代码块和构造代码块
,示例如下:
class Test2_Extends {
static {
System.out.println("主类静态块");
}
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
此时的执行结果:
主类静态块
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
执行顺序分析:
返回值类型
可以是子父类
。子类有自己的特有内容
时,可以重写父类中的方法。即沿用了父类的功能,又定义了子类特有的内容
两同两小一大
”规则: 方法名
、形参列表
相同返回值类型
应比父类方法返回值类型更小或相等
声明抛出的异常类
应比父类方法声明抛出的异常类更小或相等
访问权限
应比父类方法访问权限更大或相等
私有方法不能被重写
,该方法对于子类是隐藏
的,因此其子类无法访问
该方法,也无法重写静态方法
,子类也必须
通过静态方法进行覆盖,即静态只能覆盖静态
最好
声明得一模一样子类中定义
了一个与父类private方法
具有相同的方法名、相同的形参列表、相同的返回值类型的方
法,依然不是
重写,只是在子类中重新定义了一个新的方法
,所以该新方法不会受父类方法的任何限制
。Override
和 Overload
的区别?Overload
能改变返回值类型吗? Override是重写
,Overload是重载
。重载可以改变返回值类型
,它是方法名相同,参数列表不同,与返回值类型无关
。重写
:子类
中出现和父类
中方法声明一模一样的方法
。返回值类型相同(或者是子父类,多态),方法名和参数列表一模一样
。主要发生在子类和父类的同名方法之间。重载
:本类
中出现方法名相同
,参数列表不同
的方法,和返回值类型无关,可以改变
。主要发生同一类的多个同名方法之间。先找子类
本身的方法,再找父类
中的方法。破坏了父类的封装性
,每个类都应该它内部信息和实现细节,而只暴露必要的方法给其它类使用
。但在继承关系中
,子类可以直接访问父类的成员变量(内部信息)和方法
, 从而造成子类和父类的严重耦合。不再透明
,从而导致子类可以恶意篡改父类的方法为了保证父类有良好的封装性
,不会被子类随意改变,设计父类通常应该遵循如下规则:
尽量隐藏父类的内部数据。
尽量把父类的所有成员变量都设置成private访问类型
,不要让子类直接访问父类的成员变量。
不要让子类随意访问、修改父类的方法。
父类
中那些仅为辅助其他的工具方法
,应该使用private
修饰,让子类无法访问方法;
需要被外部类调用
,则必须以public
修饰,但又不想让子类重写
,就可以使用final
修饰符。如果希望父类的某个方法被子类重写
,但不希望被其他类自由访问
,则可以使用protected
来修饰方法。
尽量不要在父类构造器中调用将要被子类重写的方法。
查看下面例子说明在父类构造器中调用被子类重写的方法引发的错误:
package extend;
class Base
{
public Base()
{
System.out.println("父类构造器");
test();
}
public void test() // ①号test()方法
{
System.out.println("将被子类重写的方法");
}
}
public class Sub extends Base
{
public Sub(){
System.out.println("子类构造器");
}
private String name="aa";
public void test() // ②号test()方法
{
System.out.println("子类test");
System.out.println("子类重写父类的方法,"
+ "其name字符串长度" + name.length());
}
public static void main(String[] args)
{
// 下面代码会引发空指针异常
Sub s = new Sub();
}
}
执行结果:
父类构造器
子类test
Exception in thread "main" java.lang.NullPointerException
分析:
先执行其父类构造器
,如果父类构造器调用了被子类重写覆盖的方法,就会调用被子类重写后的②号test()方法
,子类的test方法调用了子类的实例变量name
,父类直接调用的子类的test方法,此时子类还未初始化,还未调用子类构造器,实例变量name还未被指定初始值,仍然为默认值null
,所以引发了空指针异常。