ওহে! অন্য ধরনের নেস্টেড ক্লাসের কথা বলা যাক। আমি স্থানীয় ক্লাস (পদ্ধতি-স্থানীয় অভ্যন্তরীণ ক্লাস) সম্পর্কে কথা বলছি। ডাইভিং করার আগে, আমাদের প্রথমে নেস্টেড ক্লাসের কাঠামোতে তাদের জায়গাটি মনে রাখতে হবে। আমাদের ডায়াগ্রাম থেকে, আমরা দেখতে পাচ্ছি যে স্থানীয় শ্রেণী হল অভ্যন্তরীণ শ্রেণীগুলির একটি উপ-প্রজাতি, যা আমরা পূর্ববর্তী উপকরণগুলিতে বিস্তারিতভাবে বলেছি । যাইহোক, স্থানীয় শ্রেণীতে সাধারণ অভ্যন্তরীণ শ্রেণীর থেকে অনেক গুরুত্বপূর্ণ বৈশিষ্ট্য এবং পার্থক্য রয়েছে। প্রধান জিনিসটি তাদের ঘোষণায় রয়েছে: একটি স্থানীয় ক্লাস শুধুমাত্র কোডের একটি ব্লকে ঘোষণা করা হয়। প্রায়শই, এই ঘোষণাটি বাইরের শ্রেণীর কিছু পদ্ধতির ভিতরে থাকে। উদাহরণস্বরূপ, এটি এই মত দেখতে হতে পারে:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...number validation code
}
}
গুরুত্বপূর্ণ!আপনার যদি Java 7 ইনস্টল করা থাকে, তাহলে IDEA-তে পেস্ট করা হলে এই কোডটি কম্পাইল হবে না। আমরা পাঠের শেষে এর কারণ সম্পর্কে কথা বলব। সংক্ষেপে, স্থানীয় ক্লাসগুলি কীভাবে কাজ করে তা ভাষার সংস্করণের উপর অত্যন্ত নির্ভরশীল। final
যদি এই কোডটি আপনার জন্য কম্পাইল না করে, আপনি হয় IDEA-তে ভাষা সংস্করণটিকে Java 8-এ স্যুইচ করতে পারেন, অথবা মেথড প্যারামিটারে শব্দটি যোগ করতে পারেন যাতে এটি দেখতে এরকম হয়: validatePhoneNumber(final String number)
. এর পরে, সবকিছু কাজ করবে। এটি একটি ছোট প্রোগ্রাম যা ফোন নম্বর যাচাই করে। এর validatePhoneNumber()
পদ্ধতি ইনপুট হিসাবে একটি স্ট্রিং নেয় এবং এটি একটি ফোন নম্বর কিনা তা নির্ধারণ করে। এবং এই পদ্ধতির ভিতরে, আমরা আমাদের স্থানীয় PhoneNumber
ক্লাস ঘোষণা করি। আপনি যুক্তিসঙ্গতভাবে জিজ্ঞাসা করতে পারেন কেন. কেন আমরা একটি পদ্ধতির ভিতরে একটি ক্লাস ঘোষণা করব? কেন একটি সাধারণ অভ্যন্তরীণ শ্রেণী ব্যবহার করবেন না? সত্য, আমরা তৈরি করতে পারতামPhoneNumber
ক্লাস একটি অভ্যন্তরীণ শ্রেণী। কিন্তু চূড়ান্ত সমাধান আপনার প্রোগ্রামের গঠন এবং উদ্দেশ্য উপর নির্ভর করে। আসুন অভ্যন্তরীণ ক্লাসের একটি পাঠ থেকে আমাদের উদাহরণটি স্মরণ করি:
public class Bicycle {
private String model;
private int maxWeight;
public Bicycle(String model, int maxWeight) {
this.model = model;
this.maxWeight = maxWeight;
}
public void start() {
System.out.println("Let's go!");
}
public class HandleBar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left!");
}
}
}
এটিতে, আমরা HandleBar
বাইকের একটি অভ্যন্তরীণ শ্রেণী তৈরি করেছি। পার্থক্য কি? প্রথমত, ক্লাসটি যেভাবে ব্যবহার করা হয় তা ভিন্ন। HandleBar
দ্বিতীয় উদাহরণের ক্লাসটি প্রথম উদাহরণের ক্লাসের চেয়ে আরও জটিল সত্তা PhoneNumber
। প্রথমত, HandleBar
সর্বজনীন right
এবং left
পদ্ধতি রয়েছে (এগুলি সেটার্স/গেটার নয়)। Bicycle
দ্বিতীয়ত, এটি এবং এর বাইরের শ্রেণির কোথায় আমাদের প্রয়োজন হতে পারে তা আগে থেকে অনুমান করা অসম্ভব । এমনকি একটি একক প্রোগ্রামেও কয়েক ডজন বিভিন্ন স্থান এবং পদ্ধতি থাকতে পারে। কিন্তু PhoneNumber
ক্লাসের সাথে, সবকিছু অনেক সহজ। আমাদের প্রোগ্রাম খুব সহজ. এটির একটিই উদ্দেশ্য: একটি নম্বর একটি বৈধ ফোন নম্বর কিনা তা পরীক্ষা করা৷ অধিকাংশ ক্ষেত্রে, আমাদেরPhoneNumberValidator
এমনকি একটি স্বতন্ত্র প্রোগ্রামও হবে না, বরং একটি বড় প্রোগ্রামের জন্য অনুমোদনের যুক্তির একটি অংশ। উদাহরণস্বরূপ, ব্যবহারকারীরা সাইন আপ করার সময় বিভিন্ন ওয়েবসাইট প্রায়ই একটি ফোন নম্বর জিজ্ঞাসা করে। আপনি নম্বরের পরিবর্তে কিছু বাজে কথা লিখলে ওয়েবসাইটটি একটি ত্রুটি রিপোর্ট করবে: "এটি একটি ফোন নম্বর নয়!" এই ধরনের একটি ওয়েবসাইটের বিকাশকারীরা (অথবা বরং, এর ব্যবহারকারীর অনুমোদনের প্রক্রিয়া) আমাদের অনুরূপ কিছু অন্তর্ভুক্ত করতে পারেPhoneNumberValidator
তাদের কোডে। অন্য কথায়, আমাদের একটি পদ্ধতি সহ একটি বাইরের ক্লাস রয়েছে, যা প্রোগ্রামের এক জায়গায় ব্যবহার করা হবে এবং অন্য কোথাও নয়। এবং যদি এটি ব্যবহার করা হয়, তবে এতে কিছুই পরিবর্তন হবে না: একটি পদ্ধতি তার কাজ করে - এবং এটিই। এই ক্ষেত্রে, যেহেতু সমস্ত যুক্তি একটি পদ্ধতিতে একত্রিত করা হয়েছে, সেখানে একটি অতিরিক্ত শ্রেণীকে এনক্যাপসুলেট করা অনেক বেশি সুবিধাজনক এবং সঠিক হবে। গেটার এবং সেটার ছাড়া এর নিজস্ব কোনো পদ্ধতি নেই। আসলে, আমাদের শুধুমাত্র কনস্ট্রাক্টর থেকে ডেটা দরকার। এটি অন্যান্য পদ্ধতির সাথে জড়িত নয়। তদনুসারে, একমাত্র পদ্ধতি যেখানে এটি ব্যবহার করা হয় তার বাইরে এটি সম্পর্কে তথ্য নেওয়ার কোনও কারণ নেই। আমরা একটি উদাহরণও দিয়েছি যেখানে একটি স্থানীয় ক্লাস একটি পদ্ধতিতে ঘোষণা করা হয়েছে, তবে এটি একমাত্র বিকল্প নয়। এটি একটি কোড ব্লকে সহজভাবে ঘোষণা করা যেতে পারে:
public class PhoneNumberValidator {
{
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
public void validatePhoneNumber(String phoneNumber) {
// ...number validation code
}
}
নাকি লুপেও for
!
public class PhoneNumberValidator {
public void validatePhoneNumber(String phoneNumber) {
for (int i = 0; i < 10; i++) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...some logic
}
// ...number validation code
}
}
কিন্তু এই ধরনের ঘটনা অত্যন্ত বিরল। বেশিরভাগ ক্ষেত্রে, ঘোষণা পদ্ধতির ভিতরে ঘটবে। সুতরাং, আমরা ঘোষণাগুলি বের করেছি, এবং আমরা "দর্শন" সম্পর্কেও কথা বলেছি :) অভ্যন্তরীণ ক্লাসের তুলনায় স্থানীয় ক্লাসের কী অতিরিক্ত বৈশিষ্ট্য এবং পার্থক্য রয়েছে? একটি স্থানীয় শ্রেণীর একটি অবজেক্ট যে পদ্ধতি বা ব্লকে এটি ঘোষণা করা হয়েছে তার বাইরে তৈরি করা যাবে না। কল্পনা করুন যে আমাদের এমন একটি পদ্ধতি দরকার generatePhoneNumber()
যা একটি এলোমেলো ফোন নম্বর তৈরি করবে এবং একটি PhoneNumber
বস্তু ফেরত দেবে। আমাদের বর্তমান পরিস্থিতিতে, আমরা আমাদের ভ্যালিডেটর ক্লাসে এমন একটি পদ্ধতি তৈরি করতে পারি না:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...number validation code
}
// Error! The compiler does not recognize the PhoneNumber class
public PhoneNumber generatePhoneNumber() {
}
}
স্থানীয় ক্লাসের আরেকটি গুরুত্বপূর্ণ বৈশিষ্ট্য হল স্থানীয় ভেরিয়েবল এবং পদ্ধতির পরামিতি অ্যাক্সেস করার ক্ষমতা। যদি আপনি ভুলে গেছেন, একটি পদ্ধতির ভিতরে ঘোষিত একটি ভেরিয়েবল একটি "স্থানীয়" ভেরিয়েবল হিসাবে পরিচিত। অর্থাৎ, যদি আমরা কোনো কারণে মেথডের String usCountryCode
ভিতরে একটি লোকাল ভেরিয়েবল তৈরি করি validatePhoneNumber()
, তাহলে আমরা লোকাল PhoneNumber
ক্লাস থেকে এটি অ্যাক্সেস করতে পারি। যাইহোক, অনেক সূক্ষ্মতা রয়েছে যা প্রোগ্রামে ব্যবহৃত ভাষার সংস্করণের উপর নির্ভর করে। পাঠের শুরুতে, আমরা লক্ষ্য করেছি যে উদাহরণগুলির একটির কোড জাভা 7 এ কম্পাইল নাও হতে পারে, মনে আছে? এখন এর কারণগুলি বিবেচনা করা যাক :) জাভা 7-এ, একটি লোকাল ক্লাস স্থানীয় ভেরিয়েবল বা মেথড প্যারামিটার অ্যাক্সেস করতে পারে যদি সেগুলিকে final
পদ্ধতি হিসাবে ঘোষণা করা হয়:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
// Error! The method parameter must be declared as final!
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
// Error! The local variable must be declared as final!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
এখানে কম্পাইলার দুটি ত্রুটি তৈরি করে। এবং এখানে সবকিছু ঠিক আছে:
public void validatePhoneNumber(final String number) {
final String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
System.out.println(usCountryCode);
}
}
// ...number validation code
}
এখন আপনি জানেন কেন পাঠের শুরু থেকে কোড কম্পাইল করা হবে না: জাভা 7-এ, একটি স্থানীয় ক্লাসের শুধুমাত্র final
মেথড প্যারামিটার এবং final
স্থানীয় ভেরিয়েবলের অ্যাক্সেস আছে। জাভা 8 এ, স্থানীয় ক্লাসের আচরণ পরিবর্তিত হয়েছে। ভাষার এই সংস্করণে, একটি স্থানীয় শ্রেণির কেবল final
স্থানীয় ভেরিয়েবল এবং পরামিতিগুলিতেই নয়, সেইগুলিরও অ্যাক্সেস রয়েছে effective-final
। Effective-final
একটি পরিবর্তনশীল যার মান আরম্ভ করার পর থেকে পরিবর্তিত হয়নি। উদাহরণস্বরূপ, জাভা 8-এ, আমরা সহজেই usCountryCode
কনসোলে ভেরিয়েবল প্রদর্শন করতে পারি, এমনকি তা না হলেও final
। গুরুত্বপূর্ণ বিষয় হল এর মান পরিবর্তন হয় না। নিম্নলিখিত উদাহরণে, সবকিছু যেমন করা উচিত তেমন কাজ করে:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
public void printUsCountryCode() {
// Java 7 would produce an error here
System.out.println(usCountryCode);
}
}
// ...number validation code
}
কিন্তু আমরা যদি ইনিশিয়ালাইজেশনের সাথে সাথে ভেরিয়েবলের মান পরিবর্তন করি তবে কোডটি কম্পাইল হবে না।
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
usCountryCode = "+8";
class PhoneNumber {
public void printUsCountryCode() {
// Error!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
আশ্চর্যের কিছু নেই যে একটি স্থানীয় শ্রেণী হল অভ্যন্তরীণ শ্রেণীর ধারণার একটি উপ-প্রজাতি! তাদের সাধারণ বৈশিষ্ট্যও রয়েছে। একটি স্থানীয় শ্রেণীর বাইরের শ্রেণীর সমস্ত (এমনকি ব্যক্তিগত) ক্ষেত্র এবং পদ্ধতিতে অ্যাক্সেস রয়েছে: স্ট্যাটিক এবং নন-স্ট্যাটিক উভয়ই। উদাহরণস্বরূপ, আসুন String phoneNumberRegex
আমাদের যাচাইকারী ক্লাসে একটি স্ট্যাটিক ক্ষেত্র যোগ করি:
public class PhoneNumberValidator {
private static String phoneNumberRegex = "[^0-9]";
public void validatePhoneNumber(String phoneNumber) {
class PhoneNumber {
// ......
}
}
}
এই স্ট্যাটিক ভেরিয়েবল ব্যবহার করে বৈধকরণ করা হবে। পদ্ধতিটি পরীক্ষা করে যে পাস করা স্ট্রিংটিতে এমন অক্ষর রয়েছে যা রেগুলার এক্সপ্রেশন " " এর সাথে মেলে না [^0-9]
(অর্থাৎ, 0 থেকে 9 পর্যন্ত কোনো অক্ষর নয়)। আমরা সহজেই স্থানীয় PhoneNumber
ক্লাস থেকে এই ভেরিয়েবলটি অ্যাক্সেস করতে পারি। উদাহরণস্বরূপ, একটি গেটার লিখুন:
public String getPhoneNumberRegex() {
return phoneNumberRegex;
}
স্থানীয় ক্লাসগুলি অভ্যন্তরীণ ক্লাসগুলির মতো, কারণ তারা কোনও স্ট্যাটিক সদস্যকে সংজ্ঞায়িত বা ঘোষণা করতে পারে না। স্ট্যাটিক পদ্ধতিতে স্থানীয় ক্লাসগুলি শুধুমাত্র এনক্লোজিং ক্লাসের স্ট্যাটিক সদস্যদের উল্লেখ করতে পারে। উদাহরণস্বরূপ, যদি আপনি এনক্লোজিং ক্লাসের একটি ভেরিয়েবল (ক্ষেত্র) স্ট্যাটিক হিসাবে সংজ্ঞায়িত না করেন, তাহলে জাভা কম্পাইলার একটি ত্রুটি তৈরি করে: "অ-স্ট্যাটিক ভেরিয়েবল একটি স্ট্যাটিক প্রসঙ্গ থেকে উল্লেখ করা যাবে না।" স্থানীয় ক্লাসগুলি স্থির নয়, কারণ তাদের এনক্লোজিং ব্লকের ইনস্ট্যান্স সদস্যদের অ্যাক্সেস রয়েছে। ফলস্বরূপ, তারা বেশিরভাগ ধরণের স্ট্যাটিক ঘোষণা ধারণ করতে পারে না। আপনি একটি ব্লকের ভিতরে একটি ইন্টারফেস ঘোষণা করতে পারবেন না: ইন্টারফেসগুলি সহজাতভাবে স্ট্যাটিক। এই কোড কম্পাইল না:
public class PhoneNumberValidator {
public static void validatePhoneNumber(String number) {
interface I {}
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
// ...number validation code
}
}
কিন্তু যদি একটি ইন্টারফেস একটি বহিরাগত ক্লাসের ভিতরে ঘোষণা করা হয়, PhoneNumber
ক্লাস এটি বাস্তবায়ন করতে পারে:
public class PhoneNumberValidator {
interface I {}
public static void validatePhoneNumber(String number) {
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
// ...number validation code
}
}
স্থানীয় ক্লাসে স্ট্যাটিক ইনিশিয়ালাইজার (ইনিশিয়ালাইজেশন ব্লক) বা ইন্টারফেস ঘোষণা করা যাবে না। কিন্তু স্থানীয় ক্লাসে স্ট্যাটিক সদস্য থাকতে পারে, শর্ত থাকে যে তারা ধ্রুবক ভেরিয়েবল ( static final
)। এবং এখন আপনি স্থানীয় ক্লাস সম্পর্কে জানেন, লোকেরা! আপনি দেখতে পাচ্ছেন, তাদের সাধারণ অভ্যন্তরীণ শ্রেণীর থেকে অনেক পার্থক্য রয়েছে। এমনকি তারা কীভাবে কাজ করে তা বোঝার জন্য আমাদেরকে ভাষার নির্দিষ্ট সংস্করণগুলির বৈশিষ্ট্যগুলিও অধ্যয়ন করতে হয়েছিল :) পরবর্তী পাঠে, আমরা বেনামী অভ্যন্তরীণ ক্লাসগুলি সম্পর্কে কথা বলব — নেস্টেড ক্লাসগুলির শেষ গ্রুপ। আপনার পড়াশোনায় সৌভাগ্য কামনা করছি! :)
GO TO FULL VERSION