继承
基本概念
在一个类中定义一些基本属性和功能,其他派生的类都直接具备该类的特性,这一族类就形成了一个功能组,由基本功能类和其拓展类组成。使用上提高了代码的复用性和维护性,思想上java拓展性和维护性的体现,弊端就是增强了类之间的耦合。
- java中不像C++有多继承,其取而代之的是多重继承和接口
- 子类能继承或使用父类所有非私有成员,同时可以通过父类非私有成员函数间接访问其私有成员,但不能直接访问或继承父类私有成员。——父辈也需要自己独有的风格
继承实现过程
- 子类不能继承父类构造方法,但是可以利用super关键字去访问父类的构造方法。
- this和super的关系,this代表本类对应的引用,super代表父类存储空间的标识(可以代表父类的引用)。使用时,
A.访问成员变量,this.成员变量,super.成员变量
B.调用构造方法,this(...),super(...)
C.调用成员方法,this.成员方法,super.成员方法 - 子类中所有构造方法都会默认访问父类中的空参数构造方法。暗含了java设计者的意图:在于子类is a 父类,在子类初始化时候,默认要先初始化父类,再初始化子类特有内容,子类的每一个构造方法第一条默认语句都是super(),这条语句无论是否手动添加都会执行,除非手动改为其他的超类构造器,而且super声明必须在子类构造器中最前面(is a 思想,并防止父类多次初始化)。
开发注意点:
1.不要因为有共同特性就创建父类,这会带来封装性的破坏并限制了子类行为的灵活性。理想的搭配是子类is a 父类的关系。
2.动态绑定原则——成员变量,根据调用者类型决定从哪个类开始寻找。子父类中出现同名的成员变量情况,系统使用成员变量的优先级:如果调用者是子类对象,依照就近原则,从子类开始寻找,在子类方法中局部变量>子类成员变量>父类成员变量。
如果调用者是父类对象,同样依照就近原则,不过从父类开始寻找。
3.子类对象创建时一定要先创建父类内容---顺序为父类静态内容,子类静态内容,父类成员变量和构造函数,子类成员变量和构造函数。如果父类中没有给无参构造器,会导致子类无法通过编译,再次印证了编程时尽量提供无参构造器的规范。如果开发要求父类不能有无参构造器,那么需要在子类构造器中添加带参的父类构造器。,或者通过在当前构造器中添加this(...)访问其他构造器,然后间接访问父类构造方法。总之,一定要先执行父类的构造器。
4.this()或super()必须放在构造器的第一行,防止父类多次初始化。
5.对象初始化顺序,成员变量默认初始化,显示初始化,构造函数初始化,从实践看成员变量和构造代码块是同一优先级。
所以java在初始化对象的时候是从类到对象,从父类到子类的分层初始化。构造方法中super关键字(默认包含)表示先对父类进行初始化。
7.在继承结构中,子类如果想访问父类的私有成员??这需要反向思考,父类私有成员通过哪些方式对外开放了?答案便是共有方法和共有构造函数,还有反射
- final关键字:
至于方法,不能被重写,这对于想被子类调用但不远被修改实现的方法很有用;
至于类,不能被继承;
至于变量,变成常量,必须一次性赋值且不能被更改。赋值时机——联系对象初始化过程,若是静态变量,就必须显式初始化或者在静态代码块中初始化;若是成员变量,就显式初始化或者在构造代码块或构造函数中初始化;若是局部变量,可以先声明再初始化 无论如何,使用前必须要初始化。
final可以用在方法中修饰局部变量,修饰基本数据类型时,其值不能被改变;修饰引用数据类型时,其引用地址值不能改变,不是对象成员不能改变
多态
BaseClass bc = new SubClass();
相同的类型变量,调用同一个方法时出现多种不同的行为特征。
1.方法有重写时,调用重写方法;
2.仅子类有的方法,在多态代码中不能通过编译;
3.实例变量不具备多态性。
- 核心特征,Fu fu = new Zi();中,fu引用的成员变量指向父类,但成员方法指向子类。即对成员变量,指向决定于编译期,由具体引用类型决定,使用也取决于编译期的结果,不存在变量重写一说(之前已经了解到,子类对象构建时,一定会先将父类的静态成员、成员变量和构造函数的初始化,所以子类天然带有父类的特征)。对于成员函数,指向也决定于编译期(所以fu引用不能访问子类的特有内容),使用则取决于运行时,虚拟机根据实际对象是何种类型来决定采用哪个方法实现,这就是动态绑定。
成员变量是描述外在特征,成员方法才是描述具体功能实现 - 对于静态方法,其和类相关,不属于对象范畴,运行取决于调用的具体引用类型,在编译期决定。但父类中的方法如果是静态的,子类中的同签名方法也必须是静态,否则编译期会当做覆盖错误处理。这算不上重写,重写只是成员函数的概念。
方法签名包括方法名和参数列表,不包括访问修饰符、返回类型和返回值
static、private、final修饰的方法和constructor都不能被重写,编译器知道具体应该调用父类还是子类的值,属于静态绑定的范畴。
- 有时候需要将数据进行强制向下转型,这样就能使用目标类型的全部特性,前提是对象的实际类型能够顺利转型,防止ClassCastException异常出现的时候,可以使用 x instanceof Class 进行类型判断(x如果为null不会报异常,只会返回false)
子类构造过程图解
堆中对象区分为this区和super区,分别保存了本身和超类的特征。方法区中同时保存了this类和super类的方法
- 抽象类
将子类共同的特征经过简化抽象,使得上层的类更具备通用性。我认为这样就能减少对父类具体实现的依赖。
1.实现为用abstract修饰类和方法,方法不能有方法体,抽象类也有构造方法和具体的成员变量,用于子类访问父类的初始化数据,这符合继承的概念;
2.抽象类可以不包含抽象方法,但是有抽象方法的一定是抽象类
3.抽象类不能实例化,是通过子类实例化,这是多态的一种
4.只有当所有抽象方法都被重写,子类才能被正确实例化。
无抽象方法的抽象类有什么意义:不被实例化
抽象方法修饰符不能为private,final,static,原因:
private:不能被重写,与抽象概念冲突;
final:不能被重写,与抽象概念冲突;
static: 无意义,为何要用类名调用一个抽象方法?
接口
对类的一组需求的描述,其实现必须依靠具体的类。
- 接口中只能有抽象方法和常量,方法自动属于public abstract,常量自动属于public static final;
- 接口不能含有成员变量,实现的方法和构造方法,静态方法也不能包含;
- 接口不是类,不能通过new进行实例化;
- 可以声明接口变量,必须引用实现了接口的类对象,类似于类地,可以通过instanceof检查一个对象是否实现了特定的接口
- 接口也可以通过extends一样被拓展,接口是可以多继承接口的。
- 实现了接口的类,其对接口的实现会传递到子类。
接口存在最大的意义就是变向实现了多继承,而多继承特性(如C++)会使语言变得非常复杂且低效,接口同时避免了这个缺陷
总的来说,接口与抽象类有本质差别:
成员:
抽象类有成员变量和常量,接口只能设置常量;
抽象类有成员方法也可以有抽象方法和静态方法,接口只能有抽象方法;
抽象类有构造方法,接口无;
关系:
抽象类可以单继承,接口可以多继承;
抽象类依然按照不能实例化的类来使用,接口是独立于类体系之外的作为继承体系的拓展功能