การใช้นิยามคลาสภายในเมธอดใน Java


105

ตัวอย่าง:

public class TestClass {

    public static void main(String[] args) {
        TestClass t = new TestClass();
    }

    private static void testMethod() {
        abstract class TestMethod {
            int a;
            int b;
            int c;

            abstract void implementMe();
        }

        class DummyClass extends TestMethod {
            void implementMe() {}
        }

        DummyClass dummy = new DummyClass();
    }
}

ฉันพบว่าโค้ดข้างต้นถูกต้องตามกฎหมายใน Java ฉันมีคำถามต่อไปนี้

  1. การใช้คำนิยามคลาสภายในเมธอดคืออะไร?
  2. ไฟล์คลาสจะถูกสร้างขึ้นสำหรับ DummyClass
  3. มันยากสำหรับฉันที่จะจินตนาการถึงแนวคิดนี้ในลักษณะเชิงวัตถุ มีการกำหนดคลาสภายในพฤติกรรม อาจมีใครบอกฉันด้วยตัวอย่างโลกแห่งความจริงที่เทียบเท่าได้
  4. คลาสนามธรรมภายในวิธีการฟังดูบ้าไปหน่อยสำหรับฉัน แต่ไม่อนุญาตให้ใช้อินเทอร์เฟซ มีเหตุผลเบื้องหลังนี้หรือไม่?

1
ฉันยอมรับว่ามันดูยุ่งมากอย่างไม่น่าเชื่อ ฉันตรวจสอบโค้ดที่เพื่อนร่วมงานเขียนและพบคลาสท้องถิ่นนี้ด้วยวิธีการ ... มันทำให้ฉันรู้สึกว่าโมดูลนี้มีมลทินโดยสิ้นเชิง
คนอยู่ที่ไหนสักแห่ง

7
บางครั้งมันเป็นเรื่องของการซ่อนสิ่งของที่คุณไม่ต้องการที่อื่นมากกว่าการมอง;)
sorrymissjackson

คำตอบ:


71

นี่เรียกว่าคลาสท้องถิ่น

2 เป็นเรื่องง่าย: ใช่ไฟล์คลาสจะถูกสร้างขึ้น

1 และ 3 เป็นคำถามเดียวกัน คุณจะใช้คลาสท้องถิ่นที่คุณไม่จำเป็นต้องสร้างอินสแตนซ์หรือทราบเกี่ยวกับรายละเอียดการใช้งานที่ใดก็ได้นอกจากวิธีการเดียว

การใช้งานโดยทั่วไปคือการสร้างการใช้งานอินเทอร์เฟซบางส่วนทิ้งไป ตัวอย่างเช่นคุณมักจะเห็นสิ่งนี้:

  //within some method
  taskExecutor.execute( new Runnable() {
       public void run() {
            classWithMethodToFire.doSomething( parameter );
       }
  }); 

หากคุณต้องการสร้างสิ่งเหล่านี้และทำบางสิ่งกับพวกเขาคุณอาจเปลี่ยนสิ่งนี้เป็น

  //within some method
  class myFirstRunnableClass implements Runnable {
       public void run() {
            classWithMethodToFire.doSomething( parameter );
       }
  }
  class mySecondRunnableClass implements Runnable {
       public void run() {
            classWithMethodToFire.doSomethingElse( parameter );
       }
  }
  taskExecutor.execute(new myFirstRunnableClass());
  taskExecutor.execute(new mySecondRunnableClass());

เกี่ยวกับอินเทอร์เฟซ: ฉันไม่แน่ใจว่ามีปัญหาทางเทคนิคที่ทำให้อินเทอร์เฟซที่กำหนดในเครื่องเป็นปัญหาสำหรับคอมไพเลอร์หรือไม่ แต่แม้ว่าจะไม่มี แต่ก็จะไม่เพิ่มมูลค่าใด ๆ หากคลาสโลคัลที่ใช้อินเตอร์เฟสโลคัลถูกใช้นอกเมธอดอินเทอร์เฟซจะไม่มีความหมาย และถ้าจะใช้เฉพาะคลาสโลคัลภายในเมธอดทั้งอินเทอร์เฟซและคลาสจะถูกนำไปใช้ภายในเมธอดนั้นดังนั้นนิยามอินเตอร์เฟสจะซ้ำซ้อน


