প্রতিফলন API কি জন্য?

জাভার প্রতিফলন প্রক্রিয়া একজন বিকাশকারীকে তাদের নাম না জেনেই রানটাইমে ক্লাস, ইন্টারফেস, ক্ষেত্র এবং পদ্ধতি সম্পর্কে পরিবর্তন করতে এবং তথ্য পেতে দেয়।

প্রতিফলন API আপনাকে নতুন অবজেক্ট তৈরি করতে, কল করার পদ্ধতি এবং ফিল্ডের মান পেতে বা সেট করতে দেয়।

আসুন প্রতিফলন ব্যবহার করে আপনি যা করতে পারেন তার একটি তালিকা তৈরি করুন:

  • একটি বস্তুর শ্রেণী সনাক্ত/নির্ধারণ করুন
  • ক্লাস মডিফায়ার, ক্ষেত্র, পদ্ধতি, ধ্রুবক, কনস্ট্রাক্টর এবং সুপারক্লাস সম্পর্কে তথ্য পান
  • কোন পদ্ধতি বাস্তবায়িত ইন্টারফেসের অন্তর্গত তা খুঁজে বের করুন
  • একটি ক্লাসের একটি উদাহরণ তৈরি করুন যার ক্লাসের নাম প্রোগ্রামটি কার্যকর না হওয়া পর্যন্ত জানা যায় না
  • নাম দ্বারা একটি উদাহরণ ক্ষেত্রের মান পান এবং সেট করুন
  • নাম দ্বারা একটি উদাহরণ পদ্ধতি কল

প্রায় সব আধুনিক জাভা প্রযুক্তি প্রতিফলন ব্যবহার করে। এটি আজকের বেশিরভাগ জাভা/জাভা ইই ফ্রেমওয়ার্ক এবং লাইব্রেরিগুলির অন্তর্গত, উদাহরণস্বরূপ:

  • ওয়েব অ্যাপ্লিকেশন নির্মাণের জন্য স্প্রিং ফ্রেমওয়ার্ক
  • JUnit পরীক্ষার কাঠামো

আপনি যদি আগে কখনও এই প্রক্রিয়াগুলির মুখোমুখি না হন তবে আপনি সম্ভবত জিজ্ঞাসা করছেন কেন এই সমস্ত প্রয়োজনীয়। উত্তরটি বেশ সহজ কিন্তু খুব অস্পষ্ট: প্রতিফলন নাটকীয়ভাবে নমনীয়তা এবং আমাদের অ্যাপ্লিকেশন এবং কোড কাস্টমাইজ করার ক্ষমতা বাড়ায়।

কিন্তু সবসময় সুবিধা এবং অসুবিধা আছে. সুতরাং আসুন কয়েকটি অসুবিধা উল্লেখ করা যাক:

  • অ্যাপ্লিকেশন নিরাপত্তা লঙ্ঘন. প্রতিফলন আমাদের কোড অ্যাক্সেস করতে দেয় যা আমাদের উচিত নয় (এনক্যাপসুলেশন লঙ্ঘন)।
  • নিরাপত্তা সীমাবদ্ধতা. প্রতিফলনের জন্য রানটাইম অনুমতি প্রয়োজন যা নিরাপত্তা ব্যবস্থাপক চালিত সিস্টেমের জন্য উপলব্ধ নয়।
  • খারাপ করা. জাভাতে প্রতিফলন ক্লাসপাথ স্ক্যান করে লোড করার জন্য ক্লাস খুঁজে পেতে গতিশীলভাবে প্রকারগুলি নির্ধারণ করে। এটি প্রোগ্রামের কর্মক্ষমতা হ্রাস করে।
  • বজায় রাখা কঠিন। প্রতিফলন ব্যবহার করে এমন কোড পড়া এবং ডিবাগ করা কঠিন। এটি কম নমনীয় এবং বজায় রাখা কঠিন।

প্রতিফলন API ব্যবহার করে ক্লাসের সাথে কাজ করা

সমস্ত প্রতিফলন অপারেশন একটি java.lang.Class অবজেক্ট দিয়ে শুরু হয়। প্রতিটি ধরনের বস্তুর জন্য, java.lang.Class- এর একটি অপরিবর্তনীয় উদাহরণ তৈরি করা হয়। এটি বস্তুর বৈশিষ্ট্য পাওয়ার, নতুন বস্তু তৈরি এবং কল করার পদ্ধতি প্রদান করে।

আসুন java.lang.Class এর সাথে কাজ করার প্রাথমিক পদ্ধতির তালিকা দেখি :

পদ্ধতি কর্ম
স্ট্রিং getName(); ক্লাসের নাম ফেরত দেয়
int getModifiers(); অ্যাক্সেস মডিফায়ার ফেরত দেয়
প্যাকেজ getPackage(); একটি প্যাকেজ সম্পর্কে তথ্য প্রদান করে
ক্লাস getSuperclass(); একটি অভিভাবক শ্রেণীর সম্পর্কে তথ্য প্রদান করে
ক্লাস[] getInterfaces(); ইন্টারফেসের একটি অ্যারে প্রদান করে
কনস্ট্রাক্টর[] getConstructors(); ক্লাস কনস্ট্রাক্টর সম্পর্কে তথ্য প্রদান করে
ক্ষেত্র [] getFields(); একটি ক্লাসের ক্ষেত্র দেখায়
ফিল্ড গেটফিল্ড (স্ট্রিং ফিল্ডের নাম); নামের দ্বারা একটি শ্রেণীর একটি নির্দিষ্ট ক্ষেত্র প্রদান করে
পদ্ধতি[] getMethods(); পদ্ধতির একটি অ্যারে প্রদান করে

ক্লাস, ইন্টারফেস, ক্ষেত্র এবং পদ্ধতি সম্পর্কে ডেটা পাওয়ার জন্য এইগুলি সবচেয়ে গুরুত্বপূর্ণ পদ্ধতি। এমন পদ্ধতিও রয়েছে যা আপনাকে ক্ষেত্রের মান পেতে বা সেট করতে এবং ব্যক্তিগত ক্ষেত্রগুলিতে অ্যাক্সেস করতে দেয়। আমরা একটু পরে তাদের দেখব.

এই মুহূর্তে আমরা java.lang.Class নিজেই পাওয়ার বিষয়ে কথা বলব । আমরা এটি করতে তিনটি উপায় আছে.

1. Class.forName ব্যবহার করা

একটি চলমান অ্যাপ্লিকেশনে, ক্লাস পেতে আপনাকে অবশ্যই forName(String className) পদ্ধতি ব্যবহার করতে হবে।

এই কোডটি দেখায় কিভাবে আমরা প্রতিফলন ব্যবহার করে ক্লাস তৈরি করতে পারি। আসুন একটি ব্যক্তি শ্রেণি তৈরি করি যার সাথে আমরা কাজ করতে পারি:

package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

এবং আমাদের উদাহরণের দ্বিতীয় অংশ হল কোড যা প্রতিফলন ব্যবহার করে:

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

ক্লাসের পুরো নাম জানা থাকলে এই পদ্ধতিটি সম্ভব। তারপর আপনি স্ট্যাটিক Class.forName() পদ্ধতি ব্যবহার করে সংশ্লিষ্ট ক্লাস পেতে পারেন। এই পদ্ধতি আদিম ধরনের জন্য ব্যবহার করা যাবে না.

2. ক্লাস ব্যবহার করে

যদি কোন টাইপ পাওয়া যায় কিন্তু তার কোন উদাহরণ না থাকে, তাহলে আপনি টাইপের নামের সাথে .class যোগ করে ক্লাস পেতে পারেন। এটি একটি আদিম টাইপের ক্লাস পাওয়ার সবচেয়ে সহজ উপায়।

Class aClass = Person.class;

3. .getClass() ব্যবহার করে

যদি একটি অবজেক্ট পাওয়া যায়, তাহলে ক্লাস পাওয়ার সবচেয়ে সহজ উপায় হল object.getClass() কল করা ।

Person person = new Person();
Class aClass = person.getClass();

শেষ দুটি পদ্ধতির মধ্যে পার্থক্য কি?

A.class ব্যবহার করুন যদি আপনি জানেন যে আপনি কোডিং করার সময় কোন ক্লাস অবজেক্টে আগ্রহী। যদি কোন উদাহরণ পাওয়া না যায়, তাহলে আপনার .class ব্যবহার করা উচিত ।

একটি ক্লাসের পদ্ধতি পাওয়া

আমাদের ক্লাসের মেথডগুলি ফেরত দেয় এমন পদ্ধতিগুলি দেখুন: getDeclaredMethods() এবং getMethods()

getDeclaredMethods() একটি অ্যারে প্রদান করে যেটিতে ক্লাস অবজেক্টের দ্বারা উপস্থাপিত ক্লাসের সমস্ত ঘোষিত পদ্ধতি বা ইন্টারফেসের জন্য মেথড অবজেক্ট রয়েছে, যার মধ্যে পাবলিক, প্রাইভেট, ডিফল্ট এবং সুরক্ষিত পদ্ধতি রয়েছে, কিন্তু উত্তরাধিকারসূত্রে পাওয়া পদ্ধতি নয়।

getMethods() ক্লাস অবজেক্ট দ্বারা উপস্থাপিত ক্লাস বা ইন্টারফেসের সমস্ত পাবলিক পদ্ধতির জন্য মেথড অবজেক্ট ধারণকারী একটি অ্যারে প্রদান করে — যেগুলি ক্লাস বা ইন্টারফেস দ্বারা ঘোষিত, সেইসাথে সুপারক্লাস এবং সুপার ইন্টারফেস থেকে উত্তরাধিকারসূত্রে প্রাপ্ত।

আসুন তাদের প্রতিটি কিভাবে কাজ করে তা দেখে নেওয়া যাক।

getDeclaredMethods() দিয়ে শুরু করা যাক । দুটি পদ্ধতির মধ্যে পার্থক্য বুঝতে আমাদের আবার সাহায্য করার জন্য, নীচে আমরা বিমূর্ত সংখ্যা ক্লাস নিয়ে কাজ করব। আসুন একটি স্ট্যাটিক পদ্ধতি লিখি যা আমাদের মেথড অ্যারেকে List<String>- এ রূপান্তর করবে :

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

এই কোড চালানোর ফলাফল এখানে:

byteValue
shortValue
intValue
longValue
float floatValue;
দ্বিগুণ মূল্য

এগুলি হল সংখ্যা শ্রেণীর ভিতরে ঘোষিত পদ্ধতি । getMethods() কি ফেরত দেয়? উদাহরণে দুটি লাইন পরিবর্তন করা যাক:

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

এটি করার জন্য, আমরা নিম্নলিখিত পদ্ধতিগুলির সেট দেখতে পাব:

byteValue
shortValue
intValue
longValue
float floatValue;
ডাবল ভ্যালু
অপেক্ষা
অপেক্ষা
অপেক্ষা স্ট্রিং হ্যাশকোড গেটক্লাস নোটিফাই নোটিফাই অ্যাল
এর সমান




যেহেতু সমস্ত ক্লাস অবজেক্টের উত্তরাধিকারী, আমাদের পদ্ধতি অবজেক্ট ক্লাসের পাবলিক পদ্ধতিগুলিও ফেরত দেয় ।

ক্লাসের ক্ষেত্র পাওয়া

একটি ক্লাসের ক্ষেত্র পেতে getFields এবং getDeclaredFields পদ্ধতি ব্যবহার করা হয় একটি উদাহরণ হিসাবে, আসুন LocalDateTime ক্লাস তাকান . আমরা আমাদের কোড পুনরায় লিখব:

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

এই কোডটি কার্যকর করার ফলে, আমরা LocalDateTime ক্লাসে থাকা ক্ষেত্রগুলির সেট পাই।

MIN
MAX
serialVersionUID
তারিখ
সময়

আমাদের পূর্ববর্তী পদ্ধতির পরীক্ষার সাথে সাদৃশ্য অনুসারে, আসুন দেখি কি হবে যদি আমরা কোডটি একটু পরিবর্তন করি:

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

আউটপুট:

MIN
MAX

এখন এই পদ্ধতির মধ্যে পার্থক্য বের করা যাক।

getDeclaredFields পদ্ধতিটি ক্লাস বা ইন্টারফেস দ্বারা ঘোষিত সমস্ত ক্ষেত্রের জন্য ফিল্ড অবজেক্টের একটি অ্যারে প্রদান করেক্লাসবস্তু

getFields পদ্ধতি ক্লাসের সমস্ত পাবলিক ফিল্ড বা ইন্টারফেসের জন্য ফিল্ড অবজেক্টের একটি অ্যারে প্রদান করেক্লাসবস্তু

এখন আসুন LocalDateTime এর ভিতরে তাকাই ।

ক্লাস এরMINএবংMAXক্ষেত্রগুলি সর্বজনীন, যার মানে সেগুলি getFields পদ্ধতির মাধ্যমে দৃশ্যমান হবে ৷ বিপরীতে,তারিখ,সময়,serialVersionUIDমেথডের প্রাইভেট মডিফায়ার থাকে , যার মানে তারা getFields পদ্ধতির মাধ্যমে দৃশ্যমান হবে না , কিন্তু আমরা getDeclaredFields ব্যবহার করে সেগুলি পেতে পারি । এইভাবে আমরা ব্যক্তিগত ক্ষেত্রের জন্য ফিল্ড অবজেক্ট অ্যাক্সেস করতে পারি।

অন্যান্য পদ্ধতির বর্ণনা

এখন ক্লাস ক্লাসের কিছু পদ্ধতি সম্পর্কে কথা বলার সময় , যথা:

পদ্ধতি কর্ম
GetModifiers আমাদের ক্লাসের জন্য মডিফায়ার পাওয়া যাচ্ছে
getPackage আমাদের ক্লাস রয়েছে এমন প্যাকেজটি পাচ্ছেন
সুপারক্লাস পান অভিভাবক ক্লাস পাচ্ছেন
ইন্টারফেস পান একটি ক্লাস দ্বারা বাস্তবায়িত ইন্টারফেসের একটি অ্যারে পাওয়া
getName সম্পূর্ণ যোগ্য শ্রেণীর নাম পাওয়া
getSimpleName একটা ক্লাসের নাম পাচ্ছি

getModifiers()

একটি ব্যবহার করে মডিফায়ার অ্যাক্সেস করা যেতে পারেক্লাসবস্তু

মডিফায়ার হল পাবলিক , স্ট্যাটিক , ইন্টারফেস ইত্যাদির মতো কীওয়ার্ড । আমরা getModifiers() পদ্ধতি ব্যবহার করে মডিফায়ার পাই:

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

এই কোড একটি মান সেট করেintপরিবর্তনশীল যে একটি বিট ক্ষেত্র. প্রতিটি অ্যাক্সেস মডিফায়ার সংশ্লিষ্ট বিট সেট বা সাফ করে চালু বা বন্ধ করা যেতে পারে। আমরা java.lang.reflect.Modifier ক্লাসের পদ্ধতিগুলি ব্যবহার করে মডিফায়ারগুলি পরীক্ষা করতে পারি :

import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

আমাদের ব্যক্তির ঘোষণা কেমন দেখায় তা মনে করুন:

public class Person {}

আমরা নিম্নলিখিত আউটপুট পেতে:

ক্লাস সংশোধক: 1
জনসাধারণ: সত্য
স্থির: মিথ্যা
চূড়ান্ত: মিথ্যা
বিমূর্ত: মিথ্যা
ইন্টারফেস: মিথ্যা

যদি আমরা আমাদের ক্লাসের বিমূর্ত তৈরি করি, তাহলে আমাদের আছে:

public abstract class Person {}

এবং এই আউটপুট:

ক্লাস সংশোধক: 1025
সর্বজনীন: সত্য
স্থির: মিথ্যা
চূড়ান্ত: মিথ্যা
বিমূর্ত: সত্য
ইন্টারফেস: মিথ্যা

আমরা অ্যাক্সেস মডিফায়ার পরিবর্তন করেছি, যার মানে আমরা মডিফায়ার ক্লাসের স্ট্যাটিক পদ্ধতির মাধ্যমে ফিরে আসা ডেটাও পরিবর্তন করেছি ।

getPackage()

শুধুমাত্র একটি ক্লাস জেনে, আমরা এর প্যাকেজ সম্পর্কে তথ্য পেতে পারি:

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

GetSuperclass()

যদি আমাদের একটি ক্লাস অবজেক্ট থাকে, তাহলে আমরা এর মূল ক্লাস অ্যাক্সেস করতে পারি:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

আমরা সুপরিচিত অবজেক্ট ক্লাস পাই:

class java.lang.Object

কিন্তু যদি আমাদের ক্লাসে অন্য অভিভাবক শ্রেণী থাকে, তাহলে আমরা পরিবর্তে এটি দেখতে পাব:

package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

এখানে আমরা আমাদের অভিভাবক ক্লাস পাই:

class com.company.Human

গেট ইন্টারফেস()

এখানে আমরা কিভাবে ক্লাস দ্বারা বাস্তবায়িত ইন্টারফেসের তালিকা পেতে পারি:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

এবং আসুন আমাদের ব্যক্তি শ্রেণী পরিবর্তন করতে ভুলবেন না :

public class Person implements Serializable {}

আউটপুট:

[ইন্টারফেস java.io.Serializable]

একটি ক্লাস অনেক ইন্টারফেস বাস্তবায়ন করতে পারে। যে কেন আমরা একটি অ্যারে পেতেক্লাসবস্তু জাভা রিফ্লেকশন এপিআইতে, ইন্টারফেসগুলিও দ্বারা প্রতিনিধিত্ব করা হয়ক্লাসবস্তু

অনুগ্রহ করে মনে রাখবেন: পদ্ধতিটি শুধুমাত্র নির্দিষ্ট শ্রেণী দ্বারা বাস্তবায়িত ইন্টারফেস প্রদান করে, এর মূল শ্রেণী নয়। ক্লাস দ্বারা বাস্তবায়িত ইন্টারফেসের একটি সম্পূর্ণ তালিকা পেতে, আপনাকে উত্তরাধিকার শৃঙ্খলে বর্তমান শ্রেণী এবং এর সমস্ত পূর্বপুরুষ উভয়কেই উল্লেখ করতে হবে।

getName() এবং getSimpleName() এবং getCanonicalName()

আসুন একটি আদিম, একটি নেস্টেড ক্লাস, একটি বেনামী ক্লাস এবং স্ট্রিং ক্লাস জড়িত একটি উদাহরণ লিখি:

public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

আমাদের প্রোগ্রামের ফলাফল:

int ক্লাস (আদিম):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (সাধারণ ক্লাস):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): স্ট্রিং
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (নেস্টেড ক্লাস):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (বেনামী ভিতরের ক্লাস):
getName() ): TestReflection$1
getCanonicalName()): নাল
getSimpleName()):
getTypeName(): TestReflection$1

এখন আমাদের প্রোগ্রামের আউটপুট বিশ্লেষণ করা যাক:

  • getName() সত্তার নাম প্রদান করে।

  • getCanonicalName() বেস ক্লাসের ক্যানোনিকাল নাম প্রদান করে, যা জাভা ল্যাঙ্গুয়েজ স্পেসিফিকেশন দ্বারা সংজ্ঞায়িত করা হয়েছে। যদি বেস ক্লাসের একটি ক্যানোনিকাল নাম না থাকে (অর্থাৎ, যদি এটি একটি স্থানীয় বা বেনামী শ্রেণী বা একটি অ্যারে যার উপাদানের প্রকারের একটি আদর্শ নাম না থাকে) নাল ফেরত দেয়।

  • getSimpleName() সোর্স কোডে উল্লিখিত বেস ক্লাসের সাধারণ নাম প্রদান করে। বেস ক্লাস বেনামী হলে একটি খালি স্ট্রিং প্রদান করে।

  • getTypeName() এই ধরনের নামের জন্য একটি তথ্যপূর্ণ স্ট্রিং প্রদান করে।