serialVersionUID คืออะไรและทำไมฉันจึงควรใช้


2999

Eclipse ออกคำเตือนเมื่อserialVersionUIDไม่มีข้อมูล

คลาส Foo ที่ทำให้เป็นอนุกรมไม่ได้ประกาศฟิลด์ serialVersionUID สุดท้ายที่เป็นสแตติกชนิดยาว

อะไรserialVersionUIDและทำไมจึงสำคัญ โปรดแสดงตัวอย่างที่หายไปserialVersionUIDจะทำให้เกิดปัญหา


1
ค้นหาแนวปฏิบัติที่ดีเกี่ยวกับ serialversionUID; dzone.com/articles/what-is-serialversionuid
ceyun

คำตอบ:


2290

เอกสารสำหรับjava.io.Serializableอาจเป็นคำอธิบายที่ดีเกี่ยวกับ:

ความสัมพันธ์รันไทม์ของการทำให้เป็นอนุกรมเชื่อมโยงกับหมายเลขเวอร์ชันของคลาสที่เรียกว่า a serialVersionUIDซึ่งถูกใช้ในระหว่างการดีซีเรียลไลเซชันเพื่อตรวจสอบว่าผู้ส่งและผู้รับของวัตถุที่เป็นอนุกรมนั้นได้โหลดคลาสสำหรับวัตถุนั้น หากผู้รับโหลดคลาสสำหรับวัตถุที่มีความแตกต่างserialVersionUIDจากคลาสของผู้ส่งที่สอดคล้องกันการดีซีเรียลInvalidClassExceptionไลซ์เซชั่นจะส่งผลให้ คลาสที่สามารถทำให้เป็นอนุกรมสามารถประกาศตัวของมันเองserialVersionUIDอย่างชัดเจนโดยการประกาศชื่อเขตข้อมูลserialVersionUIDที่จะต้องคงที่สุดท้ายและประเภทlong:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

หากคลาสที่ทำให้เป็นอนุกรมไม่ได้ประกาศอย่างชัดเจนserialVersionUIDดังนั้นการรันไทม์ของการทำให้เป็นอนุกรมจะคำนวณค่าเริ่มต้นserialVersionUIDสำหรับคลาสนั้นตามแง่มุมต่าง ๆ ของคลาสดังอธิบายใน Java (TM) Object Serialization Specification แต่ก็เป็นที่ขอแนะนำว่าการเรียน serializable ทุกอย่างชัดเจนประกาศserialVersionUIDค่าตั้งแต่เริ่มต้นserialVersionUIDการคำนวณเป็นอย่างสูงที่มีความสำคัญกับรายละเอียดระดับที่อาจแตกต่างกันไปขึ้นอยู่กับการใช้งานคอมไพเลอร์และทำให้ได้ผลในการที่ไม่คาดคิดInvalidClassExceptionsในช่วง deserialization ดังนั้นเพื่อรับประกันserialVersionUIDค่าที่สอดคล้องกันระหว่างการประยุกต์ใช้คอมไพเลอร์ Java ที่แตกต่างกันคลาสที่สามารถต่ออนุกรมserialVersionUIDได้ ขอแนะนำอย่างชัดเจนด้วยว่าserialVersionUIDการประกาศใช้โมดิฟายเออร์ส่วนตัวถ้าเป็นไปได้เนื่องจากการประกาศเช่นนั้นจะใช้กับserialVersionUIDฟิลด์คลาสที่ประกาศทันทีเท่านั้นจึงไม่มีประโยชน์ในฐานะสมาชิกที่สืบทอด


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

366
สาเหตุที่อยู่ในย่อหน้าที่สอง: หากคุณไม่ได้ระบุ serialVersionUID อย่างชัดเจนค่าจะถูกสร้างขึ้นโดยอัตโนมัติ - แต่จะเปราะเนื่องจากการใช้งานคอมไพเลอร์ขึ้นอยู่กับ
Jon Skeet

14
และทำไม Eclipse บอกว่าฉันต้องการ "private static final long serialVersionUID = 1L แบบยาวสุดท้าย" เมื่อฉันขยายชั้นข้อยกเว้น?
JohnMerlino

21
@ JohnMerlino: ดีฉันไม่คาดหวังว่ามันจะบอกว่าคุณต้องการ - แต่มันอาจจะแนะนำหนึ่งเพื่อช่วยให้คุณเป็นอันดับข้อยกเว้นอย่างถูกต้อง หากคุณไม่ต้องการทำให้เป็นอนุกรมคุณไม่จำเป็นต้องมีค่าคงที่
Jon Skeet

16
@JohnMerlino เพื่อตอบคำถามที่ว่าทำไมส่วนของคุณ: Exception ใช้Serializableและ eclipse เตือนว่าคุณไม่ได้ตั้ง serialVersionUID ซึ่งจะเป็นความคิดที่ดี (ถ้าคุณไม่ทำให้อันดับของ JonSkeet เป็นปัญหา) โครงร่าง
zpon

475

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

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

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


78
ฉันจะบอกว่าถ้าคุณไม่ได้ใช้ซีเรียลไลซ์เซชั่นสำหรับการจัดเก็บข้อมูลถาวรคุณควรใช้ @SuppressWarnings แทนที่จะเพิ่มมูลค่า มันตัดชั้นน้อยลงและรักษาความสามารถของกลไก serialVersionUID เพื่อปกป้องคุณจากการเปลี่ยนแปลงที่เข้ากันไม่ได้
Tom Anderson

25
ฉันไม่เห็นว่าการเพิ่มหนึ่งบรรทัด (คำอธิบายประกอบ @SuppressWarnings) ตรงข้ามกับอีกบรรทัดหนึ่ง (id ที่สามารถทำให้เป็นอนุกรมได้) "clutters the class less" และถ้าคุณไม่ได้ใช้ซีเรียลไลซ์เซชั่นสำหรับการจัดเก็บแบบถาวรทำไมคุณไม่ใช้ "1"? คุณจะไม่สนใจ ID ที่สร้างอัตโนมัติในกรณีนั้นอย่างไรก็ตาม
MetroidFan2002

71
@ MetroidFan2002: ฉันคิดว่า @ TomAnderson กำลังserialVersionUIDปกป้องคุณจากการเปลี่ยนแปลงที่เข้ากันไม่ถูกต้อง การใช้@SuppressWarningsเอกสารแสดงเจตนาดีกว่าหากคุณไม่ต้องการใช้คลาสสำหรับการจัดเก็บถาวร
AbdullahC

