জাভা: জেনেরিক টাইপ থেকে আমি কীভাবে ক্লাসের আক্ষরিক পেতে পারি?


193

সাধারণত, আমি দেখেছি লোকেরা ক্লাসটিকে আক্ষরিকভাবে ব্যবহার করে:

Class<Foo> cls = Foo.class;

তবে কী যদি টাইপটি জেনেরিক হয়, যেমন তালিকা? এটি সূক্ষ্মভাবে কাজ করে, তবে তালিকাটির পরামিতি করা উচিত বলে একটি সতর্কতা রয়েছে:

Class<List> cls = List.class

তাহলে কেন যোগ করবেন না <?>? ঠিক আছে, এটি এক ধরণের অমিল ত্রুটির কারণ:

Class<List<?>> cls = List.class

আমি অনুভব করেছি যে এটির মতো কিছু কাজ করবে তবে এটি কেবল একটি সরল ওল 'সিনট্যাক্স ত্রুটি:

Class<List<Foo>> cls = List<Foo>.class

আমি কীভাবে একটি Class<List<Foo>>স্ট্যাটিকালি পেতে পারি , যেমন ক্লাস আক্ষরিক ব্যবহার করে?

আমি পারে ব্যবহার @SuppressWarnings("unchecked"), প্রথম উদাহরণে তালিকা অ স্থিতিমাপ ব্যবহার দ্বারা সৃষ্ট সতর্কবার্তা পরিত্রাণ পেতে Class<List> cls = List.class, কিন্তু আমি বরং চাই না।

কোনও পরামর্শ?

উত্তর:


160

টাইপ ইরেজরের কারণে আপনি পারবেন না ।

জাভা জেনেরিকস বস্তু বর্ণের জন্য সিনট্যাকটিক চিনির চেয়ে কিছুটা বেশি। প্রদর্শন করার জন্যে:

List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal

প্রতিযোগিতার Field.getGenericType()মাধ্যমে কোনও শ্রেণীর সদস্যদের জিজ্ঞাসাবাদ করা হলে রানটাইমে জেনেরিক ধরণের তথ্য একমাত্র নজরে রাখা হয় ।

এই সমস্ত কারণেই Object.getClass()এই স্বাক্ষর রয়েছে:

public final native Class<?> getClass();

গুরুত্বপূর্ণ অংশ হচ্ছে Class<?>

এটি জাভা জেনেরিক্স এফএকিউ থেকে অন্য কোনও উপায়ে রাখতে :

কেন কংক্রিট প্যারামিটারাইজড ধরণের জন্য কোনও শ্রেণির আক্ষরিক নেই?

কারণ প্যারামিটারাইজড টাইপের কোনও সঠিক রানটাইম টাইপের উপস্থাপনা নেই।

একটি শ্রেণীর আক্ষরিক Class একটি প্রদত্ত প্রকারের প্রতিনিধিত্ব করে এমন একটি বস্তুকে বোঝায় । উদাহরণস্বরূপ, String.classশ্রেণিক আক্ষরিক এমন Class বস্তুকে নির্দেশ করে যা প্রকারটি উপস্থাপন করে Stringএবং Classযখন getClassকোনও Stringবস্তুর উপর পদ্ধতিটি আহ্বান করা হয় তখন প্রত্যাবর্তিত বস্তুর সাথে সমান হয় । একটি শ্রেণীর আক্ষরিক রানটাইম টাইপ চেক এবং প্রতিবিম্ব জন্য ব্যবহার করা যেতে পারে।

প্যারামিটারাইজড প্রকারগুলি টাইপ ইরেজর প্রক্রিয়ায় সংকলনের সময় বাইট কোডে অনুবাদ করা হয় তখন তাদের প্রকারের আর্গুমেন্টগুলি হারাতে থাকে। প্রকার মুছে ফেলার একটি পার্শ্ব প্রতিক্রিয়া হিসাবে, জেনেরিক ধরণের সমস্ত তাত্পর্য একই রানটাইম উপস্থাপনা ভাগ করে, যেমন সম্পর্কিত কাঁচা টাইপের that অন্য কথায়, প্যারামিটারাইজড ধরণের নিজস্ব নিজস্ব উপস্থাপনা থাকে না। ফলে, সেখানে বর্গ লিটারেল যেমন বিরচন কোন বিন্দু আছে List<String>.class, List<Long>.classএবং List<?>.class যেহেতু এই ধরনের কোন Classবস্তু বিদ্যমান। কেবল কাঁচা টাইপের Listএকটি Class অবজেক্ট থাকে যা তার রানটাইম টাইপের প্রতিনিধিত্ব করে। এটি হিসাবে উল্লেখ করা হয় List.class


