第4章 面向对象(下)


学习目标

1. 继承

继承是面向对象思想中的三个非常重要的概念(封装、继承、多态)之一。

1.1 继承的概念

在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。

在Java中,继承描述的是类之间的扩展关系,通过继承可以使多种类之间形成一种扩展关系体系。

图1 继承关系图谱

Java中类的继承

在Java中,类的继承是指在一个现有类的基础上构建一个新的类,构建出来的新类被称作子类,现有类被称作父类。子类会自动继承父类的属性和方法,使得子类具有父类的特征(属性)和行为(方法)

extend 英 [ɪkˈstend] 扩展

class 父类{
  ……
}

class 子类 extends 父类{
  …… 
}
图2 新建项目

创建软件包 cn.xj.ch04.ex01

package cn.xj.ch04.ex01;

// 定义Animal类
class Animal {
    private String name;                      //声明name属性
    private int age;                          //声明age属性
    public final String COLOR = "黑色";       //定义COLOR属性
    public String getName() {                //定义name属性的getter方法
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {                    //定义age属性的getter方法
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

// 定义Dog类继承Animal类
class Dog extends Animal {
    //此处不写任何代码
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例01】继承举例1  (许杰)");
        Dog dog = new Dog();        //创建一个Dog类的对象
        dog.setName("牧羊犬");       //此时调用的是父类Animal中的setter方法
        dog.setAge(3);              //此时调用的是父类Animal中的setter方法
        System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge()
                +",颜色:"+dog.COLOR);
    }
}
图3 继承举例1
package cn.xj.ch04.ex02;

// 定义Animal类
class Animal {
    private String name;                 // 声明name属性
   private int age;                     // 声明age属性
    public String getName() {
        return name;
   }
   public void setName(String name) {
       this.name = name;
    }
   public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

// 定义Dog类继承Animal类
class Dog extends Animal {
    private String color;        // 声明color属性
    public String getColor() {
        return color;
       }
   public void setColor(String color) {
        this.color = color;
    }
}
// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例02】继承举例2  (许杰)");
        Dog dog = new Dog(); // 创建并实例化dog对象
        dog.setName("牧羊犬");// 此时访问的是父类Animal中的setter方法
        dog.setAge(4);       // 此时访问的是父类Animal中的setter方法
        dog.setColor("黄色"); // 此时访问的是Dog类中的setter方法
        System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge()+
                ",颜色:"+dog.getColor());
    }
}
图4 继承举例2

注意:子类虽然可以通过继承访问父类的成员和方法,但不是所有的父类属性和方法都可以被子类访问。

类的继承注意事项

  class A{}
  class B extends A{}   // 类B继承类A,类B是类A的子类
  class C extends B{}   // 类C继承类B,类C是类B的子类,同时也是类A的子类

1.2 方法的重写

在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改或改进,即对父类的方法进行重写。在子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表(参数个数、类型顺序)以及返回值类型

package cn.xj.ch04.ex03;
// 定义Animal类
class Animal {
    //定义动物叫的方法
    void shout() {
        System.out.println("动物发出叫声");
    }
}
// 定义Dog类继承Animal类
class Dog extends Animal {
    //重写父类Animal中的shout()方法
    void shout() {
        System.out.println("汪汪汪……");
    }
}
// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例03】方法重写  (许杰)");
        Animal animal = new Animal();
        animal.shout();
        Dog dog = new Dog();    // 创建Dog类的实例对象
        dog.shout();            // 调用Dog类重写的shout()方法
    }
}
图5 继承方法的重写

子类重写父类方法时的访问权限

子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限。

package cn.xj.ch04.ex04;

//// 定义Animal类
class Animal {
    //定义动物叫的方法
    public void shout() {
        System.out.println("动物发出叫声");
    }
}
// 定义Dog类继承Animal类
class Dog extends Animal {
    //重写父类Animal中的shout()方法
    private void shout() {  //尝试分配较弱的访问权限('private');曾为 'public'
        System.out.println("汪汪汪……");
    }
}
public class Example_xj {
    public static void main(String[] args) {
        Dog dog = new Dog();    // 创建Dog类的实例对象
        dog.shout();            // 调用Dog类重写的shout()方法
    }
}
图6 继承方法--尝试分配较弱的访问权限