11
"คุณควรเพิ่มขึ้นหากรุ่นปัจจุบันของคลาสของคุณไม่เข้ากันได้กับรุ่นก่อนหน้า:" คุณควรสำรวจการสนับสนุนการกำหนดเวอร์ชันของออบเจ็กต์อย่างกว้างขวางของ Serialization (a) เพื่อให้แน่ใจว่าชั้นเรียนเป็นแบบที่เข้ากันไม่ได้ ซึ่งตามข้อกำหนดนั้นค่อนข้างยากที่จะบรรลุ (b) เพื่อลองโครงร่างเช่นเมธอด read / writeObject () แบบกำหนดเอง, เมธอด readResolve / writeReplace (), การประกาศ serializableFields เป็นต้นเพื่อให้แน่ใจว่าสตรีมยังคงใช้งานร่วมกันได้ การเปลี่ยนแปลงที่เกิดขึ้นจริงserialVersionUIDเป็นทางเลือกสุดท้ายเป็นคำแนะนำแห่งความสิ้นหวัง
user207421

4
@EJP ที่เพิ่มขึ้นของ serialVersionUID เข้ามาในรูปภาพเมื่อผู้เขียนเริ่มต้นของชั้นเรียนได้แนะนำอย่างชัดเจนฉันจะบอกว่า jvm สร้างรหัสซีเรียลควรจะดี นี่เป็นคำตอบที่ดีที่สุดที่ฉันเห็นในการทำให้เป็นอันดับ
แลกเปลี่ยนที่มากเกินไป

307

ฉันไม่สามารถใช้โอกาสนี้ในการเสียบหนังสือJava Bloch ของEffective Java (2nd Edition) บทที่ 11 เป็นทรัพยากรที่ขาดไม่ได้ในการทำให้เป็นอันดับ Java

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

ถ้าคุณไม่สนใจพวกเขาในขณะนี้และพบในภายหลังว่าคุณจำเป็นต้องเปลี่ยนชั้นในบางวิธี แต่ยังคงเข้ากันได้ w / รุ่นเก่าของชั้นที่คุณสามารถใช้เครื่องมือ JDK serialverเพื่อสร้างserialVersionUIDบนเก่าชั้นเรียนและกำหนดอย่างชัดเจนว่า ในชั้นเรียนใหม่ (ขึ้นอยู่กับการเปลี่ยนแปลงของคุณคุณอาจต้องใช้การทำให้เป็นอันดับที่กำหนดเองโดยการเพิ่มwriteObjectและreadObjectวิธีการ - ดูSerializablejavadoc หรือบทที่ 11 ดังกล่าวข้างต้น)


33
ดังนั้นหนึ่งอาจรำคาญกับ SerializableVersionUID ถ้ามีความกังวลเกี่ยวกับความเข้ากันได้กับรุ่นเก่าของคลาส?
Ziggy

10
ใช่ในกรณีที่ถ้าเวอร์ชั่นที่ใหม่กว่าเปลี่ยนสมาชิกสาธารณะใด ๆ เป็นการป้องกันค่าเริ่มต้น SerializableVersionUID จะแตกต่างกันและจะเพิ่ม InvalidClassExceptions
Chander Shivdasani

3
ชื่อคลาสอินเทอร์เฟซที่นำไปใช้วิธีสาธารณะและการป้องกันทั้งหมดตัวแปรอินสแตนซ์ทั้งหมด
Achow

31
เป็นที่น่าสังเกตว่า Joshua Bloch แนะนำว่าสำหรับทุก ๆ Serializable Class มันคุ้มค่าที่จะระบุ uid ของอนุกรม อ้างอิงจากบทที่ 11: ไม่ว่าคุณจะเลือกแบบฟอร์มใดต่อเนื่องให้ประกาศ UID เวอร์ชันอนุกรมที่ชัดเจนในทุก ๆ คลาสที่คุณเขียนได้ สิ่งนี้จะกำจัด UID เวอร์ชันอนุกรมเป็นแหล่งที่มาของความไม่ลงรอยกัน (รายการ 74) นอกจากนี้ยังมีประโยชน์ด้านประสิทธิภาพเล็กน้อย หากไม่มีการระบุ UID แบบอนุกรมการคำนวณราคาแพงจะต้องสร้างขึ้นมาหนึ่งรันไทม์
Ashutosh Jindal

136

คุณสามารถบอกให้ Eclipse ละเว้นคำเตือน serialVersionUID เหล่านี้:

หน้าต่าง> การตั้งค่า> Java> คอมไพเลอร์> ข้อผิดพลาด / คำเตือน> ปัญหาการเขียนโปรแกรมที่อาจเกิดขึ้น

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

  • ปัญหาการเขียนโปรแกรมที่อาจเกิดขึ้น: การกำหนดบูลีนโดยบังเอิญที่อาจเกิดขึ้น
  • ปัญหาการเขียนโปรแกรมที่อาจเกิดขึ้น: การเข้าถึงตัวชี้ Null
  • รหัสที่ไม่จำเป็น: ไม่เคยอ่านตัวแปรในตัวเครื่อง
  • รหัสที่ไม่จำเป็น: การตรวจสอบ null ซ้ำซ้อน
  • รหัสที่ไม่จำเป็น: นักแสดงที่ไม่จำเป็นหรือ 'อินสแตนซ์'

และอื่น ๆ อีกมากมาย.


21
upvote แต่เพียงเพราะโปสเตอร์ต้นฉบับดูเหมือนจะไม่เป็นอนุกรมอะไร หากผู้โพสต์บอกว่า "ฉันจะทำให้สิ่งนี้เป็นแบบนี้และ ... " คุณจะได้รับการโหวตแทน: P
John Gardner

3
จริง - ฉันสมมติว่าผู้ถามไม่ต้องการถูกเตือน
แมตต์ b

14
@Gardner -> เห็นด้วย! แต่ผู้ถามก็อยากรู้ว่าทำไมเขาถึงไม่อยากถูกเตือน
Ziggy

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

111

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

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

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

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

ในกรณีนี้คุณไม่สนใจที่จะรักษาความเข้ากันได้ย้อนหลัง คุณมีส่วนเกี่ยวข้องกับการทำให้แน่ใจว่ารหัสพื้นฐานที่กำลังสื่อสารนั้นมีคลาสที่เกี่ยวข้องรุ่นเดียวกันเท่านั้น เพื่อความสะดวกในการตรวจสอบดังกล่าวคุณต้องดูแลserialVersionUIDเหมือนเดิมและอย่าลืมอัปเดตเมื่อทำการเปลี่ยนแปลงชั้นเรียนของคุณ

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

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

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

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


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

