博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

04java基础——多态

Posted on 2019-01-22 21:52  心默默言  阅读(263)  评论(0编辑  收藏  举报

1.多态

1.1多态的概念

  现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。

  所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

       比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

      酒 a = 剑南春

      酒 b = 五粮液

      酒 c = 酒鬼酒

      …

      这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

1.2多态的要点

1.多态是方法的多态,不是属性的多态(多态与属性无关)

2.多态存在要有3个必要条件:继承、方法重写、父类引用指向子类对象。

3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。

1.3多态的定义

多态的定义格式:就是父类的引用变量指向子类对象

父类类型  变量名 = new 子类类型();

变量名.方法名();

 

  • 普通类多态定义的格式

父类 变量名 = new 子类();

如: class Fu {}

    class Zi extends Fu {}

    //类的多态使用

Fu f = new Zi();

 

  • 抽象类多态定义的格式

抽象类 变量名 = new 抽象类子类();

如: abstract class Fu {

         public abstract void method();

         }

class Zi extends Fu {

public void method(){

              System.out.println(“重写父类抽象方法”);

}

}

//类的多态使用

Fu fu= new Zi();

 

  • 接口多态定义的格式

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

如: interface Fu {

             public abstract void method();

}

class Zi implements Fu {

             public void method(){

              System.out.println(“重写接口抽象方法”);

}

}

//接口的多态使用

Fu fu = new Zi();

注意事项:

同一个父类的方法会被不同的子类重写。在调用方法时,调用的为各个子类重写后的方法。

如 Person p1 = new Student();

   Person p2 = new Teacher();

   p1.work(); //p1会调用Student类中重写的work方法

   p2.work(); //p2会调用Teacher类中重写的work方法

当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法。

1.4动态绑定

1.5使用多态的原因

举个具体的例子:

需求:现在有一些动物,猫、狗、猪等,抽象出它们一个共同的特性就是吃,至于怎么吃,吃什么每个动物都不同,因此把吃定义为一个抽象方法。

 

package cn.jxufe.java.chapter4.demo01;

public abstract class Animal {
    public abstract void eat();
}
package cn.jxufe.java.chapter4.demo01;

public class Cat extends Animal{
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}
package cn.jxufe.java.chapter4.demo01;

public class Dog extends Animal {
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
package cn.jxufe.java.chapter4.demo01;

public class Pig extends Animal {
    public void eat() {
        System.out.println("猪吃饲料");
    }
}
package cn.jxufe.java.chapter4.demo01;public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        /*
         * Cat cat1 = new Cat(); cat1.eat();
         * 
         * Cat cat2 = new Cat(); cat2.eat();
         */

        /*
         * 每次来一只猫吃东西,我都需要调用一次eat()方法,太麻烦,直接封装成函数。
         */
        function(new Cat());
        function(new Cat());

        /*
         * 现在狗也想做同样的操作,怎么办?那我需要再重载一个function方法。
         */
        function(new Dog());
        function(new Dog());
        
        /*
         * 现在又来了一个新的动物,也想做同样的操作,怎么办?那我需要再重载一个function方法。
         */
        
        function(new Pig());
        function(new Pig());
        
    }

    public static void function(Cat cat) {
        cat.eat();
    }

    public static void function(Dog dog) {
        dog.eat();
    }
    
    public static void function(Pig pig ) {
        pig.eat();
    }

    
}

每来一个动物,想调用吃这样的方法,都要重载一次function函数,是不是太麻烦了?这些动物调用的是它们父类中抽象出来的共同的方法吃,我们能不能简化这种操作,这就引出了多态。

package cn.jxufe.java.chapter4.demo01;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
      
        function(new Cat());
        function(new Cat());

        function(new Dog());
        function(new Dog());

        function(new Pig());
        function(new Pig());

    }

    public static void function(Animal a) {
        a.eat();
    }
}

 多态的好处:

  提高了程序的扩展性。

多态的弊端:

  只能使用父类的引用访问父类的成员方法,本例中Animal a = new Cat() ,a 就只能调用eat()方法,不能调用catch()方法。

1.6向上转型和向下转型

 

package cn.jxufe.java.chapter4.demo01;

public class Test2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Animal animal  = new Cat(); // 向上转型
        animal.eat();
        
        Cat cat = (Cat)animal; // 向下转型
        cat.eat();
        cat.catchMouse();
    }

}

1.7instanceof运算符

对于1.4中的例子,我就想在function函数中调用catchMouse方法,我该怎么做呢?

public static void function(Animal a) {
        a.eat();
        Cat cat = (Cat) a;
        cat.catchMouse();
    }

