मुख्य सामग्री पर जाएँ

Java में OOP: क्लासेज़, ऑब्जेक्ट्स, एन्कैप्सुलेशन, इनहेरिटेंस और एब्स्ट्रैक्शन

व्यावहारिक उदाहरणों के साथ Java में ऑब्जेक्ट‑ओरिएंटेड प्रोग्रामिंग सीखें। रेस्तरां मेनू सिस्टम का उपयोग करके क्लासेज़, ऑब्जेक्ट्स, इनहेरिटेंस, एन्कैप्सुलेशन, और एब्स्ट्रैक्ट क्लासेज़ में महारत हासिल करें।
· 15 मि॰ पढ़ें

AI के साथ खोजें

Java लगातार दुनिया की शीर्ष तीन सबसे लोकप्रिय भाषाओं में शामिल रहती है। एंटरप्राइज़ सॉफ्टवेयर डेवलपमेंट, Android मोबाइल ऐप्स, और बड़े पैमाने के वेब अनुप्रयोगों जैसे क्षेत्रों में इसका अपनाव बेजोड़ है। इसका स्ट्रॉन्ग टाइप सिस्टम, विशाल इकोसिस्टम, और “एक बार लिखें, कहीं भी चलाएँ” क्षमता इसे मजबूत, स्केलेबल सिस्टम बनाने के लिए विशेष रूप से आकर्षक बनाती है। इस लेख में, हम देखेंगे कि Java की ऑब्जेक्ट‑ओरिएंटेड प्रोग्रामिंग विशेषताएँ डेवलपर्स को इन क्षमताओं का प्रभावी ढंग से उपयोग करने में कैसे सक्षम बनाती हैं, ताकि उचित कोड संगठन और पुन: उपयोग के जरिए मेंटिनेबल और स्केलेबल एप्लिकेशन बनाए जा सकें।

Java कोड को व्यवस्थित और चलाने पर एक नोट

कोई भी कोड लिखने से पहले, आइए कुछ सेटअप कर लें।

अपनी सिंटैक्स की तरह, Java में कोड संगठन के भी कड़े नियम हैं।

पहला, हर public क्लास अपनी अलग फ़ाइल में होनी चाहिए, जिसका नाम ठीक उसी क्लास जैसा हो, लेकिन .java एक्सटेंशन के साथ। तो, अगर मैं Laptop क्लास लिखना चाहता/चाहती हूँ, तो फ़ाइल का नाम Laptop.java—केस‑सेंसिटिव होना चाहिए। आप उसी फ़ाइल में non‑public क्लासेज़ रख सकते हैं, लेकिन उन्हें अलग रखना ही बेहतर है। मुझे पता है हम थोड़ा आगे निकल रहे हैं—क्लासेज़ के संगठन की बात कर रहे हैं, उन्हें लिखने से पहले ही—लेकिन चीज़ों को पहले से कहाँ रखना है, इसका मोटा अंदाज़ा होना अच्छा है।

हर Java प्रोजेक्ट में Main.java फ़ाइल में Main क्लास होनी चाहिए। यहीं पर आप अपनी क्लासेज़ से ऑब्जेक्ट बनाकर उनका परीक्षण करते हैं।

Java कोड चलाने के लिए हम IntelliJ IDEA का उपयोग करेंगे, जो एक लोकप्रिय Java IDE है। IntelliJ इंस्टॉल करने के बाद:

  1. एक नया Java प्रोजेक्ट बनाएँ (File > New > Project)
  2. src फ़ोल्डर पर राइट‑क्लिक करके Main.java फ़ाइल बनाएँ और नीचे दी गई सामग्री पेस्ट करें:
public class Main {
   public static void main(String[] args) {
       // Create and tests objects here
      
   }
}

जब भी हम क्लासेज़ की बात कर रहे होते हैं, हम Main.java के अलावा अन्य फ़ाइलों में कोड लिखते हैं। लेकिन जब हम ऑब्जेक्ट्स बनाने और टेस्ट करने की बात कर रहे होते हैं, तो हम Main.java पर स्विच करते हैं।

प्रोग्राम चलाने के लिए, आप main मेथड के बगल में हरे प्ले बटन पर क्लिक कर सकते हैं:

IntelliJ IDEA में Java के लिए एडिटर विंडो का एक स्क्रीनशॉट

आउटपुट नीचे Run टूल विंडो में दिखेगा।

