سلام! در مقاله امروز ما به اصلاح کننده گذرا در جاوا خواهیم پرداخت. بیایید در مورد چرایی نیاز به این اصلاح کننده و نحوه استفاده صحیح از آن صحبت کنیم. برو! اصلاح کننده گذرا در جاوا چه چیزی را پنهان می کند - 1

سریال سازی را به یاد بیاوریم

اصلاح کننده transientدر فرآیند سریال سازی و سریال زدایی اشیاء استفاده می شود. بنابراین اجازه دهید ابتدا به طور خلاصه در این مورد صحبت کنیم. اصلاح کننده گذرا در جاوا - 2 چه چیزی را پنهان می کندفرض کنید یک شی داریم، و آن فیلدهایی دارد که هر کدام مقداری ارزش دارند. همه اینها حالت جسم نامیده می شود. سریال سازی تبدیل حالت یک شی به دنباله ای از بایت است. این بایت ها معمولا در برخی از فایل ها ذخیره می شوند. سریال زدایی فرآیند معکوس است. بیایید تصور کنیم که یک شی را به صورت بایت سریال کرده و این مجموعه از بایت ها را در یک فایل ذخیره می کنیم. هنگام deserialization، برنامه به موارد زیر نیاز دارد:
  1. مجموعه ای از بایت ها را از یک فایل بخوانید.
  2. یک شی اولیه از این مجموعه بایت بسازید و هر فیلد را به مقداری که شی در زمان سریال سازی داشت تنظیم کنید.
چه زمانی این ممکن است مفید باشد؟ به عنوان مثال، زمانی که می خواهیم برنامه هنگام خاموش شدن حالت خود را ذخیره کند و دفعه بعد که روشن شد آن را بازیابی کند. وقتی IntelliJ IDEA را خاموش می‌کنید، به احتمال زیاد بار بعدی که آن را روشن می‌کنید همان برگه‌ها و کلاس‌ها را خواهید داشت.

سریال سازی را در عمل به یاد بیاوریم

خب حالا بیایید سریال سازی را در عمل بررسی کنیم. اگر می خواهید موضوع را بهتر درک کنید، توصیه می کنیم مطالب Serialization and deserialization در جاوا را مطالعه کنید . خوب، در این مقاله به بالا می پردازیم و مستقیماً به سراغ مثال ها می رویم. فرض کنید یک کلاس داریم Userبا مجموعه ای از چند فیلد، گیرنده و تنظیم کننده، و یک متد toString:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private String password;

    public User() {}

    public User(String firstName, String lastName, String email, LocalDate birthDate, String login, String password) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.birthDate = birthDate;
        this.login = login;
        this.password = password;
    }

    /*
        Геттеры, Сеттеры
     */

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", birthDate=" + birthDate +
                ", login='" + login + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
ما می خواهیم در آینده اشیاء این کلاس را سریال کنیم. بیایید روشی بنویسیم که یک شی Userو یک رشته را می گیرد path- مسیر فایلی که در آن بایت ها را ذخیره می کنیم:
static void serialize(User user, String path) throws IOException {
    FileOutputStream outputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        //create 2 threads to serialize the object and save it to a file
        outputStream = new FileOutputStream(path);
        objectOutputStream = new ObjectOutputStream(outputStream);

        // сохраняем an object в файл
        objectOutputStream.writeObject(user);
    } finally {
        // Закроем потоки в блоке finally
        if (objectOutputStream != null) {
            objectOutputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
    }
}
ما همچنین یک روش برای deserialization خواهیم نوشت. متد یک رشته می گیرد path(مسیر فایلی که شی از آن بارگذاری می شود) و یک شی از نوع را برمی گرداند User:
static User deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fileInputStream = null;
    ObjectInputStream objectInputStream = null;

    try {

        //создаем 2 потока для десериализации an object из file
        fileInputStream = new FileInputStream(path);
        objectInputStream = new ObjectInputStream(fileInputStream);

        //загружаем an object из file
        return  (User) objectInputStream.readObject();
    } finally {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
        if (objectInputStream != null) {
            objectInputStream.close();
        }
    }
}
همه ابزارها آماده استفاده هستند. وقت آن است که بایت ها را به اتم ها تقسیم کنیم . بیایید متدی بنویسیم mainکه در آن یک شی کلاس ایجاد کرده Userو آن را سریالی کنیم. سپس آن را بارگیری می کنیم و با آنچه در ابتدا بود مقایسه می کنیم:
public static void main(String[] args) throws IOException, ClassNotFoundException {
    // вставьте свой путь до file
    final String path = "/home/zor/user.ser";

    // create our object
    User user = new User();
    user.setFirstName("Stefan");
    user.setLastName("Smith");
    user.setEmail("ssmith@email.com");
    user.setBirthDate(LocalDate.of(1991, 7, 16));
    user.setLogin("ssmith");
    user.setPassword("gemma_arterton_4ever_in_my_heart91");

    System.out.println("Initial user: " + user + "\r\n");


    serialize(user, path);
    User loadedUser = deserialize(path);
    System.out.println("Loaded user from file: " + loadedUser + "\r\n");
}
اگر متد را اجرا کنیم، خروجی زیر را خواهیم دید:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
همانطور که از خروجی می بینید، اشیاء یکسان هستند. اما یک اما کوچک وجود دارد... و این دقیقاً جایی است که شرم اسپانیایی transient وارد عمل می شود .

اصلاح کننده (در نهایت)transient

