继承和多态

(一)继承

1、什么是继承

在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。
例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a。

实现继承的格式:

定义父类的格式:(一个普通的类定义)
public class 父类名称 {
    // ...
}

定义子类的格式:
public class 子类名称 extends 父类名称 {
    // ...
}

2、成员变量同名的访问

在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

  • 直接通过子类对象访问成员变量:

    等号左边是谁,就优先用谁,没有则向上找。

  • 间接通过成员方法访问成员变量:

    该方法属于谁,就优先用谁,没有则向上找。

例如:

        // 等号左边是谁,就优先用谁
        System.out.println(zi.num); // 优先子类,200
		//System.out.println(zi.abc); // 到处都没有,编译报错!
        System.out.println("===========");

        // 这个方法是子类的,优先用子类的,没有再向上找
        zi.methodZi(); // 200
        // 这个方法是在父类当中定义的,
        zi.methodFu(); // 100

3、super关键字

super关键字的用法有三种:

  1. 在子类的成员方法中,访问父类的成员变量。
  2. 在子类的成员方法中,访问父类的成员方法。
  3. 在子类的构造方法中,访问父类的构造方法。

4、成员方法的访问

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先用谁,如果没有则向上找。

Warning

无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。

5、方法重写Override

在继承关系当中,方法的名称一样,参数列表也一样。

  • 重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写。
  • 重载(Overload):方法的名称一样,参数列表【不一样】。

方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。例如:

public class Demo01ExtendsMethod {

    public static void main(String[] args) {
        Zi zi = new Zi();
        // 创建的是new了子类对象,所以优先用子类方法
        zi.method();
    }

}


//父类:
    public class Fu {

    public void method() {
        System.out.println("父类重名方法执行!");
    }

}
//子类:
    public class Zi extends Fu {

    public void method() {
        System.out.println("子类重名方法执行!");
    }

}

方法覆盖重写的注意事项:

  1. 必须保证父子类之间方法的名称相同,参数列表也相同。
    @Override:写在方法前面,用来检测是不是有效的正确覆盖重写。

  2. 子类方法的返回值必须【小于等于】父类方法的返回值范围。
    小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类)

  3. 子类方法的权限必须【大于等于】父类方法的权限修饰符。
    小扩展提示:public > protected > (default) > private 备注:(default)不是关键字default,而是什么都不写,留空。

6、父子类构造方法的访问

继承关系中,父子类构造方法的访问特点:

  1. 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造。
  2. 子类构造可以通过super关键字来调用父类重载构造。
  3. super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。

总结:

  • 子类必须调用父类构造方法,不写则赠送super();
  • 写了则用写的指定的super调用,super只能有一个,还必须是第一个。

例如:

//父类:
public class Fu {

    int num = 10;

    public void method() {
        System.out.println("父类方法");
    }

}
    
//子类:
public class Zi extends Fu {

    int num = 20;

    public Zi() {
        super();
    }

    public void methodZi() {
        System.out.println(super.num); // 父类中的num
    }

    public void method() {
        super.method(); // 访问父类中的method
        System.out.println("子类方法");
    }

}

7、super与this

super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:

  1. 在本类的成员方法中,访问本类的成员变量。
  2. 在本类的成员方法中,访问本类的另一个成员方法。
  3. 在本类的构造方法中,访问本类的另一个构造方法。

在第三种用法当中要注意:
A. this(...)调用也必须是构造方法的第一个语句,唯一一个。
B. super和this两种构造调用,不能同时使用。

例如:

this(123); // 本类的无参构造,调用本类的有参构造
//this(1, 2); // 错误写法!

public Zi(int n) {
	this(1, 2);
}

public Zi(int n, int m) {
}

8、抽象类与抽象方法

抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。

Tip
  • 一个抽象类不一定含有抽象方法,只要保证抽象方法所在的类是抽象类,即可。没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途。

  • 抽象类的子类也可以是一个抽象类

如何使用抽象类和抽象方法:

  1. 不能直接创建new抽象类对象。
  2. 必须用一个子类来继承抽象父类。
  3. 子类必须覆盖重写抽象父类当中所有的抽象方法。
    覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
  4. 创建子类对象进行使用。
//抽象类
    public abstract class Animal {

    // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
    public abstract void eat();

    // 这是普通的成员方法
//    public void normalMethod() {
//
//    }

}
//子类继承抽象类
    public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

}
//测试类
    public class DemoMain {

    public static void main(String[] args) {
//        Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象

        Cat cat = new Cat();
        cat.eat();
    }

}

