目录
继承
什么是继承
Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联(具有某些相同的共性),而Java中用继承的语法便能建立类的联系。继承就是专门用来进行共性抽取,实现代码复用Java中如果要表示类之间的继承关系,需要借助extends关键字
//动物类(具有猫和狗的共性) class Animal{ String name; public void eat(){ System.out.println(name + "正在吃饭"); } } //狗类(继承了动物类,具有动物类的成员方法和成员变量) class Dog extends Animal{ public void bark(){ System.out.println(name + "汪汪汪"); } } //猫类(继承了动物类,具有动物类的成员方法和成员变量) class Cat extends Animal{ public void mew(){ System.out.println(name + "喵喵喵"); } } public class Test { public static void main(String[] args) { Cat cat = new Cat(); Dog dog = new Dog(); dog.name = "狗狗";//name属性从Animal类中继承下来 cat.name = "喵喵";//name属性从Animal类中继承下来 System.out.println(dog.name); System.out.println(cat.name); dog.eat();//eat方法从Animal类中继承下来 cat.eat();//eat方法从Animal类中继承下来 } }
注:上述代码中Animal类称作父类/基类/超类,Dog和Cat称作子类/派生类,继承后子类可以实现父类代码的复用
继承关系下的访问
1.子类方法中访问父类成员变量
成员变量访问遵循就近原则,自己有优先自己的,如果没有则在父类中找
class Base{ int a; int b; } class Derived extends Base{ int a;//同名且同类型 char b;//同名不同类型 public void method(){ a = 1;//子类与父类都有,优先子类 b = 1;//同名,但类型不同,根据赋值内容访问对应类,这里访问的是父类 b = 'b';//同名,但类型不同,根据赋值内容访问对应类,这里访问的是子类 } }
2.子类方法中访问父类成员方法
依然遵循就近原则,自己有优先自己的,当子父类方法构成重载时,根据重载规则调用
class Base { public void methodA(){ System.out.println("Base中的methodA()"); } public void methodB(){ System.out.println("Base中的methodB()"); } } class Derived extends Base{ public void methodA(int a) { System.out.println("Derived中的method(int)方法"); } public void methodB(){ System.out.println("Derived中的methodB()方法"); } public void methodC(){ methodA();// 没有传参,访问父类中的methodA() methodA(20); // 传递int参数,访问子类中的methodA(int) methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到 } }
当子类与父类存在相同成员时会优先访问子类的,那可不可以访问父类的呢?
super关键字
super关键字就来用于在子类方法中访问父类的成员
class Base{ int a; public void method(){ System.out.println("Base中的methodA方法"); } } class Derived extends Base{ int a; public void method(){ System.out.println("Derived中的method方法"); } public void test(){ a = 10;//等价于this.a = 10;(访问子类的a) //this是当前对象的引用 super.a = 20;//访问父类的a //super是当前对象中从父类继承下来的那部分的引用 method();//访问子类的method方法 super.method();//访问父类的method方法 } }
注:super关键字与this关键字一样,不能在静态方法中使用
子类的构造方法
子类中也存在构造方法,且在子类的构造方法会默认提供一个调用基类的无叁构造方法(super();)
class Base{ int a; } class Derived extends Base{ int a; public Derived(){ super();//若没有写编译器会默认添加,必须在子类构造方法的第一条语句,并且只能出现一次 } }
若父类构造方法没有无参的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用
class Base{ int a; public Base(int a){ this.a = 1; } } class Derived extends Base{ int b; public Derived(int a, int b){ super(a);//Base中没有无叁构造方法,此时必须自己添加引用父类的构造方法 this.b = b; } }
注:因为super(...)和this(...)都必须在构造方法中的第一条语句,因此他们两个不能同时出现
super和this
super 和 this 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?相同点:1. 都是 Java 中的关键字2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在不同点:1. this 是当前对象的引用,当前对象即调用实例方法的对象, super 相当于是子类对象中从父类继承下来部分成员的引用2. 在非静态成员方法中, this 用来访问本类的方法和属性, super 用来访问父类继承下来的方法和属性3. 在构造方法中: this(...) 用于调用本类构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现4. 构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加,但是 this(...) 用户不写则没有继承关系下代码的执行顺序
我们已经知道没有继承关系下的先执行顺序是:静态代码块➞实例代码块➞构造方法,那在 加了继承关系后的执行顺序又是什么呢?
class Base { public Base() { System.out.println("父类构造方法执行"); } { System.out.println("父类实例代码块执行"); } static { System.out.println("父类静态代码块执行"); } } class Derived extends Base{ public Derived() { super(); System.out.println("子类构造方法执行"); } { System.out.println("子类实例代码块执行"); } static { System.out.println("子类静态代码块执行"); } } public class Test{ public static void main(String[] args) { Derived derived1 = new Derived(); System.out.println("==========================="); Derived derived2 = new Derived(); } }
代码运行结果如下:
可见在继承关系下的执行顺序是:
父类静态代码块➞子类静态代码块➞父类实例代码块➞父类构造方法➞子类实例代码块➞子类构造方法(静态代码块同样只执行一次)
继承方式
现实生活中事物之间的关系错综复杂,但在Java中只支持以下几种方式:
1.单继承
public class A{ } public class B extends A{ }
2.多层继承
public class A{ } public class B extends A{ } public class C extends B{ }
3.不同类继承同一类
public class A{ } public class B extends A{ } public class C extends A{ }
注:Java中不支持多继承(一个类不能同时继承两个类)
final关键字
Java中有这么一个关键字,它可以使变量或字段表示常量,使方法不能被重写,使类不能被修饰
1.修饰变量或字段,表示常量
final int a = 10;//a被修饰成常量 a = 20; // 编译出错
2. 修饰方法:表示该方法不能被重写class Base { final public void method(){ System.out.println("hhh"); }//method方法被final关键字修饰 } class Derived extends Base{ public void method(){ System.out.println("aaa"); }//编译错误 Error:102:17 java: Derived中的method()无法覆盖Base中的method() }
3.修饰类:表示此类不能被继承final public class Animal { ... }//Animal类被final修饰不能被继承 public class Bird extends Animal { ... }// 编译出错 Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
注:String字符串类介绍被final修饰,不能被继承
继承和组合
继承我们已经知道了,那什么是组合呢?
组合和继承类似 , 也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字 ), 仅仅是将一个类的实例作为另外一个类的字段。下面是一个汽车的组合:// 轮胎类 class Tire{ // ... } // 发动机类 class Engine{ // ... } // 车载系统类 class VehicleSystem{ // ... } class Car{ private Tire tire; // 可以复用轮胎中的属性和方法 private Engine engine; // 可以复用发动机中的属性和方法 private VehicleSystem vs; // 可以复用车载系统中的属性和方法 // ... }
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。
多态
多态就是多种形态,指不同对象对一个行为会产生不同的状态(如:猫吃饭是吃猫粮,狗吃饭是吃狗粮)
要实现多态,我们首先得了解重写和向上转型
重写
重写也称为覆盖。是子类对父类非静态、非 private 修饰,非 final 修饰,非构造方法等的实现过程进行重新编写, 返回值,方法名和参数列表一般都不能改变。即外壳不变,核心重写。重写的权限不能比父类中被重写的方法的访问权限低,且可以通过@Override注释来帮我们进行一些合法性检验class Base{ public void method(){ System.out.println("父类"); } } class Derived extends Base{ @Override//用于重写的合法性检查 public void method() { System.out.println("子类");//对method方法进行重写 } }
注:重写的方法若返回值构成父子类关系也是可以的
向上转型
向上装型就是将子类当做父类来使用 (子类可访问的访问比父类大,因此转化是安全的)向上转型有三种使用场景:直接赋值,方法传参和方法返回//父类 class Base{ int a; } //子类 class Derived extends Base{ int b; } public class Test{ public static Base method(Base b){//方法传参:参数为父类引用,可以接受任意子类对象 return new Derived();//作返回值:返回任意子类对象 } public static void main(String[] args) { Base derived = new Derived();//直接赋值:子类对象直接赋值给父类对象 Base base = method(derived);//父类对象接收返回的子类对象 } }
注:向上转型不能调用到子类特有的成员属性和成员方法
多态的实现
多态的实现必须在继承体系下,子类对父类中的方法进行重写,使用者通过父类的引用调用重写方法
//父类Animal class Animal{ String name; public void eat(){ System.out.println(name + "吃饭"); } public Animal(String name){ this.name = name; } } //子类Cat class Cat extends Animal{ public Cat(String name){ super(name); } //重写eat方法 @Override public void eat(){ System.out.println(name + "吃猫粮"); } } //子类Dog class Dog extends Animal{ public Dog(String name){ super(name); } //重写eat方法 @Override public void eat(){ System.out.println(name + "吃够粮"); } } public class Test{ public static void eat(Animal a){//向上转型中的方法传叁,此处的类型必须为父类 a.eat();//根据a的不同,调用不同类中重写的eat方法 } public static void main(String[] args) { Cat cat = new Cat("猫"); Dog dog = new Dog("狗"); eat(cat); eat(dog); } }
上述代码中,当类的调用者在编写 eat() 方法时,参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 a 引用指向的是哪个类型(哪个子类)的实例.此时 a 这个引用调用 eat() 方法可能会有多种不同的表现(和 a 引用的实例相关), 这种行为就称为多态。
向下转型
前面介绍了向上转型,有上就有下,Java中还有一种与向上转型对应的向下转型
向下转型是指将经过向上转型成父类的子类再通过强制转换转换成子类
class Base{ int a; } class Derived extends Base{ int b; } public class Test { public static void main(String[] args) { Base derived = new Derived();//向上转型 derived = (Derived)derived;//向下转型 } }
注:向下转型不能转换未经向上转型的类,且只能将类转换成向上转型之前的类
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 Java 中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为 true, 则可以安全转换。class Base { int a; } class Derived extends Base{ int b; } public class Test{ public static void main(String[] args) { Base derived = new Derived(); while(derived instanceof Derived){//判断是否可以进行向下转型 derived = (Derived)derived; } } }
多态的优缺点
使用多态能降低代码的圈复杂度,避免使用大量的if-else,使代码的可扩展能力更强,但
多态也会使代码的运行效率较低