यदि आप Java में बिल्कुल नए हैं, तो आगे बढ़ने से पहले हमारे Introduction to Java कोर्स को देखें, जिसमें Java डेटा टाइप्स और कंट्रोल फ्लो की बुनियादी बातें शामिल हैं।

अन्यथा, आइए सीधे शुरू करते हैं।

Java क्लासेज़ और ऑब्जेक्ट्स

तो, क्लासेज़ होती क्या हैं?

क्लासेज़ Java में वास्तविक‑दुनिया की संकल्पनाओं को दर्शाने के लिए प्रोग्रामिंग कॉन्स्ट्रक्ट हैं। उदाहरण के लिए, यह MenuItem क्लास पर विचार करें (अपने IDE में इस क्लास को लिखने के लिए एक फ़ाइल बनाएँ):

public class MenuItem {
   public String name;
   public double price;
}

क्लास हमें रेस्तरां के विभिन्न मेनू आइटम्स को दर्शाने के लिए एक ब्लूप्रिंट या टेम्पलेट देती है। क्लास की दो विशेषताएँ name, और price बदलकर हम अनगिनत मेनू ऑब्जेक्ट्स बना सकते हैं, जैसे बर्गर या सलाद।

तो, Java में क्लास बनाने के लिए, आप एक लाइन से शुरू करते हैं जो क्लास का एक्सेस लेवल बताती है (private, public, या protected) और उसके बाद क्लास का नाम। तत्पश्चात ब्रैकेट्स के अंदर आप अपनी क्लास की विशेषताएँ बताते हैं।

लेकिन हम इस क्लास से संबंधित ऑब्जेक्ट्स कैसे बनाएँ? Java इसे कंस्ट्रक्टर मेथड्स के माध्यम से सक्षम बनाता है:

public class MenuItem {
   public String name;
   public double price;
  
   // Constructor
   public MenuItem(String name, double price) {
       this.name = name;
       this.price = price;
   }
}

कंस्ट्रक्टर एक विशेष मेथड है जो तब कॉल होता है जब हम किसी क्लास से नया ऑब्जेक्ट बनाते हैं। यह ऑब्जेक्ट की विशेषताओं को हमारे दिए गए मानों से इनिशियलाइज़ करता है। ऊपर दिए उदाहरण में, कंस्ट्रक्टर नाम और कीमत पैरामीटर लेता है और ‘this’ कीवर्ड का उपयोग करके उन्हें भविष्य के ऑब्जेक्ट इंस्टेंस के फ़ील्ड्स में असाइन करता है।

कंस्ट्रक्टर की सिंटैक्स अन्य क्लास मेथड्स से अलग होती है क्योंकि इसमें रिटर्न टाइप बताने की आवश्यकता नहीं होती। साथ ही, कंस्ट्रक्टर का नाम क्लास के नाम जैसा ही होना चाहिए, और इसमें उतनी ही विशेषताएँ सेट होनी चाहिए, जितनी आपने क्लास डेफिनिशन के बाद घोषित की हैं। ऊपर, कंस्ट्रक्टर दो विशेषताएँ बना रहा है क्योंकि हमने दो घोषित की थीं: name और price।

जब आप अपनी क्लास और उसका कंस्ट्रक्टर लिख लेते हैं, तो आप main मेथड में उसके इंस्टेंसेज़ (ऑब्जेक्ट्स) बना सकते हैं:

public class Main {
   public static void main(String[] args) {
       // Create objects here
       MenuItem burger = new MenuItem("Burger", 3.5);
       MenuItem salad = new MenuItem("Salad", 2.5);
       System.out.println(burger.name + ", " + burger.price);
   }
}

आउटपुट:

Burger, 3.5

ऊपर, हम दो MenuItem ऑब्जेक्ट्स burger और salad वेरिएबल्स में बना रहे हैं। Java में आवश्यकतानुसार, वेरिएबल के टाइप की घोषणा करनी होती है, जो यहाँ MenuItem है। फिर, अपनी क्लास का इंस्टेंस बनाने के लिए हम new कीवर्ड के बाद कंस्ट्रक्टर को कॉल करते हैं।

कंस्ट्रक्टर के अलावा, आप ऐसे सामान्य मेथड्स बना सकते हैं जो आपकी क्लास को व्यवहार (behavior) दें। उदाहरण के लिए, नीचे हम टैक्स के बाद कुल कीमत निकालने का मेथड जोड़ते हैं:

