ওহে! আপনি ইতিমধ্যে "সংশোধনকারী" শব্দটির সাথে পরিচিত। সর্বনিম্নভাবে, আপনি অ্যাক্সেস মডিফায়ার (সর্বজনীন, ব্যক্তিগত) এবং স্ট্যাটিক মডিফায়ারের সম্মুখীন হয়েছেন। আজ আমরা ফাইনাল নামে একটি বিশেষ সংশোধক নিয়ে আলোচনা করব । আপনি আমাদের প্রোগ্রামের চূড়ান্ত সংশোধক "সিমেন্টস" অংশ বলতে পারেন যেখানে ধ্রুবক, দ্ব্যর্থহীন, অপরিবর্তনীয় আচরণ প্রয়োজন। আপনার প্রোগ্রামে তিনটি জায়গা আছে যা আপনি এটি ব্যবহার করতে পারেন: ক্লাস, পদ্ধতি এবং ভেরিয়েবল। জাভাতে স্থির মান: চূড়ান্ত, ধ্রুবক, এবং অপরিবর্তনীয় - 2 এর ক্রম তাদের মাধ্যমে চালানো যাক. যদি চূড়ান্ত সংশোধকটি একটি শ্রেণি ঘোষণায় ব্যবহার করা হয় তবে এর অর্থ হল ক্লাসটি উত্তরাধিকারসূত্রে পাওয়া যাবে না। পূর্ববর্তী পাঠে, আমরা একটি সাধারণ উত্তরাধিকার উদাহরণ ব্যবহার করেছি: আমাদের একটি Animalঅভিভাবক শ্রেণি এবং দুটি শিশু শ্রেণি ছিল: CatএবংDog

public class Animal {
}



public class Cat extends Animal {
   // Fields and methods of the Cat class
}


public class Dog extends Animal {

   // Fields and methods of the Dog class
}
যাইহোক, যদি আমরা ক্লাসে চূড়ান্তAnimal পরিবর্তনকারী ব্যবহার করি , Catএবং Dogক্লাসগুলি এটিকে উত্তরাধিকারী করতে পারে না।

public final class Animal {

}

public class Cat extends Animal {

   // Error! Cannot inherit from final Animal
}
কম্পাইলার অবিলম্বে একটি ত্রুটি তৈরি করে। জাভাতে, অনেক চূড়ান্ত ক্লাস ইতিমধ্যেই বাস্তবায়িত হয়েছে। আপনি ঘন ঘন ব্যবহার যাদের মধ্যে, Stringসবচেয়ে সুপরিচিত. তদুপরি, যদি একটি ক্লাস চূড়ান্ত হিসাবে ঘোষণা করা হয় , তবে ক্লাসের সমস্ত পদ্ধতিও চূড়ান্ত হয়ে যায় । ওটার মানে কি? যদি একটি পদ্ধতি চূড়ান্ত সংশোধক ব্যবহার করে ঘোষণা করা হয়, আপনি সেই পদ্ধতিটিকে ওভাররাইড করতে পারবেন না। উদাহরণস্বরূপ, এখানে আমাদের একটি Animalক্লাস রয়েছে যা একটি speak()পদ্ধতি ঘোষণা করে। কিন্তু, কুকুর এবং বিড়াল অবশ্যই বিভিন্ন উপায়ে "কথা বলে"। সুতরাং, আমরা উভয় Catএবং Dogক্লাসে speak() পদ্ধতি ঘোষণা করব, কিন্তু আমরা সেগুলি ভিন্নভাবে প্রয়োগ করব।

public class Animal {
  
   public void speak() {
       System.out.println("Hello!");
   }
}

public class Cat extends Animal {

   @Override
   public void speak() {
       System.out.println("Meow!");
   }
}

public class Dog extends Animal {

   @Override
   public void speak() {
       System.out.println("Woof!");
   }
}
আমরা Catএবং Dogক্লাসগুলিকে প্যারেন্ট ক্লাসে ঘোষিত পদ্ধতিটিকে ওভাররাইড করেছি৷ এখন, একটি প্রাণী ভিন্নভাবে কথা বলবে, এটি কি ধরনের বস্তুর উপর নির্ভর করে:

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       Dog dog = new Dog();
      
       cat.speak();
       dog.speak();
   }
}
আউটপুট: মিউ! উফ ! যাইহোক, যদি আমরা Animalক্লাসের speak()পদ্ধতিকে চূড়ান্ত হিসাবে ঘোষণা করি, তাহলে আমরা অন্য ক্লাসে এটিকে ওভাররাইড করতে পারি না:

public class Animal {

   public final void speak() {
       System.out.println("Hello!");
   }
}


public class Cat extends Animal {

