文章目录
前言
上一篇我们讲到了面向对象的三大特性的封装,下来我们将继承
1 为什么需要继承
Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是
现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。
比如:狗和猫,它们都是一个动物。
使用Java语言来进行描述,就会设计出
猫:
public class Cat {
public String name;
public int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name+"正在吃饭....");
}
public void miaomiao(){
System.out.println(this.name+"正在喵喵叫....");
}
}
狗:
public class Dog {
public String name;
public int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name+"正在吃饭....");
}
public void bark(){
System.out.println(this.name+"正在汪汪叫....");
}
}
通过观察上述代码会发现,猫和狗的类中存在大量重复。
那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
2 继承概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。
Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后序讲)
3.继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
public class Animal {
public String name;
public int age;
public void eat(){
System.out.println(this.name+"正在吃饭....");
}
}
修饰符 class 子类 extends 父类 {
// …
}
Animal类
public class Animal {
public String name;
public int age;
public void eat(){
System.out.println(this.name+"正在吃饭....");
}
}
Cat类
public class Cat extends Animal {
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void miaomiao(){
System.out.println(this.name+"正在喵喵叫....");
}
}
Dog类
public class Dog extends Animal{
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public void bark(){
System.out.println(this.name+"正在汪汪叫....");
}
}
4.父类成员访问
为了解释这个问题,我们在创建一个包,包中有两个类
Base类
public class Base {
int a=1;
int b=2;
}
Derived类
public class Derived extends Base {
int c=10;
public void method(){
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
public static void main(String[] args) {
Derived derived=new Derived();
derived.method();
}
}
4.1.子类和父类不存在同名成员变量
就跟上面代码一样,结果是各输入自己的
结果是:各输入自己的
4.2.子类和父类成员变量同名
public class Derived extends Base {
int a=10;
public void method(){
System.out.println(a);
System.out.println(b);
}
public static void main(String[] args) {
Derived derived=new Derived();
derived.method();
}
}
Base类
public class Base {
int a=1;
int b=2;
}
结果是
输出的结果是子类的a,那么怎么可以输入父类的a呢?
使用super关键字
Base类不变
Derived类发生变化
public class Derived extends Base {
int a=10;
public void method(){
// System.out.println(a);
System.out.println("子类的a:"+this.a);
System.out.println("父类的a:"+super.a);
System.out.println(b);
}
public static void main(String[] args) {
Derived derived=new Derived();
derived.method();
}
}
4.3.super关键字
上面我们使用了super关键字,去调用成员变量,现在我们使用super,去调用构造方法使用上面Animal类,Dog类和Cat类的例子。
现在我们在Animal类中创建一个构造方法
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name+"正在吃饭....");
}
}
此时Dog和Cat类中的原来的构造方法会报错,为了解决这个问题,我们使用super关键字
public class Cat extends Animal {
public Cat(String name, int age) {
super(name,age);
}
public void miaomiao(){
System.out.println(this.name+"正在喵喵叫....");
}
}
public class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);;
}
public void bark(){
System.out.println(this.name+"正在汪汪叫....");
}
}
【注意事项】
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法。
super的其他用法在后文中介绍。
5.this和super
super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?
【相同点】
- 都是Java中的关键字
- 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。
【不同点】 - this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成
员的引用
. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性 - 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造
方法中出现 - 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
6.再谈初始化
Animal类
public class Animal {
public String name;
public int age;
static {
System.out.println("static()::Animal....");
}
{
System.out.println("Animal实例代码块...");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Animal构造代码块");
}
public void eat(){
System.out.println(this.name+"正在吃饭....");
}
}
Cat类
public class Cat extends Animal {
static {
System.out.println("static()::Cat....");
}
{
System.out.println("Cat实例代码块...");
}
public Cat(String name, int age) {
super(name,age);
System.out.println("Cat构造代码块...");
}
public void miaomiao(){
System.out.println(this.name+"正在喵喵叫....");
}
}
Dog类
public class Dog extends Animal{
static {
System.out.println("static()::Dog....");
}
{
System.out.println("Dog实例代码块...");
}
public Dog(String name, int age) {
super(name, age);
System.out.println("Dog构造代码块");
}
public void bark(){
System.out.println(this.name+"正在汪汪叫....");
}
}
我们还记得之前讲过的代码块吗?我们简单回顾一下几个重要的代码块:实例代码块和静态代码块。在没有继承关系时的执行顺序。
7.protected关键字
B和D类
public class B {
private int a;
protected int b;
public int c;
int d;
}
class D extends B{
public void method(){
//super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
super.c = 30; // 父类中public成员在相同包子类中可以直接访问
super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
C类
public class C extends B {
public void method(){
// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
TestProtect类
public class TestProtect {
public static void main(String[] args) {
C c = new C();
c.method();
//System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
//System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
//System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
什么时候下用哪一种呢?
我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用。
8.继承方式
注意:Java中不支持多继承。
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字
9.final 关键字
final关键可以用来修饰变量、成员方法以及类。
9.1.修饰变量
public static void main(String[] args) {
final int a=10;
//a=20;
System.out.println(a);
}
9.2.修饰类
见下面图
9.3.修饰方法
修饰方法:表示该方法不能被重写(后序介绍)
10.继承和组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法
(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车(也可以是a part of)
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。
完