(二)接口、 多态

1、接口

Note

换成了关键字interface之后,编译生成的字节码文件仍然是:.java --> .class。

接口就是多个类的公共规范。
接口是一种引用数据类型,最重要的内容就是其中的:抽象方法。

  • 如何定义一个接口的格式:
public interface 接口名称 {
    // 接口内容
}

接口包含的内容

  • 如果是Java 7,那么接口中可以包含的内容有:

    • 常量

    • 抽象方法

      public abstract 返回值类型 方法名称(参数列表);
      

      注意事项:1. 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract2. 这两个关键字修饰符,可以选择性地省略。3. 方法的三要素,可以随意定义。

  • 如果是Java 8,还可以额外包含有:

    • 默认方法:使用 default 修饰,不可省略,供子类调用或者子类重写。接口当中的默认方法,可以解决接口升级的问题。

      接口升级

      在原有接口中添加新的方法而不需要在接口实现类中重写,相当于被接口实现类继承

    • 静态方法:使用 static 修饰,供接口直接调用。 代码如下:

      public interface InterFaceName {
          public default void method() {
          // 执行语句
          }
          public static void method2() {
          // 执行语句
          }
      }
      
  • 如果是Java 9,还可以额外包含有:

    • 私有方法:使用 private 修饰,供接口中的默认方法或者静态方法调用。 代码如下:

      public interface InterFaceName {
          private void method() {
          // 执行语句
          }
      }
      
    • 私有方法的使用:只有默认方法可以调用。不能被实现类或别人使用。

    • 私有静态方法:默认方法和静态方法可以调用。 例如:

      /*
      问题描述:
      我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
      但是这个共有方法不应该让实现类使用,应该是私有化的。
      
      解决方案:
      从Java 9开始,接口当中允许定义私有方法。
      1. 普通私有方法,解决多个默认方法之间重复代码问题
      格式:
      private 返回值类型 方法名称(参数列表) {
          方法体
      }
      
      2. 静态私有方法,解决多个静态方法之间重复代码问题
      格式:
      private static 返回值类型 方法名称(参数列表) {
          方法体
      }
       */
      public interface MyInterfacePrivateA {
      
          public default void methodDefault1() {
              System.out.println("默认方法1");
              methodCommon();
          }
      
          public default void methodDefault2() {
              System.out.println("默认方法2");
              methodCommon();
          }
      
          private void methodCommon() {
              System.out.println("AAA");
              System.out.println("BBB");
              System.out.println("CCC");
          }
      
      }
      

2、接口使用步骤

  1. 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
    格式:
public class 实现类名称 implements 接口名称 {
    // ...
}
  1. 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
    实现:去掉abstract关键字,加上方法体大括号。

  2. 创建实现类的对象,进行使用。

注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

public class Demo01Interface {

    public static void main(String[] args) {
        // 错误写法!不能直接new接口对象使用。
//        MyInterfaceAbstract inter = new MyInterfaceAbstract();

        // 创建实现类的对象使用
        MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
        impl.methodAbs1();
        impl.methodAbs2();
    }

}

3、 接口的多实现

使用接口的时候,需要注意:

  1. 接口是没有静态代码块或者构造方法的。

  2. 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。

格式:

public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
    // 覆盖重写所有抽象方法
}
  1. 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
  2. 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
  3. 如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
  4. 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。

接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。

4、接口的多继承【了解】

小贴士

子接口重写默认方法时,default关键字可以保留。

子类重写默认方法时,default关键字不可以保留。

  1. 类与类之间是单继承的。直接父类只有一个。
  2. 类与接口之间是多实现的。一个类可以实现多个接口。
  3. . 接口与接口之间是多继承的。

注意事项:1. 多个父接口当中的抽象方法如果重复,没关系。2. 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。

//定义父接口
    interface A {
    public default void method(){
    System.out.println("AAAAAAAAAAAAAAAAAAA");
    }
    }
    interface B {
    public default void method(){
    System.out.println("BBBBBBBBBBBBBBBBBBB");
    }
}
//定义子接口
    interface D extends A,B{
    @Override
    public default void method() {
    System.out.println("DDDDDDDDDDDDDD");
    }
}

5、其他成员特点

  1. 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
  2. 接口中,没有构造方法,不能创建对象。
  3. 接口中,没有静态代码块。