1.3 super关键字

当子类重写父类的方法后,子类对象将无法访问父类中被子类重写过的原来方法。为了解决这个问题,Java提供了super关键字,使用super关键字可以在子类中访问父类的非私有方法、非私有属性以及构造方法

super 英 [ˈsuːpə(r)] 超级的

// 非私有的属性
super.属性名

// 非私有的方法
super.方法名(参数1, ...)

// 构造方法
super(参数1, ...)

注意:通过super()调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次

package cn.xj.ch04.ex05;

// 定义Animal类
class Animal {
    String name = "牧羊犬";
    // 定义动物叫的方法
    void shout() {
        System.out.println("动物发出叫声");
    }
}
// 定义Dog类继承Animal类
class Dog extends Animal {
    // 重写父类Animal中的shout()方法,扩大了访问权限
    public void shout() {
        super.shout();      // 调用父类中的shout()方法
        System.out.println("汪汪汪……");
    }
    public void printName(){
        System.out.println("名字:"+super.name);      // 访问父类中的name属性
    }
}
// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例05】super调用父类中的属性、方法  (许杰)");
        Dog dog = new Dog();    // 创建Dog类的对象
        dog.shout();            // 调用Dog类重写的shout()方法
        dog.printName();        // 调用Dog类中的printName()方法
    }
}
图7 super调用父类中的属性、方法
package cn.xj.ch04.ex06;

// 定义Animal类
class Animal {
    private String 名称;
    private int 年龄;
    public Animal(String 名称, int 年龄) {    // Animal类有参构造方法
        this.名称 = 名称;
        this.年龄 = 年龄;
    }
    public String get名称() {
        return 名称;
    }
    public void set名称(String 名称) {
        this.名称 = 名称;
    }
    public int get年龄() {
        return 年龄;
    }
    public void set年龄(int 年龄) {
        this.年龄 = 年龄;
    }
    public String info() {
        return "名称:"+this.get名称()+",年龄:"+this.get年龄();
    }
}

// 定义Dog类继承Animal类
class Dog extends Animal {
    private String 颜色;
    public Dog(String name, int age, String 颜色) {
        super(name, age);    //通过super关键字调用Animal类有两个参数的构造方法
        this.set颜色(颜色);
    }
    public String get颜色() {
        return 颜色;
    }
    public void set颜色(String 颜色) {
        this.颜色 = 颜色;
    }
    // 重写父类的info()方法
    public String info() {
        return super.info()+",颜色:"+this.get颜色();  // 扩充父类中的方法
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例06】super调用父类中的构造方法  (许杰)");
        Dog dog = new Dog("牧羊犬",3,"黑色");             // 创建Dog类的对象
        System.out.println(dog.info());
    }
}
图8 super调用父类中的构造方法

super与this的区别

区别点 super this
访问属性 直接访问父类中的非私有属性 访问本类中的属性。如果本类中没有该属性,则从父 类中继续查找
调用方法 直接调用父类中的非私有方法 调用本类中的方法。如果本类中没有该方法,则从父 类中继续查找
调用构造方法 调用 父 类 构 造 方 法,必 须 放 在 子类构造方法的首行 调用本类构造方法,必须放在构造方法的首行

注意:this和super不可以同时出现,因为使用this和super调用构造方法的代码都要求必须放在构造方法的首行

2. final关键字

2.1 final关键字修饰类

Java中使用final关键字修饰的类不可以被继承,也就是不能派生子类。 ---- final -- 最终的 不能派生--生育 绝后

final 英 英 [ˈfaɪnl] 最后的

package cn.xj.ch04.ex07;

// 使用final关键字修饰Animal类
final class Animal {
}

// Dog类继承Animal类
class Dog extends Animal {  // 无法从final 'cn.xj.ch04.ex07.Animal' 继承
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        Dog dog = new Dog();         // 创建Dog类的对象
    }
}
图9 final类继承 -- 编译就出错

2.2 final关键字修饰方法

package cn.xj.ch04.ex08;

// 定义Animal类
class Animal {
    // 使用final关键字修饰shout()方法
    public final void shout() {}
}

// 定义Dog类继承Animal类
class Dog extends Animal {
    // 重写Animal类的shout()方法
    public void shout() {}
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        Dog dog=new Dog(); // 创建Dog类的对象
    }
}
图10 被覆盖的方法为final -- 编译就出错

