۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

نحوه کپی کردن اشیا در جاوا: کپی کم عمق و کپی عمیق

کپی کردن اشیا یک عملیات برنامه نویسی رایج است که یک دام جدی دارد. در اینجا نحوه جلوگیری از کپی کردن از یک مرجع شی و فقط کپی کردن نمونه و مقادیر مورد نظر شما آمده است.

کپی کردن اشیا یک عملیات برنامه نویسی رایج است که یک دام جدی دارد. در اینجا نحوه جلوگیری از کپی کردن از یک مرجع شی و فقط کپی کردن نمونه و مقادیر مورد نظر شما آمده است.

کپی کردن اشیا یک عملیات رایج در پروژه های سازمانی است. هنگام کپی کردن یک شی، باید اطمینان حاصل کنیم که در نهایت با یک نمونه جدید مواجه می شویم که مقادیر مورد نظر ما را در خود نگه می دارد.

اشیاء دامنه معمولاً پیچیده هستند. ایجاد یک کپی با شی ریشه و اشیاء ترکیبی نیز بی اهمیت نیست.

بیایید مؤثرترین راه‌ها برای کپی کردن یک شی با استفاده از تکنیک‌های کپی کم عمق و عمیق را بررسی کنیم.

مرجع شی

برای اجرای صحیح یک کپی شی کم عمق یا عمیق، ابتدا باید بدانیم چه کاری را نباید انجام داد. درک ارجاعات شیء برای استفاده از تکنیک های کپی کم عمق و عمیق ضروری است.

هنگام کپی کردن یک شی، مهم است که از استفاده از همان مرجع شیء خودداری کنید. همانطور که این مثال نشان می دهد این یک اشتباه آسان است. برای شروع، در اینجا شی Product است که در مثال های خود استفاده خواهیم کرد:


public class Product {

  private String name;
  private double price;

  public Product(String name, double price) {
    this.name = name;
    this.price = price;
  }

  public String getName() { return name; }
  public double getPrice() { return price; }

  public void setName(String name) { this.name = name; }
  public void setPrice(double price) { this.price = price; }

}

حالا، بیایید یک مرجع شی Product را ایجاد و به متغیر دیگری اختصاص دهیم. به نظر می رسد یک کپی است، اما در واقع، همان شی است:


public static void main(String[] args) {
    Product product = new Product("Macbook Pro", 3000);
    Product copyOfProduct = product;

    product.name = "Alienware";
    System.out.println(product.name);
    System.out.println(copyOfProduct.name);
  }

خروجی این کد

است


Alienware
Alienware

در کد بالا توجه کنید که ما مقدار شی را به یک متغیر محلی دیگر اختصاص می دهیم، اما این متغیر به همان مرجع شی اشاره می کند. اگر اشیاء product یا copyOfProduct را تغییر دهیم، نتیجه تغییر به شی Product اصلی خواهد بود.

این به این دلیل است که هر بار که یک شی در جاوا ایجاد می کنیم، یک مرجع شی در پشته حافظه جاوا ایجاد می شود. این به ما امکان می دهد اشیاء را با استفاده از متغیرهای مرجع آنها تغییر دهیم.

اطلاعات بیشتر در مورد مراجع شی

به مقاله InfoWorld من مراجعه کنید، آیا جاوا با مرجع عبور می کند یا با مقدار عبور می کند؟ برای کاوش عمیق تر در مورد ارجاعات شی.

کپی کم عمق

تکنیک کپی کم عمق به ما امکان می دهد مقادیر شیء ساده را بدون درج مقادیر شیء داخلی در یک شی جدید کپی کنیم. به عنوان مثال، در اینجا نحوه استفاده از تکنیک کپی سطحی برای کپی کردن شی Product بدون استفاده از مرجع شی آن آورده شده است:


// Omitted the Product object