public class MenuItem {
   public String name;
   public double price;
  
   // Constructor
   public MenuItem(String name, double price) {
       this.name = name;
       this.price = price;
   }
  
   // Method to calculate price after tax
   public double getPriceAfterTax() {
       double taxRate = 0.08; // 8% tax rate
       return price + (price * taxRate);
   }
}

अब हम टैक्स सहित कुल कीमत निकाल सकते हैं:

public class Main {
   public static void main(String[] args) {
       MenuItem burger = new MenuItem("Burger", 3.5);
       System.out.println("Price after tax: $" + burger.getPriceAfterTax());
   }
}

आउटपुट:

Price after tax: $3.78

एन्कैप्सुलेशन

क्लासेज़ का उद्देश्य ऑब्जेक्ट्स बनाने के लिए ब्लूप्रिंट प्रदान करना है। ये ऑब्जेक्ट्स फिर अन्य स्क्रिप्ट्स या प्रोग्रामों द्वारा उपयोग किए जाएँगे। उदाहरण के लिए, हमारे MenuItem ऑब्जेक्ट्स का उपयोग एक यूज़र इंटरफ़ेस द्वारा किया जा सकता है जो उनके नाम, कीमत और छवि को स्क्रीन पर दिखाता है।

इसी कारण, हमें अपनी क्लासेज़ को इस तरह डिज़ाइन करना चाहिए कि उनके इंस्टेंसेज़ का उपयोग केवल हमारी मंशा के अनुसार ही हो सके। अभी, हमारी MenuItem क्लास बहुत बुनियादी और त्रुटि‑प्रवण है। कोई व्यक्ति हास्यास्पद विशेषताओं वाले ऑब्जेक्ट बना सकता है, जैसे नकारात्मक कीमत वाली एप्पल पाई या एक मिलियन‑डॉलर का सैंडविच:

// Inside Main.java
MenuItem applePie = new MenuItem("Apple Pie", -5.99);  // Negative price!
MenuItem sandwich = new MenuItem("Sandwich", 1000000); // Unreasonably expensive

System.out.println("Apple pie price: $" + applePie.price);
System.out.println("Sandwich price: $" + sandwich.price);

इसलिए, कोई क्लास लिखने के बाद पहला काम इसकी विशेषताओं को सुरक्षित करना है, यानी वे कैसे बनाई और एक्सेस की जाती हैं, इसे सीमित करना। शुरुआत के लिए, हम price के लिए केवल धनात्मक मानों की अनुमति देना चाहते हैं और अत्यधिक महंगे आइटम्स के आकस्मिक प्रदर्शन से बचने के लिए अधिकतम मान तय करना चाहते हैं।

Java हमें यह काम सेटर मेथड्स का उपयोग करके करने देता है:

public class MenuItem {
   private String name;
   private double price;
   private static final double MAX_PRICE = 100.0;
  
   public MenuItem(String name, double price) {
       this.name = name;
       setPrice(price);
   }
  
   public void setPrice(double price) {
       if (price < 0) {
           throw new IllegalArgumentException("Price cannot be negative");
       }
       if (price > MAX_PRICE) {
           throw new IllegalArgumentException("Price cannot exceed $" + MAX_PRICE);
       }
       this.price = price;
   }
  
}

आइए ऊपर दिए गए कोड ब्लॉक में नया क्या है, देखें:

1. हमने private कीवर्ड जोड़कर विशेषताओं को private कर दिया। इसका अर्थ है कि वे केवल MenuItem क्लास के भीतर ही एक्सेस की जा सकती हैं। एन्कैप्सुलेशन का आरंभ इसी अहम कदम से होता है।

2. हमने एक नया constant MAX_PRICE जोड़ा, जो:

  • private है (सिर्फ क्लास के भीतर उपलब्ध)
  • static है (सभी इंस्टेंसेज़ में साझा)
  • final है (इनिशियलाइज़ेशन के बाद बदला नहीं जा सकता)
  • एक उचित अधिकतम कीमत के रूप में $100.0 पर सेट है

3. हमने एक setPrice() मेथड जोड़ा, जो:

  • एक price पैरामीटर लेता है
  • सत्यापित करता है कि कीमत नकारात्मक न हो
  • सत्यापित करता है कि कीमत MAX_PRICE से अधिक न हो
  • वैलिडेशन विफल होने पर वर्णनात्मक संदेशों के साथ IllegalArgumentException फेंकता है
  • सभी वैलिडेशन पास होने पर ही कीमत सेट करता है

