Inner Classes小结

本文详细探讨了Java内部类的概念及应用,包括常规内部类如何访问外部类的私有成员,以及局部内部类如何增强代码封装性和安全性。

   Inner Classes 在平常的应用中虽然不是很多,但是仍然有很多细节问题值得注意,需要掌握。因此,这里对这方面内容做一小结。

   Inner Classes,顾名思义,就是定义在另一个类中的类,之所以出现这一概念,主要是因为它具有如下两个特点:  

  1. inner classes中的方法可以访问类定义域内的所有数据, 哪怕数据是private的。
  2. inner classes对同包内的其它类是不可见的。

   常规内部类

   下面,我将针对一段具体代码,对Inner Classes的特性和应用作具体的阐述。

public class TimerTestForInnerClass {
    public static void main(String[] args) {
        TalkingClock clock = new TalkingClock(100, true);
        clock.start();
	
        JOptionPane.showMessageDialog(null, "Quit Program?");
        System.exit(0);
    }
}

class TalkingClock {
  private int interval;
  private boolean beep;
  
  public TalkingClock(int interval, boolean beep) {
      this.interval = interval;
      this.beep = beep;
  }
  
  public void start() {
      ActionListener listener = new TimePrinter();
      Timer t = new Timer(interval, listener);
      t.start();
  }
  
  public class TimePrinter implements ActionListener {
      public void actionPerformed(ActionEvent event) {
          Date now = new Date();
          System.out.println("At the tone, the time is " + now);
          if (beep) Toolkit.getDefaultToolkit().beep();
      }
  }
  
}

 

    在上面的代码中,TimePrinter是TalkingClock的内部类。仔细观察,发现actionPerformed方法中引用的beep变量在TimePrinter中并没有定义,它是外部类TalkingClock的成员变量。这就是inner classes特点一的展示,之所以可以如此,是因为inner class在被创建时获得了外部类的一个引用,这个引用是由编译器自动传给内部类的构造函数的,因此,内部类中使用的beep变量的完整形式其实是:

 

if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();

   上述代码中的OuterClass.this代表的就是外部类的引用。

   如果内部类是public的,那么我们也可以在外部类以外来创建内部类,方法如下:

TalkingClock jabberer = new TalkingClock(1000, true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();

  

Local Inner Classes 局部内部类

   内部类除了可以作为外部类的成员,还可以定义在外部类的方法中,这样的内部类称为:Local Inner Classes。Local Inner Classes不需要访问权限修饰符(access specifier),因为它们的作用域受它们被定义的块域的限制。Local Inner Classes的一个重要的优点是,除了定义它们的方法以外,外界任何类或方法都无法访问它们。因此,当一个内部类只需要被一个方法使用,并且想屏蔽任何其他方法时,就可以将它们定义为Local的。

public void start() {
    class TimePrinter implements ActionListener {
      public void actionPerformed(ActionEvent event) {
          Date now = new Date();
          System.out.println("At the tone, the time is " + now);
          if (beep) Toolkit.getDefaultToolkit().beep();
      }
    }
    ActionListener listener = new TimePrinter();
    Timer t = new Timer(interval, listener);
    t.start();
}

   上面的代码就是Local Inner Class的应用实例。

   

    Local Inner Class不光可以使用Outer Class的成员变量,还可使用它所在方法的参数以及方法中的局部变量。但要注意一点,这些外部引用变量都必须是final型,否则会收到编译器错误。见下面的代码:

public void start(int interval, final boolean beep) {
    class TimePrinter implements ActionListener {
      public void actionPerformed(ActionEvent event) {
          Date now = new Date();
          System.out.println("At the tone, the time is " + now);
          if (beep) Toolkit.getDefaultToolkit().beep();
      }
    }
    ActionListener listener = new TimePrinter();
    Timer t = new Timer(interval, listener);
    t.start();
}

   我们来简单了解下beep参数是如何传入内部类的方法中。直观来看,beep变量是无法存留到actionPerformed方法执行之时的,它在start()方法执行结束之后就被回收了,而actionPerformed则要过短时间才执行,因此,为了正常使用beep变量,我们需要为它做一个copy,并将这个copy传进内部类中。所以,内部类中所谓的beep,其实是原有变量的副本。

 

TIP:为什么只有final型的参数或局部变量可以传入inner class?(整理自Core Java,如有不妥,敬请指正)

在很多时候,这个final的限制其实为我们带来了很多不便,因为我们有的时候确实是想要在inner class中改变局部变量的,如下面这段代码:

int counter = 0;
Date[] dates = new Date[100];
for (int i = 0; i < dates.length; i++)
   dates[i] = new Date() {
          public int compareTo(Date other) {
              counter++; // ERROR

              return super.compareTo(other);
          }
   };

Arrays.sort(dates);
System.out.println(counter + " comparisons.");

 

   我们只是想获取比较次数,但是由于内部类的final限制,这样的代码显然是不合法的。但如果直接加上final,我们的update目标又无法达到,所以,只能做如下的转换:

final int[] counter = new int[1];
for (int i = 0; i < dates.length; i++)
   dates[i] = new Date() {
          public int compareTo(Date other) {
              counter[0]++;
              return super.compareTo(other);
          }
   };

    这样,虽然counter是final的,但是我们却可以随意改变它内部的值,从而变相的达到了update的目标。

    事实上,在inner class起初创建时,这种从primitive型到array型的转换最开始是由编译器自动完成的,但是开发者们很害怕编译器在“背着他们”创建那么些对象,因此,最后还是决定加上final这个限制,如果需要在inner class中改变变量值,那就让programer自己去做转换。而且,也并不排除,随着Java语言的不断完善,会有更好的方式来替代final限制,达到原有的安全效果。

  

<think> 我们正在处理一个关于Java编译错误的问题:'Cannot use '<>' with anonymous inner classes'。根据提供的引用,我们可以总结如下: 1. 引用[1]和[2]指出,在Java 8及更早版本中,匿名内部类不能使用钻石操作符(<>)。这是因为匿名内部类的类型推断在Java 8中不被支持。编译器会报错。 2. 引用[3]提到,在Java 9中,匿名内部类可以使用类型推断(即钻石操作符),因此同样的代码在Java 9及以上版本可以正常编译。 3. 引用[4]展示了一个在Java 9中可以使用钻石操作符创建匿名内部类(这里是匿名HashMap子类)的例子,并指出在Java 8及之前版本中,这样的代码会导致编译错误。 因此,解决方案取决于你使用的Java版本: - 如果你使用的是Java 8或更早版本,你需要显式地指定泛型类型,而不能使用钻石操作符<>。 - 如果你使用的是Java 9或更高版本,那么钻石操作符在匿名内部类中是被允许的,代码可以正常编译。 具体到错误示例,假设你正在尝试创建一个匿名内部类的实例,并使用了钻石操作符,例如: ```java Map<String, Integer> map = new HashMap<>() { { put("a", 1); put("b", 2); } }; ``` 在Java 8中,这会报错:Cannot use '<>' with anonymous inner classes。 **解决方法(针对Java 8及以下):** 1. 显式指定泛型类型: ```java Map<String, Integer> map = new HashMap<String, Integer>() { { put("a", 1); put("b", 2); } }; ``` 2. 或者,考虑使用其他方式初始化Map,比如使用静态工厂方法(如果不需要修改Map,可以使用Collections.unmodifiableMap,或者使用Java 9引入的Map.of等)。但是注意,在Java 8中,你可以使用静态初始化块或双括号初始化(如上所示,但需要指定类型)来初始化。 另外,引用[4]还提到了使用双括号初始化可能带来的缺点(例如,创建了额外的类,且不能表示为单个表达式),所以也可以考虑使用其他初始化方法。 **升级Java版本:** 如果你可以升级到Java 9或更高版本,那么这个问题就不存在了,因为Java 9开始支持匿名内部类的钻石操作符(类型推断)。 综上所述,请根据你的Java版本选择相应的解决方案。 §§相关问题§§ 1. 如何在Java 8中正确初始化一个匿名内部类的泛型集合? 2. Java 9在类型推断方面有哪些改进? 3. 除了使用匿名内部类,还有哪些初始化Map的方法? 4. 双括号初始化有什么优缺点? </think> ### 解决 `Cannot use '<>' with anonymous inner classes` 错误的方法 这个错误发生在 **Java 8 及更早版本** 中,当使用钻石操作符 `<>` 创建匿名内部类时。以下是完整的解决方案: #### 1. **显式声明泛型类型(Java 8 推荐方案)** 移除钻石操作符 `<>`,**显式指定泛型类型**: ```java // 错误写法(Java 8 会报错) Map<String, Integer> map = new HashMap<>() { { put("a", 1); } }; // 正确写法(显式声明类型) Map<String, Integer> map = new HashMap<String, Integer>() { { put("a", 1); } }; ``` > 💡 原理:匿名内部类在编译时需要完整的类型信息,Java 8 的类型推断无法处理这种场景[^1][^2]。 #### 2. **升级到 Java 9+** 从 **Java 9 开始** 支持匿名内部类使用钻石操作符: ```java // Java 9+ 正常编译 Comparator<Object> com = new Comparator<>() { @Override public int compare(Object o1, Object o2) { return 0; } }; ``` > ✅ 升级到 Java 9 或更高版本可直接解决[^3]。 #### 3. **替代方案:避免匿名内部类** 使用 **工厂方法** 或 **Lambda 表达式**: ```java // 方案 1:Collections.unmodifiableMap + 双括号初始化(需显式类型) Map<String, Integer> map = Collections.unmodifiableMap(new HashMap<String, Integer>() {{ put("a", 1); }}); // 方案 2:Java 9+ 的 Map.of()(无需匿名类) Map<String, Integer> map = Map.of("a", 1); ``` #### 关键总结 | 方案 | 适用版本 | 操作要点 | |---------------------|------------|-----------------------------| | 显式声明泛型类型 | Java 8 | 移除 `<>` 并补全类型(如 `new HashMap<String, Integer>`) | | 升级 JDK | Java 9+ | 直接使用 `<>` 无报错 | | 改用工厂方法/Lambda | Java 8+ | 避免匿名内部类 | > ⚠️ **注意**:双括号初始化 `new HashMap() {{ ... }}` 会创建匿名子类,在 Java 8 中仍需显式指定泛型类型[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值