为什么要使用方法局部抽象内部类

26

方法局部内部类可以使用的合法修饰符之一是abstract。

例如:

public class Outer {
    public void method(){
        abstract class Inner{
        }
    }
}

有什么情况下您会真正使用它?

您必须在SCJP考试中了解这个。

10个回答

14

原问题中存在一些无效的假设。某些代码是合法/有效的Java并不意味着你需要使用它或了解它。

我记不得SCJP考试中有奇怪的边角案例问题。

我尝试提出一个情况,来证明我会在方法中使用抽象类的情况,但所有的看起来都非常奇怪,让人感觉设计很糟糕。 然而,这里有一个我想到的代码示例(仍然是糟糕的代码设计,在我看来)

public class BatchExecutor {

    public static enum ResultNotification {
        JMS,
        MAIL
    };

    public Runnable createRunnable(ResultNotification type) {
        abstract class Prototype implements Runnable {
            public void run() {
                performBusinessLogic();
                publishResult();
            }

            abstract void publishResult();
        }

        switch (type) {
            case JMS: {
                return new Prototype() {
                    void publishResult() {
                        //Post result to JMS
                    }
                };
            }
            case MAIL: {
                return new Prototype() {
                    void publishResult() {
                        //Post result to MAIL
                    }
                };
            }
        }
        return null;
    }

    private void performBusinessLogic() {
        //Some business logic
    }

}

1
+1 这看起来是最好的例子。仍然相当糟糕,因为该方法过于冗长,而且最好将行为放到枚举中。 - maaartinus
我完全同意,这个设计有很大的问题,这也可能是我在15年的Java编程中从未见过任何人这样做的原因。 - Kaj
同意。此外,对于糟糕的程序员来说,这个标准太高了,而优秀的程序员则会避开它。 - maaartinus

10

我只能想到这种情况

class Outer {
    public void method() {
        abstract class A {
            void bar(){}
            abstract void foo();
        }
        class B extends A {
            @Override
            void foo() {
            }
        }
        final class C extends A {
            @Override
            void foo() {
            }
        }
        A a1 = new B();
        A a2 = new C();
    }
}

但我想象不出真实的使用场景。


7

在我看来,这个功能没有实际用途。虽然有几种可能的滥用方式,但还有许多其他编写糟糕代码的方法,您不需要学习这种方法。 :D

每当您尝试利用抽象方法本地类时,您需要定义至少两个具体的方法内部类。这意味着您最终会得到一个包含至少三个类的方法,该方法变得相当冗长,这是一种相当糟糕的风格。

您必须为SCJP考试掌握这些内容。

我真的希望不需要。方法本地内部类已经足够无用,被认为是一个边角案例(您应该理解它们,但可能永远不会使用它们)。

在我看来,一个在考试中问这个问题的人对Java的理解存在严重误解。由于(缺少方法文字),局部类无法从外部访问,因此不能有可访问性修饰符。可以有abstractfinal修饰符,因为没有理由禁止它们。允许它们有很好的理由:正交性最小惊奇原则


它在SCJP中的唯一原因是让你知道可以应用哪些修饰符到方法局部内部类。例如,如果一个类拥有private修饰符,你必须知道它不会编译。 - Gordon
@Gordon:也许...,但在方法局部类上放置修饰符毫无意义(因为无法在外部使用)。知道你无法做到这一点并没有帮助你,因为你需要非常误导才会尝试。了解编译器在奇怪的角落案例中所做的事情是无用的,无论你是否拥有编译器并尝试它,或者你没有编译器并且不需要知道。在Java中有很多复杂的东西,这些东西更加有用。 - maaartinus
2
很不幸,对于SCJP考试,你必须像编译器一样行事。你需要知道大量荒谬的东西,而这些东西编译器会立即捕捉到。 - Gordon

6

有没有任何情况下你会实际使用它?

  1. 令 S1 表示所有需要抽象类的情况。

  2. 令 S2 表示所有需要局部类的情况。

  3. 通过检查 S1 ∩ S2 可以找到你问题的答案。

相关问题:


澄清:我的观点是这两个特性(抽象类局部类)是语言中完全正交的两个特性。了解何时使用每个特性是理解它们同时有用的关键。

3
我看不到这里有任何真正的价值 - 这种方式你几乎可以回答任何问题而不提供任何信息。此外,交集似乎非常空洞。 - maaartinus
3
如果你发现交集为空,我相信问题已经得到了回答。(澄清了我的答案。) - aioobe
这样就好多了。实际上,我认为你的回答并不差,只是在我的看法中,与更加具体的答案相比,它还不够好(至少在获得如此多的赞数方面)。对于这个评论点一个赞(也因为提到了正交性)。 - maaartinus

