อะไรคือผลกำไรจากการประกาศวิธีการเป็นแบบคงที่


95

ฉันเพิ่งดูคำเตือนของฉันใน Eclipse และเจอคำเตือนนี้:

คำเตือนแบบคงที่

มันจะเตือนคอมไพเลอร์หากวิธีนี้สามารถประกาศเป็นแบบคงที่ได้

[แก้ไข]ใบเสนอราคาที่แน่นอนภายในความช่วยเหลือ Eclipse โดยเน้นเรื่องส่วนตัวและขั้นสุดท้าย:

เมื่อเปิดใช้งานคอมไพลเลอร์จะแสดงข้อผิดพลาดหรือคำเตือนสำหรับวิธีการที่เป็นส่วนตัวหรือขั้นสุดท้ายและอ้างถึงเฉพาะสมาชิกแบบคงที่

ใช่ฉันรู้ว่าฉันสามารถปิดได้ แต่ฉันต้องการทราบเหตุผลในการเปิดเครื่อง?

เหตุใดจึงเป็นเรื่องดีที่จะประกาศทุกวิธีที่เป็นไปได้ว่าเป็นแบบคงที่?

สิ่งนี้จะให้ประโยชน์ด้านประสิทธิภาพหรือไม่? (ในโดเมนมือถือ)

ชี้ให้เห็นว่าวิธีการเป็นแบบคงที่ฉันคิดว่ากำลังแสดงว่าคุณไม่ได้ใช้ตัวแปรอินสแตนซ์ใด ๆ ดังนั้นจึงสามารถย้ายไปที่คลาสสไตล์ยูทิลิตี้ได้หรือไม่

ในตอนท้ายของวันฉันควรจะปิด 'เพิกเฉย' หรือฉันควรแก้ไขคำเตือนมากกว่า 100 รายการที่ให้ไว้?

คุณคิดว่านี่เป็นเพียงคำหลักพิเศษที่ทำให้โค้ดสกปรกเนื่องจากคอมไพเลอร์จะสอดแทรกวิธีการเหล่านี้ต่อไปหรือไม่? (เหมือนกับว่าคุณไม่ได้ประกาศตัวแปรทุกตัวที่ทำได้สุดท้ายแต่คุณทำได้ )


ไม่แน่ใจ แต่สิ่งนี้อาจถูกมองว่าเป็นเครื่องมือช่วยในการเขียนโปรแกรม คำเตือนเป็นเพียงสิ่งบ่งชี้ถึงสิ่งที่ควรพิจารณา
James P.

1
ฉันสงสัยเกี่ยวกับประเภทของฟังก์ชันการทำงานที่วิธีการเหล่านี้ดำเนินการ อาจจะมีบางอย่างไม่ถูกต้องหากมีสิ่งเหล่านี้จำนวนมาก
ArjunShankar

ที่เกี่ยวข้อง: stackoverflow.com/q/790281
Brian Rasmussen

คำตอบ:


135

เมื่อใดก็ตามที่คุณเขียนวิธีการคุณปฏิบัติตามสัญญาในขอบเขตที่กำหนด ยิ่งขอบเขตแคบลงโอกาสที่คุณจะเขียนข้อบกพร่องก็จะยิ่งน้อยลง

เมื่อวิธีการเป็นแบบคงที่คุณจะไม่สามารถเข้าถึงสมาชิกที่ไม่คงที่ได้ ดังนั้นขอบเขตของคุณจึงแคบลง ดังนั้นหากคุณไม่ต้องการและไม่จำเป็นต้อง (แม้ในคลาสย่อย)สมาชิกที่ไม่คงที่เพื่อทำตามสัญญาของคุณทำไมต้องให้วิธีการของคุณเข้าถึงฟิลด์เหล่านี้ การประกาศเมธอดstaticในกรณีนี้จะทำให้คอมไพลเลอร์ตรวจสอบว่าคุณไม่ได้ใช้สมาชิกที่คุณไม่ต้องการใช้

และยิ่งไปกว่านั้นมันจะช่วยให้ผู้ที่อ่านโค้ดของคุณเข้าใจลักษณะของสัญญา