子类不能对final关键字修饰的方法进行重写(覆盖)。

2.3 final关键字修饰变量

Java中被final修饰的变量为常量,常量只能在声明时被赋值一次,在后面的程序中,常量的值不能被改变。如果再次对final修饰的常量赋值,则程序会在编译时报错。

package cn.xj.ch04.ex09;
public class Example_xj {
    public static void main(String[] args) {
        final String 使用单位名称 = "山西传媒学院";
        // 使用final关键字修饰的变量 使用单位名称 第一次可以被赋值
        System.out.println(使用单位名称);
        使用单位名称 = "浙江传媒学院";        // 再次被赋值会报错
    }
}
图11 无法为最终变量分配值 -- 编译就出错

注意:在使用final声明变量时,变量的名称通常全部的字母大写。如果一个程序中的变量使用public static final声明,则此变量将成为全局常量。如下所示

public static final String UNITNAME = "山西传媒学院";

3. 抽象类和接口

3.1 抽象类

abstract 英 [ˈæbstrækt] 抽象

(1) 抽象方法的语法格式

定义一个类时,常常需要定义一些成员方法用于描述类的行为(方法)特征(方法名、参数列表、返回类型),但有时这些方法的实现方式是无法确定的。

抽象方法是使用abstract关键字修饰的成员方法,抽象方法在定义时不需要实现方法体

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

(2) 抽象类的语法格式

当一个类包含了抽象方法,该类就是抽象类。

abstract class 抽象类名称{
    属性;
    访问权限 返回值类型 方法名称(参数              //普通方法
        return [返回值];
    }
    访问权限 abstract 返回值类型 抽象方法名称(参数);//抽象方法,无方法体
}

(3) 抽象类的定义规则

抽象类的定义比普通类多了一个或多个抽象方法,其他地方与普通类的组成基本相同。

package cn.xj.ch04.ex10;

// 定义抽象类Animal
abstract class Animal {
    // 定义抽象方法 喊叫()
    abstract void 喊叫();
}

// 定义Dog类继承抽象类Animal
class Dog extends Animal {
    // 实现抽象方法 喊叫()
    void 喊叫() {
        System.out.println("汪汪汪……");
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例10】定义抽象类、继承抽象类、使用抽象类子类  (许杰)");
        Dog dog = new Dog();         // 创建Dog类的对象
        dog.喊叫();                 // 通过dog对象调用 喊叫()方法
    }
}
图12 定义抽象类、继承抽象类、使用抽象类子类

注意:使用abstract关键字修饰的抽象方法不能使用private关键字修饰,因为抽象方法必须要被子类实现,如果使用了private关键字修饰抽象方法,则子类无法实现该方法。

3.2 接口

interface 英 [ˈɪntəfeɪs] 接口

接口是一种用来定义程序的协议,它用于描述类或结构的一组相关行为。接口是由抽象类衍生出来的一个概念,并由此产生了一种编程方式,可以称这种编程方式为面向接口编程

面向接口编程就是将程序的业务逻辑进行分离,以接口的形式去对接不同的业务模块。接口中不实现任何业务逻辑,业务逻辑由接口的实现类来完成。当业务需求变更时,只需要修改实现类中的业务逻辑,而不需要修改接口中的内容,以减少需求变更对系统产生的影响。

在Java中,使用接口的目的是为了克服单继承的限制,因为一个类只能有一个父类,而一个类可以同时实现多个父接口

接口 interface = 全局常量s( public static final 类型 全局常量名 = 值) + 抽象方法s(abstract 没有方法体) + 默认方法s(default 可有方法体) + 静态方法s(static 可有方法体)

接口的语法格式

[public] interface 接口名 [extends 接口1,接口2...] {
    [public] [static] [final] 数据类型 常量名 = 常量;
    [public] [abstract] 返回值的数据类型 方法名(参数列表);
    [public] static 返回值的数据类型 方法名(参数列表){[...]}
    [public] default 返回值的数据类型 方法名(参数列表){[...]}
}

在很多的Java程序中,经常看到编写接口中的方法时省略了public ,有很多读者认为它的访问权限是default,这实际上是错误的。不管写不写访问权限,接口中方法的访问权限永远是public。