6、多态

  • 代码当中体现多态性,其实就是一句话:父类引用指向子类对象。格式:
父类名称 对象名 = new 子类名称();
或者:接口名称 对象名 = new 实现类名称();
  • 多态的成员变量和成员方法访问

    在多态的代码当中,成员方法的访问规则是:
    看new的是谁,就优先用谁,没有则向上找。

    口诀:编译看左边,运行看右边。

    对比一下:
    成员变量:编译看左边,运行还看左边。
    成员方法:编译看左边,运行看右边。

    例如:

    public class Demo02MultiMethod {
    
        public static void main(String[] args) {
            Fu obj = new Zi(); // 多态
    
            obj.method(); // 父子都有,优先用子
            obj.methodFu(); // 子类没有,父类有,向上找到父类
    
            // 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
    //        obj.methodZi(); // 错误写法!
        }
    
    }
    

7、多态的好处

image-20240308135044855
比如:无论老师还是助教,他们都是员工(实现员工接口或继承员工类),他们都有共同的行为那就是工作(只是工作的具体内容不一样),这时候
无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变化
这就是多态!

8、引用类型转换

(1)为什么要转型

eg:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥 有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子 类特有的方法,必须做向下转型。

(2) 多态的转型分为向上转型与向下转型两种:

  • 向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。

  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

例如:

public class Demo01Main {

    public static void main(String[] args) {
        // 对象的向上转型,就是:父类引用指向之类对象。
        Animal animal = new Cat(); // 本来创建的时候是一只猫
        animal.eat(); // 猫吃鱼

//        animal.catchMouse(); // 错误写法!

        // 向下转型,进行“还原”动作
        Cat cat = (Cat) animal;
        cat.catchMouse(); // 猫抓老鼠

        // 下面是错误的向下转型
        // 本来new的时候是一只猫,现在非要当做狗
        // 错误写法!编译不会报错,但是运行会出现异常:
        // java.lang.ClassCastException,类转换异常
        Dog dog = (Dog) animal;
    }

}

9、 转型的异常与instanceof 关键字

ClassCastException 类型转换异常:

例如:

public class Test {
    public static void main(String[] args) {
    	// 向上转型
        Animal a = new Cat();
        a.eat(); // 调用的是 Cat 的 eat
        // 向下转型
        Dog d = (Dog)a;
        d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
    }
}

这段代码可以通过编译,但是运行时,却报出了ClassCastException ,类型转换异常!这是因为,明明创建了 Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。

例如:

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat(); // 调用的是 Cat 的 eat
        // 向下转型
        if (a instanceof Cat){
        Cat c = (Cat)a;
        c.catchMouse(); // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
        Dog d = (Dog)a;
        d.watchHouse(); // 调用的是 Dog 的 watchHouse
        }
    }
}

(三)final关键字

学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承 API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字, 用于修饰不可改变内容。

  • final关键字代表最终、不可改变的。

  • 常见四种用法:

  1. 可以用来修饰一个类
  2. 可以用来修饰一个方法 重写被 final 修饰的方法,编译时就会报错。
  3. 还可以用来修饰一个局部变量
  4. 还可以用来修饰一个成员变量

Tips:对于基本类型来说,不可变说的是变量当中的数据不可改变
对于引用类型来说,不可变说的是变量当中的地址值不可改变

例如:

public class Demo01Final {

    public static void main(String[] args) {
        int num1 = 10;
        System.out.println(num1); // 10
        num1 = 20;
        System.out.println(num1); // 20

        // 一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
        // “一次赋值,终生不变”
        final int num2 = 200;
        System.out.println(num2); // 200

//        num2 = 250; // 错误写法!不能改变!
//        num2 = 200; // 错误写法!

        // 正确写法!只要保证有唯一一次赋值即可
        final int num3;
        num3 = 30;

        // 对于基本类型来说,不可变说的是变量当中的数据不可改变
        // 对于引用类型来说,不可变说的是变量当中的地址值不可改变
        Student stu1 = new Student("赵丽颖");
        System.out.println(stu1);
        System.out.println(stu1.getName()); // 赵丽颖
        stu1 = new Student("霍建华");
        System.out.println(stu1);
        System.out.println(stu1.getName()); // 霍建华
        System.out.println("===============");

        final Student stu2 = new Student("高圆圆");
        // 错误写法!final的引用类型变量,其中的地址不可改变
//        stu2 = new Student("赵又廷");
        System.out.println(stu2.getName()); // 高圆圆
        stu2.setName("高圆圆圆圆圆圆");
        System.out.println(stu2.getName()); // 高圆圆圆圆圆圆
    }

}
  • 当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
    格式:
修饰符 final 返回值类型 方法名称(参数列表) {
    // 方法体
}

对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。

  • 不能使用一个final类来作为父类

    对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。

    1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
    2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
    3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。

例如:

public class Person {

    private final String name/* = "鹿晗"*/;

    public Person() {
        name = "关晓彤";
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

//    public void setName(String name) {
//        this.name = name;
//    }
}

(四)权限修饰符

Java中有四种权限修饰符:

                    public  >   protected   >   (default)   >   private
同一个类(我自己)        √            √              √               √
同一个包(我邻居)        √            √              √               NO
不同包子类(我儿子)      √            √              NO              NO
不同包非子类(陌生人)     √           NO             NO              NO
Warning

(default)并不是关键字“default”,而是根本不写。

(五)内部类

什么是内部类?

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。例如:身体和心脏的关系。又如:汽车和发动机的关系。

Note

内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名 和$符号 。 比如,Person$Heart.class

分类:

  1. 成员内部类
  2. 局部内部类(包含匿名内部类)

1、成员内部类

  • 定义格式:
修饰符 class 外部类名称 {
    修饰符 class 内部类名称 {
        // ...
    }
    // ...
}

注意:内用外,随意访问;外用内,需要内部类对象。

  • 如何使用成员内部类?有两种方式:
  1. 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
  2. 直接方式,公式:
  3. 【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】

例如:

public class Demo01InnerClass {

    public static void main(String[] args) {
        Body body = new Body(); // 外部类的对象
        // 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
        body.methodBody();
        System.out.println("=====================");

        // 按照公式写:
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }

}

public class Body { // 外部类

    public class Heart { // 成员内部类

        // 内部类的方法
        public void beat() {
            System.out.println("心脏跳动:蹦蹦蹦!");
            System.out.println("我叫:" + name); // 正确写法!
        }

    }

    // 外部类的成员变量
    private String name;

    // 外部类的方法
    public void methodBody() {
        System.out.println("外部类的方法");
        new Heart().beat();//外部类的方法,里面间接在使用内部类Heart
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
// 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
public class Outer {

    int num = 10; // 外部类的成员变量

    public class Inner /*extends Object*/ {

        int num = 20; // 内部类的成员变量

        public void methodInner() {
            int num = 30; // 内部类方法的局部变量
            System.out.println(num); // 局部变量,就近原则
            System.out.println(this.num); // 内部类的成员变量
            System.out.println(Outer.this.num); // 外部类的成员变量
        }

    }

}

2、局部内部类

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。

  • 定义格式:

    定义格式:
    修饰符 class 外部类名称 {
        修饰符 返回值类型 外部类方法名称(参数列表) {
            class 局部内部类名称 {
                // ...
            }
        }
    }
    
  • 局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】。

    备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。

    原因:

    1. new出来的对象在堆内存当中。
    2. 局部变量是跟着方法走的,在栈内存当中。
    3. 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
    4. 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。

3、小节类的权限修饰符

public > protected > (default) > private
定义一个类的时候,权限修饰符规则:

  1. 外部类:public / (default)
  2. 成员内部类:public / protected / (default) / private
  3. 局部内部类:什么都不能写

4、匿名内部类、匿名对象

内部类的简化写法。它的本质是一个 带具体实现的父类或者父接口匿名子类对象。 开发中,最常用到的内部类就是匿名内部类了。

  • 匿名内部类必须继承一个父类或者实现一个父接口

  • 匿名内部类的定义格式:

    接口名称 对象名 = new 父类名或者接口名称() {
        // 覆盖重写所有抽象方法
    };
    

    对格式“new 接口名称() {...}”进行解析:

    1. new代表创建对象的动作
    2. 接口名称就是匿名内部类需要实现哪个接口
    3. {...}这才是匿名内部类的内容
  • 另外还要注意几点问题:

  1. 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
    如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
  2. 匿名对象,在【调用方法】的时候,只能调用唯一一次。
    如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
  3. 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
    强调:匿名内部类和匿名对象不是一回事!!!
  • 匿名对象:匿名对象就是只有右边的对象,没有左边的名字和赋值运算符。格式

    new 类名称();
    

    注意事项:匿名对象只能使用唯一的一次,下次再用不得不再创建一个新对象。

    使用建议:如果确定有一个对象只需要使用唯一的一次,就可以用匿名对象。

例如:

public class DemoMain {

    public static void main(String[] args) {
//        MyInterface obj = new MyInterfaceImpl();
//        obj.method();

//        MyInterface some = new MyInterface(); // 错误写法!

        // 使用匿名内部类,但不是匿名对象,对象名称就叫objA
        MyInterface objA = new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-A");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类实现了方法!222-A");
            }
        };
        objA.method1();
        objA.method2();
        System.out.println("=================");