นั่นเป็นเหตุผลว่าทำไมจึงควรประกาศวิธีการstaticเมื่อมีการใช้สัญญาคงที่จริง

ในบางกรณีเมธอดของคุณหมายถึงบางอย่างที่สัมพันธ์กับอินสแตนซ์ของคลาสของคุณเท่านั้นและเกิดขึ้นว่าการนำไปใช้จริงไม่ได้ใช้ฟิลด์หรืออินสแตนซ์ที่ไม่คงที่ staticในกรณีเช่นนี้คุณจะไม่ทำเครื่องหมายวิธีการ

ตัวอย่างตำแหน่งที่คุณจะไม่ใช้staticคำหลัก:

  • hook ส่วนขยายที่ไม่ทำอะไรเลย (แต่สามารถทำบางอย่างกับข้อมูลอินสแตนซ์ในคลาสย่อยได้)
  • พฤติกรรมเริ่มต้นที่เรียบง่ายมากหมายถึงสามารถปรับแต่งได้ในคลาสย่อย
  • การใช้งานตัวจัดการเหตุการณ์: การนำไปใช้งานจะแตกต่างกันไปตามคลาสของตัวจัดการเหตุการณ์ แต่จะไม่ใช้คุณสมบัติใด ๆ ของอินสแตนซ์ตัวจัดการเหตุการณ์

1
+1 มันเกี่ยวกับการลดโอกาสในการยิงด้วยเท้าตัวเองและลดจำนวนเงินที่คุณต้องรู้เพื่อทำความเข้าใจวิธีการ
Peter Lawrey

7
นอกจากนี้คุณไม่จำเป็นต้องยุ่งกับการหาอินสแตนซ์เพียงเพื่อเรียกใช้ฟังก์ชันอิสระที่มีอยู่ในตัว
Marko Topolnik

1
ดังนั้นในโลกอื่นวิธีใดที่ไม่ใช้ตัวแปรอินสแตนซ์ควรถูกประกาศว่าคงที่ฉันคิดเสมอว่าวิธีการนั้นควรเป็นแบบคงที่ก็ต่อเมื่อเป็นที่ต้องการเท่านั้น (เช่นวิธีการใช้ประโยชน์)
Petr Mensik

18
@PetrMensik ถ้าprivateเมธอดสามารถประกาศแบบคงที่ได้ก็ควรจะเป็นแบบคงที่ สำหรับระดับการเข้าถึงอื่น ๆ มีปัจจัยอื่น ๆ ที่ต้องพิจารณาเช่นการจัดส่งแบบไดนามิก
Marko Topolnik

1
@JamesPoulson ฉันหมายถึงการจัดส่งวิธีการแบบไดนามิกสิ่งที่เกิดขึ้นในขณะที่ดำเนินการobject.method()เพื่อเลือกวิธีการโทร
Marko Topolnik

17

ไม่มีแนวคิดเกี่ยวกับการเพิ่มประสิทธิภาพที่นี่

staticวิธีเป็นstaticเพราะคุณประกาศอย่างชัดแจ้งวิธีการที่ไม่พึ่งพาอินสแตนซ์ชั้นล้อมรอบเพียงเพราะมันไม่จำเป็นต้อง ดังนั้นคำเตือน Eclipse ดังที่ระบุไว้ในเอกสารประกอบ:

เมื่อเปิดใช้งานคอมไพลเลอร์จะแสดงข้อผิดพลาดหรือคำเตือนสำหรับวิธีการที่เป็นส่วนตัวหรือขั้นสุดท้ายและอ้างถึงเฉพาะสมาชิกแบบคงที่

หากคุณไม่ต้องการตัวแปรอินสแตนซ์ใด ๆ และเมธอดของคุณเป็นแบบส่วนตัว (ไม่สามารถเรียกจากภายนอกได้) หรือขั้นสุดท้าย (ไม่สามารถลบล้างได้) ก็ไม่มีเหตุผลใดที่จะปล่อยให้เป็นวิธีปกติแทนที่จะเป็นแบบคงที่ วิธีการคงที่ปลอดภัยกว่าโดยเนื้อแท้แล้วเพียงเพราะคุณได้รับอนุญาตให้ทำสิ่งต่างๆน้อยลง (ไม่จำเป็นต้องมีอินสแตนซ์ใด ๆ คุณไม่มีthisวัตถุโดยนัย)