public class ShallowCopyPassingValues {
  public static void main(String[] args) {
    Product product = new Product("Macbook Pro", 3000);
    Product copyOfProduct = new Product(product.getName(), product.getPrice());

    product.setName("Alienware");
    System.out.println(product.getName());
    System.out.println(copyOfProduct.getName());
  }
}

خروجی

JSON چیست؟ قالب داده جهانی

است


Alienware
Macbook Pro

در این کد توجه کنید که وقتی مقادیر را از یک شی به شی دیگر منتقل می کنیم، دو شی متفاوت در پشته حافظه ایجاد می شود. وقتی یکی از مقادیر را در شی جدید تغییر می دهیم، مقادیر در شی اصلی ثابت می مانند. این ثابت می کند که اشیاء متفاوت هستند و ما کپی کم عمق را با موفقیت اجرا کرده ایم.

توجه: الگوی طراحی سازنده روش دیگری است برای انجام همان عمل.

کپی کم عمق با Cloneable

از جاوا ۷، ما رابط Cloneable را در جاوا داریم. این رابط راه دیگری برای کپی کردن اشیا ارائه می دهد. به جای پیاده سازی منطق کپی به صورت دستی، همانطور که قبلا انجام دادیم، می توانیم رابط Cloneable را پیاده سازی کنیم و سپس متد clone() را پیاده سازی کنیم. استفاده از Cloneable و روش clone() به طور خودکار منجر به یک کپی کم عمق می شود.

من از این تکنیک خوشم نمی‌آید، زیرا یک استثنا علامت زده می‌کند، و ما باید به صورت دستی یک نوع کلاس ارسال کنیم، که باعث می‌شود کد مفصل‌تر شود. اما استفاده از Cloneable ممکن است کد را ساده‌تر کند اگر یک شی دامنه بزرگ با ویژگی‌های زیاد داشته باشیم.

اگر رابط Cloneable را در یک شی دامنه پیاده‌سازی کنیم و سپس روش clone() را لغو کنیم، این اتفاق می‌افتد:


public class Product implements Cloneable {

  // Omitted attributes, methods and constructor

  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

}

اکنون، روش کپی دوباره در عمل آمده است:


public class ShallowCopyWithCopyMethod {
  public static void main(String[] args) throws CloneNotSupportedException {
    Product product = new Product("Macbook Pro", 3000);
    Product copyOfProduct = (Product) product.clone();

    product.setName("Alienware");
    System.out.println(product.getName());
    System.out.println(copyOfProduct.getName());
  }
}

همانطور که می بینید، روش کپی برای ایجاد یک کپی کم عمق از یک شی کاملاً کار می کند. استفاده از آن به این معنی است که ما نیازی به کپی کردن هر ویژگی به صورت دستی نداریم.

کپی عمیق

تکنیک کپی عمیق توانایی کپی کردن مقادیر یک شیء ترکیبی به یک شی جدید دیگر است. برای مثال، اگر شی Product حاوی شی Category باشد، انتظار می رود که تمام مقادیر هر دو شی در یک شی جدید کپی شوند.

اگر شی Product دارای یک شیء ترکیبی باشد چه اتفاقی می افتد؟ آیا تکنیک کپی سطحی کار می کند؟ بیایید ببینیم اگر سعی کنیم فقط از روش copy() استفاده کنیم چه اتفاقی می‌افتد.

برای شروع، کلاس Product را با شی Order می سازیم:


public class Product implements Cloneable {
  
  // Omitted other attributes, constructor, getters and setters
  private Category category;

  public Category getCategory() { return category; }

}

حالا، بیایید همین کار را با استفاده از روش super.clone() انجام دهیم:


public class TryDeepCopyWithClone {

  public static void main(String[] args) throws CloneNotSupportedException {
    Category category = new Category("Laptop", "Portable computers");
    Product product = new Product("Macbook Pro", 3000, category);
    Product copyOfProduct = (Product) product.clone();

    Category copiedCategory = copyOfProduct.getCategory();

    System.out.println(copiedCategory.getName());
  }
}

