در علوم رایانه، «باکسینگ» یا «بستهبندی» (که با عنوان wrapping نیز شناخته میشود)، به فرایند قرار دادن یک نوع دادهی ابتدایی (primitive type) درون یک شیء اطلاق میشود؛ بهگونهای که بتوان از آن مقدار بهعنوان یک مرجع (reference) استفاده کرد. «آنباکسینگ» یا «بازگشایی» (unboxing) به فرایند معکوس این عملیات اشاره دارد، یعنی استخراج مقدار اولیه از شیء بستهبندیشده. همچنین، اصطلاح «اتوباکسینگ» یا «بازگشایی خودکار» (autoboxing) به اعمال خودکار عملیات باکسینگ و/یا آنباکسینگ در زمان نیاز گفته میشود.[۱]
بستهبندی
کاربرد برجستهٔ بستهبندی در زبان جاوا دیده میشود؛ جایی که بهدلایلی مانند بهینهسازی عملکرد در زمان اجرا و تفاوتهای نحوی و معنایی، میان نوعهای ارجاعی (reference types) و مقداری (value types) تمایز وجود دارد. در جاوا، یک ساختار داده مانند LinkedList
تنها میتواند مقادیری از نوع Object
را در خود ذخیره کند. اگرچه ممکن است بخواهیم یک LinkedList
از نوع int
داشته باشیم، این کار بهصورت مستقیم امکانپذیر نیست. در عوض، جاوا برای هر نوع دادهٔ ابتدایی (primitive type)، یک کلاس بستهبندی (primitive wrapper class) متناظر تعریف کردهاست، مانند:
Integer
برایint
Character
برایchar
Float
برایfloat
- و دیگر انواع مشابه.
بدین ترتیب، میتوان یک LinkedList
با استفاده از نوع بستهبندیشدهٔ Integer
تعریف کرد و مقادیر int
را از طریق باکسینگ، بهصورت شیءهای Integer
در آن قرار داد. (با معرفی نوعهای عمومی پارامتری (generic parameterized types) در نسخهٔ J2SE 5.0، این ساختار بهصورت LinkedList<Integer>
تعریف میشود.)
از سوی دیگر، زبان #C کلاسهای بستهبندیِ نوعهای ابتدایی (primitive wrapper classes) را ندارد، اما امکان باکسکردن هر نوع مقداری (value type) را فراهم میکند؛ بهطوریکه نتیجه بهصورت یک مرجع از نوع کلی Object
بازگردانده میشود.در زبان Objective-C نیز میتوان هر مقدار ابتدایی را با قراردادن نماد @
در ابتدای آن به یک شیء از نوع NSNumber
تبدیل کرد (برای مثال: @123
یا @(123)
). این قابلیت امکان افزودن چنین مقادیری را به مجموعههای استانداردی مانند NSArray
فراهم میسازد.
در زبان Haskell، تقریباً هیچ مفهومی تحت عنوان «نوع ارجاعی» (reference type) وجود ندارد؛ با این حال، از اصطلاح «باکسشده» (boxed) برای اشاره به نمایش یکنواختِ «اشارهگر به ساختار union دارای برچسب» در زمان اجرا استفاده میشود.[۲]
شیء باکسشده همواره نسخهای کپی از مقدار اولیه است و معمولاً این شیء تغییرناپذیر (immutable) میباشد. بازگشایی (unboxing) نیز نسخهای کپی از مقدار ذخیرهشده را بازمیگرداند. تکرار عملیات بستهبندی و بازگشایی میتواند تأثیر منفی چشمگیری بر عملکرد داشته باشد؛ چراکه بستهبندی بهصورت پویا اشیای جدیدی ایجاد میکند و بازگشایی، در صورتی که مقدار باکسشده دیگر استفاده نشود، باعث میشود آن اشیا برای بازیافت حافظه (garbage collection) در دسترس قرار گیرند. با این حال، بازیابهای حافظهٔ مدرن، مانند بازیاب پیشفرض Java HotSpot، میتوانند اشیای کوتاهعمر را با کارایی بیشتری مدیریت کنند. بنابراین، در صورتی که اشیای باکسشده عمر کوتاهی داشته باشند، تأثیر آنها بر عملکرد ممکن است چندان محسوس نباشد.
در برخی زبانهای برنامهنویسی، میان نوعهای ابتداییِ بدون بستهبندی (unboxed primitives) و ارجاع به اشیای بستهبندیشدهٔ تغییریناپذیر (immutable boxed objects)، برابری مستقیمی وجود دارد. در واقع، این امکان وجود دارد که تمامی نوعهای ابتداییِ بهکاررفته در یک برنامه با نوعهای شیء بستهبندیشده جایگزین شوند. در حالیکه انتساب از یک نوع ابتدایی به نوع ابتدایی دیگر، مقدار را کپی میکند، انتساب از یک ارجاع به شیء بستهبندیشده به ارجاع دیگر، تنها مقدار ارجاع را منتقل میکند؛ بهگونهای که هر دو ارجاع به یک شیء یکسان اشاره میکنند. با این حال، این مسئله مشکلی ایجاد نمیکند، زیرا این اشیاء تغییریناپذیر هستند، و از نظر معنایی تفاوتی میان دو ارجاع به یک شیء یا ارجاع به دو شیء جداگانه با مقدار برابر وجود ندارد مگر آنکه برابری فیزیکی (physical equality) مورد نظر باشد. برای تمام عملیات بهجز انتساب، مانند عملیات ریاضی، مقایسهای و منطقی، میتوان ابتدا مقدار بستهبندیشده را بازگشایی کرد (unbox)، عملیات را انجام داد، و در صورت نیاز، نتیجه را مجدداً بستهبندی نمود (re-box). بنابراین، از نظر نظری، این امکان وجود دارد که نوعهای ابتدایی بهطور مستقیم ذخیره نشوند
بستهبندی خودکار
اصطلاح «بستهبندی خودکار» (Autoboxing) به فرایندی اطلاق میشود که طی آن یک نوع ارجاعی (reference type) تنها از طریق تبدیل نوع (چه بهصورت ضمنی و چه بهصورت صریح) از یک نوع مقداری (value type) بهدست میآید. در این حالت، کامپایلر بهطور خودکار کدی را اضافه میکند که شیء مربوطه را ایجاد مینماید.
بهعنوان مثال، در نسخههای پیش از J2SE 5.0 از زبان جاوا، قطعهکد زیر قابل کامپایل نبود:
Integer i = new Integer(9);
Integer i = 9; // خطا در نسخههای پیش از 5.0!
کامپایلرهای نسخههای پیش از 5.0 خط آخر را نمیپذیرفتند. Integer
یک شیء ارجاعی (reference object) است و در ظاهر تفاوتی با نوعهایی مانند List
، Object
و غیره ندارد. برای تبدیل یک مقدار int
به یک شیء Integer
، برنامهنویس مجبور بود شیء Integer
را بهصورت دستی ایجاد کند. اما از نسخهٔ J2SE 5.0 به بعد، کامپایلر این خط را میپذیرد و بهطور خودکار آن را طوری بازنویسی میکند که شیئی از نوع Integer
برای ذخیرهٔ مقدار ۹ ایجاد شود.[۳] این بدان معناست که از نسخهٔ J2SE 5.0 به بعد، نوشتن عبارتی مانند Integer c = a + b
ــ در حالی که a
و b
خودشان از نوع Integer
باشند ــ قابل کامپایل است. در این حالت، a
و b
بهصورت خودکار «بازگشایی» میشوند (unboxed)، مقادیر عددی آنها با هم جمع میشود، و نتیجه بهطور خودکار دوباره «بستهبندی» شده (autoboxed) و در متغیر c
بهصورت یک شیء جدید Integer
ذخیره میشود. با این حال، نمیتوان از عملگرهای مقایسهای به همین صورت استفاده کرد؛ زیرا این عملگرها برای نوعهای ارجاعی تعریف شدهاند و تنها برابریِ ارجاعها (references) را بررسی میکنند، نه مقدار درون آنها را. برای مقایسهٔ مقدار در نوعهای بستهبندیشده، همچنان باید آنها را بهصورت دستی بازگشایی کرد و مقادیر ابتدایی را مقایسه نمود، یا از متد Objects.equals
استفاده کرد.
مثال دیگر: در نسخهٔ J2SE 5.0، برنامهنویس میتواند با مجموعههایی مانند LinkedList
طوری رفتار کند که گویی شامل مقادیر int
هستند، نه اشیای Integer
. این موضوع با آنچه پیشتر گفته شد تناقضی ندارد؛ زیرا مجموعه همچنان تنها شامل ارجاعهایی به اشیای پویا است و نمیتواند نوعهای ابتدایی را مستقیماً در خود نگه دارد. در واقع، نمیتوان از LinkedList<int>
استفاده کرد، بلکه باید از LinkedList<Integer>
بهره برد. با این حال، کامپایلر بهطور خودکار کد را بهگونهای بازنویسی میکند که لیست «بیسروصدا» اشیای بستهبندیشده را دریافت کند، در حالی که در کد منبع تنها به مقادیر ابتدایی اشاره شده است. برای مثال، برنامهنویس میتواند بنویسد list.add(3)
و تصور کند که مقدار ۳ از نوع int
به لیست افزوده شده است؛ اما در عمل، کامپایلر این خط را به شکل زیر بازنویسی میکند: list.add(new Integer(3))
.
بازگشایی خودکار
در حالت «بازگشایی خودکار» (automatic unboxing)، کامپایلر بهطور خودکار کدی اضافه میکند که مقدار را از شیء بستهبندیشده استخراج میکند؛ این کار ممکن است با فراخوانی یک متد روی آن شیء یا به روشهای دیگر انجام شود.
برای نمونه، در نسخههای پیش از J2SE 5.0 از زبان جاوا، قطعهکد زیر قابل کامپایل نبود:
Integer k = new Integer(4);
int l = k.intValue(); // همیشه مجاز است
int m = k; // در گذشته خطا بود، اما اکنون مجاز است
زبان #C از «بازگشایی خودکار» (automatic unboxing) به همان شکلی که در زبان جاوا پشتیبانی میشود، پشتیبانی نمیکند؛ زیرا در #C مجموعهای مجزا از نوعهای ابتدایی (primitive types) و نوعهای شیء (object types) وجود ندارد. تمامی نوعهایی که در جاوا بهصورت جداگانه بهعنوان نوع ابتدایی و نوع شیء تعریف شدهاند، در #C توسط کامپایلر بهصورت خودکار به یکی از دو شکلِ «نوع مقداری» (value type) یا «نوع ارجاعی» (reference type) پیادهسازی میشوند.
در هر دو زبان، «بستهبندی خودکار» بهطور پیشفرض عملیات «پایینریزی نوع» (downcast) را انجام نمیدهد؛ بهعبارت دیگر، کد زیر قابل کامپایل نخواهد بود:
#C:
int i = 42;
object o = i; // بستهبندی(boxing)
int j = o; // بازگشایی(boxing) - خطا!
Console.WriteLine(j); // این خط اجرا نمیشود؛ نویسنده احتمالاً انتظار داشت خروجی "42" باشد
جاوا:
int i = 42;
object o = i; // بستهبندی(boxing)
int j = o; // بازگشایی(boxing) - خطا!
System.out.println(j); // این خط اجرا نمیشود؛ نویسنده احتمالاً انتظار داشت خروجی "42" باشد
بستهبندی در زبان Rust:
در زبان Rust، ساختاری به نام Box
وجود دارد:[۴]
let number = Box::new(42);
اگر قرار باشد مقدار به یک نخ (thread) دیگر منتقل شود، باید از ساختار Arc
استفاده شود.[۵][۶]
کمککنندههای نوع (Type Helpers):
در زبان Modern Object Pascal، روشی دیگر برای انجام عملیات بر روی نوعهای ساده وجود دارد که شباهت زیادی به باکسینگ (boxing) دارد و با عنوان «کمککنندههای نوع» (type helpers) در FreePascal یا «کمککنندههای رکورد» (record helpers) در Delphi و FreePascal (در حالت Delphi) شناخته میشود.
گویشهای اشارهشده، همگی از خانوادهٔ زبانهای Object Pascal هستند که به کد بومی (native) کامپایل میشوند و از این رو برخی از قابلیتهایی را که در زبانهایی مانند #C و Java وجود دارد، پشتیبانی نمیکنند. از جملهٔ این قابلیتها میتوان به «استنباط نوع در زمان اجرا» (runtime type inference) برای متغیرهایی با نوع ایستا (strongly typed) اشاره کرد.
با اینحال، این قابلیت تا حدی با مفهوم باکسینگ مرتبط است؛ چرا که به برنامهنویس اجازه میدهد از ساختارهایی مانند موارد زیر استفاده کند:
{$ifdef fpc}{$mode delphi}{$endif}
uses sysutils; // این واحد شامل کمککنندههایی برای نوعهای ساده است
var
x:integer=100;
s:string;
begin
s:= x.ToString;
writeln(s);
end.
منابع
- ↑ «Autoboxing and Unboxing (The Java™ Tutorials > Learning the Java Language > Numbers and Strings)». docs.oracle.com. دریافتشده در ۲۰۲۵-۰۴-۰۵.
- ↑ «7.2. Unboxed types and primitive operations». downloads.haskell.org. دریافتشده در ۲۰۲۵-۰۴-۰۵.
- ↑ «Autoboxing». docs.oracle.com. دریافتشده در ۲۰۲۵-۰۴-۰۵.
- ↑ «Box in std::boxed - Rust». doc.rust-lang.org. دریافتشده در ۲۰۲۵-۰۴-۰۵.
- ↑ «Arc - Rust By Example». doc.rust-lang.org. دریافتشده در ۲۰۲۵-۰۴-۰۵.
- ↑ «Arc in std::sync - Rust». doc.rust-lang.org. دریافتشده در ۲۰۲۵-۰۴-۰۵.