4
@EJP: ใช่มั้ย การเพิ่มข้อมูลเป็นการเปลี่ยนแปลงข้อมูลการทำให้เป็นอันดับในโลกของฉันอย่างแน่นอน
Alexander Torstling

3
@AlexanderTorstling อ่านสิ่งที่ฉันเขียน ฉันไม่ได้บอกว่ามันจะไม่ 'เปลี่ยนข้อมูลการทำให้เป็นอันดับ' ฉันบอกว่า 'ไม่ทำให้คลาสเป็นอนุกรมไม่เข้ากัน' มันไม่เหมือนกัน คุณต้องอ่านบทการกำหนดเวอร์ชันของ Object Serialization Specification
user207421

3
@EJP: ฉันรู้ว่าการเพิ่มเขตข้อมูลที่ไม่ใช่แบบชั่วคราวไม่ได้แปลว่าคุณจะทำให้คลาสเป็นอนุกรม - เข้ากันไม่ได้ แต่มันเป็นการเปลี่ยนแปลงเชิงโครงสร้างที่เปลี่ยนแปลงข้อมูลที่เป็นอนุกรมและคุณมักจะต้องการชนรุ่นเมื่อคุณทำเช่นนั้น จัดการกับความเข้ากันได้ย้อนหลังซึ่งฉันก็อธิบายต่อไปในโพสต์ ประเด็นของคุณคืออะไร
Alexander Torstling

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

72

serialVersionUIDคืออะไรและทำไมฉันจึงควรใช้

SerialVersionUIDเป็นตัวระบุที่ไม่ซ้ำกันสำหรับแต่ละคลาสJVMใช้เพื่อเปรียบเทียบเวอร์ชันของคลาสเพื่อให้แน่ใจว่าคลาสเดียวกันนั้นถูกใช้ระหว่างการทำให้เป็นอนุกรมถูกโหลดระหว่าง Deserialization

การระบุจะให้การควบคุมมากกว่านั้นแม้ว่า JVM จะสร้างขึ้นมาหากคุณไม่ได้ระบุ ค่าที่สร้างสามารถแตกต่างกันระหว่างคอมไพเลอร์ต่าง นอกจากนี้บางครั้งคุณเพียงต้องการเหตุผลบางอย่างในการห้ามการดีซีเรียลไลเซชันของวัตถุที่เป็นอนุกรมเก่า [ backward incompatibility] และในกรณีนี้คุณต้องเปลี่ยน serialVersionUID

javadocs สำหรับSerializableพูด :

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

ดังนั้นคุณจะต้องประกาศ serialVersionUID เพราะมันทำให้เรามีการควบคุมมากขึ้น

บทความนี้มีจุดที่ดีในหัวข้อ


3
@Vinothbabu แต่ serialVersionUID เป็นค่าคงที่ดังนั้นตัวแปรแบบคงที่ไม่สามารถต่อเนื่องกันได้ แล้วทำไม jvm จะตรวจสอบเวอร์ชั่นโดยไม่ทราบว่าเป็นเวอร์ชั่นของวัตถุที่ต้อง
ทำการลดความเสี่ยงอะไร

3
สิ่งหนึ่งที่ไม่ได้กล่าวถึงในคำตอบนี้คือคุณอาจทำให้เกิดผลที่ไม่ตั้งใจโดยสุ่มสี่สุ่มห้ารวมถึงserialVersionUIDโดยไม่รู้สาเหตุ ความคิดเห็นของ Tom Anderson เกี่ยวกับคำตอบของ MetroidFan2002 กล่าวถึงเรื่องนี้: "ฉันบอกว่าถ้าคุณไม่ได้ใช้ซีเรียลไลซ์เซชั่นสำหรับการจัดเก็บแบบถาวรคุณควรใช้ @SuppressWarnings แทนที่จะเพิ่มมูลค่ามัน clutters ในชั้นเรียนน้อยลง กลไก serialVersionUID เพื่อปกป้องคุณจากการเปลี่ยนแปลงที่เข้ากันไม่ได้ "
เคอร์บี้

7
serialVersionUIDคือไม่ได้ว่า 'ระบุเฉพาะสำหรับแต่ละชั้น' ชื่อคลาสที่ผ่านการรับรองโดยสมบูรณ์คือ มันเป็นตัวบ่งชี้เวอร์ชัน
user207421

58

คำถามดั้งเดิมได้ถามว่า 'เพราะเหตุใดจึงสำคัญ' และ 'ตัวอย่าง' ซึ่งสิ่งนี้Serial Version IDจะมีประโยชน์ ฉันได้พบแล้ว

สมมติว่าคุณสร้างCarคลาสยกตัวอย่างและเขียนลงในสตรีมวัตถุ อ็อบเจ็กต์รถยนต์แบบแบนอยู่ในระบบไฟล์เป็นระยะเวลาหนึ่ง ในขณะเดียวกันถ้าCarคลาสนั้นได้รับการแก้ไขโดยการเพิ่มฟิลด์ใหม่ ต่อมาเมื่อคุณพยายามอ่าน (เช่นการทำให้หมดเวลา) Carวัตถุที่แบนคุณจะได้รับjava.io.InvalidClassException- เนื่องจากคลาสที่ต่อเนื่องได้ทั้งหมดจะได้รับตัวระบุที่ไม่ซ้ำกันโดยอัตโนมัติ ข้อยกเว้นนี้ถูกส่งออกมาเมื่อตัวระบุของคลาสไม่เท่ากับตัวระบุของวัตถุแบน หากคุณคิดเกี่ยวกับมันจริงๆข้อยกเว้นจะถูกโยนเนื่องจากการเพิ่มฟิลด์ใหม่ คุณสามารถหลีกเลี่ยงข้อยกเว้นนี้ได้โดยการควบคุมเวอร์ชันด้วยตัวคุณเองโดยการประกาศ serialVersionUID อย่างชัดเจน นอกจากนี้ยังมีประโยชน์ด้านประสิทธิภาพเล็กน้อยในการประกาศของคุณอย่างชัดเจนserialVersionUID(เพราะไม่ต้องคำนวณ) ดังนั้นจึงเป็นวิธีที่ดีที่สุดในการเพิ่ม serialVersionUID ของคุณเองในคลาส Serializable ของคุณทันทีที่คุณสร้างพวกเขาดังแสดงด้านล่าง:

public class Car {
    static final long serialVersionUID = 1L; //assign a long value
}

และหนึ่งควรกำหนดจำนวนยาวแบบสุ่มไม่ได้ 1L ให้กับทุกโพสต์
abbas