آیا کسی گیج شده بود که رمز عبور کاربر را ذخیره کرده ایم؟ مخصوصا همچین پسوردی... بله، بله، خودمان به آن رسیدیم، اما باز هم... گاهی اوقات شرایطی پیش می آید که برخی از فیلدها را نمی توان سریال کرد یا بهتر است این کار را انجام ندهید. در مثال بالا، من می خواهم تمام فیلدها به جز رمز عبور را ذخیره کنم. چگونه می توان به این امر دست یافت؟ پاسخ: از اصلاح کننده استفاده کنید transient. transientاصلاح کننده ای است که قبل از یک فیلد کلاس قرار می گیرد (مشابه سایر اصلاح کننده ها مانند publicو finalغیره) تا نشان دهد که فیلد نباید سریال شود. فیلدهایی که با کلمه کلیدی علامت گذاری شده اند transientسریالی نیستند. حالا بیایید مثال را با کاربر خود ویرایش کنیم تا یک سردرگمی کوچک را اصلاح کنیم و رمز عبور کاربر را ذخیره نکنیم. برای این کار فیلد مربوطه را در کلاس با کلمه کلیدی علامت بزنید transient:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private transient String password;

    /*
        Конструкторы, геттеры, сеттеры, toString...
     */
}
اگر روش مثال بالا را دوباره اجرا کنیم main، خواهیم دید که رمز عبور ذخیره نشده است:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='null'}
عالی است، ما به هدف خود رسیدیم و اطلاعات محرمانه را ذخیره نمی کنیم. مخصوصا این نوع اطلاعات... (با عرض پوزش)

چه زمانی از گذرا استفاده کنیم؟

یک مثال با یک کاربر برای فرو رفتن در زمینه سریال سازی مورد نیاز بود. حالا بیایید به طور خاص در مورد زمان استفاده از اصلاح کننده صحبت کنیم transient.

  • فیلدهایی که به صورت برنامه نویسی محاسبه می شوند

برخی از کلاس ها گاهی دارای فیلدهایی هستند که بر اساس فیلدهای دیگر یا اطلاعات دیگر محاسبه می شوند. آنها به اصطلاح، در پرواز محاسبه می شوند. برای مثالی از چنین زمینه ای، بیایید سفارشی را در یک فروشگاه آنلاین یا برخی از خدمات تحویل غذا تصور کنیم. هر سفارش، در میان اطلاعات دیگر، شامل لیستی از کالاها و هزینه کل است. این به نوبه خود شامل هزینه کل هر محصول است. به نظر می رسد که هزینه نهایی نباید "با دست" تنظیم شود: باید به صورت برنامه ریزی شده محاسبه شود و هزینه تمام کالاها را خلاصه کند. فیلدهایی مانند اینها که باید به صورت برنامه نویسی محاسبه شوند نیازی به سریال سازی ندارند. بنابراین، آنها را با یک اصلاح کننده علامت گذاری می کنیم transient.
class Order implements Serializable {

    private List items;
    private transient BigDecimal totalAmount; //вычисляется на ходу

}

  • فیلدهایی با اطلاعات خصوصی

همچنین کلاس هایی وجود دارند که اطلاعات خصوصی را ذخیره می کنند. نمونه ای از چنین کلاسی را در ابتدای مقاله بررسی کردیم. شما نباید اجازه دهید چنین اطلاعاتی در خارج از JVM درز کند. transientبنابراین، اگر می‌خواهید چنین کلاسی را سریال‌سازی کنید، فیلدهایی با چنین داده‌هایی باید با یک اصلاح‌کننده علامت‌گذاری شوند .

  • فیلدهایی که رابط را پیاده سازی نمی کنندSerializable

گاهی اوقات یک کلاس حاوی فیلدها - اشیاء کلاس های دیگر است که رابط را پیاده سازی نمی کنند Serializable. نمونه هایی از این فیلدها عبارتند از لاگرها، جریان های ورودی/خروجی، اشیایی که اتصالات پایگاه داده و سایر کلاس های کاربردی را ذخیره می کنند. اگر بخواهید شیئی را که دارای فیلدهای غیرقابل سریال سازی است سریال کنید، یک خطا دریافت خواهید کرد java.io.NotSerializableException. برای جلوگیری از این امر، تمام فیلدهایی که رابط را پیاده سازی نمی کنند Serializableباید با یک اصلاح کننده علامت گذاری شوند transient.
public class FileReader implements Serializable {
    // Первые 2 поля не реализуют Serializable
    // Помечаем их How transient поля
    private transient InputStream is;
    private transient BufferedReader buf;
    private String fileName;

    // Constructors, Getters, Setters

    public String readFile() throws IOException {
        try {
            is = new FileInputStream(fileName);
            buf = new BufferedReader(new InputStreamReader(is));
            String line = buf.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line).append("\n");
                line = buf.readLine();
            }
            return sb.toString();
        } finally {
            if (buf != null) {
                buf.close();
            }
            if (is != null) {
                is.close();
            }
        }
    }
}

  • فیلدهایی با اطلاعاتی در مورد وضعیت جسم

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

transientوfinal

نتایج

همین. امروز در مورد اصلاح کننده صحبت کردیم transient:
  1. یاد سریال سازی در تئوری و عمل افتادیم.
  2. متوجه شدیم که برای اینکه برخی از فیلدهای کلاس را سریالی نکنیم، باید با یک اصلاح کننده علامت گذاری شوند transient.
  3. ما بحث کردیم که این اصلاح کننده در چه شرایطی باید استفاده شود. چهار موقعیت از این قبیل وجود داشت:
    1. فیلدهایی که به صورت برنامه نویسی محاسبه می شوند.
    2. فیلدهایی که حاوی اطلاعات سری هستند.
    3. فیلدهایی که رابط را پیاده سازی نمی کنند Serializable.
    4. فیلدهایی که بخشی از وضعیت جسم نیستند.