如果这样操作,传递cat的时候没有问题,但是传递dog的时候,dog怎么能抓老鼠呢?

因此我们就需要使用一个东西来进行判断,传递进来的是cat还是dog。

package cn.jxufe.java.chapter4.demo01;

public class Test {

    public static void main(String[] args) {
       
        function(new Cat());
        function(new Cat());

        function(new Dog());
        function(new Dog());

        function(new Pig());
        function(new Pig());

    }

    public static void function(Animal a) {
        a.eat();
        if(a instanceof Cat) {
            Cat cat = (Cat) a;
            cat.catchMouse();
        }
    }
}

2.final关键字

2.1final概念

继承的出现提高了代码的复用性,并方便开发。但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写。可是当子类继承了这些特殊类之后,就可以对其中的方法进行重写,那怎么解决呢?

要解决上述的这些问题,需要使用到一个关键字final,final的意思为最终,不可变。final是个修饰符,它可以用来修饰类,类的成员,以及局部变量。

2.2final特点

  • final修饰类不可以被继承,但是可以继承其他类。
class Yy {}
final class Fu extends Yy{} //可以继承Yy类
class Zi extends Fu{} //不能继承Fu类
  • final修饰的方法不可以被覆盖,但父类中没有被final修饰方法,子类覆盖后可以加final。
class Fu {
    // final修饰的方法,不可以被覆盖,但可以继承使用
    public final void method1(){}
    public void method2(){}
}
class Zi extends Fu {
    //重写method2方法
    public final void method2(){}
}
  • final修饰的变量称为常量,这些变量只能赋值一次。
final int i = 20;
i = 30; //赋值报错,final修饰的变量只能赋值一次
  • 引用类型的变量值为对象地址值,地址值不能更改,但是地址内的对象属性值可以修改。
final Person p = new Person();
Person p2 = new Person();
p = p2; //final修饰的变量p,所记录的地址值不能改变
p.name = "小明";//可以更改p对象中name属性值

p不能为别的对象,而p对象中的name或age属性值可更改。

  • 修饰成员变量,需要在创建对象前赋值,否则报错。(当没有显式赋值时,多个构造方法的均需要为其赋值。)
class Demo {
    //直接赋值
    final int m = 100;
    
    //final修饰的成员变量,需要在创建对象前赋值,否则报错。
    final int n; 
    public Demo(){
        //可以在创建对象时所调用的构造方法中,为变量n赋值
        n = 2016;
    }
}

3.static关键字

3.1static概念

当在定义类的时候,类中都会有相应的属性和方法。而属性和方法都是通过创建本类对象调用的。当在调用对象的某个方法时,这个方法没有访问到对象的特有数据时,创建对象再去调用这个方法有些多余。可是不创建对象,方法又调用不了,这时就会想,那么我们能不能不创建对象,就可以调用方法呢?

可以的,我们可以通过static关键字来实现。static它是静态修饰符,一般用来修饰类中的成员。

3.2static特点

  • 被static修饰的成员变量属于类,不属于这个类的某个对象。(也就是说,多个对象在访问或修改static修饰的成员变量时,其中一个对象将static成员变量值进行了修改,其他对象中的static成员变量值跟着改变,即多个对象共享同一个static成员变量)
class Demo {
    public static int num = 100;
}

class Test {
    public static void main(String[] args) {
        Demo d1 = new Demo();
        Demo d2 = new Demo();
        d1.num = 200;
        System.out.println(d1.num); //结果为200
        System.out.println(d2.num); //结果为200
    }
}
  • 被static修饰的成员可以并且建议通过类名直接访问。

访问静态成员的格式:

类名.静态成员变量名

类名.静态成员方法名(参数)

对象名.静态成员变量名       ------不建议使用该方式,会出现警告

对象名.静态成员方法名(参数)     ------不建议使用该方式,会出现警告

class Demo {
    //静态成员变量
    public static int num = 100;
    //静态方法
    public static void method(){
        System.out.println("静态方法");
    }
}
class Test {
    public static void main(String[] args) {
        System.out.println(Demo.num);
        Demo.method();
    }
}

3.3static注意事项

  • 静态内容是优先于对象存在,只能访问静态,不能使用this/super。静态修饰的内容存于静态区。
class Demo {
    //成员变量
    public int num = 100;
    //静态方法
    public static void method(){
        //this.num; 不能使用this/super。
        System.out.println(this.num);
    }
}
  • 同一个类中,静态成员只能访问静态成员
class Demo {
    //成员变量
    public int num = 100;
    //静态成员变量
    public static int count = 200;
    //静态方法
    public static void method(){
        //System.out.println(num); 静态方法中,只能访问静态成员变量或静态成员方法
        System.out.println(count);
    }
}

