Sử dụng các định nghĩa lớp bên trong một phương thức trong Java


105

Thí dụ:

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

Tôi phát hiện ra rằng đoạn mã trên là hoàn toàn hợp pháp trong Java. Tôi có những câu hỏi sau đây.

  1. Việc sử dụng một định nghĩa lớp bên trong một phương thức là gì?
  2. Một tệp lớp có được tạo cho DummyClass
  3. Thật khó để tôi hình dung khái niệm này theo hướng Đối tượng. Có định nghĩa lớp bên trong một hành vi. Có lẽ ai đó có thể cho tôi biết với các ví dụ tương đương trong thế giới thực.
  4. Đối với tôi, các lớp trừu tượng bên trong một phương thức có vẻ hơi điên rồ. Nhưng không có giao diện nào được phép. Có lý do gì đằng sau điều này?

1
Tôi đồng ý, nó trông cực kỳ lộn xộn. Tôi đã kiểm tra một số mã mà đồng nghiệp của tôi đã viết và tìm thấy lớp cục bộ này trong một phương thức ... nó chỉ khiến tôi cảm thấy như mô-đun này hoàn toàn bị ô uế.
Ai đó ở đâu đó vào

7
Đôi khi nó thêm về giấu đi những thứ mà bạn cần phải ở đâu khác, chứ không phải là vẻ;)
sorrymissjackson

Câu trả lời:


71

Đây được gọi là lớp cục bộ.

2 là cái dễ: có, một tệp lớp sẽ được tạo.

1 và 3 là loại câu hỏi giống nhau. Bạn sẽ sử dụng một lớp cục bộ nơi bạn không bao giờ cần phải khởi tạo một lớp hoặc biết về chi tiết triển khai ở bất kỳ đâu ngoại trừ một phương thức.

Một cách sử dụng điển hình là tạo ra một số giao diện được triển khai ngay lập tức. Ví dụ: bạn sẽ thường thấy một cái gì đó như thế này:

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

Nếu bạn cần tạo một loạt những thứ này và làm gì đó với chúng, bạn có thể thay đổi điều này thành

  //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());

Về giao diện: Tôi không chắc liệu có vấn đề kỹ thuật nào khiến các giao diện được xác định cục bộ trở thành vấn đề đối với trình biên dịch hay không, nhưng ngay cả khi không có, chúng sẽ không thêm bất kỳ giá trị nào. Nếu một lớp cục bộ triển khai một giao diện cục bộ được sử dụng bên ngoài phương thức, thì giao diện đó sẽ vô nghĩa. Và nếu một lớp cục bộ chỉ được sử dụng bên trong phương thức, thì cả giao diện và lớp sẽ được triển khai trong phương thức đó, vì vậy định nghĩa giao diện sẽ là dư thừa.


Bất kỳ ý tưởng nào trong phiên bản của các lớp địa phương Java nơi đã giới thiệu?
Xe trượt

1
Các lớp bên trong đã được thêm vào trong Java 1.1 - Tôi đoán các lớp cục bộ cũng vậy nhưng tôi không có tài liệu về điều đó.
Jacob Mattison

Bạn có thể cung cấp một ví dụ tốt hơn về trường hợp sử dụng của một lớp cục bộ không ẩn danh không? Khối mã thứ hai của bạn có thể được viết lại bằng các lớp ẩn danh.
Sergey Pauk

1
Hầu hết việc sử dụng các lớp cục bộ không ẩn danh có thể được thực hiện với các lớp ẩn danh. Tôi không làm rõ ví dụ nhưng bạn thường sử dụng một lớp cục bộ được đặt tên nếu bạn cần tạo thêm một thể hiện của cùng một loại lớp.
Jacob Mattison

1
Đối với OP: lưu ý rằng lớp cục bộ cung cấp một cách để các luồng giao tiếp - phần parametertrên có thể được khai báo trong phương thức bao và cả hai luồng đều có thể truy cập được.
flow2k

15

Chúng được gọi là các lớp địa phương . Bạn có thể tìm thấy giải thích chi tiết và ví dụ ở đây . Ví dụ trả về một triển khai cụ thể mà chúng ta không cần biết bên ngoài phương thức.


2
Liên kết tuyệt vời (vẫn hoạt động sau hơn 7 năm!). Đặc biệt, lưu ý "Giống như các lớp thành viên, các lớp cục bộ được liên kết với một cá thể chứa và có thể truy cập bất kỳ thành viên nào, kể cả các thành viên riêng, của lớp chứa ."
flow2k