خروجی

نمودارهای مجدد: مقدمه ای بر نمودارهای جاوا اسکریپت

است


Laptop

توجه داشته باشید که حتی اگر خروجی “لپ تاپ” است، عملیات کپی عمیق انجام نشد. در عوض آنچه اتفاق افتاد این است که ما همان مرجع شی Category را داریم. در اینجا اثبات است:


public class TryDeepCopyWithClone {
  public static void main(String[] args) throws CloneNotSupportedException {
    // Same code as the example above
    
    copiedCategory.setName("Phone");
    System.out.println(copiedCategory.getName());
    System.out.println(category.getName());
  }
}

خروجی:


Laptop
Phone
Phone

در این کد توجه کنید که وقتی شی Category را تغییر دادیم، کپی ایجاد نشد. در عوض، تنها یک انتساب شی به یک متغیر دیگر وجود داشت. بنابراین، هر زمان که متغیر مرجع را تغییر دهیم، شی ای را که در پشته حافظه ایجاد کرده ایم، تغییر می دهیم.

کپی عمیق با روش clone()

اکنون می دانیم که روش clone() برای یک کپی عمیق در صورتی که یک لغو ساده داشته باشیم کار نخواهد کرد. بیایید ببینیم چگونه می‌توانیم این کار را انجام دهیم.

ابتدا، Cloneable را در کلاس Category پیاده‌سازی می‌کنیم:


public class Category implements Cloneable {

  // Omitted attributes, constructor, getters and setters

  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

اکنون، باید پیاده‌سازی روش کلون Product را نیز برای شبیه‌سازی شی Category تغییر دهیم:


public class ProductWithDeepCopy implements Cloneable {

  // Omitted attributes, constructor, getters and setters

  @Override
  protected Object clone() throws CloneNotSupportedException {
    this.category = (Category) category.clone();
    return super.clone();
  }
}

اگر بخواهیم کپی عمیق را با همان مثال کد بالا انجام دهیم، یک کپی واقعی از مقادیر شیء در یک شی جدید دریافت خواهیم کرد، همانطور که در اینجا نشان داده شده است:


public class TryDeepCopyWithClone {

  public static void main(String[] args) throws CloneNotSupportedException {
    Category category = new Category("Laptop", "Portable computers");
    Product product = new Product("Macbook Pro", 3000, category);
    Product copyOfProduct = (Product) product.clone();

    Category copiedCategory = copyOfProduct.getCategory();

    System.out.println(copiedCategory.getName());
    copiedCategory.setName("Phone");
    System.out.println(copiedCategory.getName());
    System.out.println(category.getName());
  }
}

خروجی

است


Laptop
Phone
Laptop

از آنجایی که ما به صورت دستی روش دسته را در روش copy() Product کپی کردیم، در نهایت کار می کند. ما یک کپی از محصول و دسته با استفاده از روش copy() از Product دریافت خواهیم کرد.

این کد ثابت می کند که کپی عمیق کار می کند. مقادیر اشیاء اصلی و کپی شده متفاوت است. بنابراین، این یک نمونه نیست. این یک شیء کپی شده است.

کپی کم عمق با سریال سازی

گاهی اوقات لازم است که یک شیء را سریالی کنیم تا آن را به بایت تبدیل کنیم و از طریق شبکه عبور دهیم. این عملیات می تواند خطرناک باشد، زیرا اگر به درستی اعتبار سنجی نشود، ممکن است شیء سریال شده مورد سوء استفاده قرار گیرد. امنیت سریال سازی جاوا خارج از محدوده این مقاله است، اما بیایید ببینیم که چگونه با کد کار می کند.

ما از همان کلاس مثال بالا استفاده خواهیم کرد، اما این بار، رابط Serializable را پیاده سازی خواهیم کرد:


public class Product implements Serializable, Cloneable {
   // Omitted attributes, constructor, getters, setters and clone method
}

توجه داشته باشید که فقط محصول سریال می شود زیرا فقط محصول Serializable را پیاده سازی می کند. شی Category سریال نخواهد شد. این یک مثال است:


public class ShallowCopySerializable {

