一、内部类

1.1 概述

  • 什么是内部类

    将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,类B则称为外部类

    ​ 例如:身体和心脏的关系。又如:汽车和发动机的关系。

  • 分类

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

1.2 访问特点

  • 内部类可以直接访问外部类的成员,包括私有成员。
  • 外部类要访问内部类的成员,必须要建立内部类的对象。

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

1.3 成员内部类

如果一个类是定义在一个方法外部的,那么这就是一个成员内部类。

  • 成员内部类 :定义在类中方法外的类。

  • 定义格式

    	修饰符 class 外部类名称 {
         
       	
            修饰符 class 内部类名称 {
         
            	// ...
            }
        	// ...
    }
    

    在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。

    比如,汽车类 Car 中包含发动机 类 Engine ,这时, Engine 就可以使用内部类来描述,定义在成员位置。

    代码举例:

    class Car { //外部类 
        class Engine { //内部类 
            
        } 
    }
    
  • 使用方式

    1. 间接方式:在外部类的方法当中,使用内部类;然后 main 只是调用外部类的方法。

    2. 直接方式,公式:
      类名称 对象名 = new 类名称();

      外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();

    访问演示,代码如下:

    • 定义类

      // 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
      public class Outer {
              // 外部类
      
          // 外部类的成员变量
          private int num = 10; // 外部类的成员变量
      
          public class Inner {
              // 成员内部类
              private int num = 20; // 内部类的成员变量
              // 内部类的方法
              public void methodInner() {
             
                  System.out.println("内部类的方法");
                  int num = 30; // 内部类方法的局部变量
                  System.out.println(num); // 局部变量,就近原则
                  System.out.println(this.num); // 内部类的成员变量
                  System.out.println(Outer.this.num); // 外部类的成员变量
              }
          }
      
          // 外部类的方法
          public void methodOut() {
             
              System.out.println("外部类的方法");
              new Inner().methodInner(); // 调用内部类的方法
          }
      
      }
      

      定义测试类

      public class Demo02InnerClass {
             
      
          public static void main(String[] args) {
             
      
              Outer outer = new Outer(); // 外部类的对象
              // 间接方式
              // 通过外部类的对象,调用外部类的方法,里面间接在使用内部类 Inner
              outer.methodOut();
              System.out.println("=====================");
              // 直接方式,按照公式写:
              // 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
              Outer.Inner obj = new Outer().new Inner();
              // 或者
              // Inner obj = outer.new Inner();
              obj.methodInner();
          }
      }
      

    注意:

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

      ​ 比如,Outer$Inner.class

    2. 对于成员内部类的成员变量与外部类的成员变量,如果出现了重名现象,那么访问格式是:

      外部类名称.this.外部类成员变量名

1.4 局部内部类

1.4.1 概述

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。

  • 局部内部类 :定义在类中方法内的类。

    “局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。

  • 定义格式

    修饰符 class 外部类名称 {
         
        修饰符 返回值类型 外部类方法名称(参数列表) {
         
            class 局部内部类名称 {
         
                // ...
            }
        }
    }
    
  • 定义一个类的时候,权限修饰符规则

    public > protected > (default) > private

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

