Java'da `someObject.new` ne yapar?


100

Java'da, aşağıdaki kodun yasal olduğunu yeni öğrendim:

KnockKnockServer newServer = new KnockKnockServer();                    
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);

Bilginize, alıcı aşağıdaki imzaya sahip bir yardımcı sınıftır:

public class receiver extends Thread {  /* code_inside */  }

Daha önce XYZ.newnotasyonu hiç görmedim . Bu nasıl çalışıyor? Bunu daha geleneksel bir şekilde kodlamanın bir yolu var mı?


7
Referansınız için, iç sınıf .
Alvin Wong

1
Ayrıca, bunun newbirçok dilde bir operatör olduğuna inanıyordum . ( newC ++ ' da da aşırı yüklenebileceğini düşündüm ?) Java'nın iç sınıfı benim için biraz tuhaf.
Alvin Wong

5
StackOverflow'da aptalca sorular yok!
Isaac Rabinovitch

2
@IsaacRabinovitch - Aptalca sorular yok. Bununla birlikte, birçok aptalca olan var. (Ve ara sıra saçma bir cevap da.)
Hot Licks

2
@HotLicks Peki aptalca bir sorunun tanımı nedir? Sanırım sormak zorunda kalmayacak kadar akıllısın. Kendine bu kadar çok güven duyman güzel.
Isaac Rabinovitch

Yanıtlar:


120

Oracle belgelerinde açıklandığı gibi, içerdiği sınıf gövdesinin dışından statik olmayan bir iç sınıfı somutlaştırmanın yolu budur .

Her iç sınıf örneği, içerdiği sınıfın bir örneği ile ilişkilendirilir. Ne zaman newbir iç sınıf içinde kendi içeren sınıfının kullandığı thisvarsayılan olarak kabın örneğini:

public class Foo {
  int val;
  public Foo(int v) { val = v; }

  class Bar {
    public void printVal() {
      // this is the val belonging to our containing instance
      System.out.println(val);
    }
  }

  public Bar createBar() {
    return new Bar(); // equivalent of this.new Bar()
  }
}

Ancak, Foo dışında bir Bar örneği oluşturmak veya yeni bir örneği kapsayıcı bir örnekle ilişkilendirmek thisistiyorsanız, önek gösterimini kullanmanız gerekir.

Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal(); // prints 5

18
Ve sizin de söyleyebileceğiniz gibi, bu inanılmaz derecede kafa karıştırıcı olabilir. İdeal olarak, iç sınıflar, dış sınıfın uygulama ayrıntıları olmalı ve dış dünyaya açık olmamalıdır.
Eric Jablow

10
@EricJablow gerçekten de, spesifikasyonu tutarlı tutmak için var olması gereken sözdizimi bitlerinden biridir, ancak% 99,9999 oranında gerçekten kullanmanız gerekmez. Dışarıdan birinin gerçekten Bar örnekleri oluşturması gerekiyorsa, kullanmaları yerine Foo'da bir fabrika yöntemi sağlardım f.new.
Ian Roberts

Yanlış ama eğer beni düzelt publicüzerinde erişim seviyesi KnockKnockServer.receiveryapılmıştır privateo bu şekilde doğru örneğini imkansız o zaman? @EricJablow'un açıklamasını genişletmek için, iç sınıfların genellikle varsayılan olarak bir privateerişim düzeyine ayarlanması gerekir .
Andrew Bissell

1
@AndrewBissell evet, ancak receiversınıfa dışarıdan atıfta bulunmak da imkansız olurdu . Eğer onu tasarlıyorsam, muhtemelen sınıfın public, ancak kurucusunun korumalı veya paket-özel olmasını ve KnockKnockServeralıcı örneklerini oluşturmak için bir yöntemim olur.
Ian Roberts

1
@emory Bunu söylemiyorum, bir iç sınıfı genel yapmak ve iç sınıfın örneklerini dıştaki yöntemlerden döndürmek için tamamen geçerli nedenler olabileceğini biliyorum, ancak kodumu öyle tasarlama eğilimindeydim " yabancılar "doğrudan kullanarak iç sınıfın örneklerini oluşturmaya gerek yoktur x.new.
Ian Roberts

18

Şu örneğe bir göz atın:

public class Test {

    class TestInner{

    }

    public TestInner method(){
        return new TestInner();
    }

    public static void main(String[] args) throws Exception{
        Test t = new Test();
        Test.TestInner ti = t.new TestInner();
    }
}

Javap kullanarak bu kod için oluşturulan talimatları görüntüleyebiliriz

Ana yöntem:

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   new     #2; //class Test
   3:   dup
   4:   invokespecial   #3; //Method "<init>":()V
   7:   astore_1
   8:   new     #4; //class Test$TestInner
   11:  dup
   12:  aload_1
   13:  dup
   14:  invokevirtual   #5; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   17:  pop
   18:  invokespecial   #6; //Method Test$TestInner."<init>":(LTest;)V
   21:  astore_2
   22:  return
}

İç sınıf yapıcısı:

Test$TestInner(Test);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #1; //Field this$0:LTest;
   5:   aload_0
   6:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   9:   return

}

Her şey basit - TestInner yapıcısını çağırırken, java Test örneğini birinci argüman main: 12 olarak geçirir . Buna bakmamak TestInner'ın argüman oluşturucu içermemesi gerekir. TestInner, sırayla sadece Test $ TestInner: 2 üst nesnesine referansı kaydeder . Bir örnek yönteminden iç sınıf yapıcısını çağırdığınızda, ana nesneye başvuru otomatik olarak geçer, böylece bunu belirtmeniz gerekmez. Aslında her seferinde geçer, ancak dışarıdan çağrıldığında açıkça aktarılmalıdır.

t.new TestInner(); - sadece bir tür değil, TestInner yapıcısına ilk gizli argümanı belirtmenin bir yoludur

method () şuna eşittir:

public TestInner method(){
    return this.new TestInner();
}

TestInner şuna eşittir:

class TestInner{
    private Test this$0;

    TestInner(Test parent){
        this.this$0 = parent;
    }
}

7

İç sınıflar, dilin 1.1 sürümünde Java'ya eklendiğinde, başlangıçta 1.0 uyumlu koda dönüşüm olarak tanımlanıyorlardı. Bu dönüşümün bir örneğine bakarsanız, bir iç sınıfın gerçekte nasıl çalıştığını çok daha net hale getireceğini düşünüyorum.

Ian Roberts'ın cevabındaki kodu düşünün:

public class Foo {
  int val;
  public Foo(int v) { val = v; }

  class Bar {
    public void printVal() {
      System.out.println(val);
    }
  }

  public Bar createBar() {
    return new Bar();
  }
}

1.0 uyumlu koda dönüştürüldüğünde, bu iç sınıf Barşu şekilde olur:

class Foo$Bar {
  private Foo this$0;

  Foo$Bar(Foo outerThis) {
    this.this$0 = outerThis;
  }

  public void printVal() {
    System.out.println(this$0.val);
  }
}

İç sınıf adı, benzersiz kılmak için dış sınıf adının önüne eklenmiştir. this$0Dış kısmın bir kopyasını tutan gizli bir özel üye eklenir this. Ve bu üyeyi başlatmak için gizli bir kurucu oluşturulur.

createBarYönteme bakarsanız , bunun gibi bir şeye dönüşür:

public Foo$Bar createBar() {
  return new Foo$Bar(this);
}

Öyleyse aşağıdaki kodu çalıştırdığınızda ne olacağını görelim.

Foo f = new Foo(5);
Foo.Bar b = f.createBar();                               
b.printVal();

İlk olarak Foo, valüyeyi 5'e (yani f.val = 5) bir örneğini somutlaştırıyoruz .

Daha sonra f.createBar(), bir örneğini Foo$Barbaşlatan ve this$0üyeyi thisfrom createBar(ie b.this$0 = f) ' den geçirilen değerine başlatan çağırırız .

Son olarak , hangisinin b.printVal()yazdırmaya çalıştığını b.this$0.val, f.valhangisinin 5 olduğunu çağırıyoruz .

Şimdi bu, bir iç sınıfın düzenli bir örneğiydi. BarDışarıdan örnekleme yapılırken neler olduğuna bakalım Foo.

Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal();

1.0 dönüşümümüzü tekrar uyguladığımızda, bu ikinci satır şu şekilde olacaktır:

Foo$Bar b = new Foo$Bar(f);

Bu, f.createBar()aramayla neredeyse aynı . Yine bir örnek oluşturuyoruz Foo$Barve this$0üyeyi f'ye başlatıyoruz. Yani yine b.this$0 = f.

Aradığınızda Ve yine b.printVal(), yazdırdığınız b.thi$0.valolan f.val5 olan.

Hatırlanması gereken en önemli şey, iç sınıfın, thisdış sınıftan bir kopyasını tutan gizli bir üyeye sahip olmasıdır . Dış sınıfın içinden bir iç sınıfı başlattığınızda, bu sınıf geçerli değeriyle örtük olarak başlatılır this. İç sınıfı dış sınıfın dışından başlattığınızda, newanahtar sözcük üzerindeki önek aracılığıyla dış sınıfın hangi örneğinin kullanılacağını açıkça belirtirsiniz .


4

new receiverTek bir jeton olarak düşünün . İçinde boşluk olan bir işlev adı gibi.

Tabii ki, sınıfın KnockKnockServertam anlamıyla adlandırılmış bir işlevi yok new receiver, ancak sözdiziminin bunu önermek için olduğunu tahmin ediyorum. Kapsayıcı sınıfa herhangi bir erişim için KnockKnockServer.receiverbelirli bir örneğini kullanarak yeni bir örnek oluşturan bir işlevi çağırıyormuşsunuz gibi görünmesi amaçlanmıştır KnockKnockServer.


Teşekkürler, evet - şimdi new receiverbir jeton olarak düşünmeme yardımcı oluyor ! çok teşekkürler!
Kahve

1

Gölgeleme

Belirli bir kapsamdaki (bir iç sınıf veya bir yöntem tanımı gibi) bir tür bildirimi (bir üye değişkeni veya bir parametre adı gibi), çevreleyen kapsamdaki başka bir bildirimle aynı ada sahipse, bildirim bildirimi gölgeler çevreleyen kapsamın. Gölgeli bir bildirime yalnızca adıyla başvuramazsınız. Aşağıdaki örnek, ShadowTest, bunu gösterir:

public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

Aşağıdaki, bu örneğin çıktısıdır:

x = 23
this.x = 1
ShadowTest.this.x = 0

Bu örnek, x adlı üç değişkeni tanımlar: ShadowTest sınıfının üye değişkeni, FirstLevel iç sınıfının üye değişkeni ve methodInFirstLevel yöntemindeki parametre. MethodInFirstLevel metodunun bir parametresi olarak tanımlanan x değişkeni, FirstLevel iç sınıfının değişkenini gölgeler. Sonuç olarak, methodInFirstLevel yönteminde x değişkenini kullandığınızda, yöntem parametresine başvurur. FirstLevel iç sınıfının üye değişkenine başvurmak için, kapsayan kapsamı temsil etmek için this anahtar sözcüğünü kullanın:

System.out.println("this.x = " + this.x);

Daha büyük kapsamları ait oldukları sınıf adına göre çevreleyen üye değişkenlere bakın. Örneğin, aşağıdaki ifade, methodInFirstLevel yönteminden ShadowTest sınıfının üye değişkenine erişir:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

Dokümanlara bakın

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.