7

ฉันไม่มีข้อมูลเกี่ยวกับประสิทธิภาพฉันคิดว่ามันดีกว่าเล็กน้อยที่สุดเนื่องจากโค้ดไม่จำเป็นต้องทำการจัดส่งแบบไดนามิกตามประเภท

อย่างไรก็ตามข้อโต้แย้งที่แข็งแกร่งกว่ามากในการปรับโครงสร้างใหม่เป็นวิธีการคงที่คือการใช้แบบคงที่ในปัจจุบันถือเป็นการปฏิบัติที่ไม่ดี วิธีการ / ตัวแปรคงที่ไม่สามารถรวมเข้ากับภาษาเชิงวัตถุได้ดีและยากที่จะทดสอบอย่างถูกต้อง นี่คือเหตุผลว่าทำไมภาษาใหม่ ๆ บางภาษาจึงละทิ้งแนวคิดของวิธีการ / ตัวแปรแบบคงที่ทั้งหมดหรือพยายามทำให้เป็นภาษาในลักษณะที่เล่นกับ OO ได้ดีขึ้น (เช่น Objects ใน Scala)

โดยส่วนใหญ่คุณต้องใช้วิธีการแบบคงที่เพื่อใช้ฟังก์ชันที่ใช้เฉพาะพารามิเตอร์เป็นอินพุตและสร้างเอาต์พุตโดยใช้สิ่งนั้น (เช่นฟังก์ชันยูทิลิตี้ / ตัวช่วย) ในภาษาสมัยใหม่มีแนวคิดฟังก์ชันชั้นหนึ่งที่อนุญาตให้เป็นแบบคงที่ ไม่จำเป็น Java 8 จะมีนิพจน์แลมบ์ดารวมอยู่ด้วยดังนั้นเราจึงก้าวไปสู่ทิศทางนี้แล้ว


7
วิธีการคงที่เป็นเรื่องเล็กน้อยในการทดสอบ (ตราบเท่าที่มีอยู่ในตัว - โดยเฉพาะฟังก์ชันบริสุทธิ์ซึ่งเป็นเป้าหมายหลักสำหรับวิธีการคงที่) แนวคิด OO ไม่มีอะไรให้ในกรณีนั้น นอกจากนี้แนวคิดของฟังก์ชั่นชั้นหนึ่งยังมีส่วนเกี่ยวข้องกับแนวคิดของวิธีการคงที่น้อยมาก หากคุณต้องการฟังก์ชันระดับเฟิร์สคลาสที่ทำงานของวิธีการคงที่ที่มีอยู่คุณต้องใช้อักขระเพียงไม่กี่ตัวที่จะนำไปใช้
Marko Topolnik

5
ข้อสังเกตในการทดสอบอาจไม่ได้มุ่งไปที่วิธีการคงที่โดยตรง แต่วิธีการแบบคงที่นั้นยากกว่ามากในการจำลองดังนั้นจึงทำให้การทดสอบวิธีการที่เรียกว่าวิธีคงที่ซับซ้อนขึ้น
Buhb

2
วิธีการคงเป็นเรื่องง่ายที่จะเยาะเย้ยถ้าคุณใช้กรอบเยาะเย้ยที่มีประสิทธิภาพเช่นJMockit , PowerMockหรือGroovy
Jeff Olson

นี่เป็นprivate staticวิธีการที่คุณไม่จำเป็นต้องล้อเลียนพวกเขา
Blundell

3

