ในบทความนี้ คุณจะได้เรียนรู้และทำความเข้าใจว่าปรากฏการณ์ Java StackTrace หรือที่เรียกว่า Call Stack Tracing ทำงานอย่างไร ข้อมูลนี้จัดทำขึ้นสำหรับผู้เริ่มต้นที่พบแนวคิดนี้เมื่อเริ่มต้น Java Syntax ระดับ 9 ฉันคิดว่าพวกคุณทุกคนอย่างน้อยก็ครั้งหนึ่งเคยพบข้อผิดพลาดที่คล้ายกันเมื่อทำงานใน IDE ของคุณ ไม่ว่าจะเป็นIdea , Eclipseหรืออย่างอื่นก็ตาม
Exception in thread "main" java.lang.ArithmeticException
at com.example.task01.Test.division(Test.java:10)
at com.example.task01.Test.main(Test.java:6)
อย่างที่คุณอาจเดาได้นี่คือการติดตามของเรา แต่อย่ารีบตื่นตระหนก ตอนนี้เราจะแจกแจงตัวอย่างนี้ให้คุณ ก่อนอื่นคุณต้องเข้าใจความจริงที่ว่ามันStackTrace
ใช้งานได้ตามСтэк
ชื่อของมัน ณ จุดนี้เราจะกล่าวถึงรายละเอียดเพิ่มเติมอีกเล็กน้อย ในระดับที่แปด คุณคุ้นเคยกับคอลเลกชันแล้วและรู้ว่าคอลเลกชันเหล่านี้แบ่งออกเป็นสามกลุ่มSet
- ชุดList
- รายการMap
- พจนานุกรม (หรือแผนที่) ตาม JavaRush (c) ของเรา เป็นStack
ส่วนหนึ่งของกลุ่ม List
หลักการทำงานของมันสามารถอธิบายได้ว่าLIFOซึ่งย่อมาจากLast In First Out กล่าวคือ นี่คือรายการที่คล้ายกับกองหนังสือ เพื่อที่จะนำองค์ประกอบที่เราใส่ไว้Stack
ก่อน เราต้องแยกองค์ประกอบทั้งหมดที่เราเพิ่มเข้าไปในรายการของเราหลังจากนั้นก่อน ตามที่ระบุไว้ในภาพด้านบน ซึ่งแตกต่างจากรายการปกติArrayList
ที่เราสามารถรับองค์ประกอบใดๆ จากรายการตามดัชนี อีกครั้งสำหรับการเสริมกำลัง การได้รับองค์ประกอบนั้นСтэка
เป็นไปได้ตั้งแต่ตอนท้ายเท่านั้น! ในขณะที่องค์ประกอบแรกที่เพิ่มเข้าไปนั้นอยู่ที่จุดเริ่มต้น (หรือที่ด้านล่างตามที่สะดวกกว่า) นี่คือวิธีที่Stack
Object ของเรามี push()
- เพิ่มองค์ประกอบที่ด้านบนของสแต็ก วัตถุpop()
- ส่งคืนองค์ประกอบที่ด้านบนของสแต็ก โดยลบออกในกระบวนการ วัตถุpeek()
- ส่งคืนองค์ประกอบที่ด้านบนของสแต็ก แต่ไม่ได้ลบออก int search()
- ค้นหาองค์ประกอบบนสแต็ก หากพบ ระบบจะส่งคืนออฟเซ็ตจากด้านบนของสแต็ก มิฉะนั้น -1 จะถูกส่งกลับ boolean empty()
- ตรวจสอบว่าสแต็กว่างเปล่าหรือไม่ คืนค่าเป็นจริงหากสแต็กว่างเปล่า คืนค่าเท็จหากสแต็กมีองค์ประกอบ แล้วเหตุใดคุณจึงJava
ต้องStackTrace
สร้างมันขึ้นมาตามหลักการทำงานStack
? ลองดูตัวอย่างข้อผิดพลาดด้านล่างที่เกิดขึ้นระหว่างการทำงานของโปรแกรมง่ายๆ ดังกล่าว
public class Test {
public static void main(String[] args) {
System.out.println(convertStringToInt(null));
}
public static int convertStringToInt(String s) {
int x = Integer.parseInt(s);
return x;
}
}
เรามีคลาสTest
ที่มีสองวิธี ทุกคนคุ้นเคยmain
และconvertStringToInt
ตรรกะคือการแปลงและส่งกลับสตริงที่ได้รับจากภายนอก (กล่าวคือจาก method main
) เป็นจำนวนเต็มint
ประเภท อย่างที่คุณเห็น เราตั้งใจส่งพารามิเตอร์แทนสตริงที่มีตัวเลขจำนวนnull
หนึ่ง NumberFormatException
วิธีการของเราไม่สามารถประมวล ผลพารามิเตอร์นี้ได้อย่างถูกต้องและทำให้เกิดข้อผิดพลาด ดังที่คุณทราบโปรแกรมเริ่มทำงานจากวิธีการmain
และในขณะนี้จะสร้างโปรแกรมใหม่Стэк
โดยมีชื่อStackTrace
โดยให้ค่าปัจจุบันของงานอยู่ภายใต้หมายเลข1จากนั้นเราไปที่วิธีการconvertStringToInt
และโปรแกรมอีกครั้ง ป้อนพารามิเตอร์ของตำแหน่งของเราลงในอันที่สร้างไว้ก่อนหน้านี้StackTrace
ภายใต้หมายเลข2จากนั้นเรียกว่าวิธีที่มองไม่เห็นด้วยตาของเราparseInt
ซึ่งอยู่ในคลาสInteger
และนี่จะเป็นองค์ประกอบหมายเลข3ของเราStackTrace
แล้ว ในวิธีนี้จะมีการเพิ่มการโทรภายในอีกครั้ง ไปที่StackTrace
หมายเลข4เพื่อตรวจสอบองค์ประกอบเพื่อหาค่าว่างซึ่งจะทำให้เกิดข้อผิดพลาด โปรแกรมจำเป็นต้องแสดงข้อผิดพลาดของเราโดยระบุห่วงโซ่การเปลี่ยนแปลงทั้งหมดของเราจนกระทั่งเกิดข้อผิดพลาด นี่คือจุดที่ข้อมูลการเปลี่ยนแปลงของเราสร้างขึ้นก่อนหน้านี้มาเพื่อช่วยเหลือStackTrace
เธอ
Exception in thread "main" java.lang.NumberFormatException: null
at java.base/java.lang.Integer.parseInt(Integer.java:614)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at com.example.task01.Test.convertStringToInt(Solution.java:10)
at com.example.task01.Test.main(Solution.java:6)
ก่อนที่ข้อผิดพลาดจะเกิดขึ้น โปรแกรมจะเจาะลึกวิธีการต่าง ๆ แต่ทันทีที่เกิดข้อผิดพลาด ทุกอย่างจะเริ่มเกิดขึ้นในลำดับย้อนกลับ บรรทัดที่อธิบายปัญหาจะถูกพิมพ์ (หมายเลข 1 ในตัวอย่าง) จากนั้นนำค่าสุดท้าย (และที่ด้านบน) ที่เพิ่มให้กับเราСтэк
มันคือหมายเลขสี่และพิมพ์ไปที่คอนโซล (หมายเลข 2 ในตัวอย่าง) และ เราพบว่าปัญหาเกิดขึ้นในคลาสInteger
ที่โค้ดบรรทัด 614 และเรียกบรรทัดนี้ว่าบรรทัด 770 ของเมธอดparseInt
คลาสเดียวกัน (หมายเลข 3 ในตัวอย่าง) ซึ่งเมื่อบวกเข้าไปแล้วСтэк
คือหมายเลข 3 และเมธอดคลาสนี้Integer
ยังไม่ปรากฏให้เราเห็นถูกเรียกแล้วโดยวิธีการของเราconvertStringToInt
ซึ่งอยู่ที่บรรทัด 10 ของโปรแกรมของเรา (หมายเลข 4 ในตัวอย่างและเมื่อเพิ่มเป็นครั้งที่สอง) และในทางกลับกันก็ถูกเรียกmain
ในบรรทัด 6 (หมายเลข 5 ในตัวอย่างและเมื่อเพิ่มตามลำดับแรก) ดังนั้น ด้วยการจัดเก็บСтек
วิธีการที่เราเรียกไว้ทีละขั้นตอน เราจึงสามารถกลับไปใช้main
การพิมพ์ข้อมูลแบบขนานซึ่งนำเราไปสู่ข้อผิดพลาดอย่างแน่นอน แต่StackTrace
นี่ไม่เพียงแต่การทำงานกับข้อผิดพลาดเท่านั้น แต่ยังช่วยให้เราได้รับข้อมูลที่น่าสนใจมากมายเกี่ยวกับกระบวนการสมัครของเรา ลองดูตัวอย่างยอดนิยมอีกตัวอย่างหนึ่งในความคิดเห็นในการบรรยายหลักของระดับ 9 เรามีโค้ดแล้ว และฉันจะแนบรูปภาพที่แสดงภาพกระบวนการของโปรแกรมลงไปทันที:
public class Test {
public static void main(String[] args) {
method1();
method2();
}
public static void method1() {
//не вызывает ничего
}
public static void method2() {
method3();
method4();
}
public static void method3() {
//не вызывает ничего
}
public static void method4() {
method5();
}
public static void method5() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement element:stackTraceElements) {
System.out.println(element.getMethodName());
}
}
}
ที่นี่โปรแกรมของเราทำงานได้อย่างไร้ที่ติและสิ้นสุด นี่คือสิ่งที่เราจะเห็นในเอาต์พุตคอนโซล:
getStackTrace
method5
method4
method2
main
Process finished with exit code 0
เราได้ข้อสรุปนี้มาได้อย่างไร และเกิดอะไรขึ้นในวิธีที่ 5 เริ่มจากบรรทัดที่ 20? ฉันเกรงว่าสิ่งที่ดีที่สุดที่ฉันสามารถทำได้คือเพิ่มคำอธิบายยอดนิยม (ตัวย่อ) โดยผู้ใช้Kirillจากความคิดเห็นไปยังการบรรยาย มาดูบรรทัดการสร้างStackTrace
แล้ววิเคราะห์ทีละองค์ประกอบ:
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement[]
- การระบุประเภทของอาร์เรย์ (ในระดับแรกๆ คุณได้เรียนรู้เกี่ยวกับอาร์เรย์เช่น int[], String[] แล้ว นี่ก็เหมือนกัน) stackTraceElements
- ชื่อของอาร์เรย์สามารถเป็นอะไรก็ได้โดยคำนึงถึงกฎการตั้งชื่อทั่วไปซึ่งจะไม่ส่งผลกระทบต่องาน Thread.currentThread()
- รับลิงก์ไปยังเธรดปัจจุบันซึ่งมีการดำเนินการวิธีการที่เราต้องการติดตาม (สำหรับตอนนี้สิ่งนี้ไม่สำคัญ คุณจะวิเคราะห์เธรดโดยละเอียดเพิ่มเติมที่ระดับ 16 ในภารกิจ Java Core) getStackTrace()
- เราได้รับСтэк
วิธีการที่เรียกว่า ทั้งหมด (นี่คือ getter ปกติสำหรับStackTrace
) ทีนี้มาดูกันว่าอาร์เรย์ที่สร้างขึ้นอาจมีประโยชน์กับเราอย่างไร เราเข้าใจว่าอาเรย์เก็บข้อมูลเกี่ยวกับวิธีการดำเนินการ (c) และสำหรับสิ่งนี้ในบรรทัดที่ 21 เราเปิดตัววงจรที่แก้ไขfor
ที่เรียกว่าforEach
(โดยวิธีการสำหรับผู้ที่ยังไม่ได้ศึกษาวงจรนี้ฉันแนะนำให้คุณอ่านเกี่ยวกับมัน) และส่งออกข้อมูลจากอาเรย์ไปยังคอนโซล คือข้อมูลเกี่ยวกับวิธีการดำเนินการระหว่างการทำงานโดยใช้การelement.getMethodName()
ก่อสร้าง ตามที่เราเห็น องค์ประกอบศูนย์ของอาเรย์กลายเป็นตัวมันเองgetStackTrace()
ตามลำดับ เนื่องจากในขณะที่รับอาเรย์ข้อมูล มันเป็นวิธีสุดท้ายที่ถูกดำเนินการและด้วยเหตุนี้จึงจบลงที่ด้านบนСтэка
และจดจำการก่อสร้างของเรา” Last in, first out ” คือค่าแรกที่จะถูกเพิ่มในอาร์เรย์ภายใต้องค์ประกอบศูนย์ทันที นี่คือสิ่งอื่นที่เราได้จากStackTraceElement
: String getClassName()
- ส่งกลับชื่อของคลาส สตริงgetMethodName()
- ส่งกลับชื่อของวิธีการ สตริงgetFileName()
- ส่งกลับชื่อไฟล์ (สามารถมีได้หลายคลาสในไฟล์เดียว) สตริงgetModuleName()
- ส่งกลับชื่อโมดูล (อาจเป็นค่าว่างได้) String getModuleVersion()
- ส่งกลับเวอร์ชันของโมดูล (อาจเป็นค่าว่างได้) int getLineNumber()
- ส่งกลับหมายเลขบรรทัดในไฟล์ที่มีการเรียกใช้เมธอด เมื่อคุณเข้าใจหลักการทำงานทั่วไปแล้ว ฉันขอแนะนำให้คุณลองวิธีการต่างๆ ด้วยตนเองStackTrace
ในIdeของ คุณ แม้ว่าคุณจะยังไม่เชี่ยวชาญทุกอย่างอย่างสมบูรณ์ แต่ให้เรียนรู้ต่อไปแล้วปริศนาจะกลายเป็นแบบเดียวกับที่ฉันพบในเรื่องนี้ ฉันขอให้คุณประสบความสำเร็จ! Ps หากคุณชอบเนื้อหานี้โปรดสนับสนุนด้วยการกดไลค์ ไม่ใช่เรื่องยากสำหรับคุณ ฉันยินดี ขอบคุณ แล้วพบกันใหม่ตอนเลเวล 41 ;)
GO TO FULL VERSION