接口实现类的语法格式

implement [ˈɪmplɪment,ˈɪmplɪmənt] 实现

接口本身不能直接实例化,接口中的抽象方法和默认方法只能通过接口实现类的实例对象进行调用。实现类通过implements关键字实现接口,并且实现类必须重写接口中所有的抽象方法。需要注意的是,一个类可以同时实现多个接口,实现多个接口时,多个接口名需要使用英文逗号(,)分隔。

修饰符 class 类名 implements 接口1,接口2,...{
    ...
}
package cn.xj.ch04.ex11;

// 定义接口Animal
interface Animal {
    public static final int APPID = 100001; // public, static, final 对于接口成员是冗余的             // 定全局常量,编号
    String NAME = "牧羊犬";                  // 定全局常量,名称
    void shout();                            // 定义抽象方法shout() 缺省 就是 抽象方法
    public void info();                     // 定义抽象方法info() public对于接口成员是冗余的
    static int getID(){                        // 定义静态方法getID(),用于返回ID值
        return Animal.APPID;
    }
}

interface Action {
    public void eat();                         // 定义抽象方法eat()
}

// 定义Dog类实现Animal接口和Action接口
class Dog implements Animal,Action{
    // 实现Action接口中的抽象方法eat()
    public void eat() {
        System.out.println("  喜欢吃骨头");
    }
    // 实现Animal接口中的抽象方法shout()
    public void shout() {
        System.out.println("  汪汪汪……");
    }
    // 实现Animal接口中的抽象方法info()
    public void info() {
        System.out.println("名称:"+NAME);
    }
}

// 定义测试类
class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例11】接口的定义、实现、测试  (许杰)");
        System.out.println("App编号: "+Animal.getID());
        Dog dog = new Dog();            // 创建Dog类的实例对象
        dog.info();                        // 调用Dog实现的info()方法
        dog.shout();                    // 调用Dog类中实现的shout()方法
        dog.eat();                         // 调用Dog类中实现的eat()方法
    }
}

注意:接口的实现类,必须实现接口中的所有抽象方法,否则程序编译报错。

图13 接口的定义类、实现、测试

继承类(含抽象类)、实现接口的语法格式

修饰符 class 类名 extends 父类名 implements 接口1, 接口2,... {
    ...
}
package cn.xj.ch04.ex12;

// 定义接口Animal
interface Animal {
    public String NAME = "牧羊犬";       // 定全局常量,名称
    public void shout();                // 定义抽象方法shout()
    public void info();                 // 定义抽象方法info()
}

//定义抽象类Action
abstract class Action {
    public abstract void eat();       // 定义抽象方法eat()
}

// 定义Dog类继承Action抽象类,并实现Animal接口
class Dog extends Action implements Animal{
    // 实现Action抽象类中的抽象方法eat()
    public void eat() {
        System.out.println("  喜欢吃骨头");
    }
    //实现写Animal接口中的抽象方法shout()
    public void shout() {
        System.out.println("  汪汪汪汪……");
    }
    // 实现Animal接口中的抽象方法info()
    public void info() {
        System.out.println("名称:"+NAME);
    }
}

// 定义测试类
class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例12】定义接口、定义抽象类、继承抽象类、实现接口、测试  (许杰)");
        Dog dog = new Dog();            // 创建Dog类的实例对象
        dog.info();                       // 调用Dog类中实现的info()方法
        dog.shout();                      // 调用Dog类中实现的shout()方法
        dog.eat();                       // 调用Dog类中实现的eat()方法
    }
}
图14 定义接口、定义抽象类、继承抽象类、实现接口、测试
package cn.xj.ch04.ex13;

// 定义接口Animal
interface Animal {
    public String NAME = "牧羊犬";  // public 冗余
    public void info();         // 定义抽象方法info()
}

//定义接口Color
interface Color {
    public void black();           // 定义抽象方法shout()
}

//定义接口Action,Action同时继承接口Animal和接口Color
interface Action extends Animal,Color{
    public void shout();           // 定义抽象方法black()
}

