জাভাতে তুলনাকারী এবং তুলনা সম্পর্কে লেখার জন্য শুধুমাত্র অলসরাই নয়। আমি অলস নই, তাই অনুগ্রহ করে প্রেম করুন এবং অন্য একটি ব্যাখ্যা সম্পর্কে আঁকড়ে ধরুন। আমি আশা করি এটি অতিরিক্ত হবে না। এবং হ্যাঁ, এই নিবন্ধটি প্রশ্নের উত্তর: " আপনি মেমরি থেকে একটি তুলনা লিখতে পারেন? " আমি আশা করি এই নিবন্ধটি পড়ার পরে প্রত্যেকে মেমরি থেকে একটি তুলনামূলক লিখতে সক্ষম হবে।
ভূমিকা
আপনি জানেন, জাভা একটি বস্তু-ভিত্তিক ভাষা। ফলস্বরূপ, জাভাতে বস্তুগুলিকে ম্যানিপুলেট করার প্রথাগত। কিন্তু শীঘ্রই বা পরে, আপনি কিছু বৈশিষ্ট্যের উপর ভিত্তি করে বস্তুর তুলনা করার কাজটির মুখোমুখি হন। উদাহরণস্বরূপ : ধরুন আমাদেরMessage
ক্লাস দ্বারা বর্ণিত কিছু বার্তা রয়েছে:
public static class Message {
private String message;
private int id;
public Message(String message) {
this.message = message;
this.id = new Random().nextInt(1000);
}
public String getMessage() {
return message;
}
public Integer getId() {
return id;
}
public String toString() {
return "[" + id + "] " + message;
}
}
এই ক্লাসটি টিউটোরিয়াল পয়েন্ট জাভা কম্পাইলারে রাখুন । পাশাপাশি আমদানি বিবৃতি যোগ করতে ভুলবেন না:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
পদ্ধতিতে main
, বেশ কয়েকটি বার্তা তৈরি করুন:
public static void main(String[] args){
List<Message> messages = new ArrayList();
messages.add(new Message("Hello, World!"));
messages.add(new Message("Hello, Sun!"));
System.out.println(messages);
}
আসুন আমরা তাদের তুলনা করতে চাইলে আমরা কি করতাম তা নিয়ে ভাবি? উদাহরণস্বরূপ, আমরা আইডি অনুসারে সাজাতে চাই। এবং একটি ক্রম তৈরি করার জন্য, কোন বস্তুটি প্রথমে আসা উচিত (অর্থাৎ ছোটটি) এবং কোনটি অনুসরণ করা উচিত (অর্থাৎ বড়টি) তা বোঝার জন্য আমাদের কোনওভাবে বস্তুর তুলনা করতে হবে। java.lang.Object এর মত একটি ক্লাস দিয়ে শুরু করা যাক । আমরা জানি যে সমস্ত শ্রেণী অন্তর্নিহিতভাবে Object
ক্লাসের উত্তরাধিকারী। এবং এটি বোধগম্য কারণ এটি ধারণাটিকে প্রতিফলিত করে যে "সবকিছুই একটি বস্তু" এবং সমস্ত শ্রেণীর জন্য সাধারণ আচরণ প্রদান করে। এই ক্লাসটি নির্দেশ করে যে প্রতিটি ক্লাসের দুটি পদ্ধতি রয়েছে: → hashCode
পদ্ধতিটি hashCode
কিছু সংখ্যাসূচক প্রদান করে (int
) বস্তুর উপস্থাপনা। ওটার মানে কি? এর মানে হল যে আপনি যদি একটি ক্লাসের দুটি ভিন্ন উদাহরণ তৈরি করেন, তাহলে তাদের আলাদা hashCode
s থাকতে হবে। পদ্ধতির বর্ণনায় বলা হয়েছে: "যতটা যুক্তিসঙ্গতভাবে ব্যবহারিক, ক্লাস অবজেক্ট দ্বারা সংজ্ঞায়িত হ্যাশকোড পদ্ধতিটি স্বতন্ত্র বস্তুর জন্য স্বতন্ত্র পূর্ণসংখ্যা প্রদান করে"। অন্য কথায়, দুটি ভিন্ন s-এর জন্য , ভিন্ন s instance
হওয়া উচিত । hashCode
অর্থাৎ, এই পদ্ধতিটি আমাদের তুলনার জন্য উপযুক্ত নয়। → equals
_ পদ্ধতিটি equals
প্রশ্নের উত্তর দেয় "এই বস্তুগুলি কি সমান?" এবং একটি প্রদান করে boolean
।" ডিফল্টরূপে, এই পদ্ধতিতে নিম্নলিখিত কোড রয়েছে:
public boolean equals(Object obj) {
return (this == obj);
}
অর্থাৎ, যদি এই পদ্ধতিটি ওভাররাইড না করা হয় তবে এটি মূলত বলে যে বস্তুর উল্লেখগুলি মেলে কিনা। আমরা আমাদের বার্তাগুলির জন্য এটি চাই না, কারণ আমরা বার্তা আইডিতে আগ্রহী, বস্তুর উল্লেখ নয়৷ এবং এমনকি যদি আমরা equals
পদ্ধতিটি ওভাররাইড করি, আমরা সবচেয়ে বেশি আশা করতে পারি যে তারা সমান কিনা তা শিখতে হবে। এবং অর্ডার নির্ধারণের জন্য এটি আমাদের পক্ষে যথেষ্ট নয়। তাহলে আমাদের কি দরকার? আমাদের এমন কিছু দরকার যা তুলনা করে। যিনি তুলনা করেন তিনি একটি Comparator
. Java API খুলুন এবং তুলনাকারী খুঁজুন । java.util.Comparator
প্রকৃতপক্ষে, একটি ইন্টারফেস আছেjava.util.Comparator and java.util.Comparable
আপনি দেখতে পারেন, যেমন একটি ইন্টারফেস বিদ্যমান। একটি শ্রেণী যা এটি প্রয়োগ করে বলে, "আমি এমন একটি পদ্ধতি প্রয়োগ করি যা বস্তুর তুলনা করি।" একমাত্র জিনিস যা আপনাকে সত্যিই মনে রাখতে হবে তা হল তুলনাকারী চুক্তি, যা নিম্নরূপ প্রকাশ করা হয়েছে:
Comparator returns an int according to the following rules:
- It returns a negative int if the first object is smaller
- It returns a positive int if the first object is larger
- It returns zero if the objects are equal
এখন একটি তুলনামূলক লিখুন. আমাদের আমদানি করতে হবে java.util.Comparator
। আমদানি বিবৃতির পরে, পদ্ধতিতে নিম্নলিখিত যোগ করুন main
: Comparator<Message> comparator = new Comparator<Message>();
অবশ্যই, এটি কাজ করবে না, কারণ Comparator
একটি ইন্টারফেস। তাই আমরা {}
বন্ধনীর পরে কোঁকড়া ধনুর্বন্ধনী যোগ করি। ধনুর্বন্ধনী ভিতরে নিম্নলিখিত পদ্ধতি লিখুন:
public int compare(Message o1, Message o2) {
return o1.getId().compareTo(o2.getId());
}
এমনকি আপনার বানান মনে রাখার দরকার নেই। তুলনাকারী এমন একজন যিনি তুলনা করেন, অর্থাৎ তুলনা করেন। বস্তুর আপেক্ষিক ক্রম নির্দেশ করতে, আমরা একটি ফেরত দিই int
। যে মূলত এটা. সুন্দর এবং সহজ. যেমন আপনি উদাহরণ থেকে দেখতে পাচ্ছেন, তুলনাকারী ছাড়াও, আরেকটি ইন্টারফেস রয়েছে — , যা আমাদের পদ্ধতিটি java.lang.Comparable
বাস্তবায়ন করতে হবে । compareTo
এই ইন্টারফেসটি বলে, "একটি ক্লাস যা আমাকে প্রয়োগ করে ক্লাসের উদাহরণ তুলনা করা সম্ভব করে তোলে।" উদাহরণস্বরূপ, To Integer
এর বাস্তবায়ন compare
নিম্নরূপ:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 কিছু চমৎকার পরিবর্তন এনেছে। আপনি যদি ইন্টারফেসটি ঘনিষ্ঠভাবে দেখেন তবে আপনি এটির উপরে টীকাটি Comparator
দেখতে পাবেন । @FunctionalInterface
এই টীকাটি তথ্যের উদ্দেশ্যে এবং আমাদের বলে যে এই ইন্টারফেসটি কার্যকরী। এর মানে হল যে এই ইন্টারফেসে শুধুমাত্র 1টি বিমূর্ত পদ্ধতি রয়েছে, যা একটি বাস্তবায়ন ছাড়াই একটি পদ্ধতি। এটা আমাদের কি দেয়? এখন আমরা তুলনাকারীর কোডটি এভাবে লিখতে পারি:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
আমরা ভেরিয়েবলের নাম বন্ধনীতে রাখি। জাভা দেখতে পাবে কারণ শুধুমাত্র একটি পদ্ধতি আছে, তাহলে প্রয়োজনীয় সংখ্যা এবং ইনপুট প্যারামিটারের ধরন পরিষ্কার। তারপর আমরা কোডের এই অংশে তাদের পাস করার জন্য তীর অপারেটর ব্যবহার করি। আরও কি, জাভা 8 এর জন্য ধন্যবাদ, আমাদের এখন ইন্টারফেসে ডিফল্ট পদ্ধতি রয়েছে। এই পদ্ধতিগুলি ডিফল্টরূপে প্রদর্শিত হয় যখন আমরা একটি ইন্টারফেস প্রয়োগ করি। ইন্টারফেসটিতে Comparator
বেশ কয়েকটি রয়েছে। উদাহরণ স্বরূপ:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
আরেকটি পদ্ধতি আছে যা আপনার কোডকে ক্লিনার করে তুলবে। উপরের উদাহরণটি দেখুন, যেখানে আমরা আমাদের তুলনাকারীকে সংজ্ঞায়িত করেছি। এটার কাজ কি? এটা বেশ আদিম. এটি কেবল একটি বস্তু নেয় এবং কিছু মান বের করে যা "তুলনাযোগ্য"। উদাহরণস্বরূপ, Integer
ইমপ্লিমেন্টস comparable
, তাই আমরা বার্তা আইডি ক্ষেত্রগুলির মানগুলির উপর একটি তুলনা করার জন্য অপারেশন করতে সক্ষম হয়েছি। এই সাধারণ তুলনাকারী ফাংশনটি এভাবে লেখা যেতে পারে:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
অন্য কথায়, আমাদের একটি আছে Comparator
যা এইরকম তুলনা করে: এটি বস্তু নেয়, তাদের থেকে getId()
একটি পেতে পদ্ধতি ব্যবহার করে Comparable
এবং তারপর compareTo
তুলনা করতে ব্যবহার করে। এবং আর কোন ভয়ঙ্কর নির্মাণ নেই। এবং পরিশেষে, আমি আরও একটি বৈশিষ্ট্য নোট করতে চাই। তুলনাকারীদের শৃঙ্খলিত করা যেতে পারে। উদাহরণ স্বরূপ:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());
আবেদন
একটি তুলনাকারী ঘোষণা করা বেশ যৌক্তিক হতে দেখা যাচ্ছে, আপনি কি মনে করেন না? এখন আমাদের দেখতে হবে কিভাবে এবং কোথায় ব্যবহার করতে হবে। →Collections.sort(java.util.Collections)
আমরা অবশ্যই এইভাবে সংগ্রহ সাজাতে পারি। কিন্তু প্রতিটি সংগ্রহ নয়, শুধুমাত্র তালিকা। এখানে অস্বাভাবিক কিছু নেই, কারণ তালিকাগুলি হল এক ধরনের সংগ্রহ যেখানে আপনি তাদের সূচী দ্বারা উপাদানগুলি অ্যাক্সেস করেন। এটি দ্বিতীয় উপাদানটিকে তৃতীয় উপাদানের সাথে অদলবদল করার অনুমতি দেয়। এই কারণেই বাছাই করার নিম্নলিখিত পদ্ধতি শুধুমাত্র তালিকার জন্য:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
→ Arrays.sort(java.util.Arrays)
অ্যারেগুলি সাজানোও সহজ। আবার, একই কারণে — তাদের উপাদানগুলি সূচক দ্বারা অ্যাক্সেস করা হয়। → Descendants of java.util.SortedSet and java.util.SortedMap
আপনি এটি স্মরণ করবেন Set
এবং Map
উপাদানগুলি যে ক্রমে সংরক্ষণ করা হয়েছে তার গ্যারান্টি দেবেন না। কিন্তু, আমাদের বিশেষ বাস্তবায়ন রয়েছে যা অর্ডারের নিশ্চয়তা দেয়। এবং যদি একটি সংগ্রহের উপাদানগুলি বাস্তবায়ন না করে java.util.Comparable
, তাহলে আমরা Comparator
তার কন্সট্রাকটরকে একটি পাস করতে পারি:
Set<Message> msgSet = new TreeSet(comparator);
→ Stream API
স্ট্রীম API-এ, যা জাভা 8-এ উপস্থিত হয়েছে, তুলনাকারীরা আপনাকে স্ট্রিম উপাদানগুলির সাথে কাজ সহজ করতে দেয়। উদাহরণস্বরূপ, ধরুন আমাদের 0 থেকে 999 পর্যন্ত র্যান্ডম সংখ্যার একটি ক্রম প্রয়োজন, অন্তর্ভুক্ত:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
.limit(10)
.sorted(Comparator.naturalOrder())
.forEach(e -> System.out.println(e));
আমরা এখানে থামতে পারি, তবে আরও আকর্ষণীয় সমস্যা রয়েছে। উদাহরণস্বরূপ, ধরুন আপনাকে একটি প্রস্তুত করতে হবে Map
, যেখানে কীটি একটি বার্তা আইডি। উপরন্তু, আমরা এই কীগুলি সাজাতে চাই, তাই আমরা নিম্নলিখিত কোড দিয়ে শুরু করব:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
আমরা আসলে একটি HashMap
এখানে পেতে. এবং আমরা জানি, এটি কোনো অর্ডারের নিশ্চয়তা দেয় না। ফলস্বরূপ, আমাদের উপাদানগুলি, যা আইডি দ্বারা বাছাই করা হয়েছিল, কেবল তাদের ক্রম হারায়। ভাল না. আমাদের সংগ্রাহককে কিছুটা পরিবর্তন করতে হবে:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
কোডটি একটু ভয়ঙ্কর দেখাতে শুরু করেছে, কিন্তু এখন সমস্যাটি সঠিকভাবে সমাধান করা হয়েছে। এখানে বিভিন্ন গ্রুপিং সম্পর্কে আরও পড়ুন:
আপনি আপনার নিজস্ব সংগ্রাহক তৈরি করতে পারেন। এখানে আরও পড়ুন: "জাভা 8 এ একটি কাস্টম সংগ্রাহক তৈরি করা" । এবং আপনি এখানে আলোচনাটি পড়ে উপকৃত হবেন: "স্ট্রিমের সাথে মানচিত্র করার জন্য জাভা 8 তালিকা" ।
ফাল-ফাঁদ
Comparator
এবং Comparable
ভাল। কিন্তু আপনার মনে রাখা উচিত একটি nuance আছে. যখন একটি ক্লাস বাছাই করা হয়, তখন এটি প্রত্যাশা করে যে আপনার ক্লাসটি একটি তে রূপান্তরিত হতে পারে Comparable
। যদি এটি না হয়, তাহলে আপনি রান টাইমে একটি ত্রুটি পাবেন। আসুন একটি উদাহরণ দেখি:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
মনে হচ্ছে এখানে কিছু ভুল নেই। কিন্তু আসলে, আমাদের উদাহরণে, এটি একটি ত্রুটির সাথে ব্যর্থ হবে: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable
এবং সব কারণ এটি উপাদানগুলিকে সাজানোর চেষ্টা করেছে (এটি একটি SortedSet
, সর্বোপরি)...কিন্তু পারেনি। SortedMap
এবং এর সাথে কাজ করার সময় এটি ভুলবেন না SortedSet
।
আরো পড়া: |
---|
GO TO FULL VERSION