1.วิธีการประกาศstaticให้ประโยชน์ด้านประสิทธิภาพเล็กน้อย แต่สิ่งที่มีประโยชน์กว่านั้นช่วยให้สามารถใช้งานได้โดยไม่ต้องมีอินสแตนซ์วัตถุอยู่ในมือ (ลองนึกถึงวิธีการของโรงงานหรือการรับซิงเกิลตัน) นอกจากนี้ยังทำหน้าที่เป็นเอกสารในการบอกลักษณะของวิธีการ ไม่ควรละเลยวัตถุประสงค์ในการจัดทำเอกสารนี้เนื่องจากเป็นการให้คำแนะนำในทันทีเกี่ยวกับลักษณะของวิธีการแก่ผู้อ่านโค้ดและผู้ใช้ API และยังทำหน้าที่เป็นเครื่องมือในการคิดสำหรับโปรแกรมเมอร์ดั้งเดิม - การมีความชัดเจนเกี่ยวกับความหมายที่ตั้งใจไว้จะช่วยได้ คุณคิดตรงและสร้างโค้ดที่มีคุณภาพดีขึ้นด้วย (ฉันคิดจากประสบการณ์ส่วนตัวของฉัน แต่คนต่างกัน) ตัวอย่างเช่นมันเป็นตรรกะและด้วยเหตุนี้จึงเป็นที่พึงปรารถนาที่จะแยกแยะระหว่างวิธีการที่ใช้งานกับประเภทและวิธีการที่กระทำกับอินสแตนซ์ของประเภท (ตามที่ระบุโดยJon Skeet ในความคิดเห็นของเขาต่อคำถาม C # )

อีกกรณีหนึ่งสำหรับstaticวิธีการใช้คือการเลียนแบบอินเทอร์เฟซการเขียนโปรแกรมขั้นตอน นึกถึงjava.lang.System.println()คลาสและวิธีการและคุณลักษณะในนั้น คลาสjava.lang.Systemนี้ใช้เหมือนพื้นที่ชื่อการจัดกลุ่มแทนที่จะเป็นอ็อบเจ็กต์ที่สามารถสร้างได้ทันที

2. Eclipse (หรือโปรแกรมอื่น ๆ หรือเอนทิตี - เอนทิตีทางชีวภาพหรือไม่สามารถย่อยสลายทางชีวภาพได้) จะรู้ได้อย่างไรว่าวิธีใดที่สามารถประกาศว่าเป็นสถิตได้? แม้ว่าคลาสพื้นฐานจะไม่ได้เข้าถึงตัวแปรอินสแตนซ์หรือเรียกใช้เมธอดแบบไม่คงที่ แต่กลไกของการสืบทอดสิ่งต่าง ๆ สามารถเปลี่ยนแปลงได้ เฉพาะในกรณีที่วิธีการที่ไม่สามารถแทนที่โดยสืบทอด subclass เราสามารถเรียกร้องที่มีความมั่นใจ 100% staticว่าวิธีการที่มันสามารถประกาศ การลบล้างวิธีการเป็นไปไม่ได้อย่างแน่นอนในสองกรณีนี้

  1. private (ไม่มีคลาสย่อยที่สามารถใช้งานได้โดยตรงและโดยหลักการแล้วไม่ทราบ) หรือ
  2. final (แม้ว่าคลาสย่อยจะเข้าถึงได้ แต่ก็ไม่มีวิธีใดที่จะเปลี่ยนวิธีการอ้างถึงข้อมูลอินสแตนซ์หรือฟังก์ชันได้)

ดังนั้นตรรกะของตัวเลือก Eclipse

3.ผู้โพสต์ต้นฉบับยังถามว่า: "การชี้ให้เห็นวิธีการเป็นแบบคงที่ฉันคิดว่ากำลังแสดงว่าคุณไม่ได้ใช้ตัวแปรอินสแตนซ์ใด ๆ ดังนั้นจึงสามารถย้ายไปที่คลาสสไตล์ยูทิลิตี้ได้หรือไม่ " นี่เป็นจุดที่ดีมาก บางครั้งการเปลี่ยนแปลงการออกแบบประเภทนี้จะระบุโดยคำเตือน

เป็นตัวเลือกที่มีประโยชน์มากซึ่งโดยส่วนตัวแล้วฉันจะเปิดใช้งานให้แน่ใจว่าฉันใช้ Eclipse และฉันจะเขียนโปรแกรมใน Java หรือไม่


1

ดูคำตอบของซามูเอลเกี่ยวกับการเปลี่ยนแปลงขอบเขตของวิธีการ ฉันเดาว่านี่เป็นส่วนหลักของการสร้างวิธีการคงที่

คุณยังถามเกี่ยวกับประสิทธิภาพ:

อาจมีประสิทธิภาพที่เพิ่มขึ้นเล็กน้อยเนื่องจากการเรียกใช้วิธีการแบบคงที่ไม่จำเป็นต้องมีการอ้างอิง "this" โดยปริยายเป็นพารามิเตอร์

อย่างไรก็ตามผลกระทบด้านประสิทธิภาพนี้มีน้อยมาก ดังนั้นทุกอย่างเกี่ยวกับขอบเขต


1

จากหลักเกณฑ์ด้านประสิทธิภาพของ Android:

ต้องการ Static Over Virtual หากคุณไม่ต้องการเข้าถึงฟิลด์ของวัตถุให้กำหนดวิธีการของคุณให้คงที่ การเรียกใช้จะเร็วขึ้นประมาณ 15% -20% นอกจากนี้ยังเป็นแนวทางปฏิบัติที่ดีเพราะคุณสามารถบอกได้จากลายเซ็นของเมธอดว่าการเรียกใช้เมธอดนั้นไม่สามารถเปลี่ยนสถานะของวัตถุได้

http://developer.android.com/training/articles/perf-tips.html#PreferStatic


"การเรียกใช้เมธอดนั้นไม่สามารถเปลี่ยนสถานะของวัตถุได้" - ทำให้เข้าใจผิดอย่างไม่น่าเชื่อ ฉันไม่รู้เกี่ยวกับคุณ แต่ฉันคิดว่าคุณสมบัติคงที่ของคลาสเป็นส่วนหนึ่งของสถานะของวัตถุ
Adam Parkin

0

เอกสาร Eclipse กล่าวเกี่ยวกับคำเตือนที่เป็นปัญหา:

วิธีการสามารถคงที่

เมื่อเปิดใช้งานคอมไพลเลอร์จะแสดงข้อผิดพลาดหรือคำเตือนสำหรับวิธีการที่เป็นส่วนตัวหรือขั้นสุดท้ายและอ้างถึงเฉพาะสมาชิกแบบคงที่

ฉันคิดว่ามันพูดได้ทั้งหมด หากเมธอดนั้นเป็นแบบส่วนตัวและเป็นขั้นสุดท้ายและอ้างถึงสมาชิกแบบคงที่เท่านั้นเมธอดที่เป็นปัญหาอาจถูกประกาศแบบคงที่ด้วยเช่นกันและด้วยเหตุนี้ทำให้เห็นได้ชัดว่าเราตั้งใจจะเข้าถึงเนื้อหาคงที่จากมันเท่านั้น

ฉันไม่คิดว่าจะมีเหตุผลลึกลับอื่นใดอยู่เบื้องหลัง


0

ฉันไม่มีตัวเลขบางตัวเนื่องจากความแตกต่างของความเร็ว ดังนั้นฉันจึงพยายามเปรียบเทียบพวกเขาซึ่งกลายเป็นว่าไม่ใช่เรื่องง่าย: Java loop ทำงานช้าลงหลังจากการรัน / ความผิดพลาดของ JIT?

ในที่สุดฉันก็ใช้ Caliper และผลลัพธ์ก็เหมือนกับการทดสอบด้วยมือของฉัน:

ไม่มีความแตกต่างที่วัดได้สำหรับการโทรแบบคงที่ / ไดนามิก อย่างน้อยก็ไม่ใช่สำหรับ Linux / AMD64 / Java7

ผล Caliper อยู่ที่นี่: https://microbenchmarks.appspot.com/runs/1426eac9-36ca-48f0-980f-0106af064e8f#r:scenario.benchmarkSpec.methodName,scenario.vmSpec.options.CMSLargeCoalSurplusPercent,scenario.vmSpions CMSLargeSplitSurplusPercent, scenario.vmSpec.options.CMSSmallCoalSurplusPercent, Scenario.vmSpec.options.CMSSmallSplitSurplusPercent, scenario.vmSpec.options.FLSLargestBlockCoalesceProximity, scenario.vmSpec.options

และผลลัพธ์ของฉันเองคือ:

Static: 352 ms
Dynamic: 353 ms
Static: 348 ms
Dynamic: 349 ms
Static: 349 ms
Dynamic: 348 ms
Static: 349 ms
Dynamic: 344 ms

คลาสทดสอบคาลิปเปอร์คือ:

public class TestPerfomanceOfStaticMethodsCaliper extends Benchmark {

    public static void main( String [] args ){

        CaliperMain.main( TestPerfomanceOfStaticMethodsCaliper.class, args );
    }

    public int timeAddDynamic( long reps ){
        int r=0;
        for( int i = 0; i < reps; i++ ) {
            r |= addDynamic( 1, i );
        }
        return r;
    }

    public int timeAddStatic( long reps ){
        int r=0;
        for( int i = 0; i < reps; i++ ) {
            r |= addStatic( 1, i );
        }
        return r;
    }

    public int addDynamic( int a, int b ){

        return a+b;
    }

    private static int addStatic( int a, int b ){

        return a+b;
    }

}

และชั้นเรียนทดสอบของฉันเองคือ:

public class TestPerformanceOfStaticVsDynamicCalls {

    private static final int RUNS = 1_000_000_000;

    public static void main( String [] args ) throws Exception{

        new TestPerformanceOfStaticVsDynamicCalls().run();
    }

    private void run(){

        int r=0;
        long start, end;

        for( int loop = 0; loop<10; loop++ ){

            // Benchmark

            start = System.currentTimeMillis();
            for( int i = 0; i < RUNS; i++ ) {
                r += addStatic( 1, i );
            }
            end = System.currentTimeMillis();
            System.out.println( "Static: " + ( end - start ) + " ms" );

            start = System.currentTimeMillis();
            for( int i = 0; i < RUNS; i++ ) {
                r += addDynamic( 1, i );
            }
            end = System.currentTimeMillis();
            System.out.println( "Dynamic: " + ( end - start ) + " ms" );

            // Do something with r to keep compiler happy
            System.out.println( r );

        }

    }

    private int addDynamic( int a, int b ){

        return a+b;
    }

    private static int addStatic( int a, int b ){

        return a+b;
    }

}

น่าสนใจที่จะเห็นผลลัพธ์บนอุปกรณ์ Android และเวอร์ชันต่างๆ
Blundell

ใช่. ฉันคิดว่าคุณจะสนใจ :) แต่เนื่องจากคุณกำลังสร้างซอฟต์แวร์ Android และอาจมีอุปกรณ์ Android ที่เชื่อมต่อกับสถานีพัฒนาของคุณฉันขอแนะนำให้คุณเลือกรหัสเรียกใช้และแบ่งปันผลลัพธ์?
Scheintod