1.4.2 匿名内部类【重点】

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

  • 使用情况

    如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。

  • 前提

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

  • 格式

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

    对格式“new 父类名或者接口名() {…}”进行解析:

    1. new代表创建对象的动作
    2. 父类名(接口名)就是匿名内部类需要继承(实现)哪个类(接口)
    3. {…}这才是匿名内部类的内容
  • 使用方法

    以接口为例,匿名内部类的使用,代码如下:

    • 定义接口:

      public interface MyInterface {
             
      
          void method1(); // 抽象方法
      
          void method2();
      
      }
      
    • 定义测试类:

      public class DemoMain {
             
      
          public static void main(String[] args) {
             
      
      // 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. 匿名内部类,在【创建对象】的时候,只能使用唯一一次

    如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。

    1. 匿名对象,在【调用方法】的时候,只能调用唯一一次
      如果希望同一个对象,调用多次方法,那么必须给对象起个名字。

    2. 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称
      强调:匿名内部类和匿名对象不是一回事!!!

二、引用类型用法总结

实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。

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

2.1 class 作为成员变量

在定义一个类Role(游戏角色)时,代码如下:

public class Role {
   
    int id; // 角色id 
    int blood; // 生命值 
    String name; // 角色名称
}

使用 int 类型表示 角色id和生命值,使用 String 类型表示姓名。此时, String 本身就是引用类型,由于使用的方式类似常量,所以往往忽略了它是引用类型的存在。如果我们继续丰富这个类的定义,给 Role 增加武器,穿戴装备等属性,我们将如何编写呢?

定义武器类,将增加攻击能力:

public class Weapon {
    
    String name; // 武器名称 
    int hurt;	// 伤害值 
   
    public Weapon() {
   
    }

    public Weapon(String name, int hurt) {
   
        this.name = name;
        this.hurt = hurt;
    }
// ... 省略get/set方法
}

定义穿戴盔甲类,将增加防御能力,也就是提升生命值:

public class Armour {
    
    String name;	// 装备名称
    int protect;	// 防御值 
    
     public Armour() {
   
    }

    public Armour(String name, int protect) {
   
        this.name = name;
        this.protect = protect;
    }
// ... 省略get/set方法
 }

再次定义角色类:

public class Role {
    
    int id; 
    int blood;
    String name; 
    // 添加武器属性 
    Weapon wp;
    // 添加盔甲属性 
    Armour 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()); 
    } 
    
// ... 省略get/set方法
}

定义测试类:

public class DemoMain {
   

    public static void main(String[] args) {
   
        // 创建Weapon 对象
        Weapon wp = new Weapon("屠龙宝刀" , 999999);
       //创建Armour 对象
        Armour ar = new Armour("麒麟甲",10000);
        // 创建Role 对象
        Role r = new Role();
        // 设置武器属性
        r.setWp(wp);
        // 设置盔甲属性
        r.setAr(ar);
        // 攻击
        r.attack();
        // 穿戴盔
        r.wear();
    }
}
输出结果:
    使用屠龙宝刀, 造成999999点伤害
	穿上麒麟甲, 生命值增加10000

总结:

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

2.2 interface 作为成员变量

接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在 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();
    } 
}
输出结果: 
	发动法术攻击:纵横天下
	攻击完毕
	发动法术攻击:逆转乾坤
	攻击完毕

总结:

  1. 我们使用一个接口,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。
  2. 接口作为成员变量时,对它进行赋值的操作,实际上,是赋给它该接口的一个子类对象。

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

当接口作为方法的参数时,需要传递什么呢?当接口作为方法的返回值类型时,需要返回什么呢?对,其实都是它的子类对象ArrayList 类我们并不陌生,查看 API 我们发现,实际上,它是 java.util.List 接口的实现类。所以,当我们看见 List 接口作为参数或者返回值类型时,当然可以将 ArrayList 的对象进行传递或返回。

请观察如下方法:获取某集合中所有的偶数

定义方法

public static List<Integer> getEvenNum(List<Integer> list) {
    
    // 创建保存偶数的集合 
    ArrayList<Integer> evenList = new ArrayList<>(); 
    // 遍历集合list,判断元素为偶数,就添加到evenList中 
    for (int i = 0; i < list.size(); i++) {
    
        Integer integer = list.get(i); 
        if (integer % 2 == 0) {
    
            evenList.add(integer); 
        } 
    }
    /* 返回偶数集合 因为getEvenNum方法的返回值类型是List,而ArrayList是List的子类, 所以evenList可以返回 */
    return evenList; 
}

调用方法:

public class Test {
    
    public static void main(String[] args) {
    
        // 创建ArrayList集合,并添加数字 
        ArrayList<Integer> srcList = new ArrayList<>(); 
        for (int i = 0; i < 10; i++) {
    
            srcList.add(i); 
        }
        /* 获取偶数集合 因为getEvenNum方法的参数是List,而ArrayList是List的子类, 所以srcList可以传递 */
        List list = getEvenNum(srcList); 
        System.out.println(list);
    }
}
输出结果:
    [0, 2, 4, 6, 8]

总结:

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