12
List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal আপনি জাভাতে এটি করতে পারবেন না, আপনি টাইপ মিসর্ম্যাচ সংকলন ত্রুটি পাবেন!
ধাফিরনজ

4
তাই ... আমার যদি দরকার হয় তবে আমি কী করব?
ক্রিস্টোফার ফ্রান্সিসকো

2
আপনি সর্বদা List<String> list2 = (List<String>) (Object) list1;
সংকলককে

17
তবুও অন্য একটি "এটি কেবল সি # তে কাজ করে তবে জাভাতে নয়" for আমি একটি জেএসওএন অবজেক্টকে ডিসরিয়ালাইজ করছি, এবং টাইপফ (তালিকা <মাইক্লাস>) সি # তে পুরোপুরি সূক্ষ্মভাবে কাজ করে তবে তালিকা <মাইক্লাস> .ক্লাস জাভাতে একটি সিনট্যাক্স ত্রুটি। হ্যাঁ, ক্ল্যাটাসের মতো এটি যথারীতি একটি যৌক্তিক ব্যাখ্যা রয়েছে তবে আমি সবসময় ভাবছি কেন এই সমস্ত জিনিস কেবল সি # তে কাজ করে।
অভিশাপ শাকসবজি

2
আপনি একেবারে আইনী বলতে কী বোঝেন? কোডটির সেই অংশটি সংকলন করে না?
এডুয়ার্ডো ডেনিস

63

প্যারামিটারাইজড ধরণের জন্য কোনও শ্রেণির আক্ষরিক নেই, তবে প্রকারের অবজেক্ট রয়েছে যা সঠিকভাবে এই ধরণের সংজ্ঞা দেয়।

Java.lang.reflect.ParameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

গুগলের জিসন লাইব্রেরি একটি টাইপটোকেন শ্রেণি সংজ্ঞায়িত করে যা কেবলমাত্র প্যারামিটারাইজড প্রকার তৈরি করতে দেয় এবং জেনারিক বন্ধুত্বপূর্ণ উপায়ে জটিল প্যারামিটারাইজড ধরণের জেসন বস্তুগুলিকে নির্দিষ্ট করতে এটি ব্যবহার করে। আপনার উদাহরণে আপনি ব্যবহার করবেন:

Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()

আমি টাইপটোকেন এবং জিসন ক্লাস জাভাদোকের লিঙ্কগুলি পোস্ট করার ইচ্ছা নিয়েছিলাম কিন্তু স্ট্যাক ওভারফ্লো আমাকে একাধিক লিঙ্ক পোস্ট করতে দেয় না যেহেতু আমি একজন নতুন ব্যবহারকারী, আপনি সহজেই গুগল অনুসন্ধান ব্যবহার করে সেগুলি পেতে পারেন


1
এটির সাহায্যে আমি জেনেরিক ই দিয়ে একটি ক্লাস তৈরি করতে সক্ষম হয়েছি clzz = new TypeToken<E>(){}.getRawType();এবং পরে একইভাবে কাঠামোযুক্ত এনামগুলির সাথে পুনরাবৃত্তি করতে clzz.getEnumConstants()এবং তারপরে অবশেষে সদস্য পদ্ধতিগুলিকে লা Method method = clzz.getDeclaredMethod("getSomeFoo");এত জয় বলতে রিফেকশন ব্যবহার করতে পারি ! ধন্যবাদ!
নারুটো সেম্পাই

57

আপনি এটি একটি ডাবল কাস্ট দ্বারা পরিচালনা করতে পারেন:

@SuppressWarnings("unchecked") Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class


2
দ্বিতীয় ঢালাই পরিবর্তন করে Objectথেকে Classসম্ভবত একটি (অর্থহীন) এর মাথার উপরে সংরক্ষণ আপনি যা করতে পারেন রানটাইম ঢালাই চেক।
ক্লাশসফ্ট

2
@ ক্ল্যাশসফ্ট Classপরিবর্তে আপনার ব্যবহার Objectহিসাবে ব্যবহারটি আরও অর্থবহ বলে মনে হচ্ছে তবে এটি টীকাটির প্রয়োজনীয়তা সরিয়ে দেয় না @SuppressWarnings("unchecked"), এটি একটি নতুন Class is a raw type. References to generic type Class<T> should be parameterized
সতর্কতাও যুক্ত করে

