【问题标题】:Confused about anonymous classes vs anonymous inner class对匿名类与匿名内部类感到困惑
【发布时间】:2014-05-08 16:27:42
【问题描述】:

我去寻找学习如何在 Java 中执行 lambda 表达式,但我却遇到了困惑。所以我对匿名类的理解是这样的:

public class SomeObject {
   public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add(new SomeObject());
   }

}

之前看到过匿名内部类这个词,但当时还不知道什么是正规匿名类。我看到的很多线程和视频似乎都将匿名内部类称为“匿名类”。它们是同义词吗?我对匿名内部类的理解是:

 public class Rectangle {
 private double length;
 private double width;
 private double perimeter;

    public void calculatePerimeter() {
    perimeter = (2*length) +(2*width);
   }

     public static void main(String[] args) {
       Rectangle square = new Rectangle() {
        public void calculatePerimeter() {
            perimeter = 4*length;
        }
    };
   }

  }

所以本质上,不必为 Square 编写一个子类,然后重写 calculatePerimeter() 方法,我可以只创建一个一次性的 square 类,然后重写他们的方法。它是否正确?

因此,匿名内部类与继承有关。我不明白它的用途。也许是因为我以前从未使用过它们,或者因为我没有太多的编程经验。你能给我举个例子或解释一下什么时候有用吗?

更新:当我将匿名内部类的代码移至 IDE 时,我了解到存在错误;显然,“正方形”甚至不继承矩形的字段。这不是让它更没用了吗?

相当于:

public class Rectangle {
 private double length;
 private double width;
 private double perimeter;

    public void calculatePerimeter() {
    perimeter = (2*length) +(2*width);
   }
 }


public class Square extends Rectangle {
   @Override   
   public void calculatePerimeter() {
      perimeter = 4*getLength();
   }

  public double getLength() {
    return length;
  }



    }

【问题讨论】:

  • 如果您希望子类能够访问字段,请将它们设为 protected 而不是 private。如果一个类将其字段设为private,则表示“这些字段是我如何实现我的方法的内部细节。所以我不希望任何人对它们进行处理,甚至我的子类也不行。而且它们可以随时被删除,或者含义可以改变,所以我不希望任何人使用它们并依赖于它们的值,因为当我的内部方法改变时他们会后悔。”
  • 我没看到,谢谢。但即便如此,当我编写一个新的子类时,它还是继承了那些私有实例变量。在匿名内部类的情况下,它没有。
  • 你说,“当我写一个新的子类时,它会继承私有实例字段”。从技术上讲,它确实没有,但是您是否在超类inside 中声明了子类?在某些条件下甚至可以访问私有实例字段,因此即使它们不是,它也会使字段看起来像是被继承的。你能把代码贴在看起来像私有实例变量被继承的地方吗?这将是有益的。
  • @ajb 不,我的子类不在超类中。我有一个矩形超类,它具有声明的长度和宽度的实例变量。矩形构造函数将有参数要求它们。当我创建一个方形子类时,我不会声明相同的实例变量。我的方形子类构造函数会要求相同的参数并将它们设置为它。因此,从那种情况下,我假设这些字段是继承的,因为我从未在子类中声明它们,但我可以通过构造函数设置它们。
  • @ajb 但是,我不知道引擎盖下的东西。你是对的,他们永远不会被继承,所以我的观察是错误的。我认为这很好地解释了它:stackoverflow.com/a/10491050/3040381

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


【解决方案1】:

所以我对匿名类的理解是这样的:

public class SomeObject {
   public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add(new SomeObject());
   }
}

那里没有匿名类。 SomeObject 类有一个名字......因此它不是匿名的。事实上,它只是一个普通的(非嵌套、非内部、非匿名)Java 类。


之前看到过匿名内部类这个词,但当时还不知道什么是正规匿名类。

没有“常规匿名类”这样的东西。所有 Java 匿名类都是“内部的”。

正如 JLS 所说:

“内部类是没有显式或隐式声明为静态的嵌套类。

