1. Lấy dấu vết ngăn xếp

Nhận dấu vết ngăn xếp

Ngôn ngữ lập trình Java cung cấp nhiều cách để lập trình viên lấy thông tin về những gì đang xảy ra trong một chương trình. Và không chỉ lời nói.

Ví dụ, sau khi các chương trình C++ được biên dịch, chúng trở thành một tệp lớn chứa đầy mã máy và tất cả những gì có sẵn cho một lập trình viên khi chạy là địa chỉ của khối bộ nhớ chứa mã máy hiện đang được thực thi. Không nhiều, hãy nói.

Nhưng đối với Java, ngay cả sau khi chương trình được biên dịch, các lớp vẫn là các lớp, các phương thức và biến không biến mất và lập trình viên có nhiều cách để lấy thông tin về những gì đang diễn ra trong chương trình.

dấu vết ngăn xếp

Ví dụ, tại thời điểm thực hiện chương trình, bạn có thể tìm ra lớp và tên của phương thức hiện đang được thực thi. Và không chỉ một phương thức — bạn có thể lấy thông tin về toàn bộ chuỗi lệnh gọi phương thức từ phương thức hiện tại trở lại phương main()thức đó.

Một danh sách bao gồm phương thức hiện tại, phương thức đã gọi nó và phương thức đã gọi phương thức đó, v.v. được gọi là dấu vết ngăn xếp . Bạn có thể lấy nó với tuyên bố này:

StackTraceElement[] methods = Thread.currentThread().getStackTrace();

Bạn cũng có thể viết nó thành hai dòng:

Thread current = Thread.currentThread();
StackTraceElement[] methods = current.getStackTrace();

Phương thức tĩnh currentThread()của Threadlớp trả về một tham chiếu đến một Threadđối tượng, chứa thông tin về luồng hiện tại, tức là luồng thực thi hiện tại. Bạn sẽ tìm hiểu thêm về các luồng trong Cấp độ 17 và 18 của nhiệm vụ Java Core .

Đối tượng này Threadcó một getStackTrace()phương thức, trả về một mảng StackTraceElementcác đối tượng, mỗi đối tượng chứa thông tin về một phương thức. Khi được kết hợp với nhau, tất cả các phần tử này tạo thành một dấu vết ngăn xếp .

Ví dụ:

Mã số
public class Main
{
   public static void main(String[] args)
   {
      test();
   }

   public static void test()
   {
      Thread current = Thread.currentThread();
      StackTraceElement[] methods = current.getStackTrace();

      for(var info: methods)
         System.out.println(info);
   }
}
Đầu ra bảng điều khiển
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)

Như chúng ta có thể thấy trong đầu ra giao diện điều khiển của ví dụ, getStackTrace()phương thức trả về một mảng gồm ba phần tử:

  • getStackTrace()phương pháp của Threadlớp
  • test()phương pháp của Mainlớp
  • main()phương pháp của Mainlớp

Từ dấu vết ngăn xếp này, chúng ta có thể kết luận rằng:

  • Phương Thread.getStackTrace()thức được gọi bởi Main.test()phương thức trên dòng 11 của tệp Main.java
  • Phương Main.test()thức được gọi bởi Main.main()phương thức trên dòng 5 của tệp Main.java
  • Không ai gọi Main.main()phương thức này — đây là phương thức đầu tiên trong chuỗi các cuộc gọi.

Nhân tiện, chỉ một số thông tin có sẵn được hiển thị trên màn hình. Mọi thứ khác có thể được lấy trực tiếp từ StackTraceElementđối tượng



2.StackTraceElement

Như tên gọi của nó, StackTraceElementlớp này được tạo ra để lưu trữ thông tin về một phần tử theo dõi ngăn xếp , tức là một phương thức trong stack trace.

Lớp này có các phương thức thể hiện sau:

Phương pháp Sự miêu tả
String getClassName()
Trả về tên của lớp
String getMethodName()
Trả về tên của phương thức
String getFileName()
Trả về tên của tệp (một tệp có thể chứa nhiều lớp)
int getLineNumber()
Trả về số dòng trong tệp mà phương thức được gọi
String getModuleName()
Trả về tên của mô-đun (có thể là null)
String getModuleVersion()
Trả về phiên bản của mô-đun (có thể là null)

Họ có thể giúp bạn có được thông tin đầy đủ hơn về ngăn xếp cuộc gọi hiện tại:

Mã số Đầu ra bảng điều khiển Ghi chú
public class Main
{
   public static void main(String[] args)
   {
      test();
   }

   public static void test()
   {
      Thread current = Thread.currentThread();
      StackTraceElement[] methods = current.getStackTrace();

      for(StackTraceElement info: methods)
      {
         System.out.println(info.getClassName());
         System.out.println(info.getMethodName());

         System.out.println(info.getFileName());
         System.out.println(info.getLineNumber());

         System.out.println(info.getModuleName());
         System.out.println(info.getModuleVersion());
         System.out.println();
      }
   }
}
java.lang.Thread
getStackTrace
Thread.java
1606
java.base
11.0.2

Main
test
Main.java
11
null
null

Main
main
Main.java
5
null
null
tên lớp
phương pháp tên
tệp tên
dòng số

-đun