  public static void main(String[] args) {
      try {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);

        Product product = new Product("Macbook Pro", 3000);
        out.writeObject(product);
        out.flush();
        out.close();

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bis);
        Product clonedProduct = (Product) in.readObject();
        in.close();

        System.out.println(clonedProduct.getName());
        Category clonedCategory = clonedProduct.getCategory();
        System.out.println(clonedCategory);
      } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
      }
  }
}

خروجی

چگونه اتوماسیون هوشمند CI/CD را تغییر می دهد

است


Macbook Pro
null

اکنون، اگر بخواهیم شی Category را در شی Product پر کنیم، java.io.NotSerializableException پرتاب می شود. دلیل آن این است که شی Category Serializable را پیاده سازی نمی کند.

کپی عمیق با سریال سازی

اکنون،  بیایید ببینیم اگر از همان کد بالا استفاده کنیم اما موارد زیر را در کلاس Category اضافه کنیم، چه اتفاقی می‌افتد:


public class Category implements Serializable, Cloneable {
  // Omitted attributes, constructor, getters, setters and clone method

  // Adding toString for a good Object description
  @Override
  public String toString() {
    return "Category{" + "name='" + name + '\'' + ", description='" + description + '\'' + '}';
  }
}

با اجرای کد مشابه با کد کپی سریالی کم عمق، نتیجه را از Category نیز دریافت خواهیم کرد و خروجی باید به صورت زیر باشد:


Macbook Pro
Category{name='Laptop', description='Portable computers'}

توجه: برای بررسی بیشتر سریال‌سازی جاوا، چالش کد جاوا را امتحان کنید اینجا.

نتیجه گیری

گاهی اوقات تکنیک کپی سطحی تنها چیزی است که برای کلون کردن یک شی به صورت سطحی نیاز دارید. اما وقتی می خواهید هم شی و هم اشیاء داخلی آن را کپی کنید، باید یک کپی عمیق را به صورت دستی پیاده سازی کنید. در اینجا نکات کلیدی این تکنیک های مهم آمده است.

چه چیزی را در مورد کپی کم عمق به خاطر بسپارید

  • یک کپی کم عمق یک شی جدید ایجاد می کند اما ارجاعات اشیاء داخلی را با شی اصلی به اشتراک می گذارد.
  • اشیاء کپی شده و اصلی به همان اشیاء در حافظه اشاره دارند.
  • تغییرات ایجاد شده در اشیاء داخلی از طریق یک مرجع، در هر دو شیء کپی شده و اصلی منعکس خواهد شد.
  • کپی کم عمق یک فرآیند ساده و کارآمد است.
  • جاوا پیاده سازی پیش فرض کپی کم عمق را از طریق روش clone() ارائه می دهد.

چه چیزی را در مورد کپی عمیق باید به خاطر بسپارید

  • یک کپی عمیق یک شی جدید ایجاد می کند و همچنین کپی های جدیدی از اشیاء داخلی آن ایجاد می کند.
  • اشیاء کپی شده و اصلی دارای کپی مستقل از اشیاء داخلی هستند.
  • تغییراتی که از طریق یک مرجع در اشیاء داخلی ایجاد می شود، روی دیگری تأثیری نخواهد داشت.
  • کپی عمیق فرآیند پیچیده تری است، به ویژه هنگامی که با نمودارهای شی یا ارجاعات تودرتو سروکار داریم.
  • کپی عمیق باید به طور صریح، به صورت دستی یا با استفاده از کتابخانه ها یا چارچوب ها اجرا شود.

این داستان، “نحوه کپی کردن اشیاء در جاوا: کپی کم عمق و کپی عمیق” در ابتدا توسط

JavaWorld.