4. हमने कंस्ट्रक्टर में सीधे कीमत असाइन करने के बजाय setPrice() का उपयोग किया। इससे ऑब्जेक्ट बनाते समय ही कीमत का वैलिडेशन सुनिश्चित होता है।

अभी हमने अच्छे ऑब्जेक्ट‑ओरिएंटेड डिज़ाइन के मुख्य स्तंभों में से एक—एन्कैप्सुलेशन—को लागू किया है। यह प्रतिमान डेटा को छिपाने और ऑब्जेक्ट की विशेषताओं तक नियंत्रित पहुँच लागू करता है, ताकि आंतरिक इम्प्लीमेंटेशन विवरण बाहरी हस्तक्षेप से सुरक्षित रहें और केवल सुव्यवस्थित इंटरफेस के माध्यम से ही बदले जा सकें।

आइए इस बात को और मज़बूत करें और name विशेषता पर एन्कैप्सुलेशन लागू करें। मान लीजिए हमारे पास एक कॉफी शॉप है, जो केवल लाटे, कैप्पुचीनो, एस्प्रेसो, अमेरिकानो और मोचा परोसती है।

तो, हमारे मेनू आइटम के नाम केवल इस सूची में से ही एक हो सकते हैं। इसे कोड में इस तरह लागू कर सकते हैं:

// Rest of the class here
...
private static final String[] VALID_NAMES = {"latte", "cappuccino", "espresso", "americano", "mocha"};
private String name;
public void setName(String name) {
   String lowercaseName = name.toLowerCase();
   for (String validName : VALID_NAMES) {
       if (validName.equals(lowercaseName)) {
           this.name = name;
           return;
       }
   }
   throw new IllegalArgumentException("Invalid drink name. Must be one of: " + String.join(", ", VALID_NAMES));
}

ऊपर का कोड कॉफी शॉप के मेनू आइटम्स के लिए नाम वैलिडेशन लागू करता है। आइए इसे समझें:

1. सबसे पहले, यह एक private static final ऐरे VALID_NAMES परिभाषित करता है, जिसमें केवल अनुमत ड्रिंक के नाम हैं: latte, cappuccino, espresso, americano, और mocha। यह ऐरे:

  • private: केवल क्लास के भीतर उपलब्ध
  • static: सभी इंस्टेंसेज़ में साझा
  • final: इनिशियलाइज़ेशन के बाद संशोधित नहीं किया जा सकता

2. यह ड्रिंक का नाम स्टोर करने के लिए एक private String name फ़ील्ड घोषित करता है

3. setName() मेथड वैलिडेशन लॉजिक लागू करता है:

  • String name पैरामीटर लेता है
  • तुलना को केस‑इंसेंसिटिव बनाने के लिए इसे लोअरकेस में बदलता है
  • VALID_NAMES ऐरे में लूप करता है
  • मैच मिलने पर नाम सेट करता है और रिटर्न करता है
  • मैच न मिलने पर, सभी मान्य विकल्पों की सूची के साथ एक वर्णनात्मक संदेश के साथ IllegalArgumentException फेंकता है

अब तक की पूरी क्लास यहाँ है:

public class MenuItem {
   private String name;
   private double price;

   private static final String[] VALID_NAMES = {"latte", "cappuccino", "espresso", "americano", "mocha"};

   private static final double MAX_PRICE = 100.0;

   public MenuItem(String name, double price) {
       setName(name);
       setPrice(price);
   }

   public void setName(String name) {
       String lowercaseName = name.toLowerCase();
       for (String validName : VALID_NAMES) {
           if (validName.equals(lowercaseName)) {
               this.name = name;
               return;
           }
       }
       throw new IllegalArgumentException("Invalid drink name. Must be one of: " + String.join(", ", VALID_NAMES));
   }

   public void setPrice(double price) {
       if (price < 0) {
           throw new IllegalArgumentException("Price cannot be negative");
       }
       if (price > MAX_PRICE) {
           throw new IllegalArgumentException("Price cannot exceed $" + MAX_PRICE);
       }
       this.price = price;
   }
}

जिस तरह विशेषताओं के बनाए जाने के तरीके की सुरक्षा करते हैं, उसी तरह हम उनके एक्सेस का भी संरक्षण चाहते हैं। यह गेटर मेथड्स का उपयोग करके किया जाता है:

public class MenuItem {
   // Rest of the code here
   ...
   public String getName() {
       return name;
   }
   public double getPrice() {
       return price;
   }
}

गेटर मेथड्स किसी क्लास की private विशेषताओं तक नियंत्रित पहुँच प्रदान करते हैं। ये सीधे एट्रिब्यूट एक्सेस की समस्या को हल करते हैं, जो अनचाहे संशोधनों का कारण बन सकती है और एन्कैप्सुलेशन को तोड़ सकती है।

उदाहरण के लिए, गेटर्स के बिना हम विशेषताओं तक सीधे पहुँच सकते हैं:

MenuItem item = new MenuItem("Latte", 4.99);
String name = item.name; // Direct access to attribute
item.name = "INVALID"; // Can modify directly, bypassing validation

गेटर्स के साथ, हम उचित एक्सेस लागू करते हैं:

MenuItem item = new MenuItem("Latte", 4.99);
String name = item.getName(); // Controlled access through getter
// item.name = "INVALID"; // Not allowed - must use setName() which validates

यह एन्कैप्सुलेशन:

  1. अवैध संशोधनों को रोककर डेटा की अखंडता की रक्षा करता है
  2. हमें आंतरिक इम्प्लीमेंटेशन बदलने की अनुमति देता है, बिना उस कोड को प्रभावित किए जो क्लास का उपयोग करता है
  3. एकल एक्सेस बिंदु प्रदान करता है, जिसमें आवश्यकता पड़ने पर अतिरिक्त लॉजिक शामिल किया जा सकता है
  4. कोड को अधिक मेंटिनेबल और बग्स की दृष्टि से कम संवेदनशील बनाता है

इनहेरिटेंस

हमारी क्लास अच्छी दिखने लगी है, लेकिन इसमें काफी समस्याएँ हैं। उदाहरण के लिए, कई प्रकार के व्यंजन और पेय परोसने वाले बड़े रेस्तरां के लिए यह क्लास पर्याप्त लचीली नहीं है।

यदि हम अलग‑अलग प्रकार के खाद्य पदार्थ जोड़ना चाहते हैं, तो हमें कई चुनौतियों का सामना करना पड़ेगा। कुछ व्यंजन टेकआउट के लिए तैयार किए जा सकते हैं, जबकि अन्य को तुरंत सेवन की आवश्यकता होती है। मेनू आइटम्स की कीमतें और डिस्काउंट अलग‑अलग हो सकते हैं। व्यंजनों को तापमान ट्रैकिंग या विशेष भंडारण की आवश्यकता हो सकती है। पेय गर्म या ठंडे हो सकते हैं, जिनमें कस्टमाइज़ेबल सामग्री हो। आइटम्स में एलर्जेन जानकारी और हिस्से (portion) विकल्पों की आवश्यकता हो सकती है। वर्तमान सिस्टम इन भिन्न आवश्यकताओं को संभाल नहीं पाता।

इनहेरिटेंस इन सभी समस्याओं का एक सुंदर समाधान प्रदान करता है। यह हमें एक बेस MenuItem क्लास में सामान्य विशेषताएँ परिभाषित करके और फिर चाइल्ड क्लासेज़ बनाकर, जो इन बुनियादी बातों को इनहेरिट करें और अपनी विशिष्ट विशेषताएँ जोड़ें, मेनू आइटम्स के विशेष संस्करण बनाने देता है। 

उदाहरण के लिए, हमारे पास तापमान विकल्पों वाले पेयों के लिए Drink क्लास, तुरंत सेवन की आवश्यकता वाले आइटम्स के लिए Food क्लास, और विशेष भंडारण की ज़रूरत वाले आइटम्स के लिए Dessert क्लास हो सकती है—सभी बेस मेनू आइटम की कार्यक्षमता को इनहेरिट करते हुए।

क्लासेज़ का विस्तार (Extending classes)

आइए इन विचारों को Drink से शुरू करते हुए लागू करें:

public class Drink extends MenuItem {
   private boolean isCold;
  
   public Drink(String name, double price, boolean isCold) {
       this.name = name;
       this.price = price;
       this.isCold = isCold;
   }
  
   public boolean getIsCold() {
       return isCold;
   }
  
   public void setIsCold(boolean isCold) {
       this.isCold = isCold;
   }
}