มีความคิดใดในคลาสโลคัล Java เวอร์ชันใดที่แนะนำ
เลื่อน

1
เพิ่มคลาสภายในใน Java 1.1 - ฉันเดาว่าคลาสโลคัลก็เช่นกัน แต่ฉันไม่มีเอกสารประกอบ
Jacob Mattison

คุณช่วยยกตัวอย่างที่ดีกว่าสำหรับกรณีการใช้งานของคลาสโลคัลที่ไม่ระบุชื่อได้ไหม โค้ดบล็อกที่สองของคุณสามารถเขียนซ้ำได้ด้วยคลาสที่ไม่ระบุชื่อ
Sergey Pauk

1
การใช้คลาสโลคัลแบบไม่ระบุชื่อส่วนใหญ่สามารถทำได้ด้วยคลาสที่ไม่ระบุชื่อ ฉันไม่ได้สรุปตัวอย่าง แต่โดยทั่วไปคุณจะใช้คลาสโลคัลที่มีชื่อหากคุณต้องการสร้างอินสแตนซ์ของคลาสเดียวกันมากขึ้น
Jacob Mattison

1
สำหรับ OP: โปรดทราบว่าคลาสโลคัลจัดเตรียมวิธีสำหรับเธรดในการสื่อสาร - parameterข้างต้นอาจถูกประกาศในวิธีการปิดล้อมและสามารถเข้าถึงได้โดยทั้งสองเธรด
flow2k

15

เหล่านี้จะเรียกว่าการเรียนในท้องถิ่น คุณสามารถหาคำอธิบายรายละเอียดและตัวอย่างที่นี่ ตัวอย่างส่งคืนการใช้งานเฉพาะที่เราไม่จำเป็นต้องรู้เกี่ยวกับนอกเมธอด


2
ลิงก์ที่ยอดเยี่ยม (ยังคงใช้งานได้หลังจากผ่านไป 7 ปีขึ้นไป!) โดยเฉพาะอย่างยิ่งโปรดทราบว่า "เช่นเดียวกับคลาสสมาชิกคลาสท้องถิ่นจะเชื่อมโยงกับอินสแตนซ์ที่มีอยู่และสามารถเข้าถึงสมาชิกใด ๆ รวมถึงสมาชิกส่วนตัวของคลาสที่มี "
flow2k

10
  1. ไม่สามารถมองเห็นคลาสได้ (เช่นสร้างอินสแตนซ์เมธอดที่เข้าถึงโดยไม่มีการสะท้อนกลับ) จากภายนอกเมธอด นอกจากนี้ยังสามารถเข้าถึงตัวแปรท้องถิ่นที่กำหนดไว้ใน testMethod () แต่ก่อนกำหนดคลาส

  2. ที่จริงฉันคิดว่า: "จะไม่มีการเขียนไฟล์ดังกล่าว" จนกว่าฉันจะได้ลอง: โอ้ใช่ไฟล์ถูกสร้างขึ้น! จะเรียกว่าคลาส A $ 1B โดยที่ A คือคลาสนอกและ B คือคลาสท้องถิ่น

  3. โดยเฉพาะอย่างยิ่งสำหรับฟังก์ชันการโทรกลับ (ตัวจัดการเหตุการณ์ใน GUI เช่น onClick () เมื่อมีการคลิกปุ่มเป็นต้น) การใช้ "คลาสที่ไม่ระบุตัวตน" เป็นเรื่องปกติ - ประการแรกเพราะคุณสามารถจบลงได้ด้วยจำนวนมาก แต่บางครั้งคลาสที่ไม่ระบุตัวตนก็ไม่ดีพอ - โดยเฉพาะคุณไม่สามารถกำหนดคอนสตรัคเตอร์ได้ ในกรณีเหล่านี้คลาสท้องถิ่นวิธีการเหล่านี้อาจเป็นทางเลือกที่ดี


