Jawaban:
Dalam contoh saya nyana str
yang tidak digunakan di luar while
lingkaran, jika tidak, anda tidak akan mengajukan pertanyaan, karena menyatakan itu dalamwhile
lingkaran tidak akan menjadi pilihan, karena itu tidak akan mengkompilasi.
Jadi, karena str
ini tidak digunakan di luar loop, ruang lingkup terkecil yang mungkin untuk str
ini dalam sementara lingkaran.
Jadi, jawabannya adalah empatik yang str
mutlak harus dinyatakan dalam loop sementara. Tidak ada, tidak ada, dan tidak ada.
Satu-satunya kasus di mana aturan ini mungkin dilanggar adalah jika karena alasan tertentu itu sangat penting bahwa setiap siklus jam harus diperas dari kode, dalam hal ini Anda mungkin ingin mempertimbangkan instantiasi sesuatu dalam lingkup luar dan menggunakannya kembali daripada instantiasi ulang pada setiap iterasi dari ruang lingkup dalam. Namun, ini tidak berlaku untuk contoh Anda, karena kekekalan string di java: contoh baru dari str akan selalu dibuat di awal loop Anda dan harus dibuang di akhir, jadi ada tidak ada kemungkinan untuk mengoptimalkan di sana.
Sunting: (menyuntikkan komentar saya di bawah dalam jawaban)
Bagaimanapun, cara yang tepat untuk melakukan sesuatu adalah dengan menulis semua kode Anda dengan benar, menetapkan persyaratan kinerja untuk produk Anda, mengukur produk akhir Anda terhadap persyaratan ini, dan jika tidak memuaskannya, maka optimalkan segala sesuatunya. Dan yang biasanya terjadi adalah Anda menemukan cara untuk menyediakan beberapa optimasi algoritmik yang bagus dan formal hanya dalam beberapa tempat yang membuat program kami memenuhi persyaratan kinerjanya alih-alih harus menjelajahi seluruh basis kode Anda dan men-tweak dan meretas hal-hal di Untuk memeras siklus jam di sana-sini.
Saya membandingkan kode byte dari dua contoh (serupa) itu:
Mari kita lihat 1. contoh :
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
setelah itu javac Test.java
, javap -c Test
Anda akan mendapatkan:
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
}
Mari kita lihat 2. contoh :
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
setelah itu javac Test.java
, javap -c Test
Anda akan mendapatkan:
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
}
Pengamatan menunjukkan bahwa tidak ada perbedaan di antara dua contoh itu. Ini adalah hasil dari spesifikasi JVM ...
Tetapi atas nama praktik pengkodean terbaik, disarankan untuk mendeklarasikan variabel dalam ruang lingkup sekecil mungkin (dalam contoh ini berada di dalam loop, karena ini adalah satu-satunya tempat di mana variabel digunakan).
final
pecinta: menyatakan str
seperti final
dalam inside
paket juga tidak ada bedanya =)
Mendeklarasikan objek dalam lingkup terkecil meningkatkan keterbacaan .
Kinerja tidak masalah untuk kompiler saat ini. (Dalam skenario ini)
Dari perspektif pemeliharaan, opsi ke-2 lebih baik.
Deklarasikan dan inisialisasi variabel di tempat yang sama, dalam ruang lingkup sesempit mungkin.
Seperti yang dikatakan Donald Ervin Knuth :
"Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan"
yaitu) situasi di mana seorang programmer memungkinkan pertimbangan kinerja mempengaruhi desain sepotong kode. Ini dapat menghasilkan desain yang tidak sebersih mungkin atau kode yang tidak benar, karena kode rumit oleh optimasi dan programmer terganggu dengan optimalisasi .
Silakan lewati ke jawaban yang diperbarui ...
Bagi mereka yang peduli dengan kinerja, keluarkan System.out dan batasi loop hingga 1 byte. Menggunakan double (test 1/2) dan menggunakan String (3/4) waktu yang berlalu dalam milidetik diberikan di bawah ini dengan Windows 7 Professional 64 bit dan JDK-1.7.0_21. Bytecodes (juga diberikan di bawah ini untuk test1 dan test2) tidak sama. Saya terlalu malas untuk menguji dengan benda yang bisa berubah & relatif kompleks.
dua kali lipat
Test1 mengambil: 2710 msecs
Test2 mengambil: 2790 msecs
String (ganti saja double dengan string dalam tes)
Test3 mengambil: 1200 msecs
Test4 mengambil: 3000 msecs
Mengompilasi dan mendapatkan bytecode
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
}
Benar-benar tidak mudah untuk membandingkan kinerja dengan semua optimisasi JVM. Namun, itu agak mungkin. Tes yang lebih baik dan hasil terperinci dalam Google Caliper
Ini tidak identik dengan kode di atas. Jika Anda hanya mengkode loop dummy, JVM akan melewatkannya, jadi setidaknya Anda perlu menetapkan dan mengembalikan sesuatu. Ini juga direkomendasikan dalam dokumentasi Caliper.
@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;
}
Ringkasan: dideklarasikanSebelum menunjukkan kinerja yang lebih baik - sangat kecil - dan itu bertentangan dengan prinsip ruang lingkup terkecil. JVM seharusnya melakukan ini untuk Anda
Salah satu solusi untuk masalah ini bisa dengan menyediakan ruang lingkup variabel yang merangkum loop sementara:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Mereka akan secara otomatis kehilangan referensi ketika lingkup luar berakhir.
Jika Anda tidak perlu menggunakan str
after loop sementara (lingkup terkait) maka kondisi kedua yaitu
while(condition){
String str = calculateStr();
.....
}
lebih baik karena jika Anda mendefinisikan objek pada stack hanya jika condition
itu benar. Yaitu menggunakannya jika Anda membutuhkannya
Saya pikir sumber terbaik untuk menjawab pertanyaan Anda adalah posting berikut:
Perbedaan antara mendeklarasikan variabel sebelum atau dalam lingkaran?
Menurut pemahaman saya, hal ini tergantung pada bahasa. IIRC Java mengoptimalkan ini, jadi tidak ada perbedaan, tetapi JavaScript (misalnya) akan melakukan alokasi memori keseluruhan setiap kali dalam loop. Di Jawa khususnya saya pikir yang kedua akan berjalan lebih cepat ketika melakukan profil.
Seperti banyak orang tunjukkan,
String str;
while(condition){
str = calculateStr();
.....
}
adalah TIDAK lebih baik dari ini:
while(condition){
String str = calculateStr();
.....
}
Jadi jangan mendeklarasikan variabel di luar cakupannya jika Anda tidak menggunakannya kembali ...
Mendeklarasikan String str di luar loop wile memungkinkannya direferensikan di dalam & di luar loop while. Mendeklarasikan String str di dalam loop sementara memungkinkan untuk hanya direferensikan di dalam loop sementara.
Variabel harus dinyatakan sedekat mungkin dengan tempat mereka digunakan.
Itu membuat RAII (Akuisisi Sumber Daya Adalah Inisialisasi) lebih mudah.
Itu membuat ruang lingkup variabel ketat. Ini memungkinkan pengoptimal bekerja lebih baik.
Menurut panduan Pengembangan Android Google, ruang lingkup variabel harus dibatasi. Silakan periksa tautan ini:
The str
variabel akan tersedia dan disediakan beberapa ruang di memori bahkan setelah beberapa saat dieksekusi di bawah kode.
String str;
while(condition){
str = calculateStr();
.....
}
The str
variabel tidak akan tersedia dan juga memori akan dirilis yang dialokasikan untuk str
variabel di bawah kode.
while(condition){
String str = calculateStr();
.....
}
Jika kita mengikuti yang kedua tentunya ini akan mengurangi memori sistem kita dan meningkatkan kinerja.
Sungguh, pertanyaan yang disebutkan di atas adalah masalah pemrograman. Bagaimana Anda ingin memprogram kode Anda? Di mana Anda membutuhkan 'STR' untuk diakses? Tidak ada gunanya mendeklarasikan variabel yang digunakan secara lokal sebagai variabel global. Dasar-dasar pemrograman saya percaya.
Peringatan untuk hampir semua orang dalam pertanyaan ini: Berikut adalah contoh kode di mana di dalam loop itu dapat dengan mudah 200 kali lebih lambat di komputer saya dengan Java 7 (dan konsumsi memori juga sedikit berbeda). Tapi ini tentang alokasi dan bukan hanya ruang lingkup.
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);
}
}
Kesimpulan: Bergantung pada ukuran variabel lokal, perbedaannya bisa sangat besar, bahkan dengan variabel yang tidak terlalu besar.
Hanya mengatakan bahwa kadang-kadang, di luar atau di dalam loop itu penting.
bigStuff[(int) (value % STUFF_SIZE)] = value;
(Coba nilai 2147483649L)
Saya pikir ukuran objek juga penting. Dalam salah satu proyek saya, kami telah mendeklarasikan dan menginisialisasi array dua dimensi besar yang membuat aplikasi membuang pengecualian di luar memori. Kami memindahkan deklarasi keluar dari loop sebagai gantinya dan menghapus array pada awal setiap iterasi.
Anda berisiko NullPointerException
jika calculateStr()
metode Anda mengembalikan nol dan kemudian Anda mencoba memanggil metode di str.
Secara umum, hindari memiliki variabel dengan nilai nol . Ngomong-ngomong, atribut kelas lebih kuat.
NullPointerException.
Jika kode ini dicoba return str;
akan mengalami kesalahan kompilasi.