// 定义Dog类实现Action接口
class Dog implements Action{
    // 实现Animal接口中的抽象方法info()
    public void info() {
        System.out.println("名称:"+NAME);
    }
    // 实现Color接口中的抽象方法black()
    public void black() {
        System.out.println("黑色");
    }
    // 实现Action接口中的抽象方法shout()
    public void shout() {
        System.out.println("汪汪……");
    }
}

// 定义测试类
class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例13】定义接口、接口间继承、类实现接口、测试类 (许杰)");
        Dog dog = new Dog();     // 创建Dog类的对象dog
        dog.info();              // 调用Dog类中实现的info()方法
        dog.shout();             // 调用Dog类中实现的shout()方法
        dog.black();             // 调用Dog类中实现的eat()方法
    }
}
图15 定义接口、接口间继承、类实现接口、测试

4. 多态

4.1 多态概述

多态是面向对象思想中的三个非常重要的概念(封装、继承、多态)之一。

多态的两种主要形式

(1) 方法重载(ch02已学,复习)

方法重载指的是在同一个类中定义多个同名方法,但方法的参数类型、个数或顺序不同。

package cn.xj.ch04.ex14_;

public class example_xj {
    public static void main(String[] args) {
        System.out.println("【例14_】方法重载 -- 许杰");
        // 下面是针对求和方法的调用
        System.out.println("add(1, 2)=" + add(1, 2));
        System.out.println("add(1, 2, 3)=" + add(1, 2, 3));
        System.out.println("add(1.2, 2.3)=" + add(1.2, 2.3));
    }

    // 下面的方法实现了两个整数相加
    public static int add(int x, int y) {
        return x + y;
    }

    // 下面的方法实现了三个整数相加
    public static int add(int x, int y, int z) {
        return x + y + z;
    }

    // 下面的方法实现了两个小数相加
    public static double add(double x, double y) {
        return x + y;
    }
}
图16_ 方法重栽

(2) 方法重写(覆盖)

package cn.xj.ch04.ex14;

// 定义抽象类Animal
abstract class Animal {
    abstract void shout();         // 定义抽象shout()方法
}

// 定义Cat类继承Animal抽象类
class Cat extends Animal {
    // 实现shout()方法
    public void shout() {
        System.out.println("喵喵……");
    }
}

// 定义Dog类继承Animal抽象类
class Dog extends Animal {
    // 实现shout()方法
    public void shout() {
        System.out.println("汪汪……");
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例14】方法重写(覆盖) (许杰)");
        Animal an1 = new Cat(); // 创建Cat对象,使用Animal类型的变量an1引用
        Animal an2 = new Dog(); // 创建Dog对象,使用Animal类型的变量an2引用
        an1.shout();
        an2.shout();
    }
}
图16 方法重写

4.2 对象类型的转换

(1) 对象向上转型

对象向上转型,父类对象可以调用子类重写父类的方法。对于向上(父类)转型,程序会自动完成,对象向上转型格式如下所示。

父类类型 父类对象 = 子类实例;
// 可理解未 对象引用
package cn.xj.ch04.ex15;

// 定义Animal类
class Animal {
    public void 叫喊(){
        System.out.println("动物叫喊");
    }
}
// 定义Dog类
class Dog extends Animal {
    // 重写 叫喊()方法
    public void 叫喊() {
        System.out.println("汪汪……");
    }
    public void 吃食() {
        System.out.println("吃骨头……");
    }
}
// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例15】对象类型的转换 向上(父)转型 (许杰)");
        Dog dog = new Dog();  // 创建Dog对象
        Animal an = dog;      // 子类对于向上(父类)转型,程序会自动完成
        an.叫喊();
        // an.吃食();  // 无法解析 'Animal' 中的方法 '吃食'
        System.out.println("--------");
        dog.叫喊();
        dog.吃食();
    }
}
图17 对象类型的转换 向上(父)转型

(2) 对象向下(子类)转型

向下转型一般是为了重新获得因为向上转型而丢失的子类特性。对象在进行的向下转型前,必须先进行向上转型,否则将出现对象转换异常。向下转型时,必须指明要转型的子类类型。

父类类型 父类对象 = 子类实例;
子类类型 子类对象 = (子类)父类对象;
// 强制转型
package cn.xj.ch04.ex16;

// 定义Animal类
class Animal {
    public void 叫喊(){
        System.out.println("动物在叫喊");
    }
}