2
2. เอ่อแน่ใจว่าจะ ไฟล์คลาสจะถูกสร้างขึ้นสำหรับทุกคลาสที่ซ้อนกันโลคัลหรือคลาสที่ไม่ระบุชื่อในไฟล์ java ของคุณ
sepp2k

2
"2. จะไม่มีการเขียนไฟล์ดังกล่าว" - นี่เป็นสิ่งที่ผิด มันสร้างขึ้นTestClass$1TestMethodClass.classโดยคล้ายคลึงกับวิธีการ.classตั้งชื่อไฟล์คลาสภายใน
polygenelubricants

คำตอบที่ดีข้อยกเว้นสำหรับ 2: คุณจะได้รับคลาสที่ไม่ระบุชื่อที่สร้างขึ้นในกรณีนี้คือ "TestClass $ 1TestMethodClass.class"
Steve B.

ใช่ฉันขอโทษ! ฉันไม่รู้เลยจนกระทั่งไม่กี่วินาทีที่ผ่านมา คุณอยู่และเรียนรู้ :-))
Chris Lercher

คุณมี +1 ของฉันเพื่อเน้นความแตกต่างระหว่างคลาสที่ไม่ระบุตัวตนและคลาสโลคัล: การกำหนดคอนสตรัคเตอร์
Matthieu

7

จุดประสงค์ที่แท้จริงของสิ่งนี้คือเพื่อให้เราสามารถสร้างคลาสแบบอินไลน์ในการเรียกใช้ฟังก์ชันเพื่อปลอบใจพวกเราที่ชอบแกล้งทำเป็นว่าเรากำลังเขียนด้วยภาษาที่ใช้งานได้;)


4

กรณีเดียวที่คุณต้องการให้คลาสภายในของฟังก์ชันเป่าเต็มเทียบกับคลาสที่ไม่ระบุชื่อ (aka การปิด Java) คือเมื่อตรงตามเงื่อนไขต่อไปนี้

  1. คุณต้องจัดหาอินเทอร์เฟซหรือการใช้คลาสนามธรรม
  2. คุณต้องการใช้พารามิเตอร์สุดท้ายที่กำหนดไว้ในฟังก์ชันการโทร
  3. คุณต้องบันทึกสถานะของการเรียกใช้อินเทอร์เฟซบางอย่าง

เช่นบางคนต้องการRunnableและคุณต้องการบันทึกเมื่อการดำเนินการเริ่มต้นและสิ้นสุดลง

ด้วยคลาสที่ไม่ระบุตัวตนจะไม่สามารถทำได้ด้วยคลาสภายในคุณสามารถทำได้

นี่คือตัวอย่างที่แสดงให้เห็นถึงประเด็นของฉัน

private static void testMethod (
        final Object param1,
        final Object param2
    )
{
    class RunnableWithStartAndEnd extends Runnable{
        Date start;
        Date end;

        public void run () {
            start = new Date( );
            try
            {
                evalParam1( param1 );
                evalParam2( param2 );
                ...
            }
            finally
            {
                end = new Date( );
            }
        }
    }

    final RunnableWithStartAndEnd runnable = new RunnableWithStartAndEnd( );

    final Thread thread = new Thread( runnable );
    thread.start( );
    thread.join( );

    System.out.println( runnable.start );
    System.out.println( runnable.end );
}

ก่อนที่จะใช้รูปแบบนี้โปรดประเมินว่าคลาสระดับบนสุดเก่าธรรมดาหรือคลาสภายในหรือคลาสภายในแบบคงที่เป็นทางเลือกที่ดีกว่า


ฉันละเมิด # 2 ไม่น้อยในการกำหนดค่าที่ส่งคืนจากฟังก์ชัน
Eddie B

2