एक चाइल्ड क्लास को परिभाषित करने के लिए जो किसी पैरेंट से इनहेरिट करती है, हम चाइल्ड क्लास के नाम के बाद extends कीवर्ड का उपयोग करते हैं और उसके बाद पैरेंट क्लास का नाम लिखते हैं। क्लास डेफिनिशन के बाद, हम इस चाइल्ड की नई विशेषताएँ परिभाषित करते हैं और उसका कंस्ट्रक्टर इम्प्लीमेंट करते हैं।

लेकिन ध्यान दें कि हमें name और price की इनिशियलाइज़ेशन को isCold के साथ दोहराना पड़ रहा है। यह आदर्श नहीं है क्योंकि पैरेंट क्लास में सैकड़ों विशेषताएँ हो सकती हैं। इसके अलावा, ऊपर दिया कोड कंपाइल करते समय त्रुटि देगा क्योंकि पैरेंट क्लास की विशेषताओं को इनिशियलाइज़ करने का यह सही तरीका नहीं है। सही तरीका super कीवर्ड का उपयोग करना होगा:

public class Drink extends MenuItem {
   private boolean isCold;
  
   public Drink(String name, double price, boolean isCold) {
       super(name, price);
       this.isCold = isCold;
   }
  
   public boolean getIsCold() {
       return isCold;
   }
  
   public void setIsCold(boolean isCold) {
       this.isCold = isCold;
   }
}

super कीवर्ड का उपयोग पैरेंट क्लास के कंस्ट्रक्टर को कॉल करने के लिए किया जाता है। इस मामले में, super(name, price) MenuItem के कंस्ट्रक्टर को कॉल करता है ताकि उन विशेषताओं का इनिशियलाइज़ेशन हो जाए और कोड दोहराव से बचा जा सके। हमें केवल isCold विशेषता को, जो Drink क्लास के लिए विशिष्ट है, इनिशियलाइज़ करना होता है।

यह कीवर्ड काफी लचीला है क्योंकि आप इसे चाइल्ड क्लास के किसी भी हिस्से में पैरेंट क्लास का संदर्भ देने के लिए उपयोग कर सकते हैं। उदाहरण के लिए, पैरेंट मेथड को कॉल करने के लिए super.methodName() और एट्रिब्यूट के लिए super.attributeName का उपयोग करते हैं।

मेथड ओवरराइडिंग

अब, मान लीजिए हम अपनी क्लासेज़ में एक नया मेथड जोड़ना चाहते हैं जो टैक्स के बाद कुल कीमत की गणना करे। चूँकि अलग‑अलग मेनू आइटम्स पर अलग टैक्स रेट हो सकते हैं (उदाहरण के लिए, तैयार भोजन बनाम पैकेज्ड ड्रिंक्स), हम मेथड ओवरराइडिंग का उपयोग करके प्रत्येक चाइल्ड क्लास में विशिष्ट टैक्स गणना लागू कर सकते हैं, जबकि पैरेंट क्लास में एक समान मेथड नाम बनाए रखते हुए।

यह इस प्रकार दिखता है:

public class MenuItem {
   // Rest of the MenuItem class
  
   public double calculateTotalPrice() {
       // Default tax rate of 10%
       return price * 1.10;
   }
}
public class Food extends MenuItem {
   private boolean isVegetarian;
  
   public Food(String name, double price, boolean isVegetarian) {
       super(name, price);
       this.isVegetarian = isVegetarian;
   }
  
   @Override
   public double calculateTotalPrice() {
       // Food has 15% tax
       return super.getPrice() * 1.15;
   }
}
public class Drink extends MenuItem {
   private boolean isCold;
  
   public Drink(String name, double price, boolean isCold) {
       super(name, price);
       this.isCold = isCold;
   }
  
   @Override
   public double calculateTotalPrice() {
       // Drinks have 8% tax
       return super.getPrice() * 1.08;
   }
}

इस उदाहरण में, मेथड ओवरराइडिंग प्रत्येक सबक्लास को calculateTotalPrice() का अपना इम्प्लीमेंटेशन देने की अनुमति देता है:

बेस MenuItem क्लास 10% के डिफ़ॉल्ट टैक्स की गणना परिभाषित करती है।

जब Food और Drink क्लासेज़ MenuItem को एक्सटेंड करती हैं, तो वे अपने टैक्स रेट लागू करने के लिए इस मेथड को ओवरराइड करती हैं:

  • Food आइटम्स पर 15% का अधिक टैक्स रेट है
  • Drinks पर 8% का कम टैक्स रेट है