        // 使用了匿名内部类,而且省略了对象名称,也是匿名对象
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-B");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类实现了方法!222-B");
            }
        }.method1();
        // 因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-B");
            }

            @Override
            public void method2() {
                System.out.println("匿名内部类实现了方法!222-B");
            }
        }.method2();
    }

}

(六)引用类型总结

任何一种数据类型都可以作为成员变量

基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。

1、class作为成员变量

例如:创建一个英雄角色

  • 定义武器类,将增加攻击能力:
class Weapon {
    String name; // 武器名称
    int hurt; // 伤害值
}
  • 定义穿戴盔甲类,将增加防御能力,也就是提升生命值:
class Armour {
    String name;// 装备名称
    int protect;// 防御值
}

  • 定义角色类:
class Role {
    int id;
    int blood;
    String name;
    // 添加武器属性
    Weapon wp;
    // 添加盔甲属性
    Armour ar;
    // 提供get/set方法
    public Weapon getWp() {
        return wp;
    }
    public void setWeapon(Weapon wp) {
        this.wp = wp;
    }
    public Armour getArmour() {
        return ar;
    }
    public void setArmour(Armour ar) {
        this.ar = ar;
    }
    // 攻击方法
    public void attack(){
    	System.out.println("使用"+ wp.getName() +", 造成"+wp.getHurt()+"点伤害");
    }
    // 穿戴盔甲
    public void wear(){
    	// 增加防御,就是增加blood值
   	 	this.blood += ar.getProtect();
   		 System.out.println("穿上"+ar.getName()+", 生命值增加"+ar.getProtect());
    }
}
  • 测试类:
public class Test {
    public static void main(String[] args) {
        // 创建Weapon 对象
        Weapon wp = new Weapon("屠龙刀" , 999999);
        // 创建Armour 对象
        Armour ar = new Armour("麒麟甲",10000);
        // 创建Role 对象
        Role r = new Role();
        // 设置武器属性
        r.setWeapon(wp);
        // 设置盔甲属性
        r.setArmour(ar)
            // 攻击
        r.attack();
        // 穿戴盔甲
        r.wear();
    }
}
输出结果:
使用屠龙刀,造成999999点伤害
穿上麒麟甲 ,生命值增加10000

类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。

2、interface作为成员变量

Note

接口作为成员变量时,对它进行赋值的操作,实际上,是赋给它该接口的一个子类对象

例如: 接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在 Role 中,可以增加接口作为成员变量,来设置不同的技能。

  • 定义接口:
// 法术攻击
public interface FaShuSkill {
	public abstract void faShuAttack();
}
  • 定义角色类:
public class Role {
    FaShuSkill fs;
    public void setFaShuSkill(FaShuSkill fs) {
        this.fs = fs;
        }
        // 法术攻击
    public void faShuSkillAttack(){
        System.out.print("发动法术攻击:");
        fs.faShuAttack();
        System.out.println("攻击完毕");
    }
}
  • 定义测试类:
public class Test {
    public static void main(String[] args) {
        // 创建游戏角色
        Role role = new Role();
        // 设置角色法术技能
        role.setFaShuSkill(new FaShuSkill() {
       		 @Override
            public void faShuAttack() {
      			  System.out.println("纵横天下");
        }
    });
    // 发动法术攻击
    role.faShuSkillAttack();
    // 更换技能
    role.setFaShuSkill(new FaShuSkill() {
    @Override
    public void faShuAttack() {
   		 System.out.println("逆转乾坤");
   		 }
    });
    // 发动法术攻击
    role.faShuSkillAttack();
    }
}
/*输出结果:
发动法术攻击:纵横天下
攻击完毕
发动法术攻击:逆转乾坤
攻击完毕
*/

3、interface作为方法参数和返回值类型

  • 接口作为参数时,传递它的子类对象。
  • 接口作为返回值类型时,返回它的子类对象。