   @Override
   public void speak() {// Error! A final method can't be overridden!
       System.out.println("Meow!");
   }
}
speak()এবং আমাদের বস্তুগুলিকে প্যারেন্ট ক্লাসে সংজ্ঞায়িত পদ্ধতিটি ব্যবহার করতে বাধ্য করা হবে :

public static void main(String[] args) {

   Cat cat = new Cat();
   Dog dog = new Dog();

   cat.speak();
   dog.speak();
}
আউটপুট: হ্যালো! হ্যালো! এখন, চূড়ান্ত ভেরিয়েবল সম্পর্কে. তারা ধ্রুবক হিসাবেও পরিচিত । প্রথমত (এবং সবচেয়ে গুরুত্বপূর্ণ), একটি ধ্রুবক মানের জন্য নির্ধারিত প্রাথমিক মান পরিবর্তন করা যাবে না। এটা একবার এবং সব জন্য বরাদ্দ করা হয়.

public class Main {
  
   private static final int CONSTANT_EXAMPLE = 333;

   public static void main(String[] args) {

       CONSTANT_EXAMPLE = 999;// Error! You can't assign a new value to a final variable!
   }
}
একটি ধ্রুবক অবিলম্বে আরম্ভ করা প্রয়োজন হয় না. সেটা পরে করা যাবে। তবে, প্রাথমিকভাবে এটির জন্য নির্ধারিত মান চিরকাল একই থাকবে।

public static void main(String[] args) {

   final int CONSTANT_EXAMPLE;

   CONSTANT_EXAMPLE = 999;// This is allowed
}
দ্বিতীয়ত, আমাদের ভেরিয়েবলের নাম নোট করুন। জাভা ধ্রুবক জন্য একটি ভিন্ন নামকরণ প্রথা আছে. এটি সাধারণ ক্যামেলকেস স্বরলিপি নয় । যদি এটি একটি সাধারণ ভেরিয়েবল হত তবে আমরা এটিকে ধ্রুবক উদাহরণ বলতাম. কিন্তু, ধ্রুবকের নামগুলি সমস্ত ক্যাপগুলিতে লেখা হয়, শব্দগুলির মধ্যে আন্ডারস্কোর সহ (যদি একাধিক শব্দ থাকে), যেমন "CONSTANT_EXAMPLE"৷ কেন আমরা ধ্রুবক প্রয়োজন? এগুলি খুব দরকারী যদি, উদাহরণস্বরূপ, আপনি একটি প্রোগ্রামে নিয়মিত ব্যবহার করেন এমন একটি নির্দিষ্ট মান থাকে। উদাহরণস্বরূপ, আপনি ইতিহাস তৈরি করার এবং নিজের দ্বারা "দ্য উইচার 4" গেমটি লেখার সিদ্ধান্ত নিয়েছেন। গেমটি স্পষ্টতই নিয়মিতভাবে নায়কের নাম ব্যবহার করবে: "জিরাল্ট অফ রিভিয়া"। এই স্ট্রিংটি (এবং অন্যান্য নায়কদের নাম) একটি ধ্রুবক হিসাবে সর্বোত্তমভাবে ঘোষণা করা হয়: এর মান এক জায়গায় সংরক্ষণ করা হবে এবং এটিকে মিলিয়ন বার প্রবেশ করার সময় আপনি নিশ্চিতভাবে একটি টাইপো করবেন না।

public class TheWitcher4 {

   private static final String GERALT_NAME = "Geralt of Rivia";
   private static final String YENNEFER_NAME = "Yennefer of Wengerberg";
   private static final String TRISS_NAME = "Triss Merigold";

   public static void main(String[] args) {

       System.out.println("The Witcher 4");
       System.out.println("It's already the fourth Witcher game, but " + GERALT_NAME + " still can't decide who" +
               " he likes more: " + YENNEFER_NAME + " or " + TRISS_NAME);

       System.out.println("But, if you've never played The Witcher before, we'll start from the beginning.");
       System.out.println("The protagonist's name is " + GERALT_NAME);
       System.out.println(GERALT_NAME + " is a witcher, a monster hunter");
   }
}
আউটপুট: দ্য উইচার 4 এটি ইতিমধ্যেই চতুর্থ উইচার গেম, কিন্তু রিভিয়ার জেরাল্ট এখনও সিদ্ধান্ত নিতে পারে না যে সে কাকে বেশি পছন্দ করে: ওয়েঙ্গারবার্গের ইয়েনেফার বা ট্রিস মেরিগোল্ড কিন্তু, আপনি যদি আগে কখনও দ্য উইচার না খেলেন, আমরা শুরু করব শুরু নায়কের নাম রিভিয়ার জেরাল্ট রিভিয়ার জেরাল্ট একজন জাদুকর, একজন দানব শিকারী আমরা নায়কদের নাম ধ্রুবক হিসাবে ঘোষণা করেছি। এখন আমরা অবশ্যই একটি টাইপো করব না, এবং প্রতিবার তাদের হাতে লিখতে হবে না। আরেকটি প্লাস: যদি আমাদের কখনো পুরো প্রোগ্রাম জুড়ে ভেরিয়েবলের মান পরিবর্তন করতে হয়, তাহলে আপনি পুরো কোড বেস জুড়ে ম্যানুয়ালি পরিবর্তন না করে এটি এক জায়গায় করতে পারেন। :)

