Java面向对象:多态特性的学习

Posted 牛牛要坚持

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面向对象:多态特性的学习相关的知识,希望对你有一定的参考价值。

本文介绍了Java面向对象多态特性, 多态的介绍. 多态的实现条件–1.发生继承.2.发生重写(重写与重载的区别)3.向上转型与向下转型.4.静态绑定和动态绑定5. 实现多态 举例总结多态的优缺点 避免在构造方法内调用被重写的方法…

Java面向对象:多态特性的学习

一.什么是多态?

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成某一个行为时会产生出不同的状态。


彩色打印机 和黑白打印机 都具有打印行为,它们分别去打印图片,最后会产生不同的状态

总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。

二.多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下(多个子类继承一个父类)
  2. 发生向上转型(父类引用接受子类对象地址)
  3. 子类必须要对父类中方法进行重写(子类里有父类同名的方法)
  4. 动态绑定(通过父类的引用调用被重写的方法)

多态体现:在代码运行时,当父类引用接收不同子类对象时,调用父类方法实际会运行对应不同子类中的重写的该父类方法

1.认识多层继承

在这篇博客中讲到了继承->继承特性的学习
在继承体系下 也就是要有一个父类 其派生出多个子类,或者子类还派生出自己的子类
发生多态前提至少要有两个子类,才能体现出不同的状态!

示例:

class Animal //父类 动物类
    String name;
    int age;
     void eat()
        System.out.println(this.age+"岁的"+this.name+"正在吃食物");
    
    Animal(String name,int age)
        this.name=name;
        this.age=age;
    
    Animal()
        this.eat(); 
    

class Dog extends Animal //子类 :狗类继承动物类

    @Override //注解  表示下面的方法需要重写父类的方法 如果达到重写条件会报错, 帮你检查重写的错误
   
    void eat()
        System.out.println(this.age+"岁的"+this.name+"正在吃狗粮");
    
    
    void bark()
        System.out.println(this.age+"岁的"+this.name+"正在犬吠");
    

    Dog(String name, int age)
        super(name,age);

    
    Dog()
        super();
    

class Bird extends Animal  //子类: 鸟类继承动物类
    @Override
    void eat()
        System.out.println(this.age+"岁的"+this.name+"正在吃稻谷");
    
    void fly()
        System.out.println(this.age+"岁的"+this.name+"正在飞");
    
    Bird(String name,int age)
        super(name,age);
    

class Huskies extends Dog  //子类: 哈士奇类继承狗类 狗类又继承动物类 (多层继承)

    Huskies(String name, int age)
        super(name,age);
    
    void pullOf()
        System.out.println(this.age+"岁的"+this.name+"正在拆家");
    
    @Override
    void eat()
        System.out.println(this.age+"岁的"+this.name+"正在吃主人食物");
    
    Huskies()
        super();
    

上面代码实现了一个Animal父类有两个子类 Dog类和Bird类 而Dog类还有子类Huskies类
在此继承体系上可以发生多态,但是下一步还需满足 子类重写父类的方法,可以看到上面代码子类里有和父类同名的方法也就是发生了重写,而重写具体是什么呢?

2.认识重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。 即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要重新改写父类的方法。

注意:
重写针对的是方法而不是成员变量!

被静态static修饰的方法是属于类不属于对象,所以不能被继承更不能重写.

被private修饰的成员方法,虽然子类能够继承一份,但是没有访问权限,子类并不能重写此方法!

被final修饰的成员方法为密封方法,限制是不能被重写的,即虽然被继承,也能被访问,但是子类不同出现和其同名的方法!

构造方法是当前类对象特有的,是为当前对象成员初始化的,不会被继承到子类中所以不能被重写

【方法重写的规则】

子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致

特殊情况: 被重写的方法返回值类型和父类可以不同,但是必须是父子关系,即父类内方法返回值是父类类型,子类重写的方法返回值需是其子类类型 , 这种用法也叫作协变类型

子类访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected(子类访问权限必须大于等于父类访问权限)

父类被static、private修饰的方法、构造方法都不能被重写。