@Override एनोटेशन स्पष्ट रूप से दर्शाता है कि ये मेथड पैरेंट क्लास के मेथड को ओवरराइड कर रहे हैं। यह उस स्थिति में त्रुटियाँ पकड़ने में मदद करता है, जब मेथड सिग्नेचर पैरेंट क्लास से मेल नहीं खाता।

प्रत्येक सबक्लास अब भी पैरेंट क्लास की कीमत को super.getPrice() के जरिए एक्सेस कर सकती है, जो दर्शाता है कि ओवरराइड किए गए मेथड पैरेंट की फ़ंक्शनैलिटी का उपयोग करते हुए अपना व्यवहार जोड़ सकते हैं।

संक्षेप में, मेथड ओवरराइडिंग इनहेरिटेंस का अभिन्न हिस्सा है, जो सबक्लासेज़ को पैरेंट क्लास में परिभाषित मेथड्स के अपने इम्प्लीमेंटेशन प्रदान करने देता है, ताकि समान मेथड सिग्नेचर बनाए रखते हुए अधिक विशिष्ट व्यवहार संभव हो।

एब्स्ट्रैक्ट क्लासेज़

हमारी MenuItem क्लास पदानुक्रम काम कर रहा है, लेकिन एक समस्या है: क्या किसी को एक साधारण MenuItem ऑब्जेक्ट बनाने में सक्षम होना चाहिए? आखिरकार, हमारे रेस्तरां में हर मेनू आइटम या तो Food है या Drink—“जनरल मेनू आइटम” जैसी कोई चीज़ नहीं है।

हम इसे MenuItem को एक एब्स्ट्रैक्ट क्लास बनाकर रोक सकते हैं। एब्स्ट्रैक्ट क्लास केवल एक बेस ब्लूप्रिंट देती है—इसे केवल इनहेरिटेंस के लिए पैरेंट क्लास की तरह इस्तेमाल किया जा सकता है, सीधे इंस्टांशिएट नहीं किया जा सकता।

MenuItem को एब्स्ट्रैक्ट बनाने के लिए, हम इसके एक्सेस मॉडिफ़ायर के बाद abstract कीवर्ड जोड़ते हैं:

public abstract class MenuItem {
   private String name;
   private double price;
  
   public MenuItem(String name, double price) {
       setName(name);
       setPrice(price);
   }
  
   // Existing getters/setters remain the same
  
   // Make this method abstract - every subclass MUST implement it
   public abstract double calculateTotalPrice();
}

एब्स्ट्रैक्ट क्लासेज़ में ऊपर दिए गए calculateTotalPrice() जैसे एब्स्ट्रैक्ट मेथड भी हो सकते हैं। ये एब्स्ट्रैक्ट मेथड कॉन्ट्रैक्ट की तरह काम करते हैं, जो सबक्लासेज़ को अपना इम्प्लीमेंटेशन देने के लिए बाध्य करते हैं। दूसरे शब्दों में, किसी एब्स्ट्रैक्ट क्लास के सभी एब्स्ट्रैक्ट मेथड्स का इम्प्लीमेंटेशन चाइल्ड क्लासेज़ में होना अनिवार्य है।

तो, इन बदलावों को ध्यान में रखते हुए, Food और Drink को फिर से लिखते हैं:

public class Food extends MenuItem {
   private boolean isVegetarian;
  
   public Food(String name, double price, boolean isVegetarian) {
       super(name, price);
       this.isVegetarian = isVegetarian;
   }
  
   @Override
   public double calculateTotalPrice() {
       return getPrice() * 1.15;  // 15% tax
   }
}
public class Drink extends MenuItem {
   private boolean hasCaffeine;
  
   public Drink(String name, double price, boolean hasCaffeine) {
       super(name, price);
       this.hasCaffeine = hasCaffeine;
   }
  
   @Override
   public double calculateTotalPrice() {
       return getPrice() * 1.10;  // 10% tax
   }
}

इस मेनू सिस्टम के इम्प्लीमेंटेशन के माध्यम से, हमने देखा कि एब्स्ट्रैक्शन और इनहेरिटेंस मिलकर कैसे लचीला, मेंटिनेबल कोड बनाते हैं, जो अलग‑अलग व्यावसायिक आवश्यकताओं के अनुसार आसानी से ढल सकता है।

निष्कर्ष