2
您可以在这里获取使用方法http://java-questions.com/InnerClass_interview_questions.html,其中提到:在方法内声明的内部类称为方法局部内部类。方法局部内部类只能声明为final或abstract。如果声明为final,则方法局部类只能访问全局变量或方法局部变量。您可以在内部调用中声明静态变量并在方法中使用它们。编辑:为什么是抽象的:因为如果您不想创建内部类的对象,如果您在方法中创建对象,则该对象将存储在堆中,并且即使方法执行完成,它也不会被释放,因为当从该方法返回该对象时可能存在外部引用。因此,它取决于您是否想创建实例。如果要创建,请使用final修饰符。

这并不解释为什么示例中的内部类是abstract - Aaron Digulla
1
你不必声明一个抽象类来防止实例化。重要的是,你想要继承它,并可能使用不同的实现。 - Paŭlo Ebermann

0

我认为在某些情况下,减小方法的范围可能会很有用。

例如,在单元测试中我会使用它。有时您需要一个实用程序方法来减少测试的冗长性。但是这个实用程序方法可能与当前测试数据集相关,并且无法在此测试之外重复使用。

  @Test
  public void facetting_is_impacted_by_filtering() {
    // given
    String userId = "cd01d6b08bc29b012789ff0d05f8e8f1";
    DocumentSolrClient client = solrClientsHolder.getDocumentClient(userId);
    //
    final SolrDocument doc1 = createDocument(userId);
    doc1.setAuthorName("AuthorName1");
    doc1.setType("Type1");
    doc1.setUserTags(Arrays.asList("UserTag1", "UserTag1bis","UserTag1bisbis"));
    doc1.setSenderTags(Arrays.asList("SenderTag1", "SenderTag1bis"));
    doc1.setCreationDate( new Date(EnumDateRange.CURRENT_DAY.getBegin().getTime()+1000) );
    doc1.setLocation(DocumentLocation.INBOX);
    client.index(doc1);
    //
    final SolrDocument doc2 = createDocument(userId);
    doc2.setAuthorName("AuthorName2");
    doc2.setType("Type2");
    doc2.setUserTags(Arrays.asList("UserTag2"));
    doc2.setSenderTags(Arrays.asList("SenderTag2"));
    doc2.setCreationDate( new Date(1000) ); // cree il y a tres longtemps
    doc2.setLocation(DocumentLocation.SAFE);
    client.index(doc2);
    //
    final List<DateRange> facettedRanges = Arrays.<DateRange>asList(
            EnumDateRange.CURRENT_DAY,
            EnumDateRange.CURRENT_YEAR,
            EnumDateRange.BEFORE_CURRENT_YEAR
    );
    class TestUtils {
      ApiSearchRequest baseFacettingRequest(String userId) {
        ApiSearchRequest req = new ApiSearchRequest(userId);
        req.setDocumentTypeFacets(true);
        req.setSenderNameFacets(true);
        req.setSenderTagsFacets(true);
        req.setUserTagsFacets(true);
        req.addDateCreationFacets(facettedRanges);
        return req;
      }
      void assertDoc1FacettingResult(ApiSearchResponse res) {
        assertThat(res.getDocuments().size()).isEqualTo(1);
        assertThat(res.getDocumentTypeFacets().get().getCounts()).hasSize(1);
        assertThat(res.getSenderNameFacets().get().getCounts()).hasSize(1);
        assertThat(res.getSenderTagsFacets().get().getCounts()).hasSize(2);
        assertThat(res.getUserTagsFacets().get().getCounts()).hasSize(3);
        assertThat(res.getDateCreationFacets().get().getCounts()).isEqualTo( computeExpectedDateFacettingResult( Arrays.asList(doc1),facettedRanges) );
      }
      void assertDoc2FacettingResult(ApiSearchResponse res) {
        assertThat(res.getDocuments().size()).isEqualTo(1);
        assertThat(res.getDocumentTypeFacets().get().getCounts()).hasSize(1);
        assertThat(res.getSenderNameFacets().get().getCounts()).hasSize(1);
        assertThat(res.getSenderTagsFacets().get().getCounts()).hasSize(1);
        assertThat(res.getUserTagsFacets().get().getCounts()).hasSize(1);
        assertThat(res.getDateCreationFacets().get().getCounts()).isEqualTo( computeExpectedDateFacettingResult( Arrays.asList(doc2),facettedRanges) );
      }
    }
    TestUtils utils = new TestUtils();
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // when
    ApiSearchRequest req = utils.baseFacettingRequest(userId);
    ApiSearchResponse res = documentSearchService.search(req);
    // then
    assertThat(res.getDocuments().size()).isEqualTo(2);
    assertThat(res.getDocumentTypeFacets().get().getCounts()).hasSize(2);
    assertThat(res.getSenderNameFacets().get().getCounts()).hasSize(2);
    assertThat(res.getSenderTagsFacets().get().getCounts()).hasSize(3);
    assertThat(res.getUserTagsFacets().get().getCounts()).hasSize(4);
    assertThat(res.getDateCreationFacets().get().getCounts()).isEqualTo( computeExpectedDateFacettingResult( Arrays.asList(doc1,doc2),facettedRanges) );
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // when
    req = utils.baseFacettingRequest(userId);
    req.addLocation(DocumentLocation.SAFE);
    res = documentSearchService.search(req);
    // then
    utils.assertDoc2FacettingResult(res);
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // when
    req = utils.baseFacettingRequest(userId);
    req.addUserTag("UserTag1");
    res = documentSearchService.search(req);
    // then
    utils.assertDoc1FacettingResult(res);
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // when
    req = utils.baseFacettingRequest(userId);
    req.addSenderTag("SenderTag2");
    res = documentSearchService.search(req);
    // then
    utils.assertDoc2FacettingResult(res);
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // when
    req = utils.baseFacettingRequest(userId);
    req.setDocumentType("Type1");
    res = documentSearchService.search(req);
    // then
    utils.assertDoc1FacettingResult(res);
  }