10
আপনি ব্যবহার করতে পারেন Class<?>:(Class<List<Foo>>)(Class<?>)List.class
ডেভস্ট্রাস্ট

@ ডেভস্ট্র্ট আমি যখন দেখি যে আপনি ঠিক আছেন ঠিক তখনই দেখি ... (অবজেক্ট) বা (ক্লাস <?>) ব্যবহার করার পক্ষে কী যুক্তি রয়েছে?
cellepo

2
এই উত্তরটি সম্পূর্ণ অর্থহীন। ওপি ক্লাসের পথটিকে পরামিতি করতে চেয়েছিল কারণ সে একটি uncheckedসতর্কতা পেয়েছিল । এই উত্তরটির কোনও পরিবর্তন / উন্নতি হয় না। ওপি এমনকি তার প্রশ্নের মধ্যেও বলেছেন যে তিনি ব্যবহার করতে চান না SuppressWarnings...
স্পেনহোয়েট

6

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

List x = new ArrayList();
x.add(new SomeClass());
Iterator i = x.iterator();
SomeClass z = (SomeClass) i.next();

হয়ে

List<SomeClass> x = new ArrayList<SomeClass>();
x.add(new SomeClass());
Iterator<SomeClass> i = x.iterator();
SomeClass z = i.next();

এটি সংকলনটি সংকলনকালে আপনার কোডটি পরীক্ষা করতে সহায়তা করে তবে রানটাইম এ এটি এখনও প্রথম উদাহরণের মতো দেখায়।


অতিরিক্ত ব্যাখ্যার জন্য ধন্যবাদ - জেনারিক সম্পর্কে আমার বোঝার বিষয়টি এত বেশি স্পষ্ট হয়ে গেছে যে আমি বুঝতে পেরেছি যে তারা রানটাইম প্রক্রিয়া নয়। :)
টম

2
আমার মতে, এর ঠিক অর্থ হ'ল জেনেরিকটি সূর্যের দ্বারা একটি মধ্যম পদ্ধতিতে প্রয়োগ করা হয়েছিল, আশা করা যায় ওরাকল এটি কোনও দিন ঠিক করে দেয়। সি # এর জেনেরিকের প্রয়োগ আরও অনেক ভাল (অ্যান্ডারস godশ্বরের মতো)
মার্সেল ভালদেজ ওরোজকো

1
@ মার্সেলওয়াল্ডেজ ওরোজকো এএফএইকে, জাভাতে তারা এটি সেভাবে বাস্তবায়ন করেছিলেন কারণ তারা চাইছিলেন যে কোনও সমস্যা ছাড়াই নতুন জেভিএমগুলিতে পুরানো কোড (1.5-পূর্ববর্তী) কাজ করা উচিত। দেখে মনে হচ্ছে এটি একটি খুব স্মার্ট ডিজাইনের সিদ্ধান্ত যা সামঞ্জস্যের বিষয়ে চিন্তা করে। আমি মনে করি না যে এর মধ্যে মধ্যম কিছু আছে।
পিটার.পেট্রভ

3

জাভা জেনেরিক্স প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী এবং সেইজন্য এছাড়াও cletus ' উত্তর শব্দ থাকার কোন বিন্দু আছে মত Class<List<T>>, তবে বাস্তব সমস্যা হলো এই অত্যন্ত বিপজ্জনক হল:

@SuppressWarnings("unchecked")
Class<List<String>> stringListClass = (Class<List<String>>) (Class<?>) List.class;

List<Integer> intList = new ArrayList<>();
intList.add(1);
List<String> stringList = stringListClass.cast(intList);
// Surprise!
String firstElement = stringList.get(0);

cast()করে তোলে চেহারা যেমন যদি এটা নিরাপদ, কিন্তু বাস্তবে এটা সব সময়ে নিরাপদ নয়।


যদিও আমি সেখানে পাচ্ছি না যেখানে List<?>.class= থাকতে পারে না Class<List<?>>কারণ এটি আপনার পক্ষে কার্যকর হবে যখন কোনও পদ্ধতি রয়েছে যা Classআর্গুমেন্টের জেনেরিক ধরণের ভিত্তিতে টাইপ নির্ধারণ করে ।

