วัตถุที่ไม่เปลี่ยนรูปคืออะไร?
วัตถุ String นั้นไม่เปลี่ยนรูป แต่นั่นก็หมายความว่าคุณไม่สามารถเปลี่ยนค่าของมันได้โดยการเรียกเมธอดสาธารณะ
อย่างที่เราทราบกันดีว่าใน Java คลาส String นั้นไม่เปลี่ยนรูป แล้ววัตถุที่ไม่เปลี่ยนรูปคืออะไรกันแน่? คุณสามารถคิดได้ดังนี้: หากวัตถุไม่สามารถเปลี่ยนสถานะได้หลังจากที่ถูกสร้างขึ้น วัตถุนั้นจะไม่เปลี่ยนรูป สถานะไม่สามารถเปลี่ยนแปลงได้ ซึ่งหมายความว่าตัวแปรสมาชิกภายในออบเจ็กต์ไม่สามารถเปลี่ยนแปลงได้ รวมถึงค่าของประเภทข้อมูลพื้นฐานด้วย ตัวแปรประเภทอ้างอิงไม่สามารถชี้ไปยังออบเจ็กต์อื่นได้ และสถานะของออบเจ็กต์ที่ชี้ตามประเภทการอ้างอิงไม่สามารถทำได้ มีการเปลี่ยนแปลง
แยกแยะระหว่างวัตถุและการอ้างอิงวัตถุ
สำหรับผู้เริ่มต้นใช้งาน Java มักมีข้อสงสัยอยู่เสมอว่า String เป็นวัตถุที่ไม่เปลี่ยนรูป ดูรหัสด้านล่าง:
สตริง s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);ผลลัพธ์ที่พิมพ์ออกมาคือ:
s = ABCabc s = 123456
ขั้นแรกให้สร้างวัตถุ String s จากนั้นให้ค่าของ s เป็น "ABCabc" จากนั้นให้ค่าของ s เป็น "123456" ดังที่เห็นได้จากผลการพิมพ์ ค่าของ s มีการเปลี่ยนแปลงอย่างแน่นอน เหตุใดคุณถึงยังบอกว่าวัตถุ String นั้นไม่เปลี่ยนรูป? ในความเป็นจริง มีความเข้าใจผิดที่นี่: s เป็นเพียงการอ้างอิงถึงวัตถุ String ไม่ใช่ตัววัตถุเอง วัตถุคือพื้นที่หน่วยความจำในหน่วยความจำ ยิ่งมีตัวแปรสมาชิกมากเท่าใด พื้นที่หน่วยความจำนี้ก็จะมีขนาดใหญ่ขึ้นเท่านั้น การอ้างอิงเป็นเพียงข้อมูลขนาด 4 ไบต์ที่เก็บที่อยู่ของวัตถุที่อ้างอิงถึง วัตถุนี้สามารถเข้าถึงได้ผ่านที่อยู่นี้
กล่าวอีกนัยหนึ่ง s เป็นเพียงการอ้างอิงซึ่งชี้ไปที่วัตถุเฉพาะ เมื่อ s="123456"; หลังจากเรียกใช้โค้ดนี้แล้ว วัตถุใหม่ "123456" จะถูกสร้างขึ้น และวัตถุอ้างอิงจะชี้ไปที่หัวใจนี้อีกครั้ง วัตถุต้นฉบับ "ABCabc" ยังคงอยู่ในหน่วยความจำและไม่มีการเปลี่ยนแปลง โครงสร้างหน่วยความจำแสดงในรูปด้านล่าง:
ความแตกต่างประการหนึ่งระหว่าง Java และ C++ คือใน Java นั้นเป็นไปไม่ได้ที่จะใช้งานอ็อบเจ็กต์โดยตรง อ็อบเจ็กต์ทั้งหมดจะถูกชี้ไปโดยการอ้างอิง คุณต้องใช้การอ้างอิงนี้เพื่อเข้าถึงอ็อบเจ็กต์นั้นเอง รวมถึงการรับค่าของตัวแปรสมาชิกและการเปลี่ยนแปลง ตัวแปรสมาชิกของวัตถุ วิธีการเรียกวัตถุ ฯลฯ ในภาษา C++ มีสามสิ่ง: การอ้างอิง วัตถุ และตัวชี้ ซึ่งทั้งหมดนี้สามารถเข้าถึงวัตถุได้ ในความเป็นจริงการอ้างอิงใน Java และพอยน์เตอร์ใน C ++ เป็นค่าที่อยู่ของวัตถุที่เก็บไว้ในหน่วยความจำอย่างไรก็ตามใน Java การอ้างอิงจะสูญเสียความยืดหยุ่นบางอย่าง ตัวอย่างเช่น การอ้างอิงใน Java ไม่สามารถนำมาใช้เช่นการบวกและการลบ ดำเนินการเหมือนพอยน์เตอร์ใน C ++
เหตุใดวัตถุ String จึงไม่เปลี่ยนรูป?
เพื่อให้เข้าใจถึงความไม่เปลี่ยนรูปของ String ขั้นแรกให้ดูที่ตัวแปรสมาชิกในคลาส String ใน JDK1.6 ตัวแปรสมาชิกของ String มีดังต่อไปนี้:
สตริงคลาสสุดท้ายสาธารณะใช้ java.io.Serializable, Comparable<String>, CharSequence{ /** ค่าที่ใช้สำหรับการจัดเก็บอักขระ */ ค่าถ่านสุดท้ายส่วนตัว[]; /** ออฟเซ็ตเป็นดัชนีแรกของหน่วยเก็บข้อมูล ที่ใช้ */ ออฟเซ็ต int สุดท้ายส่วนตัว; /** การนับคือจำนวนอักขระในสตริง */ การนับ int สุดท้ายส่วนตัว; ค่าเริ่มต้นเป็น 0ใน JDK1.7 คลาส String ได้ทำการเปลี่ยนแปลงบางอย่าง โดยส่วนใหญ่จะเปลี่ยนลักษณะการทำงานเมื่อมีการดำเนินการเมธอดสตริงย่อย ซึ่งไม่เกี่ยวข้องกับหัวข้อของบทความนี้ มีเพียงสองตัวแปรสมาชิกหลักของคลาส String ใน JDK1.7:
คลาสสุดท้ายสาธารณะใช้ java.io.Serializable, Comparable<String>, CharSequence { /** ค่าที่ใช้สำหรับการจัดเก็บอักขระ */ ค่าถ่านสุดท้ายส่วนตัว[]; /** แคชรหัสแฮชสำหรับสตริง */ แฮช int ส่วนตัว // ค่าเริ่มต้นเป็น 0ดังที่เห็นได้จากโค้ดข้างต้น จริงๆ แล้วคลาส String ใน Java เป็นการห่อหุ้มอาร์เรย์อักขระ ใน JDK6 ค่าคืออาร์เรย์ที่ห่อหุ้มด้วย String ส่วน offset คือตำแหน่งเริ่มต้นของ String ในอาร์เรย์ค่า และ count คือจำนวนอักขระที่ String ครอบครอง ใน JDK7 มีตัวแปรค่าเพียงตัวเดียว นั่นคือ อักขระทั้งหมดในค่าเป็นของอ็อบเจ็กต์ String การเปลี่ยนแปลงนี้ไม่ส่งผลต่อการอภิปรายของบทความนี้ นอกจากนี้ยังมีตัวแปรสมาชิกแฮช ซึ่งเป็นแคชของค่าแฮชของออบเจ็กต์ String ตัวแปรนี้ก็ไม่เกี่ยวข้องกับการอภิปรายในบทความนี้ด้วย ใน Java อาร์เรย์ก็เป็นวัตถุเช่นกัน (โปรดอ้างอิงบทความก่อนหน้าของฉันลักษณะของอาร์เรย์ใน Java) ดังนั้นค่าจึงเป็นเพียงการอ้างอิง ซึ่งชี้ไปยังอ็อบเจ็กต์อาร์เรย์จริง ที่จริงแล้ว หลังจากรันโค้ด String s = “ABCabc”; แล้ว เลย์เอาต์หน่วยความจำจริงควรเป็นดังนี้:
ค่าตัวแปรทั้งสาม ค่าออฟเซ็ต และจำนวนล้วนเป็นค่าส่วนตัว และไม่มีวิธีการสาธารณะ เช่น setValue, setOffset และ setCount ไว้เพื่อแก้ไขค่าเหล่านี้ ดังนั้นจึงไม่สามารถแก้ไข String ภายนอกคลาส String ได้ กล่าวคือ เมื่อเตรียมใช้งานแล้ว จะไม่สามารถแก้ไขได้ และสมาชิกทั้งสามนี้ไม่สามารถเข้าถึงได้ภายนอกคลาส String นอกจากนี้ ค่าตัวแปรทั้งสาม ค่าออฟเซ็ต และการนับถือเป็นค่าสุดท้ายทั้งหมด ซึ่งหมายความว่าภายในคลาส String เมื่อกำหนดค่าเริ่มต้นทั้งสามค่านี้แล้ว จะไม่สามารถเปลี่ยนแปลงได้ ดังนั้นวัตถุ String จึงถือว่าไม่เปลี่ยนรูปได้
ดังนั้นใน String จึงมีวิธีการบางอย่างที่ชัดเจน และการเรียกมันอาจทำให้ได้รับค่าที่เปลี่ยนแปลงไป วิธีการเหล่านี้รวมถึงสตริงย่อย, แทนที่, แทนที่ทั้งหมด, toLowerCase ฯลฯ ตัวอย่างเช่น รหัสต่อไปนี้:
สตริง a = "ABCabc"; System.out.println("a = " + a); a = a.replace('A', 'a'); System.out.println("a = " + a);ผลลัพธ์ที่พิมพ์ออกมาคือ:
ก = ABCabca = ABCabc
ดูเหมือนว่ามูลค่าของ a จะเปลี่ยนไป แต่จริงๆ แล้วมันเป็นความเข้าใจผิดแบบเดียวกัน อีกครั้งที่ a เป็นเพียงการอ้างอิง ไม่ใช่วัตถุสตริงจริง เมื่อเรียก a.replace('A', 'a') วิธีการจะสร้างวัตถุ String ใหม่และกำหนดวัตถุใหม่ให้กับ Cited a ซอร์สโค้ดของวิธีการแทนที่ใน String สามารถแสดงให้เห็นถึงปัญหา:
ผู้อ่านสามารถตรวจสอบวิธีการอื่นได้ด้วยตนเอง พวกเขาทั้งหมดสร้างวัตถุ String ใหม่ภายในวิธีการและส่งคืนวัตถุใหม่นี้จะไม่มีการเปลี่ยนแปลง นี่คือเหตุผลว่าทำไมเมธอดอย่างแทนที่, สตริงย่อย, toLowerCase ฯลฯ ล้วนมีค่าส่งคืน นี่คือสาเหตุที่การโทรดังต่อไปนี้ไม่เปลี่ยนค่าของวัตถุ:
สตริง ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);พิมพ์ผลลัพธ์:
เอสเอส = 123456 เอสเอส = 123456
วัตถุ String ไม่เปลี่ยนรูปจริงหรือ?
ดังที่เห็นได้จากด้านบน ตัวแปรสมาชิกของ String ถือเป็นตัวแปรส่วนตัวขั้นสุดท้าย กล่าวคือ ไม่สามารถเปลี่ยนแปลงได้หลังจากการกำหนดค่าเริ่มต้น ในบรรดาสมาชิกเหล่านี้ ค่าจะพิเศษเนื่องจากเป็นตัวแปรอ้างอิง ไม่ใช่วัตถุจริง ค่าสุดท้ายถูกแก้ไขโดยค่าสุดท้าย ซึ่งหมายความว่าค่าสุดท้ายไม่สามารถชี้ไปยังออบเจ็กต์อาร์เรย์อื่นได้อีกต่อไป ฉันจึงเปลี่ยนอาร์เรย์ที่ค่าชี้ไปได้หรือไม่ ตัวอย่างเช่น เปลี่ยนอักขระที่ตำแหน่งใดตำแหน่งหนึ่งในอาร์เรย์เป็นขีดล่าง "_" อย่างน้อยเราก็ไม่สามารถทำได้ในโค้ดธรรมดาที่เราเขียนเอง เนื่องจากเราไม่สามารถเข้าถึงการอ้างอิงค่านี้ได้เลย ไม่ต้องพูดถึงการปรับเปลี่ยนอาร์เรย์ผ่านการอ้างอิงนี้
แล้วเราจะเข้าถึงสมาชิกส่วนตัวได้อย่างไร? ถูกต้อง โดยใช้การสะท้อน คุณสามารถสะท้อนแอตทริบิวต์ค่าในออบเจ็กต์ String จากนั้นเปลี่ยนโครงสร้างของอาร์เรย์ผ่านการอ้างอิงค่าที่ได้รับ นี่คือโค้ดตัวอย่าง:
public static void testReflection() ส่งข้อยกเว้น { //สร้างสตริง "Hello World" และกำหนดให้กับข้อมูลอ้างอิง s String s = "Hello World"; System.out.println("s = " + s); //Hello โลก // รับฟิลด์ค่าในคลาส String Field valueFieldOfString = String.class.getDeclaredField ("value"); // เปลี่ยนสิทธิ์การเข้าถึงของแอตทริบิวต์ค่า valueFieldOfString.setAccessible (true); // รับค่าของแอตทริบิวต์ค่าบนวัตถุ s char [] value = (char []) valueFieldOfString.get (s); // เปลี่ยนอักขระตัวที่ห้าในอาร์เรย์ที่อ้างอิงโดยค่า value [5] = '_' ; System.out.println("s = " + s); //Hello_World }ผลการพิมพ์คือ:
s = สวัสดีชาวโลก s = Hello_World
ในกระบวนการนี้ s อ้างถึงอ็อบเจ็กต์ String เดียวกันเสมอ แต่ก่อนและหลังการสะท้อน อ็อบเจ็กต์ String จะเปลี่ยนแปลง กล่าวอีกนัยหนึ่ง ที่เรียกว่าอ็อบเจ็กต์ "ไม่เปลี่ยนรูป" สามารถแก้ไขได้ผ่านการสะท้อน แต่โดยทั่วไปแล้วเราไม่ทำเช่นนี้ ตัวอย่างการสะท้อนนี้สามารถแสดงให้เห็นปัญหาได้ หากสถานะของวัตถุและวัตถุอื่นๆ ที่วัตถุนั้นสามารถเปลี่ยนแปลงได้ วัตถุนี้ก็อาจจะไม่ใช่วัตถุที่ไม่เปลี่ยนรูป ตัวอย่างเช่น วัตถุ Car ถูกรวมเข้ากับวัตถุ Wheel แม้ว่าวัตถุ Wheel จะถูกประกาศให้เป็นส่วนตัวขั้นสุดท้าย แต่สถานะภายในของวัตถุ Wheel สามารถเปลี่ยนแปลงได้ ดังนั้นวัตถุ Car จึงไม่สามารถรับประกันได้ว่าจะไม่เปลี่ยนรูป