// 定义Dog类
class Dog extends Animal {
    // 重写 叫喊()方法
    public void 叫喊() {
        System.out.println("汪汪……");
    }
    public void 吃食() {
        System.out.println("吃骨头……");
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例16】对象类型的转换 向下(子类)转型 (许杰)");
        Animal an = new Dog();  // 此时发生了向上转型,子类→父类
        Dog dog = (Dog)an;       // 此时发生了向下转型
        // Dog dog2 = (Dog)new Animal();//编译错误
        dog.叫喊();
        dog.吃食();
    }
}
图18 对象类型的转换 向下(子类)转型

注意:在向下转型时,不能直接将父类实例强制转换为子类实例,否则程序会报错。如,将案例中的第27行代码去掉注释,则程序运行会报错。

图18 对象类型的转换 向下(子类)转型 运行错误

4.3 instanceof关键字

Java中可以使用instanceof关键字判断一个对象是否是某个类(或接口)的实例。

英 [ˈɪnstəns] 实例

package cn.xj.ch04.ex17;

// 定义Animal类
class Animal {
    public void shout(){
        System.out.println("动物叫……");
    }
}
// 定义Dog类
class Dog extends Animal {
    // 重写shout()方法
    public void shout() {
        System.out.println("汪汪……");
    }
    public void eat() {
        System.out.println("吃骨头……");
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例17】实例类型判断 instanceof (许杰)");
        Animal a1 = new Dog();         // 通过向上转型实例化Animal对象
        System.out.println("Animal a1 = new Dog():a1是Animal的实例吗?"+(a1 instanceof Animal));
        System.out.println("Animal a1 = new Dog():a1是Dog的实例吗?"+(a1 instanceof Dog));
        Animal a2 = new Animal();     // 实例化Animal对象
        System.out.println("Animal a2 = new Animal():a2是Animal的实例吗?"+(a2 instanceof Animal));
        System.out.println("Animal a2 = new Animal():a2是Dog的实例吗?"+(a2 instanceof Dog));
    }
}
图19 实例类型判断 instanceof

5. Object类

Java提供了一个Object类,它是所有类的父类,每个类都直接或间接继承了Object类,因此Object类通常被称为超类。当定义一个类时,如果没有使用extends关键字为这个类显式地指定父类,那么该类会默认继承Object类。

object 英 [ˈɒb.dʒɪkt] 对象

Object类中常用方法

方法名称 方法说明
boolean equals() 判断两个对象是否“相等”
int hashCode() 返回对象的哈希值
String toString() 返回对象的字符串表示形式
package cn.xj.ch04.ex18;

// 定义Animal类
class Animal {
    // 定义动物叫的方法
    void shout() {
        System.out.println("动物叫!");
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args)  {
        System.out.println("【例18】Object  调用Object的toString()等方法  (许杰)");
        Animal animal = new Animal();               // 创建Animal类对象
        animal.shout();
        System.out.println(".hashCode()="+animal.hashCode());     // 调用 hashCode()
        System.out.println("类名:"+animal.getClass().getName()); 
        // 调用 .getClass().getName()
        System.out.println(".toString()="+animal.toString());     // 调用toString()
    }
}
图20 调用Object的toString()等方法
package cn.xj.ch04.ex19;

// 定义Animal类
class Animal {
    //重写Object类的toString()方法
    public String toString(){
        return "这是一个动物。";
    }
}

// 定义测试类
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例19】重写Object类的toString()方法  (许杰)");
        Animal animal = new Animal();             // 创建animal对象
        System.out.println(animal.toString()); // 调用toString()方法并打印
    }
}
图21 重写Object类的toString()方法

6. 内部类

6.1 成员内部类

在一个类中除了可以定义成员变量、成员方法,还可以定义类,这样的类被称作成员内部类。成员内部类可以访问外部类的所有成员变量、方法,无论外部类的成员是何种访问权限

如果想通过外部类访问内部类,则需要通过外部类创建内部类对象,创建内部类对象的具体语法格式如下:

外部类名 外部类对象 = new 外部类名();
外部类名.内部类名 内部类对象 = 外部类对象.new 内部类名();
package cn.xj.ch04.ex20;