-2

วิธีที่คุณสามารถประกาศว่าเป็นแบบคงที่คือวิธีที่ไม่จำเป็นต้องมีการสร้างอินสแตนซ์เช่น

public class MyClass
{
    public static string InvertText(string text)
    {
        return text.Invert();
    }
}

ซึ่งคุณสามารถเรียกกลับในคลาสอื่น ๆ ได้โดยไม่ต้องอินสแตนซ์คลาสนั้น

public class MyClassTwo
{
    public void DoSomething()
    {
        var text = "hello world";
        Console.Write(MyClass.InvertText(text));
    }
}

... แต่นั่นเป็นสิ่งที่คุณอาจรู้อยู่แล้ว มันไม่ได้ให้ประโยชน์ที่แท้จริงต่อคุณเลยนอกจากทำให้ชัดเจนมากขึ้นว่าวิธีนี้ไม่ได้ใช้ตัวแปรอินสแตนซ์ใด ๆ

กล่าวอีกนัยหนึ่งคุณสามารถปิดได้อย่างปลอดภัยที่สุด หากคุณรู้ว่าคุณจะไม่ใช้วิธีการในคลาสอื่น ๆ (ซึ่งในกรณีนี้ควรเป็นแบบส่วนตัว) คุณไม่จำเป็นต้องเป็นแบบคงที่เลย


1
ภาษานั้นคืออะไร? ตัวอย่างนั้นรวบรวมหรือไม่? ไม่มีอะไรคงที่ที่นี่
Piotr Perak

อันที่จริงดูเหมือนว่าฉันลืม 'คงที่' จากเมธอด InvertText และเป็นตัวอย่างจาก c #
NeroS
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.