【问题标题】:How are Anonymous inner classes used in Java?Java 中如何使用匿名内部类?
【发布时间】:2010-09-26 04:36:00
【问题描述】:

Java 中匿名类有什么用?我们能说匿名类的使用是Java的优势之一吗?

【问题讨论】:

  • 这不是 Java 的优势,而是一种解决 Java 中缺少闭包的方法。
  • Java 8 也引入了 Lambda 表达式。检查答案:*.com/questions/355167/…

标签: java anonymous-class anonymous-inner-class


【解决方案1】:

我认为“匿名类”是指anonymous inner class

匿名内部类可以在创建具有某些“附加”(例如覆盖方法)的对象实例时很有用,而不必实际子类化一个类。

我倾向于将它用作附加事件侦听器的快捷方式:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // do something
    }
});

使用这种方法可以使编码更快一些,因为我不需要创建一个实现ActionListener 的额外类——我可以只实例化一个匿名内部类,而无需实际创建一个单独的类。

我只将这种技术用于“快速而肮脏”的任务,让整个班级感觉不必要。拥有多个做同样事情的匿名内部类应该重构为一个实际的类,无论是内部类还是单独的类。

【讨论】:

  • 或者你可以将重复的匿名内部类重构为一个带有匿名内部类的方法(可能还有其他一些重复的代码)。
  • 很好的答案,但问题很简单。这是否意味着 Java 可以在没有匿名内部类的情况下生存,并且它们就像是可供选择的额外选项?
  • 解释得很好,即使很难,我建议任何阅读此文的人查看一下 java 8 和 lambda 表达式可以做什么来使编码更快、更易读。
  • @user2190639 准确地说,Lambda in Java8 再好不过了
  • 你为什么说overloading methods而不是overriding methods
【解决方案2】:

匿名内部类实际上是闭包,因此它们可用于模拟 lambda 表达式或“委托”。以这个接口为例:

public interface F<A, B> {
   B f(A a);
}

您可以匿名使用它在 Java 中创建 first-class function。假设您有以下方法,它返回给定列表中大于 i 的第一个数字,如果没有更大的数字,则返回 i:

public static int larger(final List<Integer> ns, final int i) {
  for (Integer n : ns)
     if (n > i)
        return n;
  return i;
}

然后你有另一个方法返回给定列表中小于 i 的第一个数字,如果没有数字更小,则返回 i:

public static int smaller(final List<Integer> ns, final int i) {
   for (Integer n : ns)
      if (n < i)
         return n;
   return i;
}

这些方法几乎相同。使用第一类函数类型 F,我们可以将它们重写为一个方法,如下所示:

public static <T> T firstMatch(final List<T> ts, final F<T, Boolean> f, T z) {
   for (T t : ts)
      if (f.f(t))
         return t;
   return z;
}

您可以使用匿名类来使用 firstMatch 方法:

F<Integer, Boolean> greaterThanTen = new F<Integer, Boolean> {
   Boolean f(final Integer n) {
      return n > 10;
   }
};
int moreThanMyFingersCanCount = firstMatch(xs, greaterThanTen, x);

这是一个非常人为的例子,但是很容易看出能够像传递值一样传递函数是一个非常有用的特性。请参阅 Joel 本人的 "Can Your Programming Language Do This"

一个很好的 Java 编程库:Functional Java.

【讨论】:

  • 不幸的是,在 java 中进行函数式编程的冗长,恕我直言,超过了它的收益 - 函数式编程的显着点之一是它倾向于减少代码大小,并使事情更容易阅读和修改.但是函数式 java 似乎根本没有这样做。
  • 函数式编程的所有可理解性,以及 Java 的简洁性!
  • 根据我的经验,Java 中的函数式风格需要预先考虑冗长,但从长远来看,它会产生简洁性。例如,myList.map(f) 比相应的 for 循环要简洁得多。
  • Scala,一种函数式编程风格的语言,据称在 JVM 中运行良好,可能是函数式编程场景的一个选项。
【解决方案3】:

匿名内部类用于以下场景:

1.) 对于覆盖(子类化),当类定义除当前情况外不可用时:

class A{
    public void methodA() {
        System.out.println("methodA");
    }
}

class B{
    A a = new A() {
        public void methodA() {
            System.out.println("anonymous methodA");
        }
    };
}

2.) 实现接口,仅当前情况需要实现接口时:

interface InterfaceA{
    public void methodA();
}

class B{
    InterfaceA a = new InterfaceA() {
        public void methodA() {
            System.out.println("anonymous methodA implementer");
        }
    };
}

3.) 参数定义匿名内部类:

interface Foo {
    void methodFoo();
}

class B{
    void do(Foo f) { }
}

class A{
    void methodA() {
        B b = new B();
        b.do(new Foo() {
            public void methodFoo() {
                System.out.println("methodFoo");
            } 
        });
    } 
} 

【讨论】:

  • 很好的答案,看起来像 3.) 是用于事件侦听器的模式
  • 很好的解释。
【解决方案4】:

我有时将它们用作 Map 实例化的语法技巧:

Map map = new HashMap() {{
   put("key", "value");
}};