class Outer {
    int m = 0;                         // 定义类的成员变量
    private int m2 = 2;                         // 定义类的成员变量
    //外部类方法test1()
    void test1() {
        System.out.println("外部类成员方法test1()");
    }
    private void test2() {
        System.out.println("外部类private成员方法test2()");
    }

    // 下面的代码定义了一个成员内部类Inner
    class Inner {
        int n = 1;
        private int n2 = 3;
        void show1() {
            // 在成员内部类的方法中访问外部类的成员变量m
            System.out.println("外部成员变量m = " + m);
            System.out.println("外部成员private变量m2 = " + m2);
            // 在成员内部类的方法中访问外部类的成员方法test1()
            test1();
            test2();
        }
        void show2() {
            System.out.println("  内部成员方法show2()");
        }
    }
    //外部类方法test2()
    void test3() {
        System.out.println("外部类成员方法test3()");
        Inner inner = new Inner();                         //实例化内部类对象inner
        System.out.println("  内部成员变量n = " + inner.n); //访问内部类变量和方法
        System.out.println("  内部成员变量n2 = " + inner.n2); //访问内部类变量和方法
        inner.show2();
    }
}

public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例20】定义内部类,访问外部类的属性和方法、外部类访问内部类  (许杰)");
        Outer outer = new Outer();                //实例化外部类对象outer
        Outer.Inner inner = outer.new Inner();    //实例化内部类对象inner
        inner.show1();         //在内部类中访问外部类的成员变量m和成员方法test1()
        outer.test3();         //在内部类中访问内部类的成员变量n和成员方法show2()
    }
}
图22 定义内部类,访问外部类的属性和方法、外部类访问内部类

6.2 局部内部类

局部内部类,也称为方法内部类,是指定义在某个局部范围中的类,它和局部变量都是在方法中定义的,有效范围只限于方法内部。

在局部内部类中,局部内部类可以访问外部类的所有成员变量和成员方法,而在外部类中无法直接访问局部内部类中的变量和方法。

如果要在外部类中访问局部内部类的成员,只能在局部内部类的所属方法中创建局部内部类的对象,通过对象访问局部内部类的变量和方法。

package cn.xj.ch04.ex21;

class Outer {
    int m = 1;                      // 定义类的成员变量
    private int m2 = 2;                      // 定义类的成员变量
    //定义一个成员方法test1()
    void test1() {
        System.out.println("外部类成员方法test1()");
    }
    private void test2() {
        System.out.println("外部类private成员方法test2()");
    }
    void test3() {
        System.out.println("外部类成员方法test3() 开始执行......");
        //定义一个局部内部类,在局部内部类中访问外部类变量和方法
        class Inner {
            int n = 3;
            private int n2 = 4;
            void show() {
                System.out.println("外部类成员变量m = " + m);
                System.out.println("外部类private成员变量m2 = " + m2);
                test1();
                test2();
            }
        }
        //访问局部内部类中的变量和方法
        Inner inner = new Inner();
        System.out.println("  局部内部类成员变量n = " + inner.n);
        System.out.println("  局部内部类private成员变量n2 = " + inner.n2);
        inner.show();
        System.out.println("----外部类成员方法test3() 结束执行");
    }
}
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例21】定义局部内部类(方法内定义的内部类) (许杰)");
        Outer outer = new Outer();
        outer.test3();     //通过外部类对象outer调用创建了局部内部类的方法test2()
    }
}
图23 定义局部内部类(方法内定义的内部类)

6.3 静态内部类

静态内部类,就是使用static关键字修饰的成员内部类。

与成员内部类相比,在形式上,静态内部类只是在内部类前增加了static关键字,但在功能上,静态内部类只能访问外部类的静态成员,通过外部类访问静态内部类成员时,因为程序已经提前在静态常量区分配好了内存,所以即使静态内部类没有加载,依然可以通过外部类直接创建一个静态内部类对象。

创建静态内部类对象的基本语法格式如下。

外部类名.静态内部类名 变量名 = new 外部类名.静态内部类名();
package cn.xj.ch04.ex22;

class Outer {
    static int m = 0; // 定义类的静态变量

    static void read(String name){
        System.out.println("静态方法 static read(): "+name +"在看书。");
    }
    // 下面的代码定义了一个静态内部类
    static class Inner {
        int n = 1;
        void show() {
            // 在静态内部类的方法中访问外部类的静态变量m
            System.out.println("外部类静态变量m = " + m);
            read("许杰");
        }
    }
}