অপরিবর্তনীয় প্রকার

যেহেতু আপনি জাভা নিয়ে কাজ করেছেন, আপনি সম্ভবত ইতিমধ্যেই এই ধারণায় অভ্যস্ত হয়ে গেছেন যে সমস্ত বস্তুর অবস্থার উপর প্রোগ্রামারদের প্রায় সম্পূর্ণ নিয়ন্ত্রণ রয়েছে। আপনি যদি একটি বস্তু তৈরি করতে চান Cat, আপনি করতে পারেন. আপনি যদি এটির নাম পরিবর্তন করতে চান তবে আপনি করতে পারেন। আপনি যদি এর বয়স বা অন্য কিছু পরিবর্তন করতে চান তবে আপনি করতে পারেন। কিন্তু জাভাতে বেশ কিছু ডাটা টাইপ আছে যেগুলোর একটি বিশেষ সম্পত্তি আছে। তারা অপরিবর্তনীয় । যদি একটি শ্রেণী অপরিবর্তনীয় হয়, তাহলে এর বস্তুর অবস্থা পরিবর্তন করা যাবে না। কিছু উদাহরণ চান? এটি আপনাকে অবাক করে দিতে পারে, তবে সবচেয়ে সুপরিচিত অপরিবর্তনীয় শ্রেণী হল স্ট্রিং! সুতরাং, আমরা সত্যিই একটি স্ট্রিং এর মান পরিবর্তন করতে পারি না? আচ্ছা, আসুন এটি চেষ্টা করে দেখি:

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   str1 = "I love Python";// but changing str1 has no impact on str2
   System.out.println(str2);// str2 continues to point to the "I love Java" string, but str1 now points to a different object
}
আউটপুট: আমি জাভা ভালোবাসি আমি জাভা ভালোবাসি লেখার পর

str1 = "I love Python";
স্ট্রিং "I love Java"অবজেক্ট পরিবর্তন বা কোথাও যায় নি। এটি এখনও সুখের সাথে বিদ্যমান এবং আগের মতোই একই পাঠ্য রয়েছে। কোড

str1 = "I love Python";
সহজভাবে আরেকটি বস্তু তৈরি করেছে, যা str1 এখন নির্দেশ করে। কিন্তু, আমরা "আমি জাভা ভালোবাসি" স্ট্রিং অবজেক্টের উপর কোন প্রভাব ফেলতে পারে বলে মনে হচ্ছে না। ঠিক আছে, এর অন্য কিছু চেষ্টা করা যাক! ক্লাসটি Stringপদ্ধতিতে পূর্ণ, এবং তাদের মধ্যে কিছু বস্তুর অবস্থা পরিবর্তন করতে দেখা যাচ্ছে! উদাহরণস্বরূপ, একটি replace()পদ্ধতি আছে. আসুন আমাদের স্ট্রিং এ "জাভা" শব্দটিকে "পাইথন" এ পরিবর্তন করি!

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   str1.replace("Java", "Python");// We try to change the state of str1 by swapping the word "Java" with "Python"
   System.out.println(str2);
}
আউটপুট: আমি জাভা ভালোবাসি আমি জাভা ভালোবাসি এটা আবার কাজ করেনি! হয়তো প্রতিস্থাপন পদ্ধতি কাজ করে না? এর অন্য কিছু চেষ্টা করা যাক. উদাহরণস্বরূপ, substring(). এটি আর্গুমেন্ট হিসাবে পাস করা অক্ষর সূচকের উপর ভিত্তি করে একটি সাবস্ট্রিং প্রদান করে। আসুন আমাদের স্ট্রিং এর প্রথম 10টি অক্ষর কেটে ফেলি:

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   str1.substring(10);// Truncate the original String 
   System.out.println(str2);
}
আউটপুট: আমি জাভা ভালোবাসি আমি জাভা ভালোবাসি জাভাতে স্থির মান: চূড়ান্ত, ধ্রুবক, এবং অপরিবর্তনীয় - 3 কিছুই পরিবর্তন হয়নি। এবং এটা উচিত নয়. যেমন আমরা আগে বলেছি, স্ট্রিংগুলি অপরিবর্তনীয়। সুতরাং, ক্লাসের সমস্ত পদ্ধতির সাথে কি String? সর্বোপরি, তারা স্ট্রিং ছেঁটে ফেলতে পারে, অক্ষর পরিবর্তন করতে পারে এবং আরও অনেক কিছু করতে পারে। কিছু না হলে লাভ কি? তারা আসলে এই জিনিসগুলি করতে পারে! কিন্তু, তারা প্রতিবার একটি নতুন স্ট্রিং ফেরত দেয়। লেখাটা অর্থহীন