Map map = new HashMap();
map.put("key", "value");

它在执行大量 put 语句时节省了一些冗余。但是,当外部类需要通过远程处理进行序列化时,我也遇到了问题。

【讨论】:

  • 要明确,第一组大括号是匿名内部类(HashMap 的子类)。第二组大括号是实例初始化器(而不是静态初始化器),然后设置 HashMap 子类的值。 +1 用于提及它,-1 用于不为菜鸟拼出它。 ;-D
  • 阅读更多关于双括号语法here
【解决方案5】:

它们通常用作回调的详细形式。

我想你可以说与没有它们相比,它们是一个优势,并且每次都必须创建一个命名类,但类似的概念在其他语言中实现得更好(如闭包或块)

这是一个摇摆示例

myButton.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e) {
        // do stuff here...
    }
});

虽然它仍然很冗长,但它比强迫你为每个丢弃的侦听器定义一个命名类要好得多(尽管取决于情况和重用,这可能仍然是更好的方法)

【讨论】:

  • 你的意思是简洁吗?如果它是冗长的,则回调将保持独立,使其更大一点,从而使其变得冗长。如果你说这仍然是冗长的,那么简洁的形式会是什么?
  • @user3081519,类似myButton.addActionListener(e -&gt; { /* do stuff here */})myButton.addActionListener(stuff) 会更简洁。
【解决方案6】:

在需要在另一个函数中为特定目的创建类的情况下使用它,例如,作为侦听器、作为可运行对象(生成线程)等。

这个想法是您从函数代码内部调用它们,因此您永远不会在其他地方引用它们,因此您不需要命名它们。编译器只是枚举它们。

它们本质上是语法糖,随着它们变大,通常应该移到别处。

我不确定这是否是 Java 的优势之一,但如果您确实使用它们(不幸的是,我们都经常使用它们),那么您可以争辩说它们是其中之一。

