对于匿名类和匿名内部类的区别感到困惑?

3

我曾经搜索学习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()方法,而是可以只创建一个一次性的方形类,并在其中覆盖该方法。这是正确的吗?
所以,匿名内部类与继承有关。我不明白它的用途。也许是因为我以前从未使用过它们,或者因为我没有太多的编程经验。你能给我举例或解释什么时候有用吗?
更新:当我将匿名内部类的代码移到IDE中时,我发现有错误。所以,显然,“square”甚至不继承“rectangle”的字段。这难道不会使它更加无用吗?
等价的方式是什么?
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;
  }



    }

1
如果你想让子类可以访问到字段,那么请将它们声明为protected而不是private。如果一个类将其字段声明为private,那么它就是在说:“这些字段是我实现方法的内部细节,所以我不希望任何人来干扰它们,甚至是我的子类。它们随时可能被删除或者意义会发生改变,所以我不希望任何人使用它们并依赖它们的值,因为当我的内部方法发生变化时,他们会后悔的。” - ajb
我没有看到这个问题,谢谢你。但即便如此,当我编写一个新的子类时,它会继承那些私有实例变量。而在匿名内部类的情况下,它并没有继承。 - Honinbo Shusaku
1
你说,“当我编写一个新的子类时,它会继承私有实例字段”。从技术上讲,实际上并不是这样的,但你是否在超类内部声明了子类?即使在某些条件下,也可以访问私有实例字段,因此它看起来像是继承了这些字段,尽管实际上并没有。你能否发布一下代码,以便我们看到似乎继承了私有实例变量的情况?这将是有益的。 - ajb
@ajb 不,我的子类不在超类内部。我有一个矩形超类,它声明了长度和宽度的实例变量。矩形构造函数将要求这些参数。当我创建一个正方形子类时,我不会声明相同的实例变量。我的正方形子类构造函数将要求相同的参数并将其设置为它。因此,从那种情况下,我会认为这些字段是继承的,因为我没有在子类中声明它们,但我可以通过构造函数来设置它们。 - Honinbo Shusaku
@ajb 但是,我不知道底层的东西。你说得对,它们从来没有被继承,所以我的观察是错误的。我认为这篇文章解释得很好:https://dev59.com/UWPVa4cB1Zd3GeqP65J-#10491050 - Honinbo Shusaku
3个回答

7
所以我对匿名类的理解是:
public class SomeObject {
   public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add(new SomeObject());
   }
}

这里没有匿名类。类SomeObject有一个名称...所以它不是匿名的。实际上,它只是一个普通(非嵌套,非内部,非匿名)的Java类。
我之前看过"anonymous inner class"这个词,但当时我不知道什么是普通的匿名类。其实,所有Java匿名类都是"内部"的。
如JLS所述:
"内部类是指未明确或隐含地声明为静态的嵌套类。 内部类包括本地类(§14.3)、匿名类(§15.9.5)和非静态成员类(§8.5)。"

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

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

我是说“list.add(我想说“list.add(new SomeObject())”;这整段时间,我一直认为你向ArrayList添加的对象称为匿名类,因为我们没有给它命名。

您是错误的。对象不是类。 new SomeObject()正在创建一个对象,而不是一个类。但这是正常的。就JLS而言,对象/实例没有名称。
现在变量和字段有名称,但变量不是对象/实例或类。它们是将名称与可以保存引用对象的插槽之间进行“绑定”的绑定(如果该类型声明允许)。

还是称为简单的匿名对象,我把两个混淆了吗?

不是的。对象没有名称。所有Java对象都是“匿名”的。这并不是一个有用的区分。(请参见上文中关于变量的讨论...)
至于您的矩形/正方形示例,它们与匿名类、内部类、嵌套类或任何其他类似的东西无关。它们只是顶级类,使用普通的Java继承。(并不是说还有另一种“非普通”的继承方式...)

1
@Abdul 理论上你可以这样做,但这个术语从未被使用过。它只是一个对象,其引用未保存在本地上下文中(当然,它保存在列表中)。 - chrylis -cautiouslyoptimistic-
@chrylis - 之所以不使用它是因为它没有用处。如果您将变量名称建模为对象名称,则对变量进行赋值可能会添加、更改或删除对象的“名称”。这不是一个有帮助/有用的思考程序行为的方式...特别是对于复杂的程序。 - Stephen C
@StephenC 所以匿名类在你想要覆盖现有方法,但又不想为此创建一个新类时非常有用? - Honinbo Shusaku
@StephenC 这就是为什么我说你 理论上 可以。从技术上讲,我更倾向于将引用本身标识为对象的名称,这意味着根本没有匿名对象。 - chrylis -cautiouslyoptimistic-
1
@chrylis - 麻烦的是,你似乎在说“你可以这么说”。但更好的回答应该是“你可以这么说,但你会>>错误<<”。(请参见我之前的评论,了解为什么是错误的。) - Stephen C
显示剩余6条评论

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和5的情况... 这个逻辑与之前完全相同,只是谓词检查不同。因此,我们将创建一个接口

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

并且针对Lambda表达式,如果将所有与i != 1无关的冗余代码剔除,不是会更容易吗?
list.stream().filter( i -> i != 1 )

1
回答一个后面的评论,“当我编写一个新的子类时,它会继承那些私有实例变量。在匿名内部类的情况下,它没有继承。”
子类从来不会“继承”超类的私有字段(使用JLS术语)。但是,子类可能仍然能够引用这些私有字段,具体取决于它们所在的位置。如果子类在超类内部声明,或者它们都嵌套在同一个顶级类中,则子类的方法仍然可以访问该字段;假设您有一个只包含一个类C的源文件C.java,在C.java中声明的private字段仍然可以从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 是静态的,所以没有封闭实例。

请注意,如果 Foo2Foo1 之外声明,则第二个 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指的是封闭实例中的字段。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接