4.定义静态常量

我们想在类中定义一个静态常量,通常使用public static final修饰的变量来完成定义。此时变量名用全部大写,多个单词使用下划线连接。

定义格式:

public static final 数据类型 变量名 = 值;

class Company {
    public static final String COMPANY_NAME = "山水集团";
    public static void method(){
        System.out.println("一个静态方法");
    }
}

当我们想使用类的静态成员时,不需要创建对象,直接使用类名来访问即可。

System.out.println(Company.COMPANY_NAME); //山水集团
Company.method(); // 调用一个静态方法

注意:

接口中的每个成员变量都默认使用public static final修饰。

所有接口中的成员变量已是静态常量,由于接口没有构造方法,所以必须显示赋值。可以直接用接口名访问。

interface Inter {
    public static final int COUNT = 100;
}

访问接口中的静态变量

Inter.COUNT

5.内部类

5.1内部类概念

将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类。其他类也称为外部类。

  • 什么时候使用内部类

在描述事物时,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机,这时发动机就可以使用内部类来描述。

class 汽车 { //外部类
    class 发动机 { //内部类
}
}
  • 内部类的分类

内部类分为成员内部类与局部内部类。

我们定义内部类时,就是一个正常定义类的过程,同样包含各种修饰符、继承与实现关系等。在内部类中可以直接访问外部类的所有成员。

5.2成员内部类

成员内部类,定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问

  • 定义格式
class 外部类 { 
    修饰符 class 内部类 {
        //其他代码
}
}
  • 访问方式
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();

成员内部类代码演示

package cn.jxufe.java.chapter4.demo02;

public class Body {// 外部类,身体
    private boolean life = true; // 生命状态

    public class Heart { // 内部类,心脏
        public void jump() {
            System.out.println("心脏噗通噗通的跳");
            System.out.println("生命状态" + life); // 访问外部类成员变量
        }
    }
}

 

访问内部类

public static void main(String[] args) {
    //创建内部类对象
    Body.Heart bh = new Body().new Heart();
    //调用内部类中的方法
    bh.jump();
}

 

5.3局部内部类

局部内部类,定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问

  • 定义格式
class 外部类 { 
    修饰符 返回值类型 方法名(参数) {
    class 内部类 {
      //其他代码
    }
  }
}
  • 访问方式

在外部类方法中,创建内部类对象,进行访问。

  • 局部内部类代码演示
class Party {// 外部类,聚会
    public void puffBall() {// 吹气球方法
        class Ball {// 内部类,气球
            public void puff() {
                System.out.println("气球膨胀了");
            }
        }
        // 创建内部类对象,调用puff方法
        new Ball().puff();
    }
}
    public static void main(String[] args) {
        // 创建外部类对象
        Party p = new Party();
        // 调用外部类中的puffBall方法
        p.puffBall();
    }

6.内部类的实际应用——匿名内部类

6.1匿名内部类的概念

  内部类是为了应对更为复杂的类间关系。查看源代码中会涉及到,而在日常业务中很难遇到,这里不做赘述。

  最常用到的内部类就是匿名内部类,它是局部内部类的一种。

  定义的匿名内部类有两个含义:

  • 临时定义某一指定类型的子类
  • 定义后即刻创建刚刚定义的这个子类的对象

6.2定义匿名内部类的作用与格式

作用:匿名内部类是创建某个类型子类对象的快捷方式。

格式:

new 父类或接口(){
    //进行方法重写
};

6.3使用内部类的好处

现在举一个实际例子:不使用匿名内部类,需要写如下代码

package cn.jxufe.java.chapter4.demo02;

public interface Smoking {
    public abstract void smoking();
}
/*
 *  实现类,实现接口 重写接口抽象方法,创建实现类对象
 *  class XXX implements Smoking{
 *      public void smoking(){
 *      
 *      }
 *  }
 *  XXX x = new XXX();
 *  x.smoking(); 
 *  Smoking s = new XXX();
 *  s.smoking();
 *  
 *  匿名内部类,简化问题:  定义实现类,重写方法,建立实现类对象,合为一步完成
 */

如果使用匿名内部类

package cn.jxufe.java.chapter4.demo02;

public class TestSmoking {
    public static void main(String[] args) {
        //使用匿名内部类
        /*
         *  定义实现类,重写方法,创建实现类对象,一步搞定
         *  格式:
         *    new 接口或者父类(){
         *       重写抽象方法
         *    };
         *    从 new开始,到分号结束
         *    创建了接口的实现类的对象
         */
        new Smoking(){
            public void smoking(){
                System.out.println("人在吸烟");
            }
        }.smoking();
    }
}