tên mô -
đun phiên bản tên lớp tên phương thức
tên tệp tên
dòng số mô -đun tên mô-đun phiên bản tên lớp phương pháp tên tệp tên dòng số mô-đun tên mô- đun phiên bản










3. Ngăn xếp

Bạn đã biết dấu vết ngăn xếp là gì, nhưng ngăn xếp (lớp Stack) là gì?

Ngăn xếp là một cấu trúc dữ liệu mà bạn có thể thêm các phần tử vào và từ đó bạn có thể truy xuất các phần tử. Khi làm như vậy, bạn chỉ có thể lấy các phần tử từ cuối: trước tiên bạn lấy phần tử được thêm vào cuối cùng, sau đó là phần tử thứ hai đến phần tử cuối cùng được thêm vào, v.v.

Bản thân ngăn tên gợi ý hành vi này, giống như cách bạn tương tác với một chồng giấy. Nếu bạn đặt các trang 1, 2 và 3 trong một ngăn xếp, bạn phải lấy chúng theo thứ tự ngược lại: đầu tiên là trang thứ ba, sau đó là trang thứ hai và chỉ sau đó là trang đầu tiên.

Java thậm chí còn có một lớp tập hợp Stack đặc biệt có cùng tên và hành vi. Lớp này chia sẻ rất nhiều hành vi với ArrayListLinkedList. Nhưng nó cũng có các phương thức thực hiện hành vi ngăn xếp:

phương pháp Sự miêu tả
T push(T obj)
Thêm objphần tử vào đầu ngăn xếp
T pop()
Lấy phần tử từ đỉnh ngăn xếp (độ sâu ngăn xếp giảm)
T peek()
Trả về mục ở đầu ngăn xếp (ngăn xếp không thay đổi)
boolean empty()
Kiểm tra xem bộ sưu tập có trống không
int search(Object obj)
Tìm kiếm một đối tượng trong bộ sưu tập và trả về đối tượng đóindex

Ví dụ:

Mã số Nội dung ngăn xếp (đỉnh của ngăn xếp ở bên phải)
Stack<Integer> stack = new Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
int x = stack.pop();
stack.push(4);
int y = stack.peek();
stack.pop();
stack.pop();

[1]
[1, 2]
[1, 2, 3]
[1, 2]
[1, 2, 4]
[1, 2, 4]
[1, 2]
[1]

Ngăn xếp được sử dụng khá thường xuyên trong lập trình. Vì vậy, đây là một bộ sưu tập hữu ích.



4. Hiển thị dấu vết ngăn xếp trong quá trình xử lý ngoại lệ

Tại sao danh sách các lệnh gọi phương thức được gọi là theo dõi ngăn xếp ? Bởi vì nếu bạn coi danh sách các phương thức là một tập giấy có tên phương thức, thì khi bạn gọi phương thức tiếp theo, bạn sẽ thêm một trang tính có tên phương thức đó vào tập giấy. Và tờ giấy tiếp theo nằm trên tờ giấy đó, v.v.

Khi một phương thức kết thúc, trang tính ở trên cùng của ngăn xếp sẽ bị xóa. Bạn không thể xóa một trang tính khỏi giữa ngăn xếp mà không xóa tất cả các trang tính phía trên nó. Tương tự như vậy, bạn không thể kết thúc một phương thức ở giữa chuỗi lệnh gọi mà không kết thúc tất cả các phương thức mà nó đã gọi.

ngoại lệ

Một cách sử dụng thú vị khác cho ngăn xếp là trong quá trình xử lý ngoại lệ.

Khi xảy ra lỗi trong một chương trình và một ngoại lệ được ném ra , ngoại lệ đó chứa dấu vết ngăn xếp hiện tại — một mảng bao gồm danh sách các phương thức bắt đầu, từ phương thức chính và kết thúc bằng phương thức xảy ra lỗi. Thậm chí còn có dòng ném ngoại lệ!

Dấu vết ngăn xếp này được lưu trữ bên trong ngoại lệ và có thể dễ dàng truy xuất từ ​​ngoại lệ đó bằng phương pháp sau:StackTraceElement[] getStackTrace()

Ví dụ:

Mã số Ghi chú
try
{
   // An exception may occur here
}
catch(Exception e)
{
   StackTraceElement[] methods = e.getStackTrace()
}




Bắt ngoại lệ

Nhận dấu vết ngăn xếp đã tồn tại khi xảy ra lỗi.

Đây là một phương thức của Throwablelớp, vì vậy tất cả các hậu duệ của nó (tức là tất cả các ngoại lệ) đều có getStackTrace()phương thức đó. Siêu tiện lợi nhỉ?

Hiển thị dấu vết ngăn xếp của ngoại lệ

Nhân tiện, Throwablelớp này có một phương thức khác để làm việc với dấu vết ngăn xếp, một phương thức hiển thị tất cả thông tin theo dõi ngăn xếp được lưu trữ bên trong ngoại lệ. Nó được gọi là printStackTrace().

Khá thuận tiện, bạn có thể gọi nó trên bất kỳ ngoại lệ nào.

Ví dụ:

Mã số
try
{
   // An exception may occur here
}
catch(Exception e)
{
   e.printStackTrace();
}
Đầu ra bảng điều khiển
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)