一、继承
1.1 为什么需要继承
在校园里,有很多人,主要可以分为两大类:一是老师,一是学生。他们都有名字,性别等共同拥有的属性,都会走路,都会吃饭等成员方法,当然也有不同的属性和方法如学生有学号属性和上课的方法,老师有工号属性和教学的方法。在老师和学生两个类中都会出现大量重复,为了解决这样的问题,面向对象的思想提出了继承的概念,专门用来实现共性的抽取,实现代码的复用
1.2 继承的概念
继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
1.3继承的语法
使用关键字extends,具体如下
修饰符 class 子类 extends 父类 {
//...
}
public class Person {
public String name;
public String gender;
public int age;
public void eat() {
System.out.println("Person::eat()");
}
public void walk() {
System.out.println("Person::walk()");
}
}
public class Student extends Person{
public int studentNum;
public void doClass() {
System.out.println("Student::doClass()");
}
}
public class Teacher extends Person{
public int teacherNum;
public void teach() {
System.out.println("Teacher::teach()");
}
}
要注意的是,子类继承父类后,必须要新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承。
1.4 父类成员访问
1.4.1 子类中访问父类的成员变量
1.子类和父类不存在同名成员变量
//Base.java
public class Base {
int a;
int b;
}
//Derievd.java
public class Derievd extends Base{
int c;
public void method() {
a = 1;//访问从父类中继承下来的a
b = 2;//访问从父类中继承下来的b
c = 3;//访问子类自己的c
}
}
2.子类和父类成员变量同名
class Base {
public int a = 1;
public int b = 2;
}
public class Derievd extends Base{
public int a = 2;
public char b = 'b';
public void fuc() {
System.out.println("子类成员方法");
System.out.println("子类a:"+this.a);
System.out.println("子类b:"+this.b);
//即使成员变量的类型不同,但子类和父类同名,也是访问子类的成员变量
System.out.println("父类a:"+super.a);//在子类成员方法中访问父类成员变量
}
}
总结:
1.如果访问的成员变量子类有,优先访问子类的成员变量
2.如果访问的成员变量子类没有,就访问父类的,如果父类也没有,就会编译出错
3.如果访问的成员变量父类和子类同名,则优先访问子类的
简而言之,成员变量的访问遵循就近原则,优先访问自己的,自己没有再访问父类
1.4.2 子类中访问父类的成员方法
1. 成员方法名不同
public class Test {
public static void main(String[] args) {
Derievd derievd = new Derievd();
derievd.method();
}
}
public class Base {
int a;
int b;
public void baseFuc() {
System.out.println("Base::baseFuc()");
}
}
public class Derievd extends Base{
public int a = 2;
public char b = 'b';
public void derievdFuc() {
System.out.println("Derievd::derievdFuc");
}
public void method() {
baseFuc();
derievdFuc();
}
}
总结:在方法名不同时,优先访问子类的成员方法,如果子类没有,就访问父类的成员方法,如果父类也没有就会报错。
2. 成员方法名相同
public class Test {
public static void main(String[] args) {
Derievd derievd = new Derievd();
derievd.Fuc3();
}
}
public class Base {
int a;
int b;
public void Fuc1() {
System.out.println("Base::Fuc1()");
}
public void Fuc2() {
System.out.println("Base::Fuc2()");
}
}
public class Derievd extends Base{
public int a = 2;
public char b = 'b';
public void Fuc1(int a) {
System.out.println("Derievd::Fuc1()" + a);
}
public void Fuc2() {
System.out.println("Derived::Fuc2()");
}
public void Fuc3() {
Fuc1();
Fuc1(20);
Fuc2();
}
}
总结
1.当父类和子类的方法名相同且参数列表相同时构成重写,则访问子类的成员方法
2.当父类和子类的方法名相同但参数列表不同时构成重载,则访问指定的成员方法
1.5 super关键字
当父类和子类的方法名相同时,会访问子类的方法,如果想在子类中访问父类的该方法时,就需要使用到super关键字,其作用就是在子类中访问到父类的成员变量和成员方法。
public class Test {
public static void main(String[] args) {
Derievd derievd = new Derievd();
derievd.Fuc3();
}
}
public class Base {
int a = 1;
int b = 2;
public void Fuc1() {
System.out.println("Base::Fuc1()");
}
public void Fuc2() {
System.out.println("Base::Fuc2()");
}
}
public class Derievd extends Base{
public int a = 2;
public char b = 'b';
public void Fuc1(int a) {
System.out.println("Derievd::Fuc1()" + a);
}
public void Fuc2() {
System.out.println("Derived::Fuc2()");
}
public void Fuc3() {
System.out.println(a);//访问子类的a
//等价于System.out.println(this.a);
System.out.println(super.a);//访问父类的a
System.out.println(b);
System.out.println(super.b);
Fuc1();
Fuc1(20);
Fuc2();
super.Fuc2();
}
}
1.6 子类构造方法
首先,先要调用父类的构造方法,然后再执行子类的构造方法。
public class Test {
public static void main(String[] args) {
Derievd derievd = new Derievd();
}
}
public class Base {
int a = 1;
int b = 2;
public Base() {
System.out.println("Base");
}
}
public class Derievd extends Base{
public Derievd() {
/*
super();
子类构造方法中会默认调用父类的构造方法
用户没有写,编译器会自动添加
并且只能出现在子类构造方法的第一条,也只能出现一次
**/
System.out.println("Derievd");
}
}
总结:
在子类构造方法中,并没有写关于任何父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,再执行子类的构造方法。这是因为:子类对象中成员是由两部分组成的,父类继承下来的和子类新增的部分。在构造子类对象时,先调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增的成员初始化完整。
注意:
1. 若父类显示定义无参或默认的构造方法,在子类构造方法第一行默认有隐含的的super()调用,即调用父类构造方法。
2. 如果父类构造方法是带有参数的,此时需要用户为子类显示定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是和子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现,因为this也要出现在构造函数的一行,所以两者相互冲突,故不能同时出现。
1.7 super和this
相同点
1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一天语句,并且不能同时存在
不同点
1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来的部分成员的引用。
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(..)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4.构造方法中一定会存在super(...)的调用,用户没有编写编译器也会增加,但是this(...)用户不写则没有
1.8 实例化
1.8.1 一般情况的实例化
public class Person {
protected String name;
protected int age;
public Person(String name,int age) {
System.out.println("Person构造方法执行了");
}
{
System.out.println("Person实例化代码块执行了");
}
static {
System.out.println("Person静态代码块执行了");
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person("zhangsan",10);
System.out.println("----------");
Person person2 = new Person("lisi",4);
}
}
总结:
1. 静态代码化先执行,并且只会执行一次,在类的加载阶段执行
2. 当有对象创建时,才会执行示例代码块,示例代码块执行完成后,最后执行构造方法
1.7.2 存在继承关系的实例化
public class Person {
protected String name;
protected int age;
public Person(String name,int age) {
System.out.println("Person构造方法执行了");
}
{
System.out.println("Person实例化代码块执行了");
}
static {
System.out.println("Person静态代码块执行了");
}
}
public class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student构造函数执行了");
}
{
System.out.println("Student实例化代码块执行了");
}
static {
System.out.println("Student静态代码块执行了");
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",10);
System.out.println("----------");
Student s2 = new Student("lisi",4);
}
}
总结:
1. 父类静态代码块优先于子类静态代码块执行,并且是最早执行
2. 父类实例化代码块和父类构造方法紧接着执行
3. 子类的实例化代码块和子类构造方法紧接着执行
4. 第二次实例化子类对象时,父类和子类静态代码块都不会再执行
1.9 protected关键字
在类和对象的文章中,提到了Java中的访问限定符,其中protected就是不允许在不同包中的非子类中访问被protected修饰的成员方法和成员变量。
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
我们希望类要尽量做到“封装”,即隐藏内部实现细节,只暴露出必要的信息给类的调用者,因此我们在使用的时候应该尽可能的使用比较严格的访问权限,能用private,就不要用public
1.10 继承方式
注意:
1. Java中不允许出现一个子类继承多个父类的情况
2.一般不要出现超过三层的继承关系
3. 如果想从语法上进行限制继承,可以使用final关键字
1.11 final关键字
1.修饰变量或字段,表示常量(不能被修改)
2. 修饰类,表示此类不能被继承
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.
3. 修饰方法,表示该方法不能被重写(后续补充)
二、组合
继承表示对象之间的关系是is - a 的关系,如猫是动物,学生是人
组合表示对象之间的关系是has - a 的关系,如汽车有轮胎、发动机、方向盘等,电脑有键盘、屏幕、散热器等
//发动机类
class Engine {
//属性...
//方法...
}
//轮胎类
class Tire {
//属性...
//方法...
}
public class Car {
private Tire tire;//可以复用Tire类的属性和方法
private Engine engine;//可以复用Engine类的属性和方法
public class benz extends Car {
//可以复用Engine类和Tire类的属性和方法
}
}
继承和组合都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。
三、多态
下章再讲