Java中类的继承:
继承的好处:
- 提高代码的复用性。
- 让类与类之间产生关系,给第三给特征多态提供了前提。
单继承与多继承:
单继承:一个子类只能有一个直接父类。
多继承:一个子类可以有多个直接父类(java中不允许,对其进行了改良)。不直接支持多继承,因为多个父类中有相同成员,会产生调用的不确定性。在java中是通过“多实现”的方式来体现的。
单继承与多继承例子:
class A
{
}
class B
{
}
class C extends A/*单继承*/,B/* 多继承*/
{
}
java中支持单继承。不直接支持多继承,针对c++中多继承的机制进行了改良。
java中支持多层(多重)继承:C继承B,B继承A,就会出现继承体系。
当要使用一个继承体系时:
- 查看该体系中的顶层类,了解该体系的基本功能。
- 创建体系中的最子类对象,完成功能的使用。
在子父类当中,成员特点的体现:
- 成员变量
- 成员函数
- 构造函数
成员变量:
- 当本类的成员与局部成员变量同名用this区分。
- 当子父类中的成员变量同名用super区分父类。
- 子类中不能直接访问父类的私有内容。
- this跟super的用法很相似。
- this代表一个本类对象的引用。
- super代表一个父类空间。
成员函数:
当子父类中出现成员函数一模一样的情况,会运行子类的函数。这种现象称为覆盖操作。这是函数在子父类中的特性。
- 重载(overload):发生在同一个类中,两个函数名相同但是功能不同的函数叫做函数的重载。
- 覆盖(override):发生在子父类当中,覆盖也称为重写,覆写。当父类中有一个output函数子类也有一个output函数。
覆盖注意事项:
- 子类方法覆盖父类方法时,子类权限必须大于等于父类的权限。public权限大于默认权限。
- 静态只能覆盖静态或者被静态覆盖。
什么时候使用覆盖操作?
- 当对一个类进行子类的扩展时,子类需要保留父类功能声明,但是要定义子类中该功能的特有内容,就使用覆盖操作。
子父类中构造函数:
- 在子类构造对象时会发现,访问子类构造函数时,父类也运行了。为什么呢?
原因在于:子类的构造函数中第一行有一个隐式的语句super(),子类实例化过程中子类中所有的构造函数默认都会访问父类中的空参数的构造函数
- 为什么子类实例化的时候要访问父类中的构造函数呢?
原因在于:那是因为子类继承了父类,获取到了父类中的内容(属性),所以在使用父类内容之前,要先看父类是如何对自己的内容初始化的。所以子类在构造对象时,必须访问父类中的构造函数。为了完成这个必须的动作,就在子类的构造函数中加入了super()语句。如果父类中没有定义空参数的构造函数,那么子类的构造函数必须使用super明确要调用父类中哪个构造函数。同时子类构造函数中如果使用this调用了本类的构造函数时,那么super()就没有了,因为super()和this()都只能定义一行,所以只能有一个。但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。
注意:super语句必须要定义在子类构造函数的第一行。因为父类的初始化动作要先完成。
代码实例:
class Fu
{
Fu()
{
System.out.println("Fu A run");
}
Fu(int x)
{
System.out.println("Fu B run");
}
}
class Zi extends Fu
{
Zi()
{
// super();//调用就是父类的空参数的构造函数。
// super(4);
System.out.println("Zi A run");
}
Zi(int x)
{
this();
System.out.println("Zi B run");
}
}
class Construct
{
public static void main(String[] args)
{
new Zi(3);
System.out.println("Hello World!");
}
}
一个对象的实例化过程:
Person p=new Person();
- JVM会读取指定路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接父类的情况下)。
- 在堆内存中开辟的空间,分配地址。
- 并在对象空间中,对对象中的属性进行默认初始化。
- 调用对应的构造函数进行初始化。
- 在构造函数中,第一行会先调用父类中构造函数进行初始化。
- 父类初始化完毕后,对子类的属性进行显示初始化。
- 在进行子类构造函数到的特定初始化。
- 初始化完毕后,将地址值赋值给引用变量。
代码实例:
class Fu
{
Fu()
{
super();
show();
return;
}
void show()
{
System.out.println("Fu run");
}
}
class Zi extends Fu
{
int num=8;
Zi()
{
super();//父类构造函数执行完之后对子类属性进行显示初始化
System.out.println("Zi run num..."+num);
return;
}
void show()
{
System.out.println("Zi show run num..."+num);
}
}
class Construct2
{
public static void main(String[] args)
{
Zi z=new Zi();
z.show();
System.out.println("Hello World!");
}
}
输出结果为:
在上述程序中强调一点对show()的调用,当执行父类的构造函数时,执行父类构造函数中show()的时候是采用this.show()调用子类中show()函数的。
如果将上述例子改为静态覆盖的话:
class Fu
{
Fu()
{
super();
show();
return;
}
static void show()
{
System.out.println("Fu show run...");
}
}
class Zi extends Fu
{
static int num=8;
Zi()
{
super();//父类构造函数执行完之后对子类属性进行显示初始化
System.out.println("Zi run num..."+num);
return;
}
static void show()
{
System.out.println("Zi show run num..."+num);
}
}
class Construct2
{
public static void main(String[] args)
{
Zi z=new Zi();
z.show();
System.out.println("Hello World!");
}
}
输出结果为:
在上述程序中强调一点对show()的调用,相比于之前非静态覆盖的话,在调用父类构造函数中show()函数时直接调用了父类的静态成员函数show()。
继承的弊端:打破了封装性。
final关键字:
- final是一个修饰符,可以修饰类、方法、变量。
- final修饰的类不可以被继承。
- final修饰的方法不可以被覆盖。
- final修饰的变量是一个常量,只能被赋值一次,并且只能是显示初始化值。为什么要用final修饰变量?如果程序中一个数据是固定的,那么直接使用这个数据就可以了,但是这样阅读性差,所以应该给它取个名称。而且这个变量名称的值不能变化,所以加上final固定。
- public static final double MY_PI=3.14为全局常量。