Java'da bir yöntem içinde sınıf tanımlarının kullanımı


105

Misal:

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

Yukarıdaki kod parçasının Java'da tamamen yasal olduğunu öğrendim. Aşağıdaki sorularım var.

  1. Bir yöntemin içinde bir sınıf tanımına sahip olmanın ne faydası var?
  2. İçin bir sınıf dosyası oluşturulacak mı? DummyClass
  3. Bu kavramı Nesneye Dayalı bir şekilde hayal etmek benim için zor. Bir davranışın içinde bir sınıf tanımına sahip olmak. Muhtemelen birisi bana eşdeğer gerçek dünya örnekleriyle söyleyebilir.
  4. Bir yöntemin içindeki soyut dersler bana biraz çılgınca geliyor. Ancak hiçbir arayüze izin verilmez. Bunun arkasında herhangi bir sebep var mı?

1
Katılıyorum, inanılmaz derecede dağınık görünüyor. Meslektaşımın yazdığı ve bu yerel sınıfı bir yöntemde bulduğu bazı kodları inceledim ... bu modül tamamen kirlenmiş gibi hissetmemi sağladı.
Someone Somewhere

7
Bazen bu, görünüşten ziyade başka hiçbir yerde ihtiyacınız olmayan şeyleri saklamakla ilgilidir;)
pardonMissjackson

Yanıtlar:


71

Buna yerel sınıf denir.

2 kolay olanıdır: evet, bir sınıf dosyası oluşturulacaktır.

1 ve 3 biraz aynı soru. Hiçbir zaman birini başlatmanız gerekmeyen veya herhangi bir yerde, ancak tek bir yöntemde uygulama ayrıntılarını bilmeniz gereken yerel bir sınıf kullanırsınız.

Tipik bir kullanım, bazı arayüzlerin atılıp atılan bir uygulamasını oluşturmaktır. Örneğin, genellikle şöyle bir şey göreceksiniz:

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

Bunlardan bir grup oluşturmanız ve onlarla bir şeyler yapmanız gerekirse, bunu şu şekilde değiştirebilirsiniz:

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

Arayüzlerle ilgili olarak: Yerel olarak tanımlanmış arayüzleri derleyici için bir sorun haline getiren teknik bir sorun olup olmadığından emin değilim, ancak olmasa bile herhangi bir değer katmazlar. Yöntemin dışında yerel bir arabirim uygulayan bir yerel sınıf kullanıldıysa, arabirim anlamsız olacaktır. Ve eğer yerel bir sınıf sadece metodun içinde kullanılacaksa, hem arayüz hem de sınıf bu metot içinde uygulanacaktır, bu yüzden arayüz tanımı gereksiz olacaktır.


Java yerel sınıflarının hangi sürümünde sunulduğuna dair bir fikriniz var mı?
Kızak

1
Java 1.1'de iç sınıflar eklendi - Sanırım yerel sınıflar da vardı, ancak bununla ilgili belgem yok.
Jacob Mattison

Anonim olmayan yerel bir sınıfın kullanım durumu için daha iyi bir örnek verebilir misiniz? İkinci kod bloğunuz anonim sınıflarla yeniden yazılabilir.
Sergey Pauk

1
Anonim olmayan yerel sınıfların çoğu kullanımı anonim sınıflarla gerçekleştirilebilir. Örneği detaylandırmadım, ancak aynı sınıf türünden birden fazla örnek oluşturmanız gerekirse, genellikle adlandırılmış bir yerel sınıf kullanırsınız.
Jacob Mattison

1
OP için: yerel sınıfın evrelerin iletişim kurması için bir yol sağladığına dikkat edin - parameteryukarıdakiler çevreleyen yöntemde bildirilebilir ve her iki evre tarafından erişilebilir.
flow2k

15

Bunlara yerel sınıflar denir . Burada ayrıntılı bir açıklama ve bir örnek bulabilirsiniz . Örnek, yöntemin dışında bilmemiz gerekmeyen belirli bir uygulama döndürür.


2
Harika bağlantı (7 yıldan sonra hala çalışıyor!). Özellikle, "Üye sınıflar gibi, yerel sınıflar da kapsayıcı bir örnekle ilişkilidir ve içerdiği sınıfın özel üyeleri dahil herhangi bir üyeye erişebilir ."
flow2k

10
  1. Sınıf, yöntemin dışından görülemez (örn. Somutlaştırılır, yöntemlerine Yansıma olmadan erişilir). Ayrıca, testMethod () içinde tanımlanan yerel değişkenlere ancak sınıf tanımından önce erişebilir.

  2. Aslında şöyle düşündüm: "Böyle bir dosya yazılmaz." ta ki deneyene kadar: Ah evet, böyle bir dosya yaratıldı! A $ 1B.class gibi bir şey olarak adlandırılacaktır, burada A dış sınıf ve B yerel sınıftır.

  3. Özellikle geri çağırma işlevleri için (GUI'lerdeki olay işleyicileri, bir Düğmeye tıklandığında onClick () gibi), "anonim sınıfları" kullanmak oldukça olağandır - her şeyden önce, bunların çoğunu elde edebileceğiniz için. Ancak bazen anonim sınıflar yeterince iyi değildir - özellikle bunlar üzerinde bir kurucu tanımlayamazsınız. Bu durumlarda, bu yöntem yerel sınıfları iyi bir alternatif olabilir.


2
2. Ehrm, elbette olacak. Java dosyanızdaki her iç içe geçmiş, yerel veya anonim sınıf için sınıf dosyaları oluşturulacaktır.
sepp2k

2
"2. Böyle bir dosya yazılmayacak." -- Bu yanlış. TestClass$1TestMethodClass.classİç sınıf .classdosyalarının nasıl adlandırıldığına benzer şekilde oluşturur .
poligenel yağlayıcılar

İyi yanıt, 2 için istisna: oluşturulan anonim sınıfı elde edeceksiniz, bu durumda "TestClass $ 1TestMethodClass.class"
Steve B.

Evet, üzgünüm! Bunu birkaç saniye öncesine kadar fark etmemiştim. Yaşa ve öğren :-))
Chris Lercher

Anonim ve yerel sınıflar arasındaki farkı vurgulamak için + 1'ime sahipsiniz: bir kurucu tanımlamak.
Matthieu

7

Bunun gerçek amacı, işlevsel bir dilde yazıyormuşuz gibi davranmaktan hoşlananlarımızı konsolide etmek için işlev çağrılarında satır içi sınıflar oluşturmamıza izin vermektir;)


4

Tam gelişmiş bir iç sınıfa karşı anonim bir sınıfa (diğer bir deyişle Java kapanışı) sahip olmak istediğiniz tek durum, aşağıdaki koşulların karşılanmasıdır.

  1. bir arayüz veya soyut sınıf uygulaması sağlamanız gerekir
  2. işlevi çağırmada tanımlanan bazı son parametreleri kullanmak istiyorsunuz
  3. arayüz çağrısının bazı yürütme durumlarını kaydetmeniz gerekir.

Örneğin, birisi a istiyor Runnableve uygulamanın ne zaman başladığını ve bittiğini kaydetmek istiyorsunuz.

Anonim sınıfla yapmak mümkün değil, iç sınıfla bunu yapabilirsiniz.

İşte bir örnek benim amacımı gösteriyor

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

Yine de bu kalıbı kullanmadan önce, lütfen düz eski üst düzey sınıfın veya iç sınıfın veya statik iç sınıfın daha iyi alternatifler olup olmadığını değerlendirin.


# 2'yi işlevlerden dönüş değerleri atamak için biraz kötüye kullanıyorum.
Eddie B

2

İç sınıfları (bir yöntem veya bir sınıf içinde) tanımlamanın ana nedeni, çevreleyen sınıf ve yöntemin üyelerinin ve değişkenlerinin erişilebilirliğiyle ilgilenmektir. Bir iç sınıf, özel veri üyelerini arayabilir ve bunlar üzerinde işlem yapabilir. Bir yöntem içindeyse, son yerel değişkenle de ilgilenebilir.

İçsel sınıflara sahip olmak, bu sınıfın dış dünya tarafından erişilebilir olmadığından emin olmaya yardımcı olur. Bu, özellikle JS üreten kodun java'da yazıldığı ve her düğme veya olay için davranışın anonim sınıflar oluşturarak tanımlanması gereken GWT veya GXT vb.


1

İlkbaharda güzel bir örnekle karşılaştım. Çerçeve, çeşitli veritabanı işlemlerini tek tip bir şekilde ele almak için yöntemin içindeki yerel sınıf tanımları kavramını kullanıyor.

Bunun gibi bir kodunuz olduğunu varsayalım:

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

İlk olarak execute () uygulamasına bakalım:

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

Lütfen son satıra dikkat edin. Spring, bu "numarayı" diğer yöntemler için de yapar:

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

Yerel sınıflarla "hile", çerçevenin StatementCallback arabirimi aracılığıyla bu sınıfları kabul eden tek bir yöntemde tüm bu senaryolarla ilgilenmesine izin verir. Bu tek yöntem, eylemler (yürütme, güncelleme) ve etraflarındaki ortak işlemler (örn. Yürütme, bağlantı yönetimi, hata çevirisi ve dbms konsol çıkışı) arasında bir köprü görevi görür.

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());
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.