4
@abbas 'ใครควรทำอย่างนั้นทำไม? โปรดอธิบายว่ามันสร้างความแตกต่างอะไร
user207421

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

2
@ abbas ความตั้งใจนี้ไม่ได้ขัดแย้งกับการใช้ตัวเลขธรรมชาติที่เพิ่มขึ้นจาก1และอื่น ๆ
Vadzim

2
@ บิลล์ฉันคิดว่าการตรวจสอบหมายเลขซีเรียลถูกผูกไว้กับคู่ของ classname และ serialVersionUID ดังนั้นโครงร่างลำดับเลขที่แตกต่างกันของคลาสและไลบรารีที่แตกต่างกันจึงไม่สามารถแทรกแซงได้ หรือคุณหมายถึงไลบรารีที่สร้างรหัส?
Vadzim

44

ก่อนอื่นฉันต้องอธิบายว่าการทำให้เป็นอนุกรมคืออะไร

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

มีกฎระเบียบบางอย่างสำหรับการเป็นอันดับเป็น

  • วัตถุนั้นสามารถทำให้เป็นอนุกรมได้ถ้าคลาสหรือซูเปอร์คลาสนั้นใช้อินเตอร์เฟสที่สามารถทำให้เป็นอนุกรมได้

  • วัตถุนั้นสามารถทำให้เป็นอนุกรมได้ (ตัวของมันเองนั้นใช้อินเตอร์เฟซแบบอนุกรมได้) แม้ว่ามันจะไม่ได้เป็นซุปเปอร์คลาส อย่างไรก็ตามซูเปอร์คลาสแรกในลำดับชั้นของคลาสที่ทำให้เป็นอนุกรมซึ่งไม่ได้ใช้อินเตอร์เฟสแบบอนุกรมได้ต้องมีคอนสตรัคเตอร์แบบไม่มีอาร์กิวเมนต์ หากสิ่งนี้ถูกละเมิด readObject () จะสร้าง java.io.InvalidClassException ในรันไทม์

  • ชนิดดั้งเดิมทั้งหมดจะต่อเนื่องกันได้

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

  • ฟิลด์แบบสแตติก (ที่มีตัวแก้ไขแบบสแตติก) จะไม่ต่อเนื่อง

เมื่อObjectมีการต่อเนื่อง, Java Runtime serialVersionIDเชื่อมโยงหมายเลขรุ่นอนุกรมอาคาที่

ตำแหน่งที่เราต้องการ serialVersionID: ระหว่างการดีซีเรียลไลซ์เซชั่นเพื่อตรวจสอบว่าผู้ส่งและผู้รับเข้ากันได้กับการทำซีเรียลไลเซชั่น หากผู้รับโหลดคลาสที่แตกต่างกันการดีserialVersionIDซีเรียลInvalidClassCastExceptionไลซ์เซชั่นจะจบลงด้วย
คลาสที่ต่อเนื่องกันได้สามารถประกาศตัวของมันเองserialVersionUIDอย่างชัดเจนโดยการประกาศเขตข้อมูลserialVersionUIDที่จะต้องคงที่สุดท้ายและประเภทยาว

ลองทำตัวอย่างนี้ดู

import java.io.Serializable;    
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String empname;
private byte empage;

public String getEmpName() {
    return name;
}
public void setEmpName(String empname) {
    this.empname = empname;
}
public byte getEmpAge() {
    return empage;
}
public void setEmpAge(byte empage) {
    this.empage = empage;
}

public String whoIsThis() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}

สร้างวัตถุเป็นอันดับ

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Writer {
public static void main(String[] args) throws IOException {
    Employee employee = new Employee();
    employee.setEmpName("Jagdish");
    employee.setEmpAge((byte) 30);

    FileOutputStream fout = new 
FileOutputStream("/users/Jagdish.vala/employee.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(employee);
    oos.close();
    System.out.println("Process complete");
}
}

ยกเลิกการทำให้วัตถุเป็นวัตถุ

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Reader {
public static void main(String[] args) throws ClassNotFoundException, 
IOException {
    Employee employee = new Employee();
    FileInputStream fin = new 
    FileInputStream("/users/Jagdish.vala/employee.obj");
    ObjectInputStream ois = new ObjectInputStream(fin);
    employee = (Employee) ois.readObject();
    ois.close();
    System.out.println(employee.whoIsThis());
 }
}    

หมายเหตุ: ตอนนี้เปลี่ยน serialVersionUID ของระดับพนักงานและบันทึก:

private static final long serialVersionUID = 4L;

และรันคลาส Reader ไม่เรียกใช้งานคลาส Writer และคุณจะได้รับการยกเว้น

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)

แก้ไขให้ฉันถ้าฉันผิด - คลาสในท้องที่นั้นเป็นสิ่งที่คุณมี / ใช้อยู่ใน classpath และกระแสหนึ่งนั้นเป็นสิ่งที่ใช้โดยอีกฝ่าย (เช่นเซิร์ฟเวอร์ที่ส่งคำตอบกลับมาให้คุณ การตอบสนอง) คุณสามารถพบสถานการณ์นี้เมื่อคุณกำลังสื่อสารกับเซิร์ฟเวอร์ที่มีการปรับปรุง libs ของบุคคลที่สาม แต่คุณ (ลูกค้า) ไม่ได้ทำเช่นนี้
วิคเตอร์

37

หากคุณไม่จำเป็นต้องทำให้เป็นอันดับวัตถุของคุณไปยังอาร์เรย์ไบต์และส่ง / เก็บพวกเขาแล้วคุณไม่จำเป็นต้องกังวลเกี่ยวกับมัน หากคุณทำเช่นนั้นคุณต้องพิจารณา serialVersionUID ของคุณเนื่องจากตัว deserializer ของวัตถุจะจับคู่กับเวอร์ชันของวัตถุที่ classloader มีอยู่ อ่านเพิ่มเติมเกี่ยวกับ Java Language Specification


10
หากคุณจะไม่ทำให้เป็นอันดับวัตถุทำไมพวกเขา Serializable?
erickson

7
@erickson - คลาสพาเรนต์อาจต่อเนื่องกันได้พูด ArrayList แต่คุณต้องการวัตถุของคุณเอง (เช่นรายการ array ที่แก้ไข) เพื่อใช้เป็นฐาน แต่จะไม่ทำให้ซีเรียลคอลเลกชันที่คุณสร้างเป็นอนุกรม
MetroidFan2002

