Penggunaan definisi kelas di dalam metode di Java

105

Contoh:

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

Saya menemukan bahwa potongan kode di atas benar-benar legal di Java. Saya memiliki pertanyaan berikut.

  1. Apa gunanya memiliki definisi kelas di dalam metode?
  2. Akankah file kelas dibuat untuk DummyClass
  3. Sulit bagi saya untuk membayangkan konsep ini dengan cara Berorientasi Objek. Memiliki definisi kelas di dalam sebuah perilaku. Mungkin seseorang bisa memberi tahu saya dengan contoh dunia nyata yang setara.
  4. Kelas abstrak di dalam sebuah metode terdengar agak gila bagi saya. Tapi tidak ada antarmuka yang diizinkan. Apakah ada alasan dibalik ini?
bragboy
sumber
1
Saya setuju, ini terlihat sangat berantakan. Saya memeriksa beberapa kode yang rekan saya tulis dan menemukan kelas lokal ini dalam sebuah metode ... itu hanya membuat saya merasa modul ini benar-benar tercemar.
Seseorang Di Suatu Tempat
7
Terkadang ini lebih tentang menyembunyikan barang-barang yang tidak Anda butuhkan di tempat lain, daripada melihat;)
sorrymissjackson

Jawaban:

71

Ini disebut kelas lokal.

2 adalah cara yang mudah: ya, file kelas akan dibuat.

1 dan 3 adalah jenis pertanyaan yang sama. Anda akan menggunakan kelas lokal di mana Anda tidak perlu membuat instance atau mengetahui tentang detail implementasi di mana pun kecuali dalam satu metode.

Penggunaan tipikal akan membuat implementasi sekali pakai dari beberapa antarmuka. Misalnya Anda akan sering melihat sesuatu seperti ini:

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

Jika Anda perlu membuat banyak dari ini dan melakukan sesuatu dengannya, Anda dapat mengubahnya menjadi

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

Mengenai antarmuka: Saya tidak yakin apakah ada masalah teknis yang membuat antarmuka yang ditentukan secara lokal menjadi masalah bagi kompiler, tetapi bahkan jika tidak ada, mereka tidak akan menambah nilai apa pun. Jika kelas lokal yang mengimplementasikan antarmuka lokal digunakan di luar metode, antarmuka akan menjadi tidak berarti. Dan jika kelas lokal hanya akan digunakan di dalam metode, baik antarmuka dan kelas akan diimplementasikan dalam metode itu, sehingga definisi antarmuka akan menjadi berlebihan.

Jacob Mattison
sumber
Adakah ide di versi kelas lokal Java mana yang diperkenalkan?
Kereta luncur
1
Kelas-kelas dalam ditambahkan di Java 1.1 - Saya menduga kelas-kelas lokal juga demikian, tetapi saya tidak memiliki dokumentasi tentang itu.
Jacob Mattison
Bisakah Anda memberikan contoh yang lebih baik tentang apa yang bisa menjadi kasus penggunaan kelas lokal non-anonim? Blok kode kedua Anda dapat ditulis ulang dengan kelas anonim.
Sergey Pauk
1
Sebagian besar penggunaan kelas lokal non-anonim dapat dilakukan dengan kelas anonim. Saya tidak menyempurnakan contoh tetapi Anda biasanya akan menggunakan kelas lokal bernama jika Anda perlu membuat lebih dari satu contoh dari jenis kelas yang sama.
Jacob Mattison
1
Untuk OP: perhatikan bahwa kelas lokal menyediakan cara bagi utas untuk berkomunikasi - yang di parameteratas mungkin dideklarasikan dalam metode penutup, dan dapat diakses oleh kedua utas.
flow2k
15

Itu disebut kelas lokal . Anda dapat menemukan penjelasan rinci dan contohnya di sini . Contoh mengembalikan implementasi spesifik yang tidak perlu kita ketahui di luar metode.

BalusC
sumber
2
Tautan hebat (masih berfungsi setelah 7+ tahun!). Secara khusus, perhatikan "Seperti kelas anggota, kelas lokal dikaitkan dengan instance yang memuat, dan dapat mengakses setiap anggota, termasuk anggota pribadi, dari kelas yang memuatnya ."
flow2k
10
  1. Kelas tidak dapat dilihat (misalnya, metode yang diakses tanpa Refleksi) dari luar metode. Selain itu, ia dapat mengakses variabel lokal yang ditentukan dalam testMethod (), tetapi sebelum definisi kelas.

  2. Saya benar-benar berpikir: "Tidak ada file seperti itu yang akan ditulis." sampai saya baru mencobanya: Oh ya, file seperti itu dibuat! Ini akan disebut sesuatu seperti A $ 1B.class, di mana A adalah kelas luar, dan B adalah kelas lokal.

  3. Khususnya untuk fungsi callback (event handler di GUI, seperti onClick () saat Tombol diklik, dll.), Biasanya menggunakan "kelas anonim" - pertama-tama karena Anda bisa mendapatkan banyak dari mereka. Tetapi terkadang kelas anonim tidak cukup baik - terutama, Anda tidak dapat mendefinisikan konstruktor padanya. Dalam kasus ini, metode kelas lokal ini bisa menjadi alternatif yang baik.

