继承和多态
继承
继承,对共性进行抽取,从而实现代码的复用
继承的语法
在Java中要表示类之间的继承的关系,要使用extends关键字
public class 子类 extends 父类{
}
继承的快速入门
创建一个animal类,在创建一个cat类,dog类,让cat和dog去继承animal类
class Animal{
String name;
int age;
public void eta(){
System.out.println(name+"在吃饭");
}
public void sleep(){
System.out.println(name+"在睡觉");
}
}
class Cat extends Animal{
public void mimi(){
System.out.println(name+"正在咪咪叫");
}
}
class Dog extends Animal{
public void wangwang(){
System.out.println(name+"正在汪汪叫");
}
}
public class MAIN {
public static void main(String[] args) {
Dog dog = new Dog();//创建一个子类对象
//dog中没有定义任何成员变量,name和age是从父类继承下来的
dog.name = "小花";//通过子类的引用去访问
dog.age = 10;
//dog中也没用eat方法和sleep方法,eat和sleep都是从父类继承下来的
dog.eta();
dog.sleep();
}
}
继承访问父类的成员
访问父类的成员属性
在继承体系中,子类将父类的方法和字段直接继承下来,如何在子类中访问父类的成员变量
1:子类和父类中成员变量没有重名的情况
//访问父类的成员变量 子类和父类的成员变量不重名时
class Base{
int a;
int b;
}
class Bus extends Base{
int c;
public void func(){
a = 10;//访问父类继承下来的a
b = 20;//访问父类继承下来的b
c = 30;//访问自己的c
}
}
2:子类和父类成员变量中有同名的情况
//访问父类的成员变量 子类和父类的成员变量重名时
class AA{
int a;
int b;
}
class BB extends AA{
int a;
int b;
int c;
public void method() {
a = 10; //访问子类的属性
b = 20;//子类的属性
c = 30;
}
}
注意:
- 在方法中或者子类对象中访问对象成员时,如果访问的成员变量子类中有,则访问子类中的属性,如果子类中没有找到,则访问父类继承下来的,如果父类也没有定义,则编译报错。如果子类中有和父类同名的属性,如果没有有super关键字,则优先访问自己的,如果有super,则访问父类的属性
- 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
访问父类的成员方法
1:成员方法名字不同
class base{
public void methodA(){
System.out.println("父类A");
}
}
class s extends base{
public void methodB(){
System.out.println("子类B");
}
public void methodC(){
methodA();//访问父类的methodA
methodB();//访问子类的methodB
}
}
public class MAIN02 {
public static void main(String[] args) {
s s = new s();
s.methodC();
}
}
输出:
父类A
子类B
注意:
成员方法没有同名时,在子类中或者通过子类对象去访问的话,则先到子类中寻找,如果找到,则访问,
如果没有找到,则到父类中找,如果父类中没有找到,则编译报错
2:成员方法名字相同
//访问成员方法中有同名的方法
class a{
public void methodA(){
System.out.println("父类A");
}
public void methodB(int n){
System.out.println("父类B");
}
}
class b extends a{
public void methodA(int n){
System.out.println("子类A");
}
public void methodB(){
System.out.println("子类B");
}
public void methodC(){
methodA(10);//子类A 有传参 先从子类找有参数的methodA方法
methodB(10);//父类B 有传参 先从子类找有参数的methodB方法,
// 子类没有找到有参数的methodB方法,到父类中找
methodB();//子类B 无参,先到子类中找无参的,如有则访问
}
}
public class MAIN03 {
public static void main(String[] args) {
b b = new b();
b.methodC();
}
}
注意:
- 通过子类对象访问子类或者父类中有不同名的方法时,优先在子类中寻找,找到则访问,子类中没有则在父类中寻找,如果有,则访问,如果没有则编译错误
- 如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;
super关键字
如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
这里可以使用super关键字
super关键字的作用,在子类中访问父类的成员变量和成员方法
class Person{
int a;
int b;
public void methodA(){
System.out.println("父类A");
}
public void methodB(){
System.out.println("父类B");
}
}
class Student extends Person{
int a;
int b;
public void methodA(){
System.out.println("子类A");
}
public void methodB(){
System.out.println("子类B");
}
public void methodC(){
//访问子类的成员属性 名字相同
a = 10;//访问的子类 对于同名的属性和方法,则现在子类中寻找,找到则访问
b = 20;//子类 相当于 this.a = 10;this.b = 20;
//访问父类的成员属性 因为父类和子类的成员属性是同名,则需要使用super
super.a = 20;
super.b = 30;
//访问子类的成员方法 名字相同
methodA();
//如果是父类的methodA(),就需要super
super.methodA();//访问的是父类的methodA();
}
}
public class SUPER {
public static void main(String[] args) {
}
}
注意:
super只能在非静态方法中使用
在子类的方法中,访问父类的成员属性和方法
子类的构造方法
继承可以理解为父子,那么父子父子,肯定是先有父,在有子,在使用构造方法是,需要先调用父类的构造方法完成父类的初始化,在完成子类的初始化。
class Person{
public Person() {
System.out.println("父类的无参构造1");
}
}
class Student extends Person{
public Student() {
super();//编译器会自动添加一个super();默认会调用父类的无参构造
//必须放在子类构造器的第一行,只能出现一次
System.out.println("子类的无参构造2");
}
}
public class MAIN01 {
public static void main(String[] args) {
Student student = new Student();
}
}
输出:
父类的无参构造1
子类的无参构造2
注意:
子类对象中的成员是由两部分组成,一部分是自己的,一部分是继承父类的,所以在构造对象的时侯,先调用父类的构造方法初始化父类的成员,在调用子类的构造方法初始化子类的成员,将自己的成员构造完整,
class p1{
String name;
int age;
public p1(String name, int age) {
this.name = name;
this.age = age;
}
}
class p2 extends p1{
double salary;
public p2(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
}
public class MAIN02 {
public static void main(String[] args) {
p2 p = new p2("hello",18,2000);
}
}
父类的成员由父类初始化,子类的成员由子类初始化。
super和this
- super和this都可以在成员方法和构造方法中使用
- 在成员方法中使用this表示访问当前对象的引用,可以访问当前对象的属性和方法。
- 在构造方法中使用this表示访问当前类的其他构造方法。
- super在成员方法中使用表示访问父类的其他成员方法。
- 在构造方法中使用表示访问父类的其他构造方法。
相同点:
- 只能在类和非静态方法中使用,用来访问非静态的成员方法,因为静态成员方法不属于任何对象。所有使用this和super 会报错
- 在构造方法中使用,只能放在第一行,并且不能同时存在。
不同点:
- this表示当前对象的引用,super相当于子类从父类继承下来的部分成员的引用,但是绝不是父类的引用。
- 在非静态的成员方法中,this表示访问本类的方法和属性,super表示访问父类继承下来的方法和属性
- 在构造方法中,this(…)表示本类的构造方法,super(…)表示调用父类的构造方法,但是要注意,两种情况不能同时出现
- 构造方法中一定会有super的调用,即使没有写,但编译器也会增加
代码的执行顺序
在没有继承关系的代码里面,执行顺序是
静态代码块 实例代码块 构造方法
注意:静态代码块只有在类加载的时候执行一次,只执行一次。
继承关系上的执行顺序
class A{
public A() {
System.out.println("父类无参构造");
}
static{
System.out.println("父类的静态代码块");
}
{
System.out.println("父类的实例代码块/构造代码块");
}
}
class B extends A{
public B() {
System.out.println("子类无参构造");
}
static{
System.out.println("子类静态代码块");
}
{
System.out.println("子类实例代码块/构造代码块");
}
}
public class MAIN01 {
public static void main(String[] args) {
B b = new B();
System.out.println("===========");
B b1 = new B();
}
}
输出:
父类的静态代码块
子类静态代码块
父类的实例代码块/构造代码块
父类无参构造
子类实例代码块/构造代码块
子类无参构造
===================
父类的实例代码块/构造代码块
父类无参构造
子类实例代码块/构造代码块
子类无参构造
静态代码块>实例代码块/构造代码块>构造方法
- 可以看出来静态的代码块只会执行一次,而且父类的静态代码块优先于子类的静态代码块
- 父类的实例代码块和父类的构造方法是紧接着执行的
- 子类的实例代码块和子类的构造方法紧接着再执行
注意:
在第二次实例化对象时,静态代码块将不会执行
下面代码将会输出什么?
class X{
Y y=new Y();//1
int a =10;
int b = 20;
public X(){//2
System.out.print("X");
}
}
class Y{
public Y(){//3
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();//4
public Z(){//5
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
需要注意的是在执行构造方法之前要先初始化本类的成员属性。
应该输出 YXYZ
代码的执行顺序:
- 父类的静态
- 子类的静态
- 父类的实例
- 父类的构造方法
- 子类的实例
- 子类的构造方法
final关键字
final可以用来修饰变量,成员方法和类。
修饰变量或字段:表示成为常量 不能修改
final int a = 10;
a = 20; // 编译出错
修饰类:表示此类不能被继承
final public class A{
}
public class a extends A{
}
//编译报错
多态
多态是什么:当不同的对象去完成不同的事,会产生不同的状态
多态的前提:继承
从程序的角度理解多态
发生在不同对象上,就会产生不同的结果
多态实现的条件
- 必须在继承关系下
- 子类必须对父类的方法进行重写
- 通过父类的引用去调用重写的方法
多态的体现
当我们的代码运行时,当传递不同的对象,就会调用对应类中的方法
class Animal{
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃骨头");
}
}
public class test01 {
public static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("小花",28);
Dog dog = new Dog("大黄", 18);
eat(cat);
eat(dog);
}
}
-
编译器在编译代码时并不知道要调用dog或者cat的eat方法
-
public static void eat(Animal animal){ animal.eat(); }//等程序运行起来之后,形参animal引用的对象确定后,才知道调用的是那个的方法
-
但是形参的类型必须是父类的类型才可以 父类的引用可以指向子类
运行结果
小花吃鱼
大黄吃骨头
当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 animal 引用指向的是哪个类型(哪个子类)的实例. 此时 animal这个引用调用 eat方法可能会有多种不同的表现(和 animal 引用的实例 相关), 这种行为就称为 多态.
重写
重写(override):也称为覆盖。重写是子类对父类实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写:
- 方法名称相同
- 参数列表相同
- 返回值相同
注意:
- 如果是private修饰的方法,则不能被重写
- static修饰的方法是不能被重写的
- 子类的访问修饰限定权限要大于等于父类的权限
- private<默认<protected<public
- 被final修饰的方法不能被重写,这个方法被称作密封方法
重写应用场景
当有一个类,不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用
正确做法是:新建一个类,对原有的这个方法重写就好了,这样就达到了我 们当今的需求了。
静态绑定和动态绑定
静态绑定:程序在编译时,编译器根据用户的参数,就已经知道了要调用用哪些方法,典型的方法重载
动态绑定:即程序在编译时,不能确定要掉那个具体的方法,只有等程序运行时,才能确定调用那个具体的方法
动态绑定 是多态的基础 运行时帮我们调用了重写的方法
向上转型和向下转型
向上转型
当我们发生向上转型之后,通过父类的引用,只能访问父类自己的成员,和子类中重写了父类的方法,不能访问子类特有的成员
父类的引用指向了子类的对象
语法格式 父类类型 引用名 = new 子类类型()
Animal animal = new Cat();
Animal animal = new Cat("小花",10);//向上转型
animal.eat();
向上转型的缺点:不能调用子类特有的方法
向上转型的优点:让代码更加的灵活
class Animal{
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void show(){
System.out.println("这是子类特有的方法");
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class test01 {
public static void main(String[] args) {
Animal animal = new Cat("小花",10);
animal.eat();
animal.show();//报错,向上转型不能调用子类特有的方法
}
}
向下转型
那如果一定要调用子类中特有的方法呢?
此时可以使用向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象即可,即向下转换。
但是向下转型是不安全的,万一转换失败,程序就会报错。
向下转型的前提:
先进行向上转型 向上转型之后,再进行向下转型
- 要强转的引用必须指向当前类型的目标对象
- 此时animal指向Cat()
- 向下转型就是将animal指向的猫还原为猫
- 只能强制转换父类的引用,不能强制转换父类的对象
class Animal{
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
public void show(){
System.out.println("这是子类特有的方法");
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class test01 {
public static void main(String[] args) {
Animal animal = new Cat("小花",10);//向上转型
animal.eat();
animal.show();//报错,向上转型不能调用子类特有的方法
Cat cat = (Cat)animal;//向下转型第一种写法
cat.show();
//((Cat)animal).show();//向下转型第二种写法
}
}
Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。
class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + "吃饭");
}
}
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
public void show() {
System.out.println("这是子类特有的方法");
}
@Override
public void eat() {
System.out.println(name + "吃鱼");
}
}
public class test01 {
public static void main(String[] args) {
Animal animal = new Cat("小花", 10);
animal.eat();
//animal.show();//报错,向上转型不能调用子类特有的方法
//要强转的引用必须指向当前类型的目标对象
//此时animal指向Cat()
//向下转型就是将animal指向的猫还原为猫
if(animal instanceof Cat){ //判断animal引用是否指向了Cat对象
Cat cat = (Cat)animal;//向下转型第一种写法
cat.show();
}
//((Cat)animal).show();//向下转型第二种写法
}
}