在这个实际例子中,我本可以使用常规的内部类,但有人可能会尝试在其他测试中重用它,而它并不是为此设计的。
顺便说一下,您将注意到能够直接在实用程序类中“捕获”测试中构建的数据集的能力。如果使用常规内部类,则无法在测试之外创建特定于测试的数据集...因此,您最终会与其他测试共享许多东西,而它们只被一个测试(应该被使用)使用。

最后,我认为允许减少可见性的功能并不是无用的。

你可以构建一个完全正常工作的应用程序,而根本不使用封装,并且可以争论同样的事情,说私有修饰符是无用的...

但是,私有修饰符肯定比方法局部内部类更有用;)


0
package dto;

public class Outer {

    public void method(int x, int y){
        abstract class Inner{
            abstract void performAction(int x,int y);
        }
        class InnnerA extends Inner{

            @Override
            void performAction(int x,int y) {
                int z =x+y;
                System.out.println("addition :" + z);

            }

        }
        class InnnerB extends Inner{

            @Override
            void performAction(int x,int y) {
                System.out.println("multiply :"+x*y);

            }

        }
        Inner inner1 = new InnnerA();
        inner1.performAction(x,y);
        Inner inner2 = new InnnerB();
        inner2.performAction(x,y);
    }
    public static void main(String args[]){
        Outer outer = new Outer();
        outer.method(10,20);
    }
}

你可以像这样使用它。


0

请查看此页面上标题为“内部类的层次结构”的部分。

要点是您可以将内部类视为另一个需要被覆盖/实现的抽象成员。我不一定同意这种做法(我可能会单独定义内部类),但我在实际开发中也见过类似的情况。

以下是他们的示例代码:

public abstract class BasicMonitorScreen {
   private Dimension resolution;

   public BasicMonitorScreen(final Dimension resolution) {
      this.resolution = resolution;
   }

   public Dimension getResolution( ) {
      return this.resolution;
   }

   protected abstract class PixelPoint {
      private int x;

      private int y;

      public PixelPoint(final int x, final int y) {
         this.x = x;
         this.y = y;
      }

      public int getX( ) {
         return x;
      }

      public int getY( ) {
         return y;
      }
   }
}

public class ColorMonitorScreen extends BasicMonitorScreen {
   public ColorMonitorScreen(final Dimension resolution) {
      super(resolution);
   }

   protected class ColorPixelPoint extends PixelPoint {
      private Color color;
      public ColorPixelPoint(final int x, final int y, final Color color) {
         super(x, y);
         this.color = color;
      }

      public Color getColor( ) {
         return this.color;
      }
   }
}

0

我能想象到唯一真正的用途是用于数据结构中的节点

这样,您可以区分方法和哨兵节点以及普通数据节点,这在递归算法中非常方便,而且您不必每次都进行空值检查。


0

不,抽象类(或一般的类)在方法内部没有什么好的用途。

只有当特定的方法需要特定的类并且还要实现它时,这才有意义。实际上,在您编写的数万亿个方法中,也许只会发生那种情况一次。


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