在谈继承之前,我们先观察下面这个代码:
//定义一个猫类
class Cat {public String name;public int age;public float weigth;public void eat(){System.out.println(this.name+"正在吃饭");}public void mimi(){System.out.println(this.name+"正在咪咪叫");}
}
//定义一个狗类
class Dog {public String name;public int age;public float weigth;public void eat(){System.out.println(this.name+"正在吃饭");}public void bark(){System.out.println(this.name+"正在狗叫");}}
//测试类
public class Test{public static void main(String[] args) {Dog dog=new Dog();//定义一个狗对象dog.name="旺财";dog.eat();dog.bark();Cat cat=new Cat();//定义一个猫对象cat.name="咪咪";cat.eat();cat.mimi();}
}
上面代码中发现,在猫类和狗类中,有大量代码存在重复:
public String name;public int age;public float weigth;public void eat(){System.out.println(this.name+"正在吃饭");}
那能不能将这些重复的部分进行抽取呢?面向对象的思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
继承概念:
继承(inheritance):是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展和增加新功能。一个类继承另一个类,这样产生的类叫子类/派生类,被继承的类叫父类/基类/超类,继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
例如:上面的猫类和狗类中,可以将共性进行抽取。然后采用继承的思想来达到共用。
//定义一个Animal类
class Animal(){public String name;public int age;public float weigth;public void eat(){System.out.println(this.name+"正在吃饭");}
}
继承的语法:
在猫类和狗类中继承Animal类,在java中要表示继承的关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类{
}
对猫类和狗类重新进行设计:让猫类和狗类都继承Animal类
//猫类
class Cat extends Animal{public void mimi(){System.out.println(this.name+"正在咪咪叫");}
}
//狗类
class Dog extends Animals{ public void bark(){System.out.println(this.name+"正在狗叫");}}
1.子类会将父类中的成员变量或者成员方法继承到子类中了
2.子类继承父类之后,必须要新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承
父类成员访问:
1.子类中访问父类的成员变量:
1.1子类和父类不存在同名字的成员变量:
public class B {public int a=1;public int b=2;
}public class C extends B{public int c=3;public void func(){System.out.println(a);//访问从父类继承下来的aSystem.out.println(b);//访问从父类继承下来的bSystem.out.println(c);//访问子类自己有的c}
}
在子类中也可以通过this调用来访问父类的成员变量。
public class B {public int a=1;public int b=2;
}public class C extends B{public int c=3;public void func(){System.out.println(this.a);//访问从父类继承下来的aSystem.out.println(this.b);//访问从父类继承下来的bSystem.out.println(this.c);//访问子类自己有的c}
}
这样写代码和上面效果一样。
1.2子类和父类存在同名的成员变量:
public class B {public int a=1;public int b=2;
}public class C extends B{public int c=3;public int a=100;//定义一个与父类同名的成员变量public void func(){System.out.println(a);//不知道是调用哪一个aSystem.out.println(b);//访问从父类继承下来的bSystem.out.println(c);//访问子类自己有的c}
}
我们在子类访问变量a时,不知道调用的哪一个a,但从运行结果:来看,这里调用的a是子类自己的a。
所以当父类和子类有同名的成员变量时:优先访问子类的。
但有的人想问,如果想要先调用父类的a呢?这里就要用到一个关键字super,
格式:super . 父类的成员变量
super可以用来访问子类继承过来父类的成员
具体下面会讲到。
成员变量访问遵循就近原则,子类有优先访问自己的,如果没有则在父类中找。
2.子类中访问父类的成员方法:
2.1子类和父类不存在同名的成员方法:
//父类
public class B {public void methodA(){System.out.println("父类的methodA方法...");}
}
//子类
public class C extends B{public void methodB(){System.out.println("子类的methodB方法...");}
}
//测试类
class Test {public static void main(String[] args) {C c=new C();c.methodA();c.methodB();}}
调用成员方法与调用成员变量的操作一样。
子类和父类成员方法没有同名的情况下,在子类方法中同对象引用访问方法时,优先调用子类自己的,若子类没有则在父类中寻找,若父类也没有则会报错。
2.2子类和父类存在同名的成员方法:
2.1子类和父类存在同名的成员方法(方法里的参数不一样):
//父类
public class B {public void methodA(char a){System.out.println("父类的methodA(char a)方法...");}
}
//子类
public class C extends B{public void methodA(){System.out.println("子类的methodA()方法...");}
}
//测试类class Test {public static void main(String[] args) {C c=new C();c.methodA();c.methodA('a');}}
父类的methodA(char a)方法和 子类的methodA( )方法,因为方法名相同,方法参数不同。所以构成了方法的重载
根据调用的方法以及传递的参数访问合适的方法,若没有找到该方法,则会报错。
2.2子类和父类存在同名的成员方法(方法里的参数一样):
//父类
public class B {public void methodA(){System.out.println("父类的methodA()方法...");}
}
//子类
public class C extends B{public void methodA(){System.out.println("子类的methodA()方法...");}
}
//测试类class Test {public static void main(String[] args) {C c=new C();c.methodA();}}
在测试类中调用methodA的方法,默认调用的是子类的methodA的方法。
如果想调用父类的methodA的方法就要用super关键字。super.methodA()
//父类
public class B {public void methodA(){System.out.println("父类的methodA()方法...");}
}
//子类
public class C extends B{public void methodA(){System.out.println("子类的methodA()方法...");}
//通过super来调用父类的methodA的方法public void methoda(){super.methodA();
}
//测试类class Test {public static void main(String[] args) {C c=new C();c.methodA();}}
super关键字:
有时候在一些场景下,子类和父类可能存在同名的成员变量或者成员方法,如果想从子类中访问父类中同名的成员变量或者成员方法,直接访问是无法做到的,所以Java中提供了super关键字,它主要的作用:在子类中访问父类的成员变量或者成员方法。
在子类中:
1.super.父类成员变量
2.super.父类成员方法
可以通过上面两种方式来访问父类中的成员。
super只能指向子类的父类,不能指向子类的父类的父类。
super还有第三种方法:
如果在父类中有构造方法,当子类继承父类之后,子类需要显示的调用父类的构造方法,要先帮助父类的成员进行初始化。可以用super调用父类的构造方法进而对父类进行初始化。
//父类
public class B {public String name;public int age;
//父类的构造方法public B(String name,int age){this.name=name;this.age=age;}
//子类
public class C extends B{
//子类显示调用父类的构造方法public C(String name,int age){super(name,age);//}
}
//测试类
class Test {public static void main(String[] args) {C c=new C("酷酷的森",19);}}
在使用super的时候,要注意:super在调用父类构造方法时,一定要在方法中的第一条语句,不然会出错。
1.super调用父类的构造方法时,必须在第一行。前面我们讲过this()调用构造方法时,也只能在第一行,所以super()和this()是不能共存的。
2.当没有提供任何的构造方法的时候,java中会提供默认的构造方法,如下:
//父类
public class B {
//默认提供的构造方法public B(){
}}
//子类
public class C extends B{
/默认提供的构造方法public C(){super();
}
}
子类对象中成员是由两部分组成,父类继承下来的以及子类新增加的部分,父子父子,肯定是先有父再有子,所以在构造子类的对象时,要先调用父类的构造方法,将父类继承下来的成员初始化完整,然后调用子类的构造方法,将子类自己新增加的成员初始化完整。
注意:
1.在public C方法中,你也可以省略super(),因为在若父类显式定义无参或者默认的构造方法时,在子类构造方法第一行默认有隐式的super()调用。调用基类的构造方法。
2.如果父类构造方法带有参数,此时需要用户显式定义构造方法,并在子类构造方法中选用合适的父类构造方法调用,否则编译出错。
3.在子类构造方法中调用父类构造方法,super()必须在子类构造函数第一条语句
4.this()和super()不能同时出现在同一个构造方法里面。
但是只要你写了一个构造方法,这个默认构造方法就不会提供。
super和this:
相同点:
1.都是java中的关键字
2.都只能在非静态方法中使用,用来访问非静态的成员或方法
3.在构造方法中使用时,都只能在该方法中的第一条语句,且super()和this()不能同时存在。
不同点:
1.this是指当前对象的引用, 当前对象即调用实例方法的对象。super是在子类对象中调用从父类继承下来的那部分成员的引用
2.在非静态成员方法中,this调用的是本类的方法和属性,而super用来访问从父类继承下来的方法和属性。
3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
再谈代码块:
之前讲过静态代码块,实例代码块,构造方法在没有谈继承的时候所执行的顺序。可以在(javaSE:对象和类(2))中复习
现在讲了继承,它们又是按照什么顺序进行执行的呢?
//父类
public class B {public String name;public int age;//父类构造方法public B(String name,int age){this.name=name;this.age=age;System.out.println("父类构造方法执行了....");}//父类静态代码块static{System.out.println("父类静态代码块执行了....");}//父类实例代码块{System.out.println("父类实例代码块执行了....");}
}
//子类
public class C extends B{//实例代码块{System.out.println("子类实例代码块执行了.....");}//构造方法public C(String name,int age){super(name,age);System.out.println("子类构造方法执行了.....");}//子类静态代码块static{System.out.println("子类静态代码块执行了....");}
//测试类
class Test {public static void main(String[] args) {C c=new C("酷酷的森",19);}}
运行结果:
可知:先执行父类和子类的静态,再执行父类的实例和构造,最后执行子类的实例和构造
如果改一下代码,增加一个对象,又会有什么不同呢?
//父类
public class B {public String name;public int age;//父类构造方法public B(String name,int age){this.name=name;this.age=age;System.out.println("父类构造方法执行了....");}//父类静态代码块static{System.out.println("父类静态代码块执行了....");}//父类实例代码块{System.out.println("父类实例代码块执行了....");}
}
//子类
public class C extends B{//实例代码块{System.out.println("子类实例代码块执行了.....");}//构造方法public C(String name,int age){super(name,age);System.out.println("子类构造方法执行了.....");}//子类静态代码块static{System.out.println("子类静态代码块执行了....");}
//测试类class Test {public static void main(String[] args) {C c=new C("酷酷的森",19);System.out.println("============");C cc=new C("111111",19);}}
运行结果:
可知:第二次实例化对象,父类和子类的静态代码块都不会执行。
访问限定符:
为了实现封装特性,java引入了限定访问操作符,主要限定:类或者类中的成员能否在类外或者其他包中使用。
private修饰的成员,只能在当前类访问
default修饰的成员,只能在同一个包低下的类中访问
public修饰的成员,公共的,任何地方都能使用
protected修饰的成员,同一个包低下可以使用,但是不同包下,只有子类才能使用。
继承方式:
1.单继承:
public class A{
....
}
public B extends A{
....
}
2.多层继承:
public class A{
....
}
public class B extends A{
....
}
public class C extends B{
....
}
但是多继承,也不能无限继承下去。一般不超过3层的继承关系
3.不同类继承同一个类:
public class A{
....
}
public class B extends A{
....
}
public class C extends A{
....
}
4.java是不支持多继承的:一个类继承有多个父类是不允许的
java不支持多继承,但是可以通过接口的修形式,支持多继承
final关键字:
3.如果一个类不想被其他类继承,此时可以用关键字final来,修饰这个类,此时这个类就叫密封类,不允许被其他类继承。
4.final修饰方法,这个方法就不能被重写。
继承和组合:
组合和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承是is-a的关系:比如:猫是动物,狗也是动物
组合是has-a的关系或者是 a part of (一部分)的关系:比如:汽车有发动机,轮子还有车载系统。