1. ทรัพยากรภายนอก

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

โดยทั่วไปแล้ว การโต้ตอบจะเป็นไปตามโครงร่างนี้:

งบทดลองกับทรัพยากร

การติดตามทรัพยากร

ระบบปฏิบัติการติดตามทรัพยากรที่มีอยู่อย่างเข้มงวด และยังควบคุมการเข้าถึงร่วมกันจากโปรแกรมต่างๆ ตัวอย่างเช่น หากโปรแกรมหนึ่งเปลี่ยนไฟล์ อีกโปรแกรมหนึ่งจะไม่สามารถเปลี่ยนแปลง (หรือลบ) ไฟล์นั้นได้ หลักการนี้ไม่จำกัดเฉพาะไฟล์ แต่ให้ตัวอย่างที่เข้าใจได้ง่ายที่สุด

ระบบปฏิบัติการมีฟังก์ชัน (API) ที่อนุญาตให้โปรแกรมรับและ/หรือปล่อยทรัพยากร หากทรัพยากรไม่ว่าง เฉพาะโปรแกรมที่ได้รับมาเท่านั้นที่สามารถทำงานกับทรัพยากรนั้นได้ หากทรัพยากรนั้นฟรี โปรแกรมใดๆ ก็สามารถรับได้

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

การได้มาซึ่ง ทรัพยากรภายนอก

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

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

ระบบปฏิบัติการจะรักษารายการของทรัพยากรที่ถูกครอบครองโดยแต่ละโปรแกรมที่รันอยู่ หากโปรแกรมของคุณมีทรัพยากรเกินขีดจำกัดที่กำหนด ระบบปฏิบัติการจะไม่ให้ทรัพยากรใหม่แก่คุณอีกต่อไป

ข่าวดีก็คือหากโปรแกรมของคุณยุติลง ทรัพยากรทั้งหมดจะถูกปล่อยโดยอัตโนมัติ (ระบบปฏิบัติการเองก็ทำเช่นนี้)

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


2. close()วิธีการ

คลาสที่ใช้รีซอร์สภายนอกมีวิธีการพิเศษในการรีลีสclose():

ด้านล่างเรามีตัวอย่างโปรแกรมที่เขียนบางอย่างลงในไฟล์ แล้วปิดไฟล์เมื่อทำงานเสร็จ นั่นคือทำให้ทรัพยากรของระบบปฏิบัติการว่างลง ดูเหมือนว่า:

รหัส บันทึก
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
เส้นทางไปยังไฟล์.
รับวัตถุไฟล์: รับทรัพยากร
เขียนลงไฟล์
ปิดไฟล์ - ปล่อยทรัพยากร

หลังจากทำงานกับไฟล์ (หรือทรัพยากรภายนอกอื่นๆ) คุณต้องเรียกใช้เมธอดclose()บนวัตถุที่เชื่อมโยงกับทรัพยากรภายนอก

ข้อยกเว้น

ทุกอย่างดูเหมือนง่าย แต่ข้อยกเว้นสามารถเกิดขึ้นได้เมื่อโปรแกรมทำงาน และทรัพยากรภายนอกจะไม่ถูกเผยแพร่ และนั่นก็แย่มาก

เพื่อให้แน่ใจว่าclose()มีการเรียกใช้เมธอดเสมอ เราจำเป็นต้องรวมโค้ดของเราใน บล็อก try- catch- finallyและเพิ่มclose()เมธอดในfinallyบล็อก มันจะมีลักษณะดังนี้:

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

รหัสนี้จะไม่คอมไพล์ เนื่องจากoutputตัวแปรถูกประกาศภายในtry {}บล็อก ดังนั้นจึงมองไม่เห็นในfinallyบล็อก

มาแก้ไขกันเถอะ:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

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

มาแก้ไขกันเถอะ:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

ยังมีเสียงวิพากษ์วิจารณ์เล็กน้อย ประการแรก หากเกิดข้อผิดพลาดขณะสร้างFileOutputStreamวัตถุ ตัวแปรนั้นoutputจะเป็นโมฆะ ความเป็นไปได้นี้จะต้องนำมาพิจารณาในfinallyบล็อก

ประการ ที่สอง เมธอดclose()ถูกเรียกใช้ในfinallyบล็อกเสมอ ซึ่งหมายความว่าไม่จำเป็นในtryบล็อก รหัสสุดท้ายจะมีลักษณะดังนี้:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

แม้ว่าเราจะไม่พิจารณาcatchบล็อกซึ่งสามารถละเว้นได้ โค้ด 3 บรรทัดของเราจะกลายเป็น 10 แต่โดยพื้นฐานแล้วเราเพิ่งเปิดไฟล์และเขียน 1 ยุ่งยากนิดหน่อย คุณว่าไหม


