对匿名类和匿名内部类感到困惑

时间:2014-03-29 01:35:14

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

我去搜索学习如何在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"甚至没有继承矩形的字段。这不会使它更无用吗?

相当于:

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



    }

3 个答案:

答案 0 :(得分:5)

  

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

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的对象被称为匿名类,因为我们没有'命名它。);“。一直以来,我认为你添加到ArrayList的对象被称为匿名类,因为我们没有命名它。

你错了。对象不是类 1

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

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

1 - 除了java.lang.Class的实例...然后,从理论的角度来看,对象实际上不是 类/类型。


  

或者它只是一个匿名对象,我有两个混合?

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


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

答案 1 :(得分:2)

首先,广场可以访问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; 
    }
});

延伸到Lambdas,不会更容易消除i != 1周围的所有臃肿

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

答案 2 :(得分:1)

要回答稍后的评论,&#34;当我编写新的子类时,它会继承这些私有实例变量。在匿名内部类的情况下,它没有。&#34;

子类从不&#34;继承&#34;超类的private个字段(使用JLS术语)。但是,子类可能无论如何都可以引用那些私有字段,具体取决于它们所处的位置。如果子类在超类中声明 ,或者如果它们都嵌套在同一个顶级类中,则子类的方法仍然可以访问该字段;假设您的源文件C.java只包含一个C个类,private中某处声明的C.java字段仍可从C.java中的大多数其他位置访问。< / p>

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

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根本不可见,因为它是私人的。这里的道德是&#34;继承&#34;和&#34;可见性&#34; (或&#34;访问&#34;)不是一回事。同样的事情适用于匿名内部类。如果您在私有实例字段可见的位置使用一个,那么您可以参考该字段;如果您在私有实例字段不可见的地方使用它,那么您就无法做到。为此目的,类声明的位置比类的类型(嵌套/内部/匿名)更重要。

假设我们删除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本身引用封闭实例中的字段。