เหตุผลหลักในการกำหนดคลาสภายใน (ภายในเมธอดหรือคลาส) คือเพื่อจัดการกับความสามารถในการเข้าถึงของสมาชิกและตัวแปรของคลาสและเมธอดที่ปิดล้อม ชั้นในสามารถค้นหาสมาชิกข้อมูลส่วนตัวและดำเนินการกับสมาชิกได้ หากอยู่ในเมธอดก็สามารถจัดการกับตัวแปรโลคัลสุดท้ายได้เช่นกัน

การมีชั้นเรียนภายในจะช่วยให้แน่ใจว่าชั้นเรียนนี้ไม่สามารถเข้าถึงโลกภายนอกได้ สิ่งนี้ถือเป็นจริงโดยเฉพาะอย่างยิ่งสำหรับกรณีของการเขียนโปรแกรม UI ใน GWT หรือ GXT ฯลฯ ที่โค้ดสร้าง JS ถูกเขียนด้วย java และต้องกำหนดพฤติกรรมสำหรับแต่ละปุ่มหรือเหตุการณ์โดยการสร้างคลาสที่ไม่ระบุชื่อ


1

ฉันเจอตัวอย่างที่ดีในฤดูใบไม้ผลิ เฟรมเวิร์กใช้แนวคิดของนิยามคลาสโลคัลภายในเมธอดเพื่อจัดการกับการดำเนินการฐานข้อมูลต่างๆในลักษณะเดียวกัน

สมมติว่าคุณมีรหัสดังนี้:

JdbcTemplate jdbcOperations = new JdbcTemplate(this.myDataSource);
jdbcOperations.execute("call my_stored_procedure()")
jdbcOperations.query(queryToRun, new MyCustomRowMapper(), withInputParams);
jdbcOperations.update(queryToRun, withInputParams);

ก่อนอื่นเรามาดูการใช้งาน execute ():

    @Override
    public void execute(final String sql) throws DataAccessException {
        if (logger.isDebugEnabled()) {
            logger.debug("Executing SQL statement [" + sql + "]");
        }

        /**
         * Callback to execute the statement.
         (can access method local state like sql input parameter)
         */
        class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
            @Override
            @Nullable
            public Object doInStatement(Statement stmt) throws SQLException {
                stmt.execute(sql);
                return null;
            }
            @Override
            public String getSql() {
                return sql;
            }
        }

        //transforms method input into a functional Object
        execute(new ExecuteStatementCallback());
    }

โปรดสังเกตบรรทัดสุดท้าย Spring ทำ "เคล็ดลับ" ที่แน่นอนสำหรับวิธีการอื่น ๆ เช่นกัน:

//uses local class QueryStatementCallback implements StatementCallback<T>, SqlProvider
jdbcOperations.query(...) 
//uses local class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider
jdbcOperations.update(...)

"เคล็ดลับ" กับคลาสโลคัลช่วยให้เฟรมเวิร์กจัดการกับสถานการณ์เหล่านั้นทั้งหมดในวิธีการเดียวซึ่งยอมรับคลาสเหล่านั้นผ่านอินเทอร์เฟซ StatementCallback วิธีการเดียวนี้ทำหน้าที่เป็นสะพานเชื่อมระหว่างการดำเนินการ (ดำเนินการอัปเดต) และการดำเนินการทั่วไปรอบ ๆ ตัว (เช่นการดำเนินการการจัดการการเชื่อมต่อการแปลข้อผิดพลาดและเอาต์พุตคอนโซล dbms)

public <T> T execute(StatementCallback<T> action) throws DataAccessException    {
        Assert.notNull(action, "Callback object must not be null");

        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        Statement stmt = null;
        try {
            stmt = con.createStatement();
            applyStatementSettings(stmt);
            //
            T result = action.doInStatement(stmt);
            handleWarnings(stmt);
            return result;
        }
        catch (SQLException ex) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            String sql = getSql(action);
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("StatementCallback", sql, ex);
        }
        finally {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.