5
ไม่ได้กล่าวถึงทุกที่ในข้อกำหนดภาษา Java มันถูกกล่าวถึงในข้อกำหนดคุณสมบัติการกำหนดเวอร์ชันของวัตถุ
user207421

4
นี่คือการเชื่อมโยงไป Java 8 วัตถุรุ่นจำเพาะ
Basil Bourque

34

หากคุณได้รับคำเตือนนี้ในชั้นเรียนที่คุณไม่เคยนึกถึงเกี่ยวกับการทำให้เป็นอันดับและคุณไม่ได้ประกาศตัวเองimplements Serializableมันก็มักจะเป็นเพราะคุณได้รับมรดกจาก Superclass ซึ่งใช้ Serializable บ่อยครั้งจะเป็นการดีกว่าถ้าจะมอบหมายให้วัตถุนั้นแทนที่จะใช้การสืบทอด

ดังนั้นแทนที่จะ

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

ทำ

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

และในวิธีการที่เกี่ยวข้องโทรmyList.foo()แทนthis.foo()(หรือsuper.foo()) (สิ่งนี้ไม่เหมาะสมในทุกกรณี แต่ยังค่อนข้างบ่อย)

ฉันมักจะเห็นคนขยาย JFrame หรือเช่นเมื่อพวกเขาต้องการเพียงมอบหมายให้สิ่งนี้ (สิ่งนี้ยังช่วยในการเติมอัตโนมัติใน IDE เนื่องจาก JFrame มีวิธีการหลายร้อยวิธีซึ่งคุณไม่ต้องการเมื่อคุณต้องการโทรหาคนที่คุณกำหนดเองในชั้นเรียนของคุณ)

กรณีหนึ่งที่คำเตือน (หรือ serialVersionUID) หลีกเลี่ยงไม่ได้คือเมื่อคุณขยายจาก AbstractAction โดยปกติในคลาสที่ไม่ระบุชื่อโดยเพิ่ม actionPerformed-method เท่านั้น ฉันคิดว่าไม่ควรมีคำเตือนในกรณีนี้ (เนื่องจากปกติแล้วคุณไม่สามารถทำให้เป็นอันดับที่เชื่อถือได้และยกเลิกคลาสที่ไม่ระบุชื่อดังกล่าวในรุ่นที่แตกต่างกันของคลาสของคุณ) แต่ฉันไม่แน่ใจว่า


4
ฉันคิดว่าคุณพูดถูกกว่าการแต่งเพลงเพื่อการหายใจมีเหตุผลมากขึ้นโดยเฉพาะเมื่อคุณพูดถึงคลาสเช่น ArrayList อย่างไรก็ตามเฟรมเวิร์กจำนวนมากต้องการให้ผู้ใช้ขยายจากซูเปอร์คลาสนามธรรมซึ่งต่อเนื่องกันได้ (เช่นคลาส ActionForm ของ Struts 1.2 หรือ Saxon ExtensionFunctionDefinition ฉันคิดว่าคุณพูดถูกมันคงจะดีถ้าการเตือนถูกเพิกเฉยในบางกรณี (เช่นถ้าคุณขยายจากคลาสที่ต่อเนื่องกันเป็นนามธรรม)
piepera

2
แน่นอนถ้าคุณเพิ่มคลาสเป็นสมาชิกแทนที่จะสืบทอดจากคลาสนั้นคุณจะต้องเขียนวิธีการ wrapper สำหรับเมธอด EVERY ของคลาสสมาชิกที่คุณต้องการใช้ซึ่งจะทำให้ไม่สามารถทำได้ในหลายสถานการณ์ .. . เว้นแต่ java มีฟังก์ชั่นคล้ายกับของ perl __AUTOLOADซึ่งฉันไม่รู้
M_M

4
@M_M: เมื่อคุณจะมอบหมายวิธีการมากมายให้กับวัตถุที่ถูกห่อของคุณแน่นอนว่ามันไม่เหมาะสมที่จะใช้การมอบหมาย แต่ฉันคิดว่ากรณีนี้เป็นสัญญาณของความผิดพลาดในการออกแบบ - ผู้ใช้ในชั้นเรียนของคุณ (เช่น "MainGui") ไม่ควรเรียกวิธีการมากมายของวัตถุที่ถูกห่อ (เช่น JFrame)
Paŭlo Ebermann

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

2
Throwable นั้นสามารถทำให้เป็นอนุกรมได้และ Throwable เพียงอย่างเดียวจึงไม่สามารถกำหนดข้อยกเว้นที่ไม่ได้ต่อเนื่องกันได้ ไม่สามารถมอบหมายได้
Phil

22

เพื่อให้เข้าใจถึงความสำคัญของ field serialVersionUID เราควรเข้าใจว่า Serialization / Deserialization ทำงานอย่างไร

เมื่ออ็อบเจ็กต์คลาส Serializable เป็นอนุกรม Java Runtime เชื่อมโยงกับหมายเลขอนุกรม (เรียกว่าเป็น serialVersionUID) กับอ็อบเจ็กต์ที่ทำให้เป็นอนุกรมนี้ ในขณะที่คุณ deserialize วัตถุ Java ต่อเนื่องนี้ตรงกับ serialVersionUID ของวัตถุที่เป็นอนุกรมกับ serialVersionUID ของชั้นเรียน หากทั้งสองเท่ากันเท่านั้นมันจะดำเนินการกับกระบวนการ deserialization ต่อไปมิฉะนั้นจะโยน InvalidClassException

ดังนั้นเราจึงสรุปได้ว่าการทำให้กระบวนการ Serialization / Deserialization ประสบความสำเร็จ serialVersionUID ของวัตถุที่เป็นอนุกรมจะต้องเทียบเท่ากับ serialVersionUID ของชั้นเรียน ในกรณีที่โปรแกรมเมอร์ระบุค่า serialVersionUID อย่างชัดเจนในโปรแกรมค่าเดียวกันจะเชื่อมโยงกับวัตถุที่เป็นอนุกรมและคลาสโดยไม่คำนึงถึงแพลตฟอร์มซีเรียลไลซ์เซชั่นและ deserialzation MS JVM และ Deserialization อาจอยู่ในแพลตฟอร์ม Linux อื่นโดยใช้ Zing JVM)