10
  1. Không thể nhìn thấy lớp (tức là được khởi tạo, các phương thức của nó được truy cập mà không có Reflection) từ bên ngoài phương thức. Ngoài ra, nó có thể truy cập các biến cục bộ được định nghĩa trong testMethod (), nhưng trước định nghĩa lớp.

  2. Tôi thực sự đã nghĩ: "Sẽ không có tệp nào như vậy được viết." cho đến khi tôi vừa thử nó: Ồ vâng, một tệp như vậy được tạo ra! Nó sẽ được gọi là A $ 1B.class, trong đó A là lớp bên ngoài và B là lớp cục bộ.

  3. Đặc biệt đối với các hàm gọi lại (trình xử lý sự kiện trong GUI, như onClick () khi một Nút được nhấp, v.v.), việc sử dụng "các lớp ẩn danh" là khá bình thường - trước hết vì bạn có thể kết thúc với rất nhiều trong số chúng. Nhưng đôi khi các lớp ẩn danh không đủ tốt - đặc biệt, bạn không thể định nghĩa hàm tạo trên chúng. Trong những trường hợp này, các lớp địa phương của phương thức này có thể là một sự thay thế tốt.


2
2. Ehrm, chắc chắn rồi. Các tệp lớp sẽ được tạo cho mọi lớp lồng nhau, cục bộ hoặc ẩn danh trong tệp java của bạn.
sepp2k

2
"2. Không có tệp nào như vậy sẽ được viết." -- cái này sai. Nó tạo ra TestClass$1TestMethodClass.class, tương tự như cách .classđặt tên các tệp bên trong .
polygenelubricants

Câu trả lời hay, ngoại lệ cho 2: bạn sẽ nhận được lớp ẩn danh được tạo, trong trường hợp này là "TestClass $ 1TestMethodClass.class"
Steve B.

Vâng, tôi xin lỗi! Tôi đã không nhận ra điều này cho đến một vài giây trước đây. Bạn sống và học hỏi :-))
Chris Lercher

Bạn đã có +1 của tôi để làm nổi bật sự khác biệt giữa các lớp ẩn danh và lớp cục bộ: xác định một phương thức khởi tạo.
Matthieu

7

Mục đích thực sự của việc này là cho phép chúng ta tạo các lớp nội tuyến trong các lệnh gọi hàm để điều khiển những người trong chúng ta, những người thích giả vờ rằng chúng ta đang viết bằng một ngôn ngữ hàm;)


4

Trường hợp duy nhất khi bạn muốn có một hàm đầy đủ bên trong lớp so với lớp ẩn danh (hay còn gọi là Java bao đóng) là khi các điều kiện sau được đáp ứng

  1. bạn cần cung cấp một giao diện hoặc triển khai lớp trừu tượng
  2. bạn muốn sử dụng một số tham số cuối cùng được xác định trong hàm gọi
  3. bạn cần ghi lại một số trạng thái thực hiện lệnh gọi giao diện.

Ví dụ: ai đó muốn a Runnablevà bạn muốn ghi lại khi quá trình thực hiện bắt đầu và kết thúc.

Với lớp ẩn danh thì không thể làm được, với lớp bên trong bạn có thể làm được điều này.

Đây là một ví dụ chứng minh quan điểm của tôi

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

Tuy nhiên, trước khi sử dụng mẫu này, vui lòng đánh giá xem lớp cấp cao nhất cũ đơn thuần hay lớp bên trong hoặc lớp bên trong tĩnh có phải là lựa chọn thay thế tốt hơn hay không.


Tôi hơi lạm dụng # 2 để gán giá trị trả về từ các hàm.
Eddie B

2

Lý do chính để xác định các lớp bên trong (trong một phương thức hoặc một lớp) là để xử lý khả năng truy cập của các thành viên và các biến của lớp và phương thức bao quanh. Một lớp bên trong có thể tra cứu các thành viên dữ liệu riêng tư và hoạt động trên chúng. Nếu bên trong một phương thức, nó cũng có thể xử lý với biến cục bộ cuối cùng.

Có các lớp bên trong giúp đảm bảo rằng lớp này không thể tiếp cận với thế giới bên ngoài. Điều này đúng đặc biệt đối với các trường hợp lập trình giao diện người dùng trong GWT hoặc GXT, v.v. trong đó mã tạo JS được viết bằng java và hành vi cho mỗi nút hoặc sự kiện phải được xác định bằng cách tạo các lớp ẩn danh


1

Tôi đã xem qua một ví dụ điển hình vào mùa xuân. Khung đang sử dụng khái niệm định nghĩa lớp cục bộ bên trong phương thức để xử lý các hoạt động cơ sở dữ liệu khác nhau một cách thống nhất.

Giả sử bạn có một đoạn mã như sau:

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

Đầu tiên chúng ta hãy xem xét việc triển khai thực thi ():

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

Hãy lưu ý dòng cuối cùng. Spring cũng thực hiện "mẹo" chính xác này đối với các phương pháp còn lại:

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

"Thủ thuật" với các lớp cục bộ cho phép khuôn khổ xử lý tất cả các tình huống đó trong một phương thức duy nhất chấp nhận các lớp đó thông qua giao diện StatementCallback. Phương thức đơn này hoạt động như một cầu nối giữa các hành động (thực thi, cập nhật) và các hoạt động phổ biến xung quanh chúng (ví dụ: thực thi, quản lý kết nối, dịch lỗi và đầu ra bảng điều khiển 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());
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.