public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例22】静态内部类(static修饰的内部类) (许杰)");
        Outer.Inner inner = new Outer.Inner();
        inner.show();
    }
}
图24 静态内部类

6.4 匿名内部类

定义:匿名内部类其实就是没有名称的内部类。开发中,最常用到的内部类就是匿名内部类了。

匿名内部类:是内部类的简化写法。它的本质是一个带具体实现的父类或者父接口的匿名的子类对象。

new 父类名或者接口名(){
  // 方法重写
  @Override
  public void 方法名(){
    //执行语句
  }
};

当某一个类或接口只有1个抽象方法时,可用匿名内部类的方法简化代码的定义和执行。

(1) 基于抽象类匿名内部类

package cn.xj.ch04.ex23_1;
public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例23】匿名内部类  传统方法实现("+
                "\n1. 定义抽象父类(只有1个抽象方法) \n2. 定义子类实现实现父类抽象方法"+
                "\n3. 定义子类对象 \n4. 执行子类对象的方法) (许杰)");
        Dog d = new Dog();
        d.eat();
    }
}

// 抽象类且只有1个抽象方法
abstract class Animal {
    public abstract void eat();
}

class Dog extends Animal{
    public void eat() {
        System.out.println("狗吃肉");
    }
}
图25 匿名内部类 传统方法实现
package cn.xj.ch04.ex23_2;
/* 匿名内部类的前提
 *   必须是类或者接口
 *
 * 格式:
 * new 类名/接口名(){
 *   重写抽象方法
 * }
 */

public class Example_xj {
    public static void main(String[] args) {
        System.out.println("【例23】匿名内部类 抽象类的匿名内部类 (许杰)");
        //整体就等效于:是Animal父类的子类对象
        new Animal() {
            public void eat() {
                System.out.println("狗吃肉");
            }
        }.eat();

        // 第2种调用方式
        Animal a = new Animal() {
            public void eat() {
                System.out.println("狗吃肉2");
            }
        };
        a.eat();

        // 带局部变量
        String name = "哈士奇";//通过匿名内部类访问局部变量。在JDK8版本之前,必须加final关键字
        // name ="金毛";  // 变量 'name' 从内部类中访问,需要为 final 或有效 final
        new Animal() {
            public void eat() {
                System.out.println(name + "在啃骨头");
            }
        }.eat();
    }
}

// 抽象类且只有1个抽象方法
abstract class Animal {
    public abstract void eat();
}
图26 匿名内部类 抽象类的匿名内部类

(2) 基于接口匿名内部类

package cn.xj.ch04.ex23_3;
/*
以接口举例,当你使用一个接口时,似乎得做如下几步操作:
1.定义接口
2.定义子类,重写接口中的方法
3.创建子类对象
4.调用重写后的方法
 */
public class Example_xj {
    public static void main(String[] args){
        System.out.println("【例23】匿名内部类 传统的接口编程 (许杰)");
        Cat c = new Cat();
        c.叫喊();
    }
}

interface Animal{                    //定义接口Animal
    void 叫喊();                    //定义抽象方法
}

class Cat implements Animal{
    public void 叫喊(){
        System.out.println("喵喵叫喊");
    }
}
图27 匿名内部类 传统的接口编程
package cn.xj.ch04.ex23_4;
public class Example_xj {
    public static void main(String[] args){
        System.out.println("【例23】匿名内部类 实现接口的匿名内部类 (许杰)");
        new Animal() {
            public void 叫喊(){
                System.out.println("喵喵叫喊");
            }
        }.叫喊();

        // 方法2
        String name = "小花猫";
        animalShout(new Animal(){    //调用animalShout()方法,参数为匿名内部类
            public void 叫喊() {
                System.out.println(name+"在喵喵叫喊");
            }
        });
    }
    public static void animalShout(Animal an){    //该方法参数为Animal接口类型
        an.叫喊();
    }
}

// 接口种只有1个抽象方法时用匿名内部类
interface Animal{                    //定义接口Animal
    void 叫喊();                    //定义抽象方法
}
图28 匿名内部类 实现接口的匿名内部类
图29 本章源码文件名

返回