3. try-พร้อมทรัพยากร

และที่นี่ผู้สร้างของ Java ตัดสินใจที่จะโรยน้ำตาลวากยสัมพันธ์ให้กับเรา เริ่มต้นด้วยเวอร์ชันที่ 7 Java มีtryคำสั่ง -with-resources ใหม่

มันถูกสร้างขึ้นอย่างแม่นยำเพื่อแก้ปัญหาด้วยการเรียกเมธอดที่close()จำเป็น กรณีทั่วไปดูค่อนข้างง่าย:

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

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

ด้านล่างนี้เป็นตัวอย่างที่เทียบเท่ากันสองตัวอย่าง:

รหัสยาว รหัสที่มีการลองด้วยทรัพยากร
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

โค้ดที่ใช้try-with-resources นั้นสั้นกว่าและอ่านง่ายกว่ามาก และยิ่งเรามีโค้ดน้อย โอกาสพิมพ์ผิดหรือข้อผิดพลาดอื่นๆ ก็จะยิ่งน้อยลง

อย่างไรก็ตาม เราสามารถเพิ่มcatchและfinallyบล็อกtryคำสั่ง -with-resources ได้ หรือคุณไม่สามารถเพิ่มได้หากไม่จำเป็น



4. ตัวแปรหลายตัวพร้อมกัน

อย่างไรก็ตาม คุณมักจะพบกับสถานการณ์ที่ต้องเปิดไฟล์หลาย ๆ ไฟล์พร้อมกัน สมมติว่าคุณกำลังคัดลอกไฟล์ ดังนั้นคุณต้องมีวัตถุสองชิ้น: ไฟล์ที่คุณกำลังคัดลอกข้อมูลและไฟล์ที่คุณกำลังคัดลอกข้อมูล

ในกรณีนี้tryคำสั่ง -with-resources ให้คุณสร้างหนึ่งแต่หลายวัตถุในนั้น รหัสที่สร้างวัตถุต้องคั่นด้วยเครื่องหมายอัฒภาค นี่คือลักษณะทั่วไปของข้อความนี้:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

ตัวอย่างการคัดลอกไฟล์:

รหัสยาว รหัสสั้น
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

เราจะพูดอะไรที่นี่ try-ด้วยทรัพยากรเป็นสิ่งที่ยอดเยี่ยม!


5. AutoCloseableอินเทอร์เฟซ

แต่นั่นไม่ใช่ทั้งหมด ผู้อ่านที่เอาใจใส่จะเริ่มมองหาข้อผิดพลาดที่จำกัดวิธีการใช้ข้อความนี้ทันที

แต่คำสั่ง -with-resources ทำงานอย่างไรtryหากคลาสไม่มีclose()เมธอด สมมติว่าไม่มีอะไรจะเรียก ไม่มีวิธีการ ไม่มีปัญหา

แต่tryคำสั่ง -with-resources ทำงานอย่างไรหากคลาสมีหลายclose()วิธี และพวกเขาต้องการข้อโต้แย้งที่จะส่งต่อไปยังพวกเขา? และคลาสไม่มีclose()เมธอดที่ไม่มีพารามิเตอร์?

ฉันหวังว่าคุณจะถามตัวเองด้วยคำถามเหล่านี้จริงๆ และบางทีอาจจะเป็นคำถามอื่นๆ

เพื่อหลีกเลี่ยงปัญหาดังกล่าว ผู้สร้าง Java จึงสร้างอินเทอร์เฟซพิเศษที่เรียกว่าAutoCloseableซึ่งมีเมธอดเดียวเท่านั้น — close()ซึ่งไม่มีพารามิเตอร์

พวกเขายังเพิ่มข้อจำกัดที่เฉพาะอ็อบเจกต์ของคลาสที่ใช้เท่านั้นAutoCloseableที่สามารถประกาศเป็นรีซอร์สในtryคำสั่ง -with-resources ด้วยเหตุนี้ อ็อบเจกต์ดังกล่าวจะมีclose()เมธอดที่ไม่มีพารามิเตอร์ เสมอ

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

ข่าวร้าย:คำตอบที่ถูกต้องคือไม่ — ชั้นเรียนต้องใช้AutoCloseableอินเทอร์เฟซ

ข่าวดี: Java มีคลาสจำนวนมากที่ใช้อินเทอร์เฟซนี้ ดังนั้นเป็นไปได้มากที่ทุกอย่างจะทำงานได้ตามปกติ