Java中如何使用匿名内部类?


Answers:


369

我将“匿名类”理解为匿名内部类

当使用某些“额外”(例如,覆盖方法)创建对象的实例时,匿名内部类会变得很有用,而不必实际子类化。

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

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

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

我只将这种技术用于“快速而肮脏的”任务,在这些任务中使整个课程变得不必要。具有多个功能完全相同的匿名内部类应重构为实际类,无论是内部类还是单独的类。


5
或者,您可以将重复的匿名内部类重构为带有匿名内部类的一种方法(可能还有其他重复的代码)。
汤姆·霍汀-大头钉

3
好的答案,但是一个快速的问题。这是否意味着Java可以在没有匿名内部类的情况下生存,并且它们就像可以选择的额外选项一样?
realPK 2014年

5
很好地解释了,即使是艰难的工作,我也建议任何阅读此书的人查找并查看java 8和lambda表达式可以做什么,以使编码更快,更易读。
Pievis'Mar

2
@ user2190639准确地讲,不能要求Java8中
bonCodigo 2014年

3
为什么你说overloading methods和不overriding methods
塔伦(Tarun)2013年

73

匿名内部类实际上是闭包,因此可以用来模拟lambda表达式或“代理”。例如,使用以下界面:

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

您可以匿名使用它在Java中创建一流的函数。假设您有以下方法返回给定列表中大于i的第一个数字,或者如果没有数字大于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,则返回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);

这是一个非常人为的示例,但是很容易看出,能够像传递值一样传递函数是一个非常有用的功能。请参见“可以将您的编程语言做到这一点”乔尔自己。

一个很好的以这种风格进行Java编程的库:Functional Java。


20
不幸的是,在Java中进行函数式编程(IMHO)的冗长程度超过了它的收益-函数式编程的显着特点之一是它倾向于减小代码大小,并使事情更易于阅读和修改。但是功能性的Java似乎根本没有做到这一点。
CHII

27
Java的简洁性,使函数式编程具有所有可理解性!
亚当·贾斯基维奇

3
以我的经验,Java的功能风格是用冗长的方式付钱的,但是从长远来看,它会带来简洁。例如,myList.map(f)的详细程度要比相应的for循环小得多。
Apocalisp

2
Scala是一种功能编程风格的语言,据说可以在JVM内很好地运行,并且可能是功能编程场景的一种选择。
Darrell Teague

51

在以下情况下使用匿名内部类:

1.)对于Overriding(Sub classing),当除当前情况以外不可用类定义时:

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");
       } 
     });
   } 
 } 

8
好的答案,看起来像3.)是用于事件侦听器的模式
xdl 2014年

47

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

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

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

在执行很多put语句时,它可以节省一些冗余。但是,当外部类需要通过远程处理进行序列化时,这样做也会遇到问题。


56
需要清楚的是,第一组花括号是匿名内部类(子类为HashMap)。第二组大括号是一个实例初始化器(而不是静态的),它然后在HashMap子类上设置值。+1表示,-1表示不为菜鸟拼写。;-D
Spencer Kormos

4
在此处阅读有关double-brace语法的更多信息。
马丁·安德森


18

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

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

这是一个摇摆的例子

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

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


1
您是说简洁吗?如果它很冗长,则回调将单独保留,使回调更大一些,从而使它变得冗长。如果您说这仍然很冗长,那么简洁的形式是什么?
user3081519

1
@ user3081519,类似myButton.addActionListener(e -> { /* do stuff here */})myButton.addActionListener(stuff)会更短。
塞缪尔·埃德温·沃德

8

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

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

它们本质上是语法糖,随着它们的变大,通常应将其移至其他位置。

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


6

匿名类准则。

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

  2. 匿名类必须扩展或实现为一个或仅一个类或接口。

  3. 由于匿名类没有名称,因此只能使用一次。

例如:

button.addActionListener(new ActionListener(){

            public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub

    }
});

关于#3:并非完全正确。您可以使用反射获取匿名类的多个实例,例如ref.getClass().newInstance()
icza 2014年

规则不能回答问题。
罗恩侯爵

5

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

使用匿名内部类,您可以访问周围类的final和member变量,这在侦听器等中很方便。

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


1
能够参加周围的课堂非常重要!我认为这是在许多情况下使用匿名类的原因,因为它需要/使用周围的类/方法的非公共属性,方法和局部变量,而这些(如果使用单独的类)则必须通过或发布。
icza 2014年

5
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();

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


3

我使用匿名对象调用新线程。

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

3

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

考虑来自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引入了术语“ 功能接口”,它是只有一种抽象方法的接口,因此可以说CheckPerson是功能接口。我们可以使用Lambda表达式,该表达式允许我们将函数作为方法参数传递为:

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

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


2

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


1

类最终定义中匿名类的主要用法之一,称为finalizer Guardian。在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方法的super.finalize()每个子类HeavyClass


1

您可以通过这种方式使用匿名类

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

1

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

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膨胀

1

优化代码的最佳方法。同样,我们可以将其用于类或接口的重写方法。

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();
    }

}

1

一个匿名内部类是用于创建再也不会被引用的对象。它没有名称,并且在同一条语句中声明和创建。在通常使用对象变量的地方使用它。您用替换变量new的关键字,在构造函数和类定义内通话{}

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

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

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

看下面的代码

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

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

在回答有关匿名内部类是否是Java的优点之一的问题时,我不得不说我不确定,因为我目前不熟悉许多编程语言。但是我可以说的是,这绝对是一种更快,更轻松的编码方法。

参考:Sams在21天中自学Java第七版


0

另一个优点:
您知道Java不支持多重继承,因此,如果您将“ Thread”类用作匿名类,则该类还剩下一个空间可以扩展其他任何类。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.