str1.replace("Java", "Python");
কারণ আপনি মূল বস্তু পরিবর্তন করতে পারবেন না। কিন্তু, আপনি যদি একটি নতুন রেফারেন্স ভেরিয়েবলে পদ্ধতির ফলাফল লেখেন, আপনি অবিলম্বে পার্থক্য দেখতে পাবেন!

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   String str1AfterReplacement =  str1.replace("Java", "Python");
   System.out.println(str2);

   System.out.println(str1AfterReplacement);
}
সমস্ত Stringপদ্ধতি এই ভাবে কাজ করে। বস্তুর কিছুই করা যাবে না "I love Java"। আপনি শুধু একটি নতুন অবজেক্ট তৈরি করতে পারেন এবং লিখতে পারেন: "<new object> = ম্যানিপুলেট করার ফলাফল। "I love Java" object "অন্য কোন প্রকার অপরিবর্তনীয়? কিছু যা আপনাকে অবশ্যই এখনই মনে রাখতে হবে তা হল আদিম প্রকারের জন্য সমস্ত মোড়ক ক্লাস। Integer, Byte, Character, Short, Boolean, Long, , Double, Float: এই সমস্ত ক্লাস immutableঅবজেক্ট তৈরি করে (আমরা আসন্ন পাঠে সেগুলি সম্পর্কে কথা বলব)৷ এর মধ্যে রয়েছে বড় সংখ্যা তৈরি করতে ব্যবহৃত ক্লাসগুলি, যেমন BigIntegerএবং BigDecimal৷ আমরা সম্প্রতি ব্যতিক্রমগুলি কভার করেছি এবং স্ট্যাক ট্রেসে স্পর্শ করেছি ৷ ভাল , অনুমান কি, java.lang.StackTraceElementবস্তুগুলিও অপরিবর্তনীয়। এটি বোধগম্য হয়: যদি কেউ আমাদের স্ট্যাকের ডেটা পরিবর্তন করতে পারে তবে এটি পুরো জিনিসটিকে অর্থহীন করে তুলবে। কল্পনা করুন যে কেউ স্ট্যাক ট্রেসের মধ্য দিয়ে যাচ্ছে এবং একটি OutOfMemoryError একটি FileNotFoundException এ পরিবর্তন করছে । এবং তারপর আপনি যে স্ট্যাক ব্যবহার ত্রুটির কারণ খুঁজে. কিন্তু প্রোগ্রাম এমনকি ফাইল ব্যবহার করে না. :) সুতরাং, তারা এই বস্তুগুলিকে অপরিবর্তনীয় করে তুলেছে, ঠিক ক্ষেত্রে। ঠিক আছে, তাই এটি StackTraceElement এর জন্য কমবেশি অর্থপূর্ণ । কিন্তু, কেন কাউকে স্ট্রিংকে অপরিবর্তনীয় করতে হবে? কেন তাদের মান পরিবর্তন একটি সমস্যা হবে? এটা সম্ভবত এমনকি আরো সুবিধাজনক হবে. :/ এর বেশ কিছু কারণ রয়েছে। প্রথমত, এটি স্মৃতি সংরক্ষণ করে। অপরিবর্তনীয় স্ট্রিংগুলি স্ট্রিং পুলে স্থাপন করা যেতে পারে, নতুন তৈরি করার পরিবর্তে স্ট্রিংগুলিকে পুনরায় ব্যবহার করার অনুমতি দেয়। দ্বিতীয়ত, নিরাপত্তার জন্য। উদাহরণস্বরূপ, ব্যবহারকারীর নাম এবং পাসওয়ার্ডগুলি প্রায় প্রতিটি প্রোগ্রামে স্ট্রিং। তাদের পরিবর্তন করা সম্ভব হলে অনুমোদনের সমস্যা হতে পারে। অন্যান্য কারণ আছে, কিন্তু জাভা নিয়ে আমাদের অধ্যয়ন এখনও সেগুলিকে কভার করেনি, তাই আমরা পরে তাদের কাছে ফিরে আসব।