7.代码块

7.1局部代码块

局部代码块是定义在方法或语句中

特点:

  • 以”{}”划定的代码区域,此时只需要关注作用域的不同即可
  • 方法和类都是以代码块的方式划定边界的
package cn.jxufe.java.chapter4.demo03;

public class CodeBlock {
    public static void main(String[] args) {
        {
            int x = 1;
            System.out.println("普通代码块" + x);
        }
        int x = 99;
        System.out.println("代码块之外" + x);
    }
}

7.2构造代码块

构造代码块是定义在类中成员位置的代码块

特点:

  • 优先于构造方法执行,构造代码块用于执行所有对象均需要的初始化动作
  • 每创建一个对象均会执行一次构造代码块。

 

package cn.jxufe.java.chapter4.demo03;

public class ConstructorCodeBlock {
    public static void main(String[] args) {
        Person p = new Person();
        Person p1 = new Person(23);
    }

}

class Person {
    private String name;
    private int age;

    Person() {
        System.out.println("Person无参数的构造函数执行");
    }

    Person(int age) {
        this.age = age;
        System.out.println("Person(age)参数的构造函数执行");
    }

    // 构造代码块
    {
        System.out.println("构造代码块执行了");
    }

}

7.3静态代码块

静态代码块是定义在成员位置,使用static修饰的代码块。

特点:

  • 它优先于主方法执行、优先于构造代码块执行,当以任意形式第一次使用到该类时执行。
  • 该类不管创建多少对象,静态代码块只执行一次。
  • 可用于给静态变量赋值,用来给类进行初始化。

 

package cn.jxufe.java.chapter4.demo03;

public class StaticBlock {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Person2 p = new Person2();
        Person2 p1 = new Person2(23);
    }

}

class Person2 {
    private String name;
    private int age;
    // 构造代码块
    {
        System.out.println("构造代码块执行了");
    }

    // 静态代码块
    static {
        System.out.println("静态代码块执行了");
    }

    Person2() {
        System.out.println("Person无参数的构造函数执行");
    }

    Person2(int age) {
        this.age = age;
        System.out.println("Person(age)参数的构造函数执行");
    }

}

public class StaticTest {
    public static void main(String[] args) {
        new Child();
    }
}

class Child extends Parent {
    static {
        System.out.println("Child的静态块");
    }
    {
        System.out.println("Child的构造块");
    }
    Child() {
        System.out.println("Child的构造方法");
    }
}

class Parent {
    Integer age = 18;
    static {
        System.out.println("Parent的静态块");
    }
    {
        System.out.println("Parent的构造块");
    }
    Parent() {
        System.out.println("Parent的构造方法");
    }
}

https://blog.csdn.net/f641385712/article/details/80350192

8.eclipse的使用技巧

8.1Ctrl+T:查看所选中类的继承树

         例如,在下面代码中,选中JavaEE类名,然后按Ctrl+T,就会显示出JavaEE类的继承关系

 

8.2文档注释导出帮助文档

在eclipse使用时,可以配合文档注释,导出对类的说明文档,从而供其他人阅读学习与使用。

通过使用文档注释,将类或者方法进行注释用@简单标注基本信息。如@author 作者、@version代码版本、@param方法参数、@return方法返回值等。

package cn.jxufe.java.chapter4.demo03;

/**
 * 我的工具类
 * 
 * @author Li
 * @version 1.0版本
 */

public class Tool {
    /**
     * 返回两个整数的累加和
     * 
     * @param num1
     *            第一个数
     * @param num2
     *            第二个数
     * @return 返回累加和
     */
    public static int getSum(int num1, int num2) {
        return num1 + num2;
    }
}

 

9.jar包的导入与导出

jar包是一个可以包含许多.class文件的压缩文件。我们可以将一个jar包加入到项目的依赖中,从而该项目可以使用该jar下的所有类;也可以把项目中所有的类打包到指定的jar包,提供给其他项目使用。

9.1导出jar包

即把项目中所有类,打包到指定的jar包中,步骤如下图:

9.2导入jar包

即把指定的jar包,加入到指项目中,提供给项目使用。

导入jar包的过程是将jar包加入到项目的.classpath文件中去,让项目识别,便可以使用jar包中所有的.class文件类。以下是加入步骤:

         1:项目根文件夹下创建lib文件夹,用于同一管理所有的jar文件

         2:把jar文件复制到lib文件夹中

         3:右键点击jar文件,点击Build Path,选择Add to Build Path,此时查看项目根文件夹下的.classpath文件,发现新加入的jar包路径被配置到了该文件中。说明可以使用jar包中所有类了。