แต่ในกรณีที่โปรแกรมเมอร์ไม่ได้ระบุ serialVersionUID ในขณะที่ทำ Serialization \ DeSerialization ของวัตถุใด ๆ , Java runtime ใช้อัลกอริทึมของตัวเองในการคำนวณ อัลกอริทึมการคำนวณ serialVersionUID นี้แตกต่างจาก JRE หนึ่งไปยังอีก อาจเป็นไปได้ว่าสภาพแวดล้อมที่วัตถุถูกทำให้เป็นอนุกรมนั้นใช้ JRE หนึ่งตัว (เช่น JVM) และสภาพแวดล้อมที่ deserialzation เกิดขึ้นกำลังใช้ Linux Jvm (zing) ในกรณีเช่นนี้ serialVersionUID ที่เกี่ยวข้องกับวัตถุที่เป็นอนุกรมจะแตกต่างจาก serialVersionUID ของคลาสที่คำนวณที่สภาพแวดล้อม deserialzation จะไม่ประสบความสำเร็จ ดังนั้นเพื่อหลีกเลี่ยงสถานการณ์ / ปัญหาโปรแกรมเมอร์ต้องระบุ serialVersionUID ของคลาส Serializable


2
อัลกอริทึมไม่ได้แตกต่างกันไป แต่มีการระบุไว้เล็กน้อย
user207421

... อัลกอริทึมไม่ได้แตกต่างกันไป แต่มีการระบุไว้เล็กน้อย ... หมายถึง jvm ใด ๆ ที่อาจแตกต่างกันไป ..... @ user207421
AnthonyJClink

18

ไม่ต้องกังวลการคำนวณเริ่มต้นนั้นดีมากและพอเพียงสำหรับกรณี 99,9999% และหากคุณพบปัญหาคุณสามารถ - ตามที่ระบุไว้แล้ว - แนะนำ UID's เป็น arrise need (ซึ่งไม่น่าเป็นไปได้สูง)


2
ขยะมูลฝอย มีเพียงพอในกรณีที่คลาสไม่เปลี่ยนแปลง คุณมีหลักฐานเป็นศูนย์เพื่อสนับสนุน '99 .9999% '
user207421

18

สำหรับตัวอย่างที่ serialVersionUID ที่หายไปอาจทำให้เกิดปัญหา:

ฉันกำลังทำงานกับแอปพลิเคชัน Java EE นี้ซึ่งประกอบด้วยโมดูลเว็บที่ใช้EJBโมดูล เว็บโมดูลเรียกใช้EJBโมดูลจากระยะไกลและส่งผ่านอุปกรณ์POJOนั้นSerializableเป็นอาร์กิวเมนต์

นี้ POJO'sคลาสถูกแพ็กเกจภายใน jar EJB และภายใน jar ของมันเองใน WEB-INF / lib ของเว็บโมดูล จริง ๆ แล้วมันเป็นคลาสเดียวกัน แต่เมื่อฉันบรรจุโมดูล EJB ฉันเปิดขวดของ POJO นี้เพื่อรวมเข้ากับโมดูล EJB

การเรียกไปที่EJBล้มเหลวด้วยข้อยกเว้นด้านล่างเพราะฉันไม่ได้ประกาศserialVersionUID:

