继承我相信大家都知道怎么使用,在学习javaSE的时候我们也都重点学过,我们当时可能理解的非常的透彻,但是随着继续的学习,总有一天你会觉得以前学的相当透彻的东西,忽然变得不是那么透彻了,既然这样,就不妨再来仔细学习一次。对于面向对象的程序设计语言来说,类毫无疑问是其最重要的基础。抽象、封装、继承、多态这四大特性都离不开类,只有存在类,才能体现面向对象编程的特点,今天我们就来了解一些类与继承的相关知识。
本文连接:http://www.cnblogs.com/dolphin0520/p/3803432.html
一.你了解类吗?
在Java中,类文件是以.java为后缀的代码文件,在每个类文件中最多只允许出现一个public类,当有public类的时候,类文件的名称必须和public类的名称相同,若不存在public,则类文件的名称可以为任意的名称(当然以数字开头的名称是不允许的)。 在类内部,对于成员变量,如果在定义的时候没有进行显示的赋值初始化,则Java会保证类的每个成员变量都得到恰当的初始化(默认自动给赋值):
1)对于 byte、char、short、int、long、float、double等基本数据类型的变量来说会默认初始化为0(boolean变量默认会被初始化为false);
2)对于引用类型的变量(不是基本数据类型的变量,通常是一个类的类型),则初始化为null。
如果没有显示地定义构造器,则编译器会自动创建一个无参构造器,但是要记住一点,如果显示地定义了构造器,编译器就不会自动添加构造器。注意,所有的构造器默认为static的(构造器是属于类的,而static的含义也是属于类的,所以这并不冲突)。
下面我们着重讲解一下 初始化 顺序:
当程序执行时,需要生成某个类的对象,Java执行引擎(Java虚拟机)会先检查是否加载了这个类,如果没有加载,则先执行类的加载再生成对象,如果已经加载,则直接生成对象。
在类的加载过程中,类的static成员变量会被初始化。另外,如果类中有static语句块(也就是静态代码块),则会执行static语句块。static成员变量和static语句块的执行顺序同代码中的顺序一致。记住,在Java中,类是按需加载,只有当需要用到这个类的时候,才会加载这个类,并且只会加载一次。看下面这个例子就明白了:
运行这段代码就会发现"Bread is loaded"只会被打印一次。 在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造器。也就是说类中的变量会在任何方法(包括构造器)调用之前得到初始化,即使变量散步于方法定义之间。
输出结果当然是:
bread
meal
二.你了解继承吗?
继承是所有OOP语言(面向对象语言)必不可少的部分,在java中使用extends关键字来表示继承关系。当创建一个类时,总是在继承,如果没有明确指出要继承的类,就总是隐式地从根类Object(Object是所有类的父类)进行继承。比如下面这段代码:
类Man继承于Person类,这样一来的话,Person类称为父类(基类),Man类称为子类(导出类)。如果两个类存在继承关系,则子类会自动继承父类的方法和变量,在子类中可以调用父类的方法和变量。在java中,只允许单继承,也就是说 一个类最多只能显示地继承于一个父类。但是一个类却可以被多个类继承,也就是说一个类可以拥有多个子类(就好比一个儿子只能由一个爸爸,而一个爸爸可以有多个儿子)。
1.子类继承父类的成员变量
当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:
1)能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;
2)对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3)对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。
2.子类继承父类的方法
同样地,子类也并不是完全继承父类的所有方法。
1)能够继承父类的public和protected成员方法;不能够继承父类的private成员方法;
2)对于父类的包访问权限成员方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖(也称为重写),即子类的成员方法会覆盖掉(重写)父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。
注意:隐藏和覆盖(重写)是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。(后面会讲到)。
三.你了解构造器吗?
子类是不能够继承父类的构造器的,但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
四.super关键字
super主要有两种用法:
1)super.成员变量/super.成员方法;
2)super(parameter1,parameter2....)
第一种用法主要用来在子类中调用父类的同名成员变量或者方法;第二种主要用在子类的构造器中显示地调用父类的构造器,要注意的是,如果是用在子类构造器中,则必须是子类构造器的第一个语句。
五.继承成员属性的代码演示
如果报Cannot make a static reference to the non-static field email这个错,因为在静态方法不能够直接访问静态成员方法和静态成员变量,要通过对象.的方式来间接访问,但是由于this对象是默认的, 而static方法中是不允许有this的,所以要自己新创建一个对象。
其实子类在继承的时候,可以考虑所要继承的这个东西,是否有继承的权限。我们可以这样想,只要有访问权限,才可以继承,没有访问权限就不可以继承。 首先,default权限的Father类可以被Son类所访问到,所以Father类可以被继承,但是其中的成员属性又有谁可以 被继承呢?pbulic protected default权限的可以被son访问到,所以就可以继承到,但是private只能在Father类中被访问,Son类是访问不到的,所以private修饰的成员属性不能被继承。
package com.java.extends1;
public class Text_extends {
public static void main(String[] args) {
//子类对象用父类引用接收
Father father=new Son();
//子类对象用子类引用接收
Son son=new Son();
System.out.println(father.str_public); //qiao_public
System.out.println(son.str_public); //qiao_public111111111
}
}
//定义一个父类
class Father{
public String str_public="qiao_public";
protected String str_protected="qiao_protected";
String str_default="qiao_default";
private String str_private="qiao_private";
public static void main(String[] args) {
}
}
//定义一个子类
class Son extends Father{
//子类和父类中有同名称的成员变量时,子类会把父类中的覆盖
public String str_public="qiao_public111111111";
protected String str_protected="qiao_protected";
String str_default="qiao_default";
private String str_private="qiao_private";
public static void main(String[] args) {
Son son=new Son();
son.text1();
}
public void text1() {
System.out.println(str_public); //qiao_public
System.out.println(str_protected); //qiao_protected
System.out.println(str_default); //qiao_default
//没有继承过来私有的
//System.out.println(str_private);
}
}
对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。当用父类引用接收子类对象时,调用属性时,不会向下去输出子类同名的属性值,而是自身的属性值,这是和同名方法不同的,在用父类引用调用方法时,如果类有同名方法,那么会调用子类中的方法,而不是父类中的。
六.继承成员方法的代码演示
package com.java.extends1;
public class Text_extends {
public static void main(String[] args) {
Father son=new Son();
Son son1=new Son();
//我们可以看到只要是子类可以访问到的方法都能够被继承,不管是静态方法还是普通方法。
son1.eat(); //eat
son1.dreak(); //dreak
//son1.play(); 会报错,没有继承到
/*当子类中有和父类中同名的方法时,son调用的父类方法时会自动调用子类的同名方法(普通方法)。因为方法被复写了。
*但是静态方法不能够被复写,当子类出现同名静态方法时,父类中的静态方法被隐藏。隐藏后的方法,再用son调用时,就不会向子类延申。
* */
son.dreak1(); //son_dreak
son.eat1(); //eat
}
}
//定义一个父类
class Father{
public Father(){
}
public static void eat() {
System.out.println("eat");
}
public void dreak() {
System.out.println("dreak");
}
private void play(){
}
public static void eat1() {
System.out.println("eat");
}
public void dreak1() {
System.out.println("dreak");
}
private void play1(){
}
}
//定义一个子类
class Son extends Father{
public static void eat1() {
System.out.println("son_eat");
}
public void dreak1() {
System.out.println("son_dreak");
}
private void play1(){
System.out.println("son_play");
}
}
七.总结
上述代码说的属实有点乱,这里总结一下。
其实子类在继承的时候,可以考虑所要继承的这个东西,是否有继承的权限。我们可以这样想,只要有访问权限,才可以继承,没有访问权限就不可以继承。 首先,default权限的Father类可以被Son类所访问到,所以Father类可以被继承,但是其中的成员属性又有谁可以 被继承呢?pbulic protected default权限的可以被son访问到,所以就可以继承到,但是private只能在Father类中被访问,Son类是访问不到的,所以private修饰的成员属性不能被继承。
如果父类中权限修饰符修饰的成员方法和成员变量可以被子类所访问到,那么该成员方法和成员变量就能够被子类所继承。
八.什么是隐藏
讲隐藏和覆盖之前先看两个概念:静态类型和动态类型。
任何一个引用变量都有两个类型:一个叫静态类型,也就是定义该引用变量的类型;另一个叫动态类型,也就是该引用实际指向的对象类型。比如对于两个类A和类B,有:A a=new B();那么,引用a的静态类型就是A,动态类型就是B。
java中引用的静态类型在编译的时候就可以确认,但是编译器无法得知这个引用的动态类型;只有当程序运行时,通过RTTI就可以检查出引用的动态类型。
再介绍一下,java中绑定的概念:对于一个程序,可以有很多的方法(这是肯定的)。这些方法的名称、参数类型和参数数量都可能相同或者不同(这是肯定的),那么在调用一个方法的时候,如何将一个方法和该方法所在的类关联起来(就是确定在调用方法的时候,确定调用的是哪一个类的方法),这就是绑定。Java中的绑定分为静态绑定和动态绑定。
静态绑定:所有依赖于静态类型 来将某方法和该方法所在的类关联起来的动作(操作)都是静态绑定(依赖静态类型来确定调用的方法是哪一个类中的方法)。因为静态绑定在程序运行前发生,所有又叫前期绑定。
动态绑定:所有依赖于动态类型 来将某方法和该方法所在的类关联起来的动作都是动态绑定。因为动态绑定是在程序运行时,通过RTTI实现,所以又叫后期绑定。
举例:假如有一个父类Father和一个子类Son,子类重写了父类中的某个方法method()。有以下语句:
Father father=new Son();
father.method();
对于这个例子,静态绑定的过程是:java文件编译时,编译器检查出引用father的静态类型是Father类,所以将method()方法和父类Father关联起来。也就是说,程序运行前编译器是无法检查出引用father的动态类型的,所以会直接调用 静态类型中对应的方法。
而动态绑定的过程是:当这个java程序运行起来了,RTTI检查出引用father的动态类型是Son类时,会将method()方法和子类Son关联起来,也就是决定调用动态类型Son类中的method()方法。具体过程为:①JVM提取对象的实际类型的方法表;②JVM搜索方法签名;③调用方法。
另外,要补充的是:java中类的成员变量(属性)都是静态绑定的。这是因为静态绑定是有很多的好处,它可以让我们在编译期就发现程序中的错误,而不是在运行期。这样就可以提高程序的运行效率!而对方法采取动态绑定是为了实现多态。
----------------------------------------------------------------------------------------------------------------分割线------------------------------------------------------------------------------------------------------------------
下面来说一下,java中的隐藏和覆盖的概念。我们知道,当子类继承父类时,除了继承父类所有的成员变量和成员方法之外,还可以声明自己的成员变量和成员方法。那么,如果父类和子类的成员变量和方法同名会发生什么?假设有一个父类Father和一个子类Son。父类有一个成员变量a=0;有一个静态成员变量b=0;有一个成员方法a,输出0;有一个静态成员方法b,输出0。子类分别重写这些变量和方法,只是修改变量的值和方法的输出,全部改为1. 我们再声明一个静态类型是父类,动态类型是子类的引用:
Father father=new Son();
通过这个引用访问子类的变量和调用子类的方法,那么,会有以下结论:
1、所有的成员变量(不管是静态还是非静态)都只进行静态绑定,所以JVM的绑定结果会是:直接访问静态类型中的成员变量,也就是父类的成员变量,输出0。
2、对于静态方法,也是只进行静态绑定,所以JVM会通过引用的静态类型,也就是Father类,来进行绑定,结果为:直接调用父类中的静态方法,输出0。
3、对于非静态方法,会进行动态绑定,JVM检查出引用father的动态类型,也就是Son类,绑定结果为:调用子类中的非静态方法,输出1。
对于1和2这两种情况,子类继承父类后,父类的属性和静态方法并没有被子类抹去,通过相应的引用可以访问的到。但是在子类中不能显示地看到,这种情况就称为隐藏。
而对于3这种情况,子类继承父类后,父类的非静态方法被子类重写后覆盖上去,通过相应的引用也访问不到了(除非创建父类的对象来调用)。这种情况称为覆盖。
总结一下,就是,子类继承父类后:
父类的成员变量只会被隐藏,而且支持交叉隐藏(比如静态变量被非静态变量隐藏)。父类的静态方法只会被静态方法隐藏,不支持交叉隐藏。父类的非静态方法会被覆盖,但是不能交叉覆盖。
九.体会
什么东西自己瞎想真不行,一定要上网看看别人总结的,自己总结的会很偏,,,,,,看完别人总结的之后在自己理解,而不是连概念都不知道啥意思,就开始自己瞎分析。。。。。。。。。