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

在Java中匿名类有什么用?我们可以说使用匿名类是Java的优点之一吗?

237895 次浏览

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

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

它们本质上是语法糖,当它们变大时通常应该转移到其他地方。

我不确定这是否是Java的优点之一,但如果您确实使用它们(不幸的是,我们都经常使用它们),那么您可能会认为它们是优点之一。

所谓“匿名类”,我认为你指的是匿名内部类

匿名内部类在创建具有某些“额外”(如重写方法)的对象实例时非常有用,而不必实际继承一个类。

我倾向于使用它作为附加事件监听器的快捷方式:

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

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

我只在“快速而肮脏”的任务中使用这种技巧,让整个类感觉没有必要。拥有多个做完全相同事情的匿名内部类应该被重构为一个实际的类,无论是内部类还是单独的类。

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

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

下面是一个swing的例子

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

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

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

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

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

匿名内部类实际上是闭包,所以它们可以用来模拟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);

这是一个非常做作的例子,但是很容易看出,能够像传递值一样传递函数是一个非常有用的特性。参见Joel自己的你的编程语言能做到这一点吗?

Functional Java。是一个很好的Java编程库

我有时使用它们作为Map实例化的语法hack:

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

vs

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

它在执行大量put语句时节省了一些冗余。然而,当外部类需要通过远程序列化时,我也遇到了这样做的问题。

匿名类指南。

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

  2. 匿名类必须扩展或实现到一个且仅一个类或接口响应。

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

例如:

button.addActionListener(new ActionListener(){


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


}
});

匿名类在类终结中的一个主要用法是终结器守护。在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()

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

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

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

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

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

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

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

您可以这样使用匿名类

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

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

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

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

考虑医生中的例子,其中有一个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,这将进一步减少所需的代码量。

优化代码的最佳方法。也可以用于类或接口的重写方法。

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


}
匿名内部类用于创建一个永远不会被再次引用的对象。它没有名称,在同一个语句中声明和创建。 这用于通常使用对象变量的地方。将变量替换为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的优点之一”这个问题时,我不得不说我不太确定,因为目前我对许多编程语言都不熟悉。但我能说的是,这绝对是一种更快更简单的编码方法。

参考资料:山姆自学Java在21天第七版

还有一个好处:
如你所知,Java不支持多重继承,所以如果你使用“线程”类作为匿名类,那么类仍然有一个空间留给任何其他类来扩展