आज, हमने एक ऑब्जेक्ट‑ओरिएंटेड प्रोग्रामिंग भाषा के रूप में Java की क्षमताओं की एक झलक देखी। हमने क्लासेज़, ऑब्जेक्ट्स की बुनियाद के साथ OOP के कुछ प्रमुख स्तंभ—एन्कैप्सुलेशन, इनहेरिटेंस और एब्स्ट्रैक्शन—को रेस्तरां मेनू सिस्टम के माध्यम से कवर किया।

इस सिस्टम को प्रोडक्शन‑रेडी बनाने के लिए, आपको अभी और बहुत सी चीज़ें सीखनी हैं, जैसे इंटरफेसेज़ (एब्स्ट्रैक्शन का हिस्सा), पॉलिमॉर्फ़िज़्म, और OOP डिज़ाइन पैटर्न्स। इन अवधारणाओं के बारे में अधिक जानने के लिए, हमारे Introduction to OOP in Java कोर्स देखें।

यदि आप Java का अपना ज्ञान परखना चाहते हैं, तो हमारे Java इंटरव्यू प्रश्न लेख में कुछ सवालों के जवाब देने की कोशिश करें।

OOP in Java FAQs

इस Java OOP ट्यूटोरियल को फॉलो करने के लिए पूर्वापेक्षाएँ क्या हैं?

आपको Java प्रोग्रामिंग का बुनियादी ज्ञान होना चाहिए, जिसमें डेटा टाइप्स, वेरिएबल्स, कंट्रोल फ्लो (if/else, लूप्स), और बेसिक सिंटैक्स शामिल हैं। यदि आपको रिफ्रेशर चाहिए, तो हमारा Introduction to Java कोर्स इन बुनियादों को कवर करता है।

OOP अवधारणाओं को समझाने के लिए रेस्तरां मेनू सिस्टम का उपयोग क्यों?

रेस्तरां मेनू सिस्टम OOP सिद्धांतों के वास्तविक‑दुनिया के अनुप्रयोगों को दर्शाने वाला सहज उदाहरण है। यह स्वाभाविक रूप से इनहेरिटेंस (मेनू आइटम्स के अलग‑अलग प्रकार), एन्कैप्सुलेशन (कीमत और नाम मानों की सुरक्षा), और इंटरफेसेज़ (लॉयल्टी प्रोग्राम, तापमान नियंत्रण) को दिखाता है। अधिकांश लोग रेस्तरां कैसे चलते हैं, समझते हैं, जिससे अवधारणाएँ समझना आसान हो जाता है।

Java में एब्स्ट्रैक्ट क्लासेज़ और इंटरफेसेज़ में क्या अंतर है?
  • एक क्लास केवल एक ही एब्स्ट्रैक्ट क्लास को एक्सटेंड कर सकती है, लेकिन कई इंटरफेसेज़ को इम्प्लीमेंट कर सकती है।
  • एब्स्ट्रैक्ट क्लासेज़ में फ़ील्ड्स और कंस्ट्रक्टर्स हो सकते हैं; इंटरफेसेज़ में नहीं।
  • एब्स्ट्रैक्ट क्लासेज़ में एब्स्ट्रैक्ट और कंक्रीट दोनों तरह के मेथड्स हो सकते हैं; पारंपरिक रूप से (Java 8 से पहले) इंटरफेसेज़ में सिर्फ एब्स्ट्रैक्ट मेथड्स होते थे।
  • एब्स्ट्रैक्ट क्लासेज़ बेस इम्प्लीमेंटेशन देती हैं, जबकि इंटरफेसेज़ एक कॉन्ट्रैक्ट परिभाषित करती हैं।
Bexruz (Bex) Tuychiev

लेखक

Bexruz (Bex) Tuychiev

मैं 2 से अधिक वर्षों के अनुभव वाला डेटा साइंस कंटेंट क्रिएटर हूँ और Medium पर सबसे बड़े फॉलोइंग्स में से एक रखता हूँ। मुझे एआई और एमएल पर विस्तार से लेख लिखना पसंद है, जिसमें हल्का-सा व्यंग्यात्मक अंदाज रहता है—क्योंकि कुछ तो करना पड़ता है ताकि वे कम उबाऊ लगें। मैंने 130 से अधिक लेख और एक DataCamp कोर्स तैयार किया है, और एक और बन रहा है। मेरा कंटेंट 50 लाख से अधिक लोगों ने देखा है, जिनमें से 20 हज़ार लोग Medium और LinkedIn पर फॉलोअर्स बन गए। 

विषय