【讨论】:

    【解决方案7】:

    匿名类指南。

    1. 匿名类同时声明和初始化。

    2. 匿名类必须扩展或实现到一个且只有一个类或接口。

    3. 由于匿名类没有名字,所以只能使用一次。

    例如:

    button.addActionListener(new ActionListener(){
    
                public void actionPerformed(ActionEvent arg0) {
            // TODO Auto-generated method stub
    
        }
    });
    

    【讨论】:

    • 关于#3:不完全正确。您可以通过反射获取匿名类的多个实例,例如ref.getClass().newInstance().
    • 规则无法回答问题。
    【解决方案8】:

    是的,匿名内部类绝对是 Java 的优势之一。

    使用匿名内部类,您可以访问周围类的最终变量和成员变量,这在侦听器等中派上用场。

    但一个主要优点是内部类代码(至少应该)与周围的类/方法/块紧密耦合,具有特定的上下文(周围的类、方法和块)。

    【讨论】:

    • 能够访问周围的类非常重要!我认为这是在许多情况下使用匿名类的原因,因为它需要/使用周围类/方法的非公共属性、方法和局部变量,否则(如果使用单独的类)将不得不被通过或发布。
    【解决方案9】:
    new Thread() {
            public void run() {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    System.out.println("Exception message: " + e.getMessage());
                    System.out.println("Exception cause: " + e.getCause());
                }
            }
        }.start();
    

    这也是匿名内部类型使用线程的例子之一

    【讨论】:

      【解决方案10】:

      inner class 与外部类的实例相关联,有两种特殊类型:本地类和Anonymous class。匿名类使我们能够同时声明和实例化一个类,从而使代码简洁。我们只需要一次本地类时使用它们,因为它们没有名称。

      考虑来自doc 的示例,其中我们有一个Person 类:

      public class Person {
      
          public enum Sex {
              MALE, FEMALE
          }
      
          String name;
          LocalDate birthday;
          Sex gender;
          String emailAddress;
      
          public int getAge() {
              // ...
          }
      
          public void printPerson() {
              // ...
          }
      }
      

      我们有一种方法可以将匹配搜索条件的成员打印为:

      public static void printPersons(
          List<Person> roster, CheckPerson tester) {
          for (Person p : roster) {
              if (tester.test(p)) {
                  p.printPerson();
              }
          }
      }
      

      CheckPerson 是这样的接口:

      interface CheckPerson {
          boolean test(Person p);
      }
      

      现在我们可以利用实现这个接口的匿名类来指定搜索条件:

      printPersons(
          roster,
          new CheckPerson() {
              public boolean test(Person p) {
                  return p.getGender() == Person.Sex.MALE
                      && p.getAge() >= 18
                      && p.getAge() <= 25;
              }
          }
      );
      

      这里的接口很简单,匿名类的语法显得笨拙和不清楚。

      Java 8 引入了一个术语Functional Interface,它是一个只有一个抽象方法的接口,因此我们可以说CheckPerson 是一个函数式接口。我们可以使用Lambda Expression,它允许我们将函数作为方法参数传递为:

      printPersons(
                      roster,
                      (Person p) -> p.getGender() == Person.Sex.MALE
                              && p.getAge() >= 18
                              && p.getAge() <= 25
              );
      

      我们可以使用标准功能接口Predicate代替接口CheckPerson,这将进一步减少所需的代码量。

      【讨论】:

        【解决方案11】:

        我使用匿名对象来调用新线程..

        new Thread(new Runnable() {
            public void run() {
                // you code
            }
        }).start();
        

        【讨论】:

          【解决方案12】:

          匿名内部类在为不同对象提供不同实现时可能是有益的。但是应该非常谨慎地使用,因为它会给程序的可读性带来问题。

          【讨论】:

            【解决方案13】:

            匿名类在类终结中的主要用法之一,称为终结器守护者。在 Java 世界中,应该避免使用 finalize 方法,直到你真正需要它们。你要记住,当你覆盖子类的finalize方法时,你也应该总是调用super.finalize(),因为超类的finalize方法不会自动调用,你可能会遇到内存泄漏的问题。

            所以考虑到上述事实,您可以使用匿名类,例如:

            public class HeavyClass{
                private final Object finalizerGuardian = new Object() {
                    @Override
                    protected void finalize() throws Throwable{
                        //Finalize outer HeavyClass object
                    }
                };
            }
            

            使用这种技术,您可以减轻自己和其他开发人员在需要 finalize 方法的 HeavyClass 的每个子类上调用 super.finalize() 的负担。

            【讨论】:

              【解决方案14】:

              你可以这样使用匿名类

              TreeSet treeSetObj = new TreeSet(new Comparator()
              {
                  public int compare(String i1,String i2)
                  {
                      return i2.compareTo(i1);
                  }
              });
              

              【讨论】:

                【解决方案15】:

                这里似乎没有人提到,但你也可以使用匿名类来保存泛型类型参数(通常由于类型擦除而丢失)

                public abstract class TypeHolder<T> {
                    private final Type type;
                
                    public TypeReference() {
                        // you may do do additional sanity checks here
                        final Type superClass = getClass().getGenericSuperclass();
                        this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
                    }
                
                    public final Type getType() {
                        return this.type;
                    }
                }
                

                如果你以匿名方式实例化这个类

                TypeHolder<List<String>, Map<Ineger, Long>> holder = 
                    new TypeHolder<List<String>, Map<Ineger, Long>>() {};
                

                那么这样的holder 实例将包含传递类型的非擦除定义。

                用法

                这对于构建验证器/反序列化器非常方便。你也可以用反射实例化泛型类型(所以如果你想在参数化类型中做new T() - 欢迎你!)

                缺点/限制

                1. 您应该显式传递泛型参数。否则会导致类型参数丢失
                2. 每次实例化都会花费您额外的类来由编译器生成,这会导致类路径污染/jar 膨胀

                【讨论】:

                  【解决方案16】:

                  匿名内部类用于创建永远不会被再次引用的对象。它没有名称,并在同一语句中声明和创建。 这用于通常使用对象变量的地方。您将变量替换为 new 关键字、对构造函数的调用以及 {} 中的类定义。

                  在 Java 中编写线程程序时,通常看起来像这样

                  ThreadClass task = new ThreadClass();
                  Thread runner = new Thread(task);
                  runner.start();
                  

                  这里使用的ThreadClass 是用户定义的。此类将实现创建线程所需的Runnable 接口。在ThreadClass 中,还需要实现run() 方法(Runnable 中的唯一方法)。 很明显,摆脱 ThreadClass 会更有效,这正是存在匿名内部类的原因。

                  看下面的代码

                  Thread runner = new Thread(new Runnable() {
                      public void run() {
                          //Thread does it's work here
                      }
                  });
                  runner.start();
                  

                  此代码替换了最上面示例中对task 的引用。 Thread() 构造函数中的匿名内部类没有单独的类,而是返回一个未命名的对象,该对象实现了Runnable 接口并覆盖了run() 方法。方法run() 将包含执行线程所需工作的语句。

                  在回答匿名内部类是否是 Java 的优势之一的问题时,我不得不说我不太确定,因为我目前还不熟悉许多编程语言。但我能说的是,这绝对是一种更快更简单的编码方法。

                  参考资料:Sams 自学 Java 21 天第七版

                  【讨论】:

                    【解决方案17】:

                    优化代码的最佳方式。此外,我们可以用于类或接口的覆盖方法。

                    import java.util.Scanner;
                    abstract class AnonymousInner {
                        abstract void sum();
                    }
                    
                    class AnonymousInnerMain {
                        public static void main(String []k){
                            Scanner sn = new Scanner(System.in);
                            System.out.println("Enter two vlaues");
                                int a= Integer.parseInt(sn.nextLine());
                                int b= Integer.parseInt(sn.nextLine()); 
                            AnonymousInner ac = new AnonymousInner(){
                                void sum(){
                                    int c= a+b;
                                    System.out.println("Sum of two number is: "+c);
                                }
                            };
                            ac.sum();
                        }
                    
                    }
                    

                    【讨论】:

                      【解决方案18】:

                      另一个优势:
                      如您所知,Java 不支持多重继承,所以如果您使用“线程”类作为匿名类,那么该类仍然有一个空间供任何其他类扩展。

                      【讨论】: