Deklarowanie zmiennych wewnątrz lub na zewnątrz pętli

236

Dlaczego poniższe działa dobrze?

String str;
while (condition) {
    str = calculateStr();
    .....
}

Ale ten jest uważany za niebezpieczny / niepoprawny:

while (condition) {
    String str = calculateStr();
    .....
}

Czy konieczne jest deklarowanie zmiennych poza pętlą?

Harry Joy
źródło

Odpowiedzi:

289

Zakres zmiennych lokalnych powinien zawsze być możliwie najmniejszy.

W twoim przykładzie zakładam, że niestr jest używany poza pętlą, w przeciwnym razie nie zadajesz pytania, ponieważ zadeklarowanie go w pętli nie byłoby opcją, ponieważ nie skompilowałoby się.whilewhile

A zatem, ponieważ niestr jest używany poza pętlą, najmniejszy możliwy zakres dla znajduje się w pętli while.str

Tak więc odpowiedź jest zdecydowanie, że strabsolutnie należy zadeklarować w pętli while. Bez ifs, bez ands, bez ale.

Jedynym przypadkiem, w którym zasada ta może zostać naruszona, jest to, że z jakiegoś powodu niezwykle ważne jest, aby każdy cykl zegara był wyciskany z kodu, w którym to przypadku możesz rozważyć utworzenie instancji czegoś w zakresie zewnętrznym i ponowne użycie go zamiast ponowne utworzenie go przy każdej iteracji zakresu wewnętrznego. Nie dotyczy to jednak twojego przykładu ze względu na niezmienność ciągów w Javie: nowa instancja str będzie zawsze tworzona na początku twojej pętli i będzie musiała zostać wyrzucona na jej końcu, więc tam nie ma tam możliwości optymalizacji.

EDYCJA: (wstawiam mój komentarz poniżej w odpowiedzi)

W każdym razie właściwym sposobem jest poprawne napisanie całego kodu, ustalenie wymagań dotyczących wydajności produktu, porównanie produktu końcowego z tym wymaganiem, a jeśli nie spełnia tego warunku, optymalizacja. I zwykle dzieje się tak, że w kilku miejscach można znaleźć dobre i formalne optymalizacje algorytmiczne, które sprawiają, że nasz program spełnia wymagania dotyczące wydajności, zamiast konieczności przeglądania całej bazy kodu oraz poprawiania i hakowania aby wycisnąć cykle zegara tu i tam.

Mike Nakis
źródło
2
Zapytanie dotyczące ostatniego akapitu: Jeśli to był inny ciąg, który nie jest niezmienny, to czy to wpływa?
Harry Joy,
1
@HarryJoy Tak, oczywiście weźmy na przykład StringBuilder, który można modyfikować. Jeśli użyjesz StringBuilder do zbudowania nowego ciągu w każdej iteracji pętli, możesz zoptymalizować rzeczy, przydzielając StringBuilder poza pętlę. Ale nadal nie jest to wskazana praktyka. Jeśli robisz to bez bardzo dobrego powodu, jest to przedwczesna optymalizacja.
Mike Nakis,
7
@HarryJoy Właściwym sposobem na zrobienie tego jest poprawne napisanie całego kodu , ustalenie wymagań dotyczących wydajności produktu, porównanie produktu końcowego z tym wymaganiem, a jeśli go nie spełnia, przejdź do optymalizacji. I wiesz co? Zwykle będziesz w stanie zapewnić kilka dobrych i formalnych optymalizacji algorytmicznych w zaledwie kilku miejscach, które załatwią sprawę, zamiast zajmować się całą bazą kodu i poprawiać i hakować rzeczy, aby wycisnąć cykle zegara tu i tam.
Mike Nakis,
2
@MikeNakis i myślę, że myślisz w bardzo wąskim zakresie.
Siten
5
Widzisz, nowoczesne wielordzeniowe, wielordzeniowe, potokowe, wielopoziomowe procesory z pamięcią podręczną pozwalają nam skupić się na przestrzeganiu najlepszych praktyk bez martwienia się o cykle zegara. Ponadto optymalizacja jest wskazana tylko wtedy i tylko wtedy, gdy zostanie stwierdzone, że jest to konieczne, a gdy jest to konieczne, kilka wysoce zlokalizowanych poprawek zwykle osiąga pożądaną wydajność, więc nie ma potrzeby zaśmiecania całego naszego kodu z małymi hackami w imię wydajności.
Mike Nakis,
293

Porównałem bajtowy kod tych dwóch (podobnych) przykładów:

Spójrzmy na 1. przykład :

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

po javac Test.java, javap -c Testotrzymasz:

public class inside.Test extends java.lang.Object{
public inside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Spójrzmy na 2. przykład :

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

po javac Test.java, javap -c Testotrzymasz:

public class outside.Test extends java.lang.Object{
public outside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Obserwacje pokazują, że nie ma różnicy między tymi dwoma przykładami. Jest to wynik specyfikacji JVM ...

Jednak w imię najlepszej praktyki kodowania zaleca się zadeklarowanie zmiennej w możliwie najmniejszym zakresie (w tym przykładzie znajduje się ona w pętli, ponieważ jest to jedyne miejsce, w którym zmienna jest używana).

PrimosK
źródło
3
Jest to wynik JVM Soecification, a nie „optymalizacja kompilatora”. Miejsca na stosy wymagane przez metodę są przydzielane przy wejściu do metody. Tak określa się kod bajtowy.
Markiz Lorne
2
@Arhimed istnieje jeszcze jeden powód, aby umieścić go w pętli (lub po prostu blok „{}”): kompilator ponownie wykorzysta pamięć przydzieloną w ramce stosu dla zmiennej w innym zakresie, jeśli zadeklarujesz w tym innym zakresie zmienną .
Serge
1
Jeśli zapętla się przez listę obiektów danych, czy będzie to miało znaczenie dla większości danych? Prawdopodobnie 40 tysięcy.
Mithun Khatri,
7
Dla każdego z was finalkochankowie: deklarowanie strjak finalw insideprzypadku paczki również nie ma znaczenia =)
skia.heliou
27

Deklarowanie obiektów w najmniejszym zakresie poprawia czytelność .

Wydajność nie ma znaczenia dla dzisiejszych kompilatorów. (W tym scenariuszu)
Z punktu widzenia konserwacji druga opcja jest lepsza.
Deklaruj i inicjalizuj zmienne w tym samym miejscu, w możliwie najwęższym zakresie.

Jak powiedział Donald Ervin Knuth :

„Powinniśmy zapomnieć o małej wydajności, powiedzmy około 97% czasu: przedwczesna optymalizacja jest źródłem wszelkiego zła”

tj. sytuacja, w której programista pozwala, aby względy wydajności wpływały na projekt fragmentu kodu. Może to spowodować, że projekt jest nie tak czysty, jak to mogło być albo kod, który jest nieprawidłowy, ponieważ kod jest skomplikowane przez optymalizację i programista jest rozproszony przez optymalizację .

Chandra Sekhar
źródło
1
„Druga opcja ma nieco szybszą wydajność” => czy ją zmierzyłeś? Zgodnie z jedną z odpowiedzi, kod bajtowy jest taki sam, więc nie widzę różnic między wydajnością.
assylias
Przykro mi, ale to naprawdę nie jest właściwa droga do testowania wydajności programu Java (i jak można przetestować wydajność nieskończoną pętlę tak?)
assylias
Zgadzam się z twoimi innymi uwagami - po prostu uważam, że nie ma różnicy w wydajności.
assylias
11

jeśli chcesz używać także strpoza looop; ogłosić to na zewnątrz. w przeciwnym razie druga wersja jest w porządku.

Azodious
źródło
11

Przejdź do zaktualizowanej odpowiedzi ...

Dla tych, którym zależy na wydajności, wyjmij System.out i ogranicz pętlę do 1 bajtu. Używając podwójnego (test 1/2) i ciąg (3/4), czasy, które upłynęły w milisekundach, podano poniżej dla Windows 7 Professional 64 bit i JDK-1.7.0_21. Kody bajtowe (podane również poniżej dla testu 1 i testu 2) nie są takie same. Byłem zbyt leniwy, by testować zmienne i stosunkowo złożone obiekty.

podwójnie

Test1 zajął: 2710 ms

Test2 zajął: 2790 ms

Ciąg (po prostu zamień podwójnie na ciąg w testach)

Test3 zajął: 1200 ms

Test4 zajął: 3000 ms

Kompilowanie i pobieranie kodu bajtowego

javac.exe LocalTest1.java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


Compiled from "LocalTest1.java"
public class LocalTest1 {
  public LocalTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


Compiled from "LocalTest2.java"
public class LocalTest2 {
  public LocalTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

ZAKTUALIZOWANA ODPOWIEDŹ

Naprawdę nie jest łatwo porównać wydajność ze wszystkimi optymalizacjami JVM. Jest to jednak w pewnym stopniu możliwe. Lepszy test i szczegółowe wyniki w Google Caliper

  1. Kilka szczegółów na blogu: czy powinieneś zadeklarować zmienną w pętli czy przed nią?
  2. Repozytorium GitHub: https://github.com/gunduru/jvdt
  3. Wyniki testu dla podwójnego przypadku i pętli 100M (i tak wszystkie szczegóły JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

Zadeklarowano Przed 1 759,209 Zadeklarowano wewnątrz 2 242,308

  • Zadeklarowano przed 175,209 ns
  • Zadeklarowano wewnątrz 2 242,308 ns

Częściowy kod testu dla podwójnej deklaracji

Nie jest to identyczne z powyższym kodem. Jeśli po prostu kodujesz sztuczną pętlę, JVM pomija ją, więc przynajmniej musisz coś przypisać i zwrócić. Jest to również zalecane w dokumentacji Calipera.

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

Podsumowanie: zadeklarowany Przed oznacza lepszą wydajność - naprawdę niewielką - i jest sprzeczny z zasadą najmniejszego zakresu. JVM powinien to zrobić za Ciebie

Onur Günduru
źródło
Nieprawidłowa metodologia testowania i nie podajesz żadnego wyjaśnienia swoich wyników.
Markiz Lorne
1
@EJP To powinno być dość jasne dla tych, którzy są zainteresowani tematem. Metodologia pochodzi z odpowiedzi PrimosK, aby dostarczyć bardziej przydatnych informacji. Szczerze mówiąc, nie mam pojęcia, jak poprawić tę odpowiedź, może możesz kliknąć edytuj i pokazać nam, jak to zrobić poprawnie?
Onur Günduru
2
1) Java Bytecode jest zoptymalizowany (ponownie uporządkowany, zwinięty itp.) W czasie wykonywania, więc nie przejmuj się zbytnio tym, co jest napisane w plikach .class. 2) jest 1.000.000.000 przebiegów, aby uzyskać wygraną wydajności na poziomie 2.8s, czyli około 2.8ns na przebieg w porównaniu do bezpiecznego i odpowiedniego stylu programowania. Dla mnie wyraźny zwycięzca. 3) Ponieważ nie podajesz żadnych informacji o rozgrzewce, twoje czasy są dość bezużyteczne.
Zakodowane
@Hardcoded lepsze testy / mikro benchmarking z suwmiarką tylko dla pętli podwójnych i 100M. Wyniki online, jeśli chcesz, aby inne przypadki były edytowalne.
Onur Günduru,
Dzięki, to eliminuje punkt 1) i 3). Ale nawet jeśli czas wzrósł do ~ 5ns na cykl, nadal jest to czas, który należy zignorować. Teoretycznie istnieje niewielki potencjał optymalizacji, w rzeczywistości rzeczy, które wykonujesz w cyklu, są zwykle znacznie droższe. Potencjał wynosiłby maksymalnie kilka sekund w ciągu kilku minut lub nawet godzin. Istnieją inne opcje o wyższym potencjale (np. Fork / Join, równoległe strumienie), które sprawdziłbym przed spędzeniem czasu na tego rodzaju optymalizacjach niskiego poziomu.
Zakodowane
7

Jednym z rozwiązań tego problemu może być zapewnienie zmiennego zakresu enkapsulującego pętlę while:

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

Zostaną one automatycznie usunięte z odniesienia po zakończeniu zakresu zewnętrznego.

Morten Madsen
źródło
6

Wewnątrz, im mniejszy zakres, zmienna jest widoczna, tym lepiej.

Jan Zyka
źródło
5

Jeśli nie musisz używać strpętli while (związanej z zakresem), to drugi warunek, tj

  while(condition){
        String str = calculateStr();
        .....
    }

jest lepszy, ponieważ jeśli zdefiniujesz obiekt na stosie, tylko jeśli conditionjest to prawda. Używam go, jeśli go potrzebujesz

Cratylus
źródło
2
Zauważ, że nawet w pierwszym wariancie żaden obiekt nie jest konstruowany, jeśli warunek jest fałszywy.
Philipp Wendler
@ Phillip: Tak, masz rację. Mój błąd. Myślałem tak, jak jest teraz. Co myślisz?
Cratylus
1
Cóż, „definiowanie obiektu na stosie” jest dość dziwnym terminem w świecie Java. Ponadto przydzielanie zmiennej na stosie jest zwykle noop w czasie wykonywania, więc po co się tym przejmować? Prawdziwym problemem jest określenie zakresu pomocy programisty.
Philipp Wendler,
3

Myślę, że najlepszym źródłem odpowiedzi na twoje pytanie będzie następujący post:

Różnica między deklarowaniem zmiennych przed lub w pętli?

Według mojego zrozumienia, ta rzecz byłaby zależna od języka. IIRC Java optymalizuje to, więc nie ma żadnej różnicy, ale JavaScript (na przykład) zajmie się przydzielaniem całej pamięci za każdym razem w pętli. W Javie szczególnie myślę, że drugi działałby szybciej po zakończeniu profilowania.

Naveen Goyal
źródło
3

Jak zauważyło wiele osób,

String str;
while(condition){
    str = calculateStr();
    .....
}

NIE jest lepsze niż to:

while(condition){
    String str = calculateStr();
    .....
}

Więc nie deklaruj zmiennych poza ich zasięgiem, jeśli nie używasz ich ponownie ...

Pavan
źródło
1
z wyjątkiem chyba w ten sposób: link
Dainius Kreivys
2

Zadeklarowanie ciągu stru na zewnątrz pętli wile umożliwia odwołanie do niej wewnątrz i na zewnątrz pętli while. Deklaracja ciągu str w pętli while pozwala na odwołanie się do niej tylko w pętli while.

Jay Tomten
źródło
1

Zmienne powinny być deklarowane tak blisko miejsca, w którym są używane, jak to możliwe.

Ułatwia to RAII (Resource Acquisition Is Initialization) .

Utrzymuje wąski zakres zmiennej. Dzięki temu optymalizator działa lepiej.

Vikiiii
źródło
1

Według Przewodnika programistycznego Google Android zakres zmiennych powinien być ograniczony. Sprawdź ten link:

Ogranicz zakres zmienny

James Jithin
źródło
1

strZmienna będzie dostępna i zastrzeżone trochę miejsca w pamięci nawet po chwili wykonywany poniżej kodu.

 String str;
    while(condition){
        str = calculateStr();
        .....
    }

strZmienna nie będzie dostępna, a także pamięć zostanie zwolniony, który został przydzielony do strzmiennej w poniżej kodu.

while(condition){
    String str = calculateStr();
    .....
}

Jeśli zastosujemy się do drugiego, z pewnością zmniejszy to pamięć systemową i zwiększy wydajność.

Ganesa Vijayakumar
źródło
0

Zadeklarowanie wewnątrz pętli ogranicza zakres odpowiedniej zmiennej. Wszystko zależy od wymagań projektu dotyczących zakresu zmiennej.

ab02
źródło
0

Naprawdę, powyższe pytanie jest kwestią programistyczną. Jak chcesz zaprogramować swój kod? Gdzie potrzebujesz dostępu do „STR”? Nie ma sensu deklarować zmiennej, która jest używana lokalnie jako zmienna globalna. Podstawy programowania wierzę.

Abhishek Bhandari
źródło
-1

Te dwa przykłady skutkują tym samym. Jednak pierwszy zapewnia użycie strzmiennej poza pętlą while; drugi nie jest.

olyanren
źródło
-1

Ostrzeżenie dla prawie wszystkich osób w tym pytaniu: Oto przykładowy kod, w którym wewnątrz pętli może być 200 razy wolniejszy na moim komputerze z Javą 7 (a zużycie pamięci jest również nieco inne). Ale dotyczy alokacji, a nie tylko zakresu.

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

    public static void main(String[] args)
    {
        long start;

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

Wniosek: w zależności od wielkości zmiennej lokalnej różnica może być ogromna, nawet przy niezbyt dużych zmiennych.

Wystarczy powiedzieć, że czasami na zewnątrz lub wewnątrz pętli ma znaczenie.

rt15
źródło
1
Jasne, druga jest szybsza, ale robisz różne rzeczy: test1 tworzy wiele Foo-Objectów z dużymi tablicami, test2 nie. test2 wielokrotnie używa tego samego obiektu Foo, co może być niebezpieczne w środowiskach wielowątkowych.
Zakodowane
Niebezpieczne w środowisku wielowątkowym ??? Proszę wyjaśnić dlaczego. Mówimy o zmiennej lokalnej. Jest tworzony przy każdym wywołaniu metody.
rt15
Jeśli przekażesz obiekt Foo do operacji, która przetwarza dane asynchronicznie, operacja może nadal działać na instancji Foo, gdy zmieniasz w niej dane. Nie musi nawet być wielowątkowy, aby wywoływać skutki uboczne.
Ponowne
Ps: Twoja metoda setValue powinna wynosić bigStuff[(int) (value % STUFF_SIZE)] = value;(Wypróbuj wartość 2147483649L)
Hardcoded
Mówiąc o skutkach ubocznych: Czy porównałeś wyniki swoich metod?
Zakodowane
-1

Myślę, że rozmiar obiektu również ma znaczenie. W jednym z moich projektów zadeklarowaliśmy i zainicjowaliśmy dużą dwuwymiarową tablicę, która powodowała, że ​​aplikacja generowała wyjątek braku pamięci. Zamiast tego usunęliśmy deklarację z pętli i wyczyściliśmy tablicę na początku każdej iteracji.

Sanjit
źródło
-2

Istnieje ryzyko, NullPointerExceptionże calculateStr()metoda zwróci null, a następnie spróbujesz wywołać metodę na str.

Mówiąc bardziej ogólnie, unikaj posiadania zmiennych o wartości zerowej . Nawiasem mówiąc, jest silniejszy dla atrybutów klasy.

Rémi Doolaeghe
źródło
2
Nie ma to żadnego związku z pytaniem. Prawdopodobieństwo NullPointerException (przy przyszłych wywołaniach funkcji) nie zależy od tego, jak deklarowana jest zmienna.
Desert Ice
1
Nie sądzę, bo pytanie brzmi: „Jak najlepiej to zrobić?”. IMHO Wolałbym bezpieczniejszy kod.
Rémi Doolaeghe
1
Istnieje zerowe ryzyko, NullPointerException.że próba wykonania tego kodu return str;napotka błąd kompilacji.
Markiz Lorne