重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了
(比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.

当发生重写后,子类对象调用的子类和父类同名方法执行的便是子类方法,同时多个不同子类都可以有自身和父类同名的方法,即每个子类都可以重写父类的方法,这也是为实现多态的前提…

【重写的设计原则】
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。自身可以对投入的类里已有的方法进行重写,在保留原有的方法下,改写方法体…

例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。
在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,
正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了同时之前的需求也不会消失。

①.重写和重载的区别

重写和重载看起来差不多,都是具有两个以上的具有相同的方法名,但其他地方有很多不同

区别点重写 (override)重载 (override)
继承关系必须要发生继承 要在子类和父类里继承和非继承下都可以
是否依赖于对象需要是成员方法且 非private 非static 非final 非构造不依赖对象,只要有访问权限其他情况都可以
参数列表(类型,个数,顺序)必须一致必须修改 三者至少要有一个不同
返回类型不能修改【除非可以构成父子类关系】可以修改
访问限定符子类权限一定要大于等于父类权限可以不同但是要有访问权限

重写要求比重载更严格,重载一般发生在一个类里(也可以在父子类) 重写一定要在父子类

即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

3.向上转型和向下转型

向上转型和向下转型都是发生在继承关系下

①.认识向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
狗类当做动物来看待,那么狗只能做动物的行为,不能再做狗自己的行为

即 用父类引用 来接受子类对象地址
语法格式:父类类型 对象名 = new 子类类型();

Animal animal = new Dog("小白",3); // 动物类 引用 接收 实例化的狗类对象

animal是父类类型,但可以引用其子类对象,
子类对象看成是具有继承的父类部分和自身特有的部分,而父类对象只存在自身特有的部分或者继承其自己父类的部分 相当于父类对象部分是子类对象的子集

而发生向上转型就是子类对象抛弃掉子类自己特有的, 此时父类引用也就只能访问父类本身特有的部分或者父类继承以上父类的部分,不能再访问子类自身特有的部分

从类和对象角度来看, 父类的范围比子类更广, 狗类和猫类都是动物类,所以狗类和猫类都可以转型为动物类

【向上转型使用场景】

  1. 直接赋值
Animal animal1=new Dog("大白",4); //父类引用 接受子类对象地址 发生向上转型
  1. 方法传参
 void func1(Animal animal)
        System.out.println(animal.name);//发生向上转型 只能访问子类继承的父类自己的部分 此时name在之前也被修改了 
    

//....
  Dog dog=new Dog("小白",3);  //狗类对象
 func1(dog);  //dog是子类引用存放子类对象地址 传参过去被父类引用接受 发生向上转型


  1. 方法返回
static Animal func2()
        return new Dog("小黄",5); //返回的是子类对象 但是通过返回类型是Animal 在返回的时候会编译器会将其转换为Animal类型 也是向上转型
    

向上转型的优点:让代码实现更简单灵活。当一个方法内可能会出现不同子类对象地址,但返回值类型只能有一个,而使用到向上转型即可以使不同子类对象转型为其父类对象返回

向上转型的缺陷:不能调用到子类特有的方法。
向上转型后,其只能访问当前子类对象里继承父类引用其自身成员以及其父类以上的父类成员,不能再访问子类对象自身的方法

如果向上转型后要想访问到子类特有的方法还需要用到向下转型…

②.认识向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。

向上转型由子类转为父类,而子类本身就是由父类派生进化而来,狗类对象转换为动物是安全的.

但是向下转型是由父类转换为子类,其前提必须得先发生向上转型,且向下转型的类型必须是对应的向上转型的子类,不能是其他子类,

哈士奇类转换为动物.其向下转型必须是动物转换为狗类或者哈士奇类,但是不能是动物转换为猫类… 所以向下转型也会有不安全的情况

 public static void main(String[] args) 
        Animal animal=new Dog("小白",1);  //发生向上转型
        Dog dog=(Dog) animal;  //通过强转 发生向下转型 
        dog.eat();//可以访问狗类自己的行为
//        上面这种写法虽然可以,但是这样向下转型不安全↓!!

        Dog dog1=(Dog)new Animal("小狗",2);
        dog1.eat();
//        在未经过向上转型时 不能直接向下转型 !!!
//
        Animal animal1=new Bird("小飞",2);
       Dog dog3=(Dog)animal1;
//        在向上转型后 不能向下转型为其他不同子类类型!!!
        Animal animal2 =new Huskies("小哈",3);
 


可以看到,当没有发生向上转型前 通过强转发生向下转型 和 发生向下转型 但是强转为另外的子类类型 都会抛出 ClassCastException --类型转换异常 …

所以 向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof 关键字,如果该表达式为true,则可以安全转换。

示例:


 public static void main(String[] args) 
Animal animal2 =new Huskies("小哈",3);
        if(animal instanceof Dog)  // animal父类引用 指向的对象 是由Dog类向上转型
            //在向下转型之前 先进行判断 左边这个引用变量指向的对象是否是由右边类实例化的对象向上转型的 ,如果是则为true可以在里面进行强转 否则false跳过
           Dog dog1=(Dog) animal;
            dog.eat();
        
        Animal animal1=new Bird("小飞",2);

        if(animal1 instanceof  Dog)  //animal1 是由Bird类对象 向上转型 此处表达式为false
            Dog dog2=(Dog)animal1;   //
        
        if(animal2 instanceof Dog)// 虽然是哈士奇类转到动物类 但是期间经过了狗类也属于是狗类转型上去的 但是不能是转型的类的子类转型上去的
            
            Dog dog3=(Dog) animal2; // 当向下转型时如果转的类型是原来向上转型前的对象的父类又是当前父类引用的子类时也可以转,

            dog3.eat(); //  此时之前转型发生的重写 绑定关系不会变 dog类使用的eat方法是 哈士奇类的


        


可以看到通过instanceof 关键字 来判断 父类引用内接受的是否是由对应子类向上转型而来的, 是则返回true 即可发生向下转型 不是则false跳过,避免了向下转型因为一些疏忽而造成不安全的行为抛出了异常
同时看到当huskies对象给 Animal 接受 虽然不是直接父类,但是其能转型为 huskies的父类->狗类 , 但调用其狗类的方法 发现执行的是huskies自身的方法,并不是狗类自身的,这里也就发生了下面要讲的 动态绑定!

4.静态绑定和动态绑定

①.认识静态绑定

静态绑定也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表方法重载。

在此篇博客中讲到了方法重载->方法重载
学了方法重载后,可以知道即便有多个方法名相同的方法 编译器判定方法名实际上是根据方法签名,也就是最后的方法名,在编译期间就能确定所调用的是哪个重载方法,最后运行时即运行对应的方法体

②.认识动态绑定

动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

动态绑定发生在继承关系,并且子类重写的父类的方法,而此时站在父类角度下调用父类的方法,最后并不会执行父类自身的方法,而会执行子类里重写的方法

class Animal
    String name;
    int age;
     void eat()
        System.out.println(this.age+"岁的"+this.name+"正在吃食物");
    


class Dog extends Animal
void eat()
        System.out.println(this.age+"岁的"+this.name+"正在吃狗粮");
    


//main...
 Animal animal1=new Dog("大白",4); //父类引用 接受子类对象地址 发生向上转型
 animal1.eat();  // 父类引用 调用父类的方法 当父类方法 在子类里被重写了 此时会发生动态绑定 调用的是被子类重写后的父类方法!


可以看到,当父类引用接受子类对象地址,调用父类方法时,本应该执行的是父类自身的方法xx岁在吃食物, 但是因为子类又重写的父类的此方法,而此时实际上运行的是子类重写的这个方法,执行到4岁大白正在吃狗粮 这也就是发生了动态绑定

而当在运行窗口 中通过反汇编指令 javap -c 字节码名 查看到 在编译阶段,实际上是执行的Animal 类的 eat方法 ,但是最后运行的是Dog类的eat方法

可以看到,动态绑定即在编译时并不确定要执行哪个方法,只有在运行程序的时候,才会知道具体是执行哪个方法,因为子类的方法重写了父类的方法,在运行时,虽然调用的是父类的方法,但是最后会执行子类里的方法体,这也是发生多态的最后一点…

5.多态的实现

实现多态需要满足上面的条件: 发生继承关系,发生子类重写父类方法 ,发生向上转型,发生动态绑定 ,通过一个父类引用接受不同子类对象的地址,使用父类引用调用父类被重写的方法,最后会运行不同子类所重写的父类的方法…
即多个对象执行同一个行为,呈现出不同的状态结果…

示例:

static void func(Animal animal) 
//多态: 当一个父类引用 存放不同子类对象地址时,可以表现出不同的子类对象行为!!
        animal.eat();//调用父类自身的eat方法 执行的是不同子类自身的方法

    
public static void main(String[] args) 
        Dog dog=new Dog("小狗",1);
        Bird bird=new Bird("小鸟",2);
        Huskies huskies=new Huskies("哈士奇",3);
        func(dog);  //传狗类对象
        func(bird); //传鸟类对象
        func(huskies); //传哈士奇对象
    


通过一个方法,一个形参,实现了不同对象的行为,
不同对象通过同一个行为,展现出了不同的状态,这便是多态!!!

三.多态的优缺点

假设有如下代码:

class Shape 
//属性....
public void draw() 
System.out.println("画图形!");
   


class Rect extends Shape
@Override
public void draw() 
System.out.println("♦");
   


class Cycle extends Shape
@Override
public void draw() 
System.out.println("●");
   


class Flower extends Shape
@Override
public void draw() 
System.out.println("❀");
   

当想输出对应的图像即实例化对应的对象然后调用其draw即可,
当我们想一次性输出多个不同的图形呢,我们可以写对应个数的实例化对象语句依次调用,但是这些重复性代码可以通过数组循环来优化↓

public static void drawShapes() 
Rect rect = new Rect();  //实例化 三个图形对象
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = "cycle", "rect", "cycle", "rect", "flower";//根据要输出的图形顺序规律对应创建一个字符串数组

for (String shape : shapes)  //foreach 遍历字符串数组

if (shape.equals("cycle"))   // 通过分支判断当前数组访问的是哪个字符串调用对应的对象方法输出对应的图形

cycle.draw();
 else if (shape.equals("rect")) 
rect.draw();

 else if (shape.equals("flower")) 
flower.draw();






上面代码 用到了数组 循环 分支 根据我们想要输出的图形顺序 调用不同对象的方法输出对应图形 , 后续还想输出已有的图形 只需在字符数组里增加字符串对象即可, 但是这种写法if–else较多,且要输出新图形时,还需要增加分支,从而使得代码可读性较差,扩展性也不高
当我们使用多态后↓

public static void drawShapes() 
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = new Cycle(), new Rect(), new Cycle(),
                  new Rect(), new Flower();// Shape数组类型 根据顺序接受不同子类对象的地址
                  // 即数组每个元素 父类引用变量都接受了子类对象地址发生向上转型
for (Shape shape : shapes)  //通过foreach遍历
shape.draw();  //每个父类引用 调用自身的draw方法 发生动态绑定执行子类的draw方法
               //实现了 调用同一个方法 执行不同对象的重写方法 展现出不同的结果


上面代码使用多态的思想也可以做到输出指定个数的图形,且想输出其它图形甚至新图形,新图型类需继承Shape类重写其draw方法后在数组位置新增子类对象地址即可,
最后通过父类引用调用同一个方法会执行其对应子类重写的方法, 简写了代码去除了大量的if else 代码可读性高, 扩展性强

【使用多态的好处】

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
    什么叫 “圈复杂度” ?

圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如 果有很多的条件分支或者循环语句,
就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.
如果一个方法的圈复杂度太高, 就需要考虑重构. 不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10

  1. 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低

对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低. 而对于不用多态的情况, 就要把
drawShapes 中的 if - else 进行一定的修改, 改动成本更高.

多态缺陷:代码的运行效率降低。

  1. 属性没有多态性 当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
  2. 构造方法没有多态性

四.避免在构造方法内调用被重写的方法

我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func 如下代码所示:
最后运行结果会是什么?

class B 
public B() 
// do nothing
func();   //构造方法内执行 子类和父类同名的方法

public void func() 
System.out.println("B.func()");




class D extends B 
private int num = 1;
@Override
public void func() 
System.out.println("D.func() " + num);



public class Test 
public static void main(String[] args) 
D d = new D();  // 实例化D对象




可以看到 最后输出了D.func() 0
在实例化子类对象D时,此时子类对象和父类对象有同名的方法此时已经发生了重写,
而在给子类构造前会先给父类构造,给父类构造调用父类构造方法 在里面又调用了func()方法
而此时站在父类的角度下调用子类和父类同名的方法(被子类重写的方法)实际上执行的是子类的方法,
而此时子类还没有构造, num虽然申请了空间 还没有就地初始化,所以里面是默认值0
最后输出了D.func()0

构造 D 对象的同时, 会调用 B 的构造方法. B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的func 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果构造方法具备多态性会先执行子类构造方法给num初始化,num的值应该是1.但是构造方法不具备多态性,
所以在构造函数内,尽量避免使用实例方法,除了final和private方法(使子类不能重写父类方法)。因为其在调用前可能被子类重写了,而导致动态绑定执行子类的方法

结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 站在父类角度调用被重写的方法就会触发动态绑定执行子类的方法, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题

java学习中,面向对象的三大特性:封装继承多态 以及 super关键字和方法的重写(java 学习中的小记录)

java学习中,面向对象的三大特性:封装、继承、多态 以及 super关键字和方法的重写(java 学习中的小记录)

作者:王可利(Star·星星)

 

封装

    权限修饰符:public 公共的,private 私有的

    封装的步骤:

         1.使用private 修饰需要封装的成员变量。

         2.提供一个公开的方法设置或者访问私有的属性

             设置 通过set方法,命名格式:     set属性名();  属性的首字母要大写

             访问 通过get方法,命名格式:     get属性名();  属性的首字母要大写

 

1 //姓名设置器方法
2     public void setName(String name){
3         this.name = name;
4     }
5     //姓名访问器方法
6     public String getName(){
7         return name;
8     }

 

    代码书写的规范:java开发中一般类成员变量(属性),都是封装起来的 。

问题:是不是所有的属性都需要使用 set 和 get 方法?

    不一定,按照需求来定。

封装的作用:1. 框架  2. 工具类

封装的好处:1. 提高了数据的安全性   2. 操作简单   3. 隐藏了方法的实现。

 1 class Student{
 2     private String name;//成员变量被修饰之后只能在本类中进行访问了,私有的了。
 3     private String sex;//如果需要在外部进行访问,就需要访问方法了 set 和 get 方法。
 4     private int age;//需要收集,但是不公开,age 就只需要一个set方法就可以了
 5     
 6     //两个参数的构造方法
 7     public Student (String name,String sex){
 8         this.name=name;
 9         if(sex.equals("男")||sex.equals("女")){
10             this.sex = sex; 
11         }
12     }
13     //普通方法
14     public void study(){
15         System.out.println(name+"在学习");
16     }
17     //性别设置器方法
18     public void setSex(String sex){
19         if(sex.equals("男")||sex.equals("女")){
20             this.sex = sex;
21         }else{
22             System.out.println("你的输入不合法,请重新输入..");
23         }
24     }
25     //性别访问器方法
26     public String getSex(){
27         return sex;
28     }
29     //姓名设置器方法
30     public void setName(String name){
31         this.name = name;
32     }
33     //姓名访问器方法
34     public String getName(){
35         return name;
36     }
37     //age设置器
38     public void setAge(int age){
39         this.age = age;
40     }
41 }
42 public class fengzhuang {
43 
44     public static void main(String[] args) {
45         Student star = new Student("星星","性别");//性别  是不合法的操作
46         star.study();
47         System.out.println(star.getSex());//性别 null
48     }
49 }

 

继承

特点:继承父类的属性和方法。 特性:方法的复写(重写)  单继承(多层继承)不是多继承,c++才是多继承一个子类N个父类。

java 中的继承 和 OC中一样。

继承的作用:优化代码,减少代码的重复使用

A:B OC中继承的写法,java中如何表现继承关系,用关键字 extends 表示继承。

继承中注意事项:

1.不要为了继承而继承,千万不要为了节省代码,任何关系都继承,这样就不伦不类了。

2.父类的私有成员变量是不能够被继承的。

3.父类的构造方法是不能被继承的。

4.父类的私有方法不能被继承。

5.子类调用自己的构造方法时,会默认调用父类中的无参构造方法。

6.子类不能够继承不在一个包中默认权限的成员变量。

 

为什么会调用父类的构造方法?
子类在创建对象的时候初始化父类的变量。

 

技巧:毕竟这两个属性是从父类上面继承下来的,初始化交给父类会好一点。。

super(name,sex);//一般由自己指定(定义),如果自己指定了下面的 super()就不可以调用了。

super();//调用的是父类的构造方法

this();//调用的是自己的构造方法

 

继承代码如下:

 1 package 面向对象的三大特性;
 2 
 3 class Person{
 4     String name;//默认权限 friendly(友好的) private、public、protected
 5     String sex;
 6     Dog dog;//人和狗建立了 关联关系      整体->部分
 7     public void eat(){
 8         System.out.println(name+"在吃饭");
 9     }
10 }
11 
12 //定于学生类继承父类
13 class Student extends Person{
14     int age;
15     public void study(){
16         System.out.println(name+"在学习");
17     }
18 }
19 
20 public class jicheng {
21 
22     public static void main(String[] args) {
23         Student s = new Student();
24         s.name = "WKL";//name 自己没有 父类有
25         s.age = 20;//age 自己有的
26         System.out.println(s.name+"今年"+s.age+"岁了..");
27     }
28 }

 

关联关系(不是继承):

 1 class person{
 2     String name;
 3     String sex;
 4     Dog dog;//人和狗建立了 关联关系      整体->部分
 5     public void eat(){
 6         System.out.println(name+"在吃饭");
 7     }
 8 }
 9 
10 class Dog{
11     String name;
12     public void cry(){
13         System.out.println(name+"在叫");
14     }
15 }
16 
17 
18 public class jicheng {
19 
20     public static void main(String[] args) {
21         person star = new person();
22         star.name = "星星";
23         Dog dog = new Dog();
24         dog .name = "小白";
25         
26         star.dog = dog;//把狗这个对象 给 star的dog对象成员属性
27         /*dog.cry();*/
28         star.dog.cry();//星星的狗在叫
29         star.dog = null;
30         star.eat();
31     }
32

super关键字(指向父类对象的引用空间)

作用:

    1.当子类和父类存在同名的成员变量时,会先在子类里面找默认有一个this(this.name),可以通过super来调用父类的成员变量(super.name)。

    2.super可以用来调用父类的构造方法。

super使用的注意的地方:

    1.用super调用父类构造方法,必须是构造方法中的第一个语句。

    2.super只能出现在子类的方法或者构造方法中。

    3.super 和 this 不能够同时调用构造方法。(因为this也是在构造方法的第一个语句)

总结:super 和 this 的区别:

   1.代表的事物不一样:

        this:代表所属方法的调用者对象。

        super:代表父类对象的引用空间。

    2.使用前提不一致:

        this:在非继承的条件下也可以使用。

        super:只能在继承的条件下才能使用。

     3.调用构造方法:

         this:调用本类的构造方法。

         super:调用的父类的构造方法

方法的复写(重写)

作用:父类的方法满足不了子类的实现,这个时候就需要通过复写(重写)方法再实现父类的行为。

方法的复写使用注意事项:

    1.方法重写的时候,必须存在继承关系。

    2.方法重写的时候,方法名和形式参数 必须跟父类是一致的

    3.方法重写的时候,子类的权限修饰符必须要大于或者等于父类的权限修饰符。( private < protected < public,friendly < public )

    4.方法重写的时候,子类的返回值类型必须小于或者等于父类的返回值类型。(  子类 < 父类 )  数据类型没有明确的上下级关系

    5.方法重写的时候,子类的异常类型要小于或者等于父类的异常类型。

方法的重载:方法名是一样的,只是参数列表的个数,类型,顺序不一样。

 

多态(一个对象同种类型不同表现形态

父类的引用类型变量指向子类的对象。

接口的引用类型变量指向类的实现。

多态使用注意事项:

    1.多态情况下,父类 和 子类存在同名的成员变量,无论是静态的还是非静态的变量,默认访问的是父类中的成员变量。

    2.多态情况下,父类 和 子类存在同名的非静态方法,访问的是子类的非静态方法。

    3.多态情况下,父类 和子类存在同名的静态方法,访问的是父类的静态方法。

    4.多态情况下,不能访问子类特有的属性、方法。

    5.多态满足的条件:必须要有继承关系。

总结:多态情况下,子类 和 父类如果存在同名的成员,访问的都是父类,除了同名的非静态变量访问的才是子类。

java 编译器编译原理导致的。

编译看左边,运行不一定看右边。

编译看左边的意思:java 编译器在编译的时候会检测引用类型中含有指定的成员,如果没有就会报错。子类的成员是特有的,父类的没有的,所以他是找不到的。

 1 package study;
 2 
 3 abstract class Animal{
 4     String name;
 5     static String color = "动物色";
 6     
 7     public abstract void run();
 8     
 9     public void eat(){
10         System.out.println("这个是父类中的方法");
11     }
12 } 
13 
14 class Dog extends Animal{
15     static String color = "黄色";
16     //重写父类中的方法
17     public void run(){
18         System.out.println(name+"用四条腿在跑步");
19     }
20     //重写父类中的eat方法
21     public void eat(){
22         System.out.println("这个是子类中的方法");
23     }
24 }
25 
26 class Fish extends Animal{
27     
28     //重写父类的方法
29     public void run(){
30         System.out.println(name+"在自由自在地游泳");
31     }
32 }
33 
34 public class star {
35     public static void main(String[] args) {
36         
37        Animal a = new Dog();//Animal 指针指向 Dog
38        a.name = "小黄";
39        a.run();
40        
41        a.eat();
42        System.out.println(a.color);
43        
44 //       a = new Fish();//Animal 指针指向 Fish
45 //       a.name = "小黄";
46 //       a.run();   
47     }
48 }

多态使用的场景:

    1.多态可以用于做形式参数,可以让方法接收更多的类型。

    2.多态用于返回类型,可以返回更多的数据类型。

问题:想用子类的类型接收回来,这个时候就需要用到强制转换。

         如:Animal  a = new Dog(); //多态

               Dog  d = (Dog)a; 

多态的另一种实现:

    实现关系下的多态:

        接口  变量名  = new  实现类();

    接口是不能创建对象的,所以就使用实现这个接口的类来创建对象

    我返回的是圆的对象,不能用圆的引用变量来接收:返回的图形 -> 圆的父类

    基本数据类型的转换:大的数据 -> 小的接收(强制转换)

 1 package study;
 2 
 3 //需求1:定义一个方法可以接收任意类型 的图形对象,求面积和周长。
 4 //需求2:定于一个方法可以返回任意类型的图形。
 5 abstract class MyShape{
 6     //定义两个方法求面积和周长。
 7     public abstract void getArea();
 8     public abstract void getLength();
 9 }
10 
11 class Circle extends MyShape{
12     int r;
13     final double PI = 3.14;
14     
15     //重写父类中的方法
16     public void getArea(){
17         System.out.println("圆的面积");
18     }
19     public void getLength(){
20         System.out.println("圆的周长");
21     }
22 }
23 
24 class Rect extends MyShape{
25     
26     public void getArea(){
27         System.out.println("矩形的面积");
28     }
29     public void getLength(){
30         System.out.println("矩形的周长");
31     }
32 }
33 
34 public class star {
35     public static void main(String[] args) {
36         
37         //需求1:定义一个方法可以接收任意类型 的图形对象,求面积和周长。
38         getAreaAndLength(new Circle());
39         getAreaAndLength(new Rect());
40         
41         //需求2:定于一个方法可以返回任意类型的图
42         MyShape a = getMyShape(0);//
43         /*getAreaAndLength(a);//在这里拿到的是哪个型就计算哪个的面积和周长。*/    
44         
45         Circle c = (Circle)a;//引用数据类型的强制转换
46         getAreaAndLength(c);//在这里拿到的是哪个型就计算哪个的面积和周长。
47     }
48     
49     public static void getAreaAndLength(MyShape ms){//这个参数拿到的是父类的对象,凡是属于这个父类的都可以使用这个方法。如果是圆就只能接收圆的
50         ms.getArea();
51         ms.getLength();
52     }
53     
54     public static MyShape getMyShape(int i){
55         if (i == 0) {
56             return new Circle();//
57         }else if (i == 1) {
58             return new Rect();//矩形
59         }
60         return null;
61     }
62 }

以上是关于Java面向对象:多态特性的学习的主要内容,如果未能解决你的问题,请参考以下文章

java学习中,面向对象的三大特性:封装继承多态 以及 super关键字和方法的重写(java 学习中的小记录)

黑马程序猿——JAVA面向对象的特性:封装,继承,多态

学习Java之面向对象

java特性之封装

Java学习 -- 多态性

Java学习 -- 多态性