Chris Lercher
sumber
2
2. Ehrm, pasti akan. File kelas akan dibuat untuk setiap kelas bersarang, lokal atau anonim di file java Anda.
sepp2k
2
"2. Tidak ada file seperti itu yang akan ditulis." -- ini salah. Ini menciptakan TestClass$1TestMethodClass.class, sejalan dengan bagaimana .classfile kelas dalam diberi nama.
poligenelubricants
Jawaban bagus, pengecualian untuk 2: Anda akan mendapatkan kelas anonim yang dihasilkan, dalam hal ini "TestClass $ 1TestMethodClass.class"
Steve B.
Ya, maaf! Saya tidak menyadarinya sampai beberapa detik yang lalu. Anda tinggal dan belajar :-))
Chris Lercher
Anda mendapatkan +1 saya untuk menyoroti perbedaan antara kelas anonim dan lokal: mendefinisikan konstruktor.
Matthieu
7

Tujuan sebenarnya dari ini adalah untuk memungkinkan kita membuat kelas inline dalam pemanggilan fungsi untuk menghibur kita yang suka berpura-pura bahwa kita sedang menulis dalam bahasa fungsional;)

Steve B.
sumber
4

Satu-satunya kasus ketika Anda ingin memiliki fungsi kelas dalam vs kelas anonim (alias penutupan Java) adalah ketika kondisi berikut terpenuhi

  1. Anda perlu menyediakan antarmuka atau implementasi kelas abstrak
  2. Anda ingin menggunakan beberapa parameter akhir yang ditentukan dalam fungsi panggilan
  3. Anda perlu merekam beberapa status eksekusi panggilan antarmuka.

Misalnya seseorang menginginkan Runnabledan Anda ingin merekam saat eksekusi telah dimulai dan diakhiri.

Dengan kelas anonim tidak mungkin dilakukan, dengan kelas dalam Anda dapat melakukan ini.

Berikut adalah contoh yang menunjukkan maksud saya

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

Sebelum menggunakan pola ini, harap evaluasi apakah kelas tingkat atas lama biasa, atau kelas dalam, atau kelas dalam statis adalah alternatif yang lebih baik.

Alexander Pogrebnyak
sumber
Saya menyalahgunakan # 2 sedikit untuk menetapkan nilai kembali dari fungsi.
Eddie B
2

Alasan utama untuk mendefinisikan kelas dalam (di dalam metode atau kelas) adalah untuk menangani aksesibilitas anggota dan variabel dari kelas dan metode yang melingkupinya. Kelas dalam dapat mencari anggota data pribadi dan mengoperasikannya. Jika dalam suatu metode itu dapat menangani variabel lokal akhir juga.

Memiliki kelas batin memang membantu memastikan kelas ini tidak dapat diakses oleh dunia luar. Ini berlaku terutama untuk kasus pemrograman UI di GWT atau GXT dll di mana kode penghasil JS ditulis dalam java dan perilaku untuk setiap tombol atau acara harus ditentukan dengan membuat kelas anonim

Fazal
sumber
1

Saya telah menemukan contoh yang bagus di musim semi. Framework ini menggunakan konsep definisi kelas lokal di dalam metode untuk menangani berbagai operasi database dengan cara yang seragam.

Asumsikan Anda memiliki kode seperti ini:

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

Pertama-tama mari kita lihat implementasi dari 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());
    }

Harap perhatikan baris terakhir. Spring melakukan "trik" yang tepat ini untuk metode lainnya juga:

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

"Trik" dengan kelas lokal memungkinkan kerangka kerja untuk menangani semua skenario tersebut dalam satu metode yang menerima kelas tersebut melalui antarmuka StatementCallback. Metode tunggal ini bertindak sebagai jembatan antara tindakan (mengeksekusi, memperbarui) dan operasi umum di sekitarnya (misalnya eksekusi, manajemen koneksi, terjemahan kesalahan dan keluaran konsol 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());
        }
    }
s-evgheni.dll
sumber