জন্য getClass()আছে JDK-6184881 ওয়াইল্ডকার্ড ব্যবহার করে সুইচ করার জন্য, অনুরোধ, তবে এটা দেখতে না এই পরিবর্তন মত সঞ্চালন করা হবে (খুব শীঘ্রই) যেহেতু এটি পূর্ববর্তী কোড সাথে সামঞ্জস্যপূর্ণ নয় (দেখুন এই মন্তব্যটি )।


2

পাশাপাশি আমরা সবাই জানি এটি মুছে যায়। তবে এটি কয়েকটি পরিস্থিতিতে জানা যেতে পারে যেখানে শ্রেণি শ্রেণিবিন্যাসে প্রকারটি স্পষ্টভাবে উল্লেখ করা হয়েছে:

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class CaptureType<T> {
    /**
     * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type.
     *
     * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType}
     */
    public Type getTypeParam() {
        Class<?> bottom = getClass();
        Map<TypeVariable<?>, Type> reifyMap = new LinkedHashMap<>();

        for (; ; ) {
            Type genericSuper = bottom.getGenericSuperclass();
            if (!(genericSuper instanceof Class)) {
                ParameterizedType generic = (ParameterizedType) genericSuper;
                Class<?> actualClaz = (Class<?>) generic.getRawType();
                TypeVariable<? extends Class<?>>[] typeParameters = actualClaz.getTypeParameters();
                Type[] reified = generic.getActualTypeArguments();
                assert (typeParameters.length != 0);
                for (int i = 0; i < typeParameters.length; i++) {
                    reifyMap.put(typeParameters[i], reified[i]);
                }
            }

            if (bottom.getSuperclass().equals(CaptureType.class)) {
                bottom = bottom.getSuperclass();
                break;
            }
            bottom = bottom.getSuperclass();
        }

        TypeVariable<?> var = bottom.getTypeParameters()[0];
        while (true) {
            Type type = reifyMap.get(var);
            if (type instanceof TypeVariable) {
                var = (TypeVariable<?>) type;
            } else {
                return type;
            }
        }
    }

    /**
     * Returns the raw type of the generic type.
     * <p>For example in case of {@code CaptureType<String>}, it would return {@code Class<String>}</p>
     * For more comprehensive examples, go through javadocs of {@link com.types.CaptureType}
     *
     * @return Class object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     * @see com.types.CaptureType
     */
    public Class<T> getRawType() {
        Type typeParam = getTypeParam();
        if (typeParam != null)
            return getClass(typeParam);
        else throw new RuntimeException("Could not obtain type information");
    }


    /**
     * Gets the {@link java.lang.Class} object of the argument type.
     * <p>If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}</p>
     *
     * @param type The type
     * @param <A>  type of class object expected
     * @return The Class<A> object of the type
     * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object
     */
    public static <A> Class<A> getClass(Type type) {
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return (Class<A>) Array.newInstance(componentClass, 0).getClass();
            } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
        } else if (type instanceof Class) {
            Class claz = (Class) type;
            return claz;
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else if (type instanceof TypeVariable) {
            throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection");
        } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
    }

    /**
     * This method is the preferred method of usage in case of complex generic types.
     * <p>It returns {@link com.types.TypeADT} object which contains nested information of the type parameters</p>
     *
     * @return TypeADT object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     */
    public TypeADT getParamADT() {
        return recursiveADT(getTypeParam());
    }

    private TypeADT recursiveADT(Type type) {
        if (type instanceof Class) {
            return new TypeADT((Class<?>) type, null);
        } else if (type instanceof ParameterizedType) {
            ArrayList<TypeADT> generic = new ArrayList<>();
            ParameterizedType type1 = (ParameterizedType) type;
            return new TypeADT((Class<?>) type1.getRawType(),
                    Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList()));
        } else throw new UnsupportedOperationException();
    }

}

public class TypeADT {
    private final Class<?> reify;
    private final List<TypeADT> parametrized;

    TypeADT(Class<?> reify, List<TypeADT> parametrized) {
        this.reify = reify;
        this.parametrized = parametrized;
    }

    public Class<?> getRawType() {
        return reify;
    }

    public List<TypeADT> getParameters() {
        return parametrized;
    }
}

এবং এখন আপনি এই জাতীয় জিনিসগুলি করতে পারেন:

static void test1() {
        CaptureType<String> t1 = new CaptureType<String>() {
        };
        equals(t1.getRawType(), String.class);
    }

    static void test2() {
        CaptureType<List<String>> t1 = new CaptureType<List<String>>() {
        };
        equals(t1.getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class);
    }


    private static void test3() {
            CaptureType<List<List<String>>> t1 = new CaptureType<List<List<String>>>() {
            };
            equals(t1.getParamADT().getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class);
    }

    static class Test4 extends CaptureType<List<String>> {
    }

    static void test4() {
        Test4 test4 = new Test4();
        equals(test4.getParamADT().getRawType(), List.class);
    }

    static class PreTest5<S> extends CaptureType<Integer> {
    }

    static class Test5 extends PreTest5<Integer> {
    }

    static void test5() {
        Test5 test5 = new Test5();
        equals(test5.getTypeParam(), Integer.class);
    }

    static class PreTest6<S> extends CaptureType<S> {
    }

    static class Test6 extends PreTest6<Integer> {
    }

    static void test6() {
        Test6 test6 = new Test6();
        equals(test6.getTypeParam(), Integer.class);
    }



    class X<T> extends CaptureType<T> {
    }

    class Y<A, B> extends X<B> {
    }

    class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {
    }

    void test7(){
        Z<String> z = new Z<>();
        TypeADT param = z.getParamADT();
        equals(param.getRawType(), Map.class);
        List<TypeADT> parameters = param.getParameters();
        equals(parameters.get(0).getRawType(), Integer.class);
        equals(parameters.get(1).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class);
    }




    static void test8() throws IllegalAccessException, InstantiationException {
        CaptureType<int[]> type = new CaptureType<int[]>() {
        };
        equals(type.getRawType(), int[].class);
    }

    static void test9(){
        CaptureType<String[]> type = new CaptureType<String[]>() {
        };
        equals(type.getRawType(), String[].class);
    }

    static class SomeClass<T> extends CaptureType<T>{}
    static void test10(){
        SomeClass<String> claz = new SomeClass<>();
        try{
            claz.getRawType();
            throw new RuntimeException("Shouldnt come here");
        }catch (RuntimeException ex){

        }
    }

    static void equals(Object a, Object b) {
        if (!a.equals(b)) {
            throw new RuntimeException("Test failed. " + a + " != " + b);
        }
    }

আরও তথ্য এখানে । তবে আবার এর জন্য পুনরুদ্ধার করা প্রায় অসম্ভব:

class SomeClass<T> extends CaptureType<T>{}
SomeClass<String> claz = new SomeClass<>();

যেখানে এটি মুছে যায়।


এটি জ্যাকএক্স-আরএস, সিএফ দ্বারা ব্যবহৃত কার্যবিধিও ar GenericEntityএবং GenericType
হেইন ব্লাড

1

শ্রেণীর আক্ষরিকের জেনেরিক ধরণের তথ্য নেই বলে প্রকাশিত সত্যের কারণে, আমি মনে করি আপনার মনে করা উচিত যে সমস্ত সতর্কতা থেকে মুক্তি পাওয়া অসম্ভব। একটি উপায়ে, Class<Something>জেনেরিক ধরণ উল্লেখ না করে কোনও সংগ্রহ ব্যবহার করা সমান। আমি যে সেরাটির সাথে বেরিয়ে আসতে পারি তা হ'ল:

private <C extends A<C>> List<C> getList(Class<C> cls) {
    List<C> res = new ArrayList<C>();
    // "snip"... some stuff happening in here, using cls
    return res;
}

public <C extends A<C>> List<A<C>> getList() {
    return getList(A.class);
}

1

আপনি @SuppressWarnings("unchecked")একটি ক্লাসের সমস্ত থেকে মুক্তি পাওয়ার জন্য একটি সহায়ক পদ্ধতি ব্যবহার করতে পারেন ।

@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
    return (Class<T>)cls;
}

তাহলে আপনি লিখতে পারেন

Class<List<Foo>> cls = generify(List.class);

অন্যান্য ব্যবহারের উদাহরণগুলি

  Class<Map<String, Integer>> cls;

  cls = generify(Map.class);

  cls = TheClass.<Map<String, Integer>>generify(Map.class);

  funWithTypeParam(generify(Map.class));

public void funWithTypeParam(Class<Map<String, Integer>> cls) {
}

তবে, যেহেতু এটি খুব কমই সত্যিই দরকারী এবং পদ্ধতির ব্যবহারটি সংকলকটির ধরণের চেককে পরাস্ত করে, তাই আমি এটি এমন জায়গায় প্রয়োগ করার পরামর্শ দেব না যেখানে এটি সর্বজনীনভাবে অ্যাক্সেসযোগ্য।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.