Caused by: java.io.IOException: Mismatched serialization UIDs : Source
 (Rep.
 IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
 = 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
 = 6227F23FA74A9A52

16

ฉันมักจะใช้serialVersionUIDในบริบทเดียว: เมื่อฉันรู้ว่ามันจะออกจากบริบทของ Java VM

ฉันจะรู้สิ่งนี้เมื่อฉันใช้ObjectInputStreamและObjectOutputStreamสำหรับแอปพลิเคชันของฉันหรือถ้าฉันรู้ว่าไลบรารี / กรอบงานที่ฉันใช้จะใช้งานได้ serialVersionID ทำให้มั่นใจได้ว่า Java VMs ที่แตกต่างกันของรุ่นที่แตกต่างกันหรือผู้ขายจะทำงานได้อย่างถูกต้องหรือถ้ามันถูกจัดเก็บและดึงข้อมูลภายนอก VM เช่นHttpSessionข้อมูลเซสชันยังคงอยู่แม้ในระหว่างการรีสตาร์ทและอัพเกรดแอพพลิเคชันเซิร์ฟเวอร์

สำหรับกรณีอื่น ๆ ฉันใช้

@SuppressWarnings("serial")

เนื่องจากเวลาส่วนใหญ่ค่าเริ่มต้นserialVersionUIDก็เพียงพอแล้ว ซึ่งรวมถึงException, HttpServlet.


ไม่รวม HttpServlet ในคอนเทนเนอร์ที่สามารถสลับหรือยกเว้นใน RMI ตัวอย่างเช่น
user207421

14

ข้อมูลภาคสนามแสดงถึงข้อมูลบางอย่างที่เก็บไว้ในชั้นเรียน คลาสใช้Serializableอินเตอร์เฟสดังนั้น eclipse ถูกเสนอให้ประกาศserialVersionUIDฟิลด์โดยอัตโนมัติ ให้เริ่มต้นด้วยค่า 1 เซตที่นั่น

หากคุณไม่ต้องการคำเตือนนั้นให้ใช้สิ่งนี้:

@SuppressWarnings("serial")

11

มันจะดีถ้า CheckStyle สามารถตรวจสอบว่า serialVersionUID ในชั้นเรียนที่ดำเนินการ Serializable มีค่าที่ดีคือว่ามันตรงกับสิ่งที่กำเนิดรุ่น id อนุกรมจะผลิต หากคุณมีโครงการที่มี DTO ที่สามารถทำให้เป็นอนุกรมได้ตัวอย่างเช่นการจำเพื่อลบ serialVersionUID ที่มีอยู่และสร้างใหม่มันเป็นความเจ็บปวดและในปัจจุบันเป็นวิธีเดียว (ที่ฉันรู้) เพื่อยืนยันว่านี่คือการสร้างใหม่สำหรับแต่ละชั้นเรียนและเปรียบเทียบ อันเก่า นี่ช่างเจ็บปวดเหลือเกิน


8
หากคุณตั้งค่า serialVersionUID ให้เป็นค่าเดียวกับที่เครื่องกำเนิดจะสร้างคุณก็ไม่จำเป็นต้องใช้เลย ท้ายที่สุดแล้ว raison d'êtreก็ยังคงเหมือนเดิมหลังจากการเปลี่ยนแปลงเมื่อชั้นเรียนยังคงเข้ากันได้
Paŭlo Ebermann

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

2
มันไม่จำเป็นต้องตรงกับสิ่งที่serialverจะผลิต -1
user207421

โดยทั่วไปแล้วมันไม่จำเป็นเลย กรณีของ @ AndrewBacker ต้องการคอมไพเลอร์สองตัวที่แตกต่างกันในไฟล์. java เดียวกันกับไฟล์. class ทั้งสองรุ่นที่สื่อสารกันซึ่งส่วนใหญ่คุณเพิ่งสร้างคลาสและแจกจ่ายคลาสนั้น หากเป็นเช่นนั้นการไม่มี SUID จะทำงานได้ดี
Bill K

1
คนที่ใช้การซีเรียลไลซ์เซชั่นเพื่อวัตถุประสงค์ในการจัดเก็บ / ดึงข้อมูลโดยทั่วไปจะตั้งค่าเป็นserialVersionUID1 หากคลาสที่ใหม่กว่าของคลาสนั้นไม่เข้ากัน แต่ยังต้องการให้สามารถจัดการข้อมูลเก่าได้คุณต้องเพิ่มหมายเลขรุ่นและเพิ่มรหัสพิเศษ จัดการกับรูปแบบที่เก่ากว่า ฉันร้องไห้ทุกครั้งที่ฉันเห็นตัวเลขที่serialVersionUIDมากกว่า 1 หลักเพราะมันเป็นตัวเลขสุ่ม (ไร้ประโยชน์) หรือเพราะชั้นเรียนต้องจัดการกับรุ่นต่าง ๆ มากกว่า 10 รุ่น
john16384

11

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

อ่านเพิ่มเติม: http://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ


9

เหตุใดจึงต้องใช้SerialVersionUIDInside Serializableclass ใน Java

ในระหว่างserializationรันไทม์ Java สร้างหมายเลขเวอร์ชันสำหรับคลาสเพื่อให้สามารถยกเลิกการทำให้เป็นอนุกรมในภายหลัง หมายเลขรุ่นนี้เรียกว่าSerialVersionUIDใน Java

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

เมื่อคุณประกาศคลาสSerializableด้วยการนำอินเตอร์เฟสมาร์java.io.Serializableกเกอร์อินสแตนซ์ Java ยังคงอินสแตนซ์ของคลาสนั้นลงในดิสก์โดยใช้กลไกการทำให้เป็นอนุกรมที่เป็นค่าเริ่มต้นหากคุณไม่ได้กำหนดกระบวนการเองโดยใช้Externalizableอินเตอร์เฟส

ดูเพิ่มเติมเหตุใดจึงต้องใช้ SerialVersionUID ภายในคลาสที่สร้างได้ใน Java

รหัส: javassist.SerialVersionUID


8

ถ้าคุณต้องการแก้ไขคลาสจำนวนมากที่ไม่มี serialVersionUID ตั้งอยู่ในตอนแรกในขณะที่รักษาความเข้ากันได้กับคลาสเก่าเครื่องมือเช่น IntelliJ Idea, Eclipse สั้นเนื่องจากพวกเขาสร้างตัวเลขสุ่มและไม่ทำงานในกลุ่มของไฟล์ ในครั้งเดียว ฉันพบสคริปต์ทุบตีต่อไปนี้ (ฉันขอโทษสำหรับผู้ใช้ Windows พิจารณาซื้อ Mac หรือแปลงเป็น Linux) เพื่อแก้ไขปัญหา serialVersionUID ได้อย่างง่ายดาย:

base_dir=$(pwd)                                                                  
src_dir=$base_dir/src/main/java                                                  
ic_api_cp=$base_dir/target/classes                                               

while read f                                                                     
do                                                                               
    clazz=${f//\//.}                                                             
    clazz=${clazz/%.java/}                                                       
    seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
    perl -ni.bak -e "print $_; printf qq{%s\n}, q{    private $seruidstr} if /public class/" $src_dir/$f
done

คุณบันทึกสคริปต์นี้พูด add_serialVersionUID.sh กับคุณ ~ / bin จากนั้นคุณเรียกใช้ในไดเรกทอรีรากของโครงการ Maven หรือ Gradle เช่น:

add_serialVersionUID.sh < myJavaToAmend.lst

.lst นี้รวมถึงรายการของไฟล์ java เพื่อเพิ่ม serialVersionUID ในรูปแบบต่อไปนี้:

com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java

สคริปต์นี้ใช้เครื่องมือ JDK serialVer ภายใต้ประทุน ดังนั้นตรวจสอบให้แน่ใจว่า $ JAVA_HOME / bin ของคุณอยู่ในเส้นทาง


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

6

คำถามนี้ได้รับการบันทึกไว้เป็นอย่างดีใน Effective Java โดย Joshua Bloch หนังสือที่ดีมากและต้องอ่าน ฉันจะสรุปเหตุผลบางประการด้านล่าง:

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

หากคลาสนั้นสามารถทำให้เป็นอนุกรมคุณสามารถประกาศ serialVersionUID ของคุณเองได้อย่างชัดเจนโดยการประกาศฟิลด์ชื่อ "serialVersionUID" ที่ต้องเป็นแบบสแตติกขั้นสุดท้ายและแบบยาว Eclipse ส่วนใหญ่ของ IDE ช่วยให้คุณสร้างสตริงที่ยาว


6

แต่ละครั้งที่วัตถุถูกทำให้เป็นอนุกรมวัตถุจะถูกประทับด้วยหมายเลขรหัสรุ่นสำหรับคลาสของวัตถุ ID นี้เรียกว่าserialVersionUIDและคำนวณจากข้อมูลเกี่ยวกับโครงสร้างคลาส สมมติว่าคุณได้สร้างคลาส Employee และมี version id # 333 (กำหนดโดย JVM) ตอนนี้เมื่อคุณจะทำให้เป็นออบเจ็กต์ของคลาสนั้น (สมมติว่าอ็อบเจกต์ Employee) JVM จะกำหนด UID ให้เป็น # 333

พิจารณาสถานการณ์ - ในอนาคตคุณต้องแก้ไขหรือเปลี่ยนคลาสและในกรณีนั้นเมื่อคุณแก้ไข JVM จะกำหนด UID ใหม่ (สมมติว่า # 444) ตอนนี้เมื่อคุณพยายามทำการ deserialize อ็อบเจกต์พนักงาน JVM จะทำการเปรียบเทียบ ID เวอร์ชันของพนักงาน (ออบเจกต์พนักงาน) (# 333) กับของคลาสเช่น # 444 (เนื่องจากมีการเปลี่ยนแปลง) ในการเปรียบเทียบ JVM จะพบว่าทั้งสองเวอร์ชัน UID แตกต่างกันดังนั้น Deserialization จะล้มเหลว ดังนั้นถ้า serialVersionID สำหรับแต่ละคลาสนั้นถูกกำหนดโดยโปรแกรมเมอร์เอง มันจะเหมือนกันแม้ว่าคลาสจะถูกพัฒนาในอนาคตและด้วยเหตุนี้ JVM จะพบว่าคลาสนั้นเข้ากันได้กับออบเจ็กต์ต่อเนื่องแม้ว่าจะเปลี่ยนคลาสแล้วก็ตาม สำหรับข้อมูลเพิ่มเติมคุณสามารถดูบทที่ 14 ของ HEAD FIRST JAVA


1
แต่ละครั้งที่คลาสของวัตถุถูกทำให้เป็นอนุกรมserialVersionUIDจะถูกส่ง มันไม่ได้ถูกส่งไปกับทุกวัตถุ
user207421

3

คำอธิบายง่ายๆ:

  1. คุณซีเรียลไลซ์ข้อมูลหรือไม่

    การทำให้เป็นอนุกรมนั้นคือการเขียนข้อมูลคลาสไปยังไฟล์ / สตรีม / ฯลฯ การทำให้เป็นอนุกรมกำลังอ่านข้อมูลนั้นกลับไปที่คลาส

  2. คุณตั้งใจจะไปผลิตหรือไม่?

    หากคุณเป็นเพียงการทดสอบบางอย่างกับข้อมูลที่ไม่สำคัญ / ข้อมูลปลอมไม่ต้องกังวลเกี่ยวกับมัน (ยกเว้นกรณีที่คุณกำลังทดสอบการทำให้เป็นอนุกรมโดยตรง)

  3. นี่เป็นรุ่นแรกหรือไม่

    serialVersionUID=1Lถ้าเป็นเช่นนั้นชุด

  4. นี่เป็นรุ่นที่สองสามและอื่น ๆ หรือไม่

    ตอนนี้คุณต้องกังวลserialVersionUIDและควรมองลึกลงไป

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


2

'serialVersionUID' เป็นหมายเลข 64 บิตที่ใช้ในการระบุคลาสในระหว่างกระบวนการดีซีเรียลไลเซชัน เมื่อคุณซีเรียลไลซ์วัตถุ serialVersionUID ของคลาสจะถูกเขียนไปยังไฟล์ เมื่อใดก็ตามที่คุณ deserialize วัตถุนี้เวลารันจาวาแยกค่า serialVersionUID นี้จากข้อมูลที่เป็นอนุกรมและเปรียบเทียบค่าเดียวกันเชื่อมโยงกับชั้นเรียน หากทั้งคู่ไม่ตรงกันดังนั้น 'java.io.InvalidClassException' จะถูกส่งออกไป

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


1

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

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

  • คุณได้เก็บข้อมูลของคุณไว้ในไฟล์บางไฟล์แล้วหลังจากนั้นให้ลองเปิดด้วยเวอร์ชันที่อัปเดตของโปรแกรมด้วยวัตถุที่ถูกดัดแปลง - คุณจะรู้ว่าไฟล์นี้เข้ากันไม่ได้หากคุณรักษาเวอร์ชันของคุณไว้อย่างถูกต้อง

มันสำคัญเมื่อไหร่?

ชัดเจนมากที่สุด - ถ้าคุณเพิ่มเขตข้อมูลบางอย่างลงในวัตถุของคุณรุ่นที่เก่ากว่าจะไม่สามารถใช้งานได้เนื่องจากไม่มีเขตข้อมูลเหล่านี้ในโครงสร้างวัตถุ

ชัดเจนน้อยลง - เมื่อคุณ deserialize วัตถุเขตข้อมูลที่ไม่มีอยู่ในสายอักขระจะถูกเก็บเป็น NULL หากคุณลบฟิลด์ออกจากวัตถุของคุณเวอร์ชันเก่าจะเก็บฟิลด์นี้เป็น allways-NULL ที่สามารถนำไปสู่การประพฤติผิดหากเวอร์ชันเก่าใช้ข้อมูลในฟิลด์นี้ (คุณได้สร้างไว้เพื่ออะไรไม่ใช่เพื่อความสนุก :-))

ชัดเจนน้อยที่สุด - บางครั้งคุณเปลี่ยนความคิดที่คุณใส่ในความหมายของบางฟิลด์ ตัวอย่างเช่นเมื่อคุณอายุ 12 ปีคุณหมายถึง "จักรยาน" ภายใต้ "จักรยาน" แต่เมื่อคุณอายุ 18 คุณหมายถึง "มอเตอร์ไซค์" - หากเพื่อนของคุณจะเชิญคุณให้ "ขี่จักรยานข้ามเมือง" และคุณจะเป็นคนเดียวที่ มากับจักรยานคุณจะแยกแยะว่ามันสำคัญแค่ไหนที่จะรักษาความหมายเดิมไว้ในทุ่งนา :-)


1

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

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


0

SerialVersionUID คืออะไร คำตอบ: - ให้บอกว่ามีสองคนหนึ่งคนจาก HQ และอีกคนหนึ่งจาก ODC ทั้งคู่กำลังทำการซีเรียลไลซ์เซชั่นและดีซีเรียลไลเซชันตามลำดับ ในกรณีนี้เพื่อตรวจสอบว่าผู้รับที่อยู่ใน ODC เป็นบุคคลที่ผ่านการตรวจสอบแล้ว JVM สร้าง ID ที่ไม่ซ้ำซึ่งรู้จักกันในชื่อ SerialVersionUID

นี่คือคำอธิบายที่ดีตามสถานการณ์

ทำไมต้อง SerialVersionUID

การทำให้เป็นอันดับ : ในเวลาของการทำให้เป็นอนุกรมกับทุกด้านผู้ส่ง JVM จะบันทึกตัวระบุที่ไม่ซ้ำกัน JVM มีหน้าที่สร้าง ID เฉพาะนั้นตามไฟล์. class ที่เกี่ยวข้องซึ่งมีอยู่ในระบบผู้ส่ง

Deserialization : ในช่วงเวลาของการดีซีเรียลไลเซชั่นฝั่งผู้รับ JVM จะทำการเปรียบเทียบ ID เฉพาะที่เชื่อมโยงกับ Object กับ Unique ID ระดับท้องถิ่นเช่น JVM จะสร้าง Unique ID ตามไฟล์. class ที่เกี่ยวข้องซึ่งมีอยู่ในระบบรับ หาก ID ที่ไม่ซ้ำกันทั้งคู่ตรงกันจะทำการดีซีเรียลไลซ์เซชั่นเท่านั้น มิฉะนั้นเราจะได้รับ Runtime Exception แจ้งว่า InvalidClassException ตัวระบุที่ไม่ซ้ำกันนี้ไม่มีอะไรนอกจาก SerialVersionUID

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