内部类包括本地(§14.3)、匿名(§15.9.5)和非静态成员类(§8.5)。”


因此,匿名内部类与继承有关。

匿名内部类确实涉及继承,但这并不是使它们成为“内部”的原因。见上文。


我的意思是“list.add(我的意思是“list.add(new SomeObject());”。一直以来,我认为您添加到 ArrayList 的对象被称为匿名类,因为我们没有t命名它。);“。一直以来,我一直认为您添加到 ArrayList 的对象被称为匿名类,因为我们没有命名它。

你错了。对象不是类1

new SomeObject() 正在创建一个对象,而不是一个类。但这很正常。对象/实例没有名称……就 JLS 而言。

现在变量和字段有了名字......但变量不是对象/实例或类。它们是名称和可以保存对对象的引用的槽之间的绑定(如果这是类型声明所允许的)。

1 - 除了java.lang.Class 的实例......即使这样,从理论的角度来看,该对象也不是实际上类/类型。


或者它只是被称为一个匿名对象而我把两个混淆了?

不。对象没有名称。所有 Java 对象都是“匿名的”。这不是一个有用的区别。 (见上文我谈论变量的地方......)


至于您的Rectangle / Square 示例,它们与匿名类、内部类、嵌套类或类似的东西无关。它们只是顶级类,使用普通的 Java 继承。 (并不是说我是在暗示还有另一种“非普通”的继承......)

【讨论】:

  • 我的意思是“list.add(new SomeObject());”。一直以来,我一直认为您添加到 ArrayList 的对象被称为匿名类,因为我们没有命名它。或者它只是简单地称为一个匿名对象而我把两个混淆了?
  • @Abdul 好吧,它不是一个类,所以我猜它不可能是一个匿名类。
  • 我很抱歉,我不知道为什么我一直把它称为匿名课程。称它为匿名对象是否正确?
  • @Abdul 从理论上讲你可以,但从未使用过这个词。它只是一个对象,其引用未保存在本地上下文中(当然,它保存在列表中)。
  • @chrylis - 问题是你似乎在说“你可以这么说”。但更好的回答是“你可以这么说,但你会是>>WRONG
【解决方案2】:

首先 - square 可以访问 Rectangle 中的字段。你需要标记它们protected 而不是private

public class Rectangle {
    protected double length;
    protected double width;
    protected double perimeter;

    public void calculatePerimeter() {
        perimeter = (2*length) +(2*width);
    }

    public static void main(String[] args) {
        Rectangle square = new Rectangle() {
            public void calculatePerimeter() {
                perimeter = 4*length;
            }
        };
    }

}

这里有一些关于内部类、匿名和本地的很好的描述

还有另外两种类型的内部类。您可以在方法体中声明内部类。这些类称为本地类。您还可以在方法体中声明内部类,而无需命名该类。这些类称为匿名类。

局部类是在一个块中定义的类,它是一组在平衡大括号之间的零个或多个语句。您通常会发现在方法体中定义的本地类。

匿名类使您的代码更简洁。它们使您能够同时声明和实例化一个类。它们类似于本地类,只是它们没有名称。如果您只需要使用一次本地类,请使用它们。

我认为当您设计 API 时,匿名类的相关性就出现了。您可以创建具体的类来为每个接口/抽象类实现每一点逻辑,但这会产生大量的依赖关系,并且您仍然会丢失一些逻辑。匿名类的一个很好的例子是使用谓词进行过滤。喜欢Google Guava

假设我有一个List<Integer>,我想过滤数字,删除 1 并返回一个新列表

public static List<Integer> filter(List<Integer> input) {
   List<Integer> rtn = new ArrayList<Integer>();
   for( Integer i : input) {
      if(i != 1) rtn.push(i);
   }
   return rtn;
} 

现在假设我要过滤掉 1 和 2

public static List<Integer> filter(List<Integer> input) {
   List<Integer> rtn = new ArrayList<Integer>();
   for( Integer i : input) {
      if(i != 1 && i != 2) rtn.push(i);
   }
   return rtn;
} 

现在让我们说 3 和 5s ...除了谓词检查之外,这个逻辑完全相同。所以我们将创建一个接口

interface FilterNumber {
    public boolean test(Integer i);
}

class Filter1s implements FilterNumber {
    public Filter1s(){};
    public boolean test(Integer i) { return i != 1; }
} 


public static List<Integer> filter(List<Integer> input, FilterNumber filterNumber) {
   List<Integer> rtn = new ArrayList<Integer>();
   for( Integer i : input) {
      if(filterNumber.test(i)) rtn.push(i);
   }
   return rtn;
} 

filter(list, new Filter1s());

正如您所见,使用组合也变得乏味。允许 api 的用户定义他们想要执行的逻辑会更容易,如果只需要一次,只需使用匿名类

filter(list, new FilterNumber() {
    @Override
    public boolean test(Integer i) {
        return i != 1 && i != 3 && i != 7; 
    }
});

并且扩展到 Lambdas,消除 i != 1 周围的所有臃肿不是更容易

list.stream().filter( i -> i != 1 )

【讨论】:

    【解决方案3】:

    回答后面的评论,“当我编写一个新的子类时,它继承了那些私有实例变量。在匿名内部类的情况下,它没有。”

    子类永远不会“继承”超类的private 字段(使用 JLS 术语)。但是,子类可能无论如何都能够引用这些私有字段,具体取决于它们所在的位置。如果子类在父类内部声明,或者它们都嵌套在同一个顶级类中,子类的方法仍然可以访问该字段;假设你有一个源文件C.java,其中只有一个类CprivateC.java 某处声明的字段仍然可以从C.java 中的大多数其他地方访问。

    但是,在测试时,我发现了一些有趣的细微差别:

    class Foo1 {    
        private int bar1;
        public static class Foo2 extends Foo1 {
            public void p() {
                System.out.println(bar1);               // illegal
                System.out.println(((Foo1)this).bar1);  // works
            }
        }
    }
    

    bar1 是可见的,即使它是超类中的私有字段;它不是继承的,但您可以通过告诉编译器将Foo2 对象视为Foo1 来访问它。但是仅仅引用bar1 本身就失败了; Java 将此解释为尝试获取 封闭实例(不是超类)的 bar1,但 Foo2 是静态的,因此没有封闭实例。

    请注意,如果Foo2 被声明在外部 Foo1,第二个println 将是非法的,因为现在bar1 根本不可见,因为它是私有的。这里的寓意是“继承”和“可见性”(或“访问”)不是一回事。同样的事情也适用于匿名内部类。如果在私有实例字段可见的地方使用一个,则可以参考该字段;如果您在私有实例字段不可见的地方使用它,那么您不能。为此,类声明的位置比类的类型(嵌套/内部/匿名)更重要。

    假设我们去掉 static 关键字并使其成为内部类:

    public class Foo1 {
    
        private int bar1;
    
        public Foo1(int x) {
            bar1 = x;
        }
    
        public class Foo2 extends Foo1 {
    
            public Foo2(int x) {
                super(x * 10);
            }
    
            public void show() {
                System.out.println("bar1 = " + bar1);
                System.out.println("((Foo1)this).bar1 = " + ((Foo1)this).bar1);
                System.out.println("Foo1.this.bar1 = " + Foo1.this.bar1);
            }
        }
    }
    
    public class Test64 {
    
        public static void main(String[] args) {
            Foo1 f1 = new Foo1(5);
            Foo1.Foo2 f2 = f1.new Foo2(6);
            f2.show();
        }
    
    }    
    

    现在Foo2 对象也是Foo1;但由于它是一个内部类,Foo2 实例也有一个 封闭实例,它是一个 不同 Foo1 对象。当我们创建 Foo2 时,它使用超类构造函数将超类 bar1 设置为 60。但是,它还有一个封闭实例,其 bar1 为 5。show() 显示以下输出:

    bar1 = 5
    ((Foo1)this).bar1 = 60
    Foo1.this.bar1 = 5
    

    所以bar1 本身就是指封闭实例中的字段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多