جافا: كيف يمكنني إجراء صب ديناميكي لمتغير من نوع إلى آخر؟

StackOverflow https://stackoverflow.com/questions/2127318

  •  22-09-2019
  •  | 
  •  

سؤال

أرغب في إجراء صب ديناميكي لمتغير Java ، يتم تخزين نوع الصب في متغير مختلف.

هذا هو صب منتظم:

 String a = (String) 5;

هذا ما أريده:

 String theType = 'String';
 String a = (theType) 5;

هل هو ممكن؟ وإذا كان الأمر كذلك ، فكيف؟ شكرًا!

تحديث

أحاول ملء فصل مع هاشماب تلقيته.

هذا هو المنشئ:

public ConnectParams(HashMap<String,Object> obj) {

    for (Map.Entry<String, Object> entry : obj.entrySet()) {
        try {
            Field f =  this.getClass().getField(entry.getKey());                
            f.set(this, entry.getValue()); /* <= CASTING PROBLEM */
        } catch (NoSuchFieldException ex) {
            log.error("did not find field '" + entry.getKey() + '"');
        } catch (IllegalAccessException ex) {
            log.error(ex.getMessage());         
        }
    }

}

المشكلة هنا هي أن بعض متغيرات الفئات هي نوع مزدوج ، وإذا تم استلام الرقم 3 ، فإنه يرى أنه عدد صحيح ولدي مشكلة في الكتابة.

هل كانت مفيدة؟

المحلول

فيما يتعلق بالتحديث الخاص بك ، فإن الطريقة الوحيدة لحل هذا في Java هي كتابة رمز يغطي جميع الحالات مع الكثير منها if و else و instanceof التعبيرات. ما تحاول القيام به يبدو كما لو كان يستخدم لبرمجة اللغات الديناميكية. بلغات ثابتة ، ما تحاول القيام به شبه مستحيل وربما يختار المرء نهجًا مختلفًا تمامًا لما تحاول القيام به. اللغات الثابتة ليست مرنة مثل اللغات الديناميكية :)

أمثلة جيدة لأفضل الممارسات جافا هي الجواب من balusc (بمعنى آخر ObjectConverter) و ال الجواب من قبل Andreas_D (بمعنى آخر Adapter) أقل.


هذا ليس منطقيا في

String a = (theType) 5;

نوع من a من المقترن بشكل ثابت أن يكون String لذلك ليس من المنطقي أن يكون لديك مصبوب ديناميكي لهذا النوع الثابت.

ملاحظة: يمكن كتابة السطر الأول من مثالك على أنه Class<String> stringClass = String.class; لكن لا يزال لا يمكنك استخدامه stringClass لإلقاء المتغيرات.

نصائح أخرى

نعم من الممكن استخدام انعكاس

Object something = "something";
String theType = "java.lang.String";
Class<?> theClass = Class.forName(theType);
Object obj = theClass.cast(something);

لكن هذا لا معنى له لأنه يجب حفظ الكائن الناتج في متغير من نوع الكائن. إذا كنت بحاجة إلى أن يكون المتغير من فئة معينة ، فيمكنك فقط الإلقاء على هذا الفصل.

إذا كنت ترغب في الحصول على فئة معينة ، الرقم على سبيل المثال:

Object something = new Integer(123);
String theType = "java.lang.Number";
Class<? extends Number> theClass = Class.forName(theType).asSubclass(Number.class);
Number obj = theClass.cast(something);

ولكن لا يزال هناك أي جدوى من القيام بذلك ، يمكنك فقط أن تصدر العدد.

صب كائن لا يغير أي شيء ؛ انه فقط الطريقة المترجم يعاملها.
السبب الوحيد في القيام بشيء من هذا القبيل ، هو التحقق مما إذا كان الكائن هو مثيل للفئة المعطاة أو أي فئة فرعية منه ، ولكن من الأفضل استخدام ذلك باستخدام instanceof أو Class.isInstance().

تحديث

وفقا الخاص بك الأخير تحديث المشكلة الحقيقية هي أن لديك عددًا صحيحًا في HashMap الذي يجب تعيينه لمضاعفة. ما يمكنك القيام به في هذه الحالة ، هو التحقق من نوع الحقل واستخدام xxxValue() طرق العدد

...
Field f =  this.getClass().getField(entry.getKey());
Object value = entry.getValue();
if (Integer.class.isAssignableFrom(f.getType())) {
    value = Integer.valueOf(((Number) entry.getValue()).intValue());
} else if (Double.class.isAssignableFrom(f.getType())) {
    value = Double.valueOf(((Number) entry.getValue()).doubleValue());
} // other cases as needed (Long, Float, ...)
f.set(this, value);
...

(لست متأكدًا مما إذا كنت أحب فكرة وجود نوع خاطئ في الخريطة)

ستحتاج إلى كتابة نوع من ObjectConverter لهذا. هذا أمر قابل للتنفيذ إذا كان لديك كلا الكائن الذي تريد تحويله وتعرف الفئة المستهدفة التي ترغب في تحويلها إليها. في هذه الحالة بالذات ، يمكنك الحصول على الفصل المستهدف بواسطة Field#getDeclaringClass().

يمكنك إيجاد هنا مثال على هذا ObjectConverter. يجب أن يعطيك الفكرة الأساسية. إذا كنت تريد المزيد من إمكانيات التحويل ، فما عليك سوى إضافة المزيد من الطرق إليها مع الوسيطة المطلوبة ونوع الإرجاع.

يمكنك القيام بذلك باستخدام Class.cast() الطريقة ، التي تلقي بشكل ديناميكي المعلمة الموردة إلى نوع مثيل الفصل لديك. للحصول على مثيل الفصل لحقل معين ، يمكنك استخدام getType() الطريقة في الحقل المعني. لقد قدمت مثالاً أدناه ، لكن لاحظ أنه يحذف جميع معالجة الأخطاء ولا ينبغي استخدامه غير المعدل.

public class Test {

    public String var1;
    public Integer var2;
}

public class Main {

    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("var1", "test");
        map.put("var2", 1);

        Test t = new Test();

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Field f = Test.class.getField(entry.getKey());

            f.set(t, f.getType().cast(entry.getValue()));
        }

        System.out.println(t.var1);
        System.out.println(t.var2);
    }
}

يمكنك كتابة castmethod بسيط مثل واحد أدناه.

private <T> T castObject(Class<T> clazz, Object object) {
  return (T) object;
}

في طريقتك يجب أن تستخدمها مثل

public ConnectParams(HashMap<String,Object> object) {

for (Map.Entry<String, Object> entry : object.entrySet()) {
    try {
        Field f =  this.getClass().getField(entry.getKey());                
        f.set(this, castObject(entry.getValue().getClass(), entry.getValue()); /* <= CASTING PROBLEM */
    } catch (NoSuchFieldException ex) {
        log.error("did not find field '" + entry.getKey() + '"');
    } catch (IllegalAccessException ex) {    
        log.error(ex.getMessage());          
    }    
}

}

إنه يعمل وهناك نمط شائع لنهجك: نمط المحول. ولكن بالطبع ، (1) لا يعمل في إلقاء جافا بدائل للكائنات و (2) يجب أن يكون الفصل قابل للتكيف (عادة عن طريق تطبيق واجهة مخصصة).

مع هذا النمط ، يمكنك أن تفعل شيئًا مثل:

Wolf bigBadWolf = new Wolf();
Sheep sheep = (Sheep) bigBadWolf.getAdapter(Sheep.class);

وطريقة getAdapter في فئة الذئب:

public Object getAdapter(Class clazz) {
  if (clazz.equals(Sheep.class)) {
    // return a Sheep implementation
    return getWolfDressedAsSheep(this);
  }

  if (clazz.equals(String.class)) {
    // return a String
    return this.getName();
  }

  return null; // not adaptable
}

بالنسبة لك فكرة خاصة - هذا مستحيل. لا يمكنك استخدام قيمة السلسلة للصب.

مشكلتك ليست عدم وجود "صب ديناميكي". يصب Integer ل Double غير ممكن على الإطلاق. يبدو أنك ترغب في إعطاء جافا كائنًا من نوع واحد ، وحقل من نوع ربما غير متوافق ، وجعله بطريقة أو بأخرى معرفة كيفية التحويل بين الأنواع.

هذا النوع من الأشياء هو لعنة بلغة مكتوبة بقوة مثل جافا ، والمنظمة البحرية الدولية لأسباب وجيهة للغاية.

ما كنت في الواقع تحاول أن تفعل؟ كل هذا استخدام الانعكاس يبدو مريب جدا.

لا تفعل هذا. فقط لديك مُنشئ المعلمة بشكل صحيح بدلاً من ذلك. يتم إصلاح مجموعة وأنواع معلمات الاتصال على أي حال ، لذلك لا فائدة من القيام بذلك ديناميكيًا.

لما يستحق الأمر ، تدعم معظم لغات البرمجة النصية (مثل Perl) ولغات وقت الترجمة غير الستاطية (مثل Pick) سلسلة ديناميكية للوقت التلقائي إلى التحويلات (التعسفية نسبيًا). يمكن تحقيق ذلك في Java أيضًا دون فقدان السلامة من النوع واللغات الجيدة التي توفرها اللغات المغطاة بشكل ثابت دون الآثار الجانبية السيئة لبعض اللغات الأخرى التي تقوم بأشياء شريرة مع صب ديناميكي. مثال بيرل يقوم ببعض الرياضيات المشكوك فيها:

print ++($foo = '99');  # prints '100'
print ++($foo = 'a0');  # prints 'a1'

في Java ، يتم إنجاز هذا بشكل أفضل (IMHO) باستخدام طريقة أسميها "الصب المتقاطع". مع الصب المتقاطع ، يتم استخدام الانعكاس في ذاكرة التخزين المؤقت للكسول من البنائين والطرق التي يتم اكتشافها ديناميكيًا عبر الطريقة الثابتة التالية:

Object fromString (String value, Class targetClass)

لسوء الحظ ، لن تقوم أي طرق Java مدمجة مثل class.cast () بذلك للسلسلة إلى BigDecimal أو سلسلة إلى عدد صحيح أو أي تحويل آخر حيث لا يوجد تسلسل هرمي فئة داعمة. من جانبي ، النقطة المهمة هي توفير طريقة ديناميكية تمامًا لتحقيق ذلك - لا أعتقد أن المرجع السابق هو النهج الصحيح - الاضطرار إلى ترميز كل تحويل. ببساطة ، فإن التنفيذ هو مجرد سلسلة من السلسلة إذا كان قانونيًا/ممكنًا.

وبالتالي فإن الحل هو انعكاس بسيط يبحث عن أعضاء عامين من:

string_class_array = (فئة جديدة [] {string.class}) ؛

A) عضو = TargetClass.getMethod (method.getName () ، string_class_array) ؛ ب) عضو عضو = targetClass.getConstructor (string_class_array) ؛

ستجد أن جميع البدائل (عدد صحيح ، طويل ، إلخ) وجميع الأساسيات (BigInteger ، BigDecimal ، إلخ) وحتى java.regex.pattern تتم تغطية جميعها عبر هذا النهج. لقد استخدمت هذا مع نجاح كبير في مشاريع الإنتاج حيث يوجد قدر كبير من مدخلات قيمة السلسلة التعسفية حيث كانت هناك حاجة إلى فحص أكثر صرامة. في هذا النهج ، إذا لم تكن هناك طريقة أو عندما يتم استدعاء الطريقة ، يتم إلقاء استثناء (لأنها قيمة غير قانونية مثل المدخلات غير الرقمية إلى regex كبيرة أو غير قانونية للنمط الفئة المستهدفة المنطق المتأصل.

هناك بعض الجوانب السلبية لهذا:

1) تحتاج إلى فهم الانعكاس جيدًا (هذا معقد بعض الشيء وليس للمبتدئين). 2) بعض فصول Java والمكتبات الثالثة في الواقع (مفاجأة) غير مشفرة بشكل صحيح. أي أن هناك طرقًا تأخذ وسيطة سلسلة واحدة كمدخلات وإرجاع مثيل للفئة المستهدفة ولكن هذا ليس ما تعتقده ... فكر في فئة عدد صحيح:

static Integer getInteger(String nm)
      Determines the integer value of the system property with the specified name.

الطريقة المذكورة أعلاه لا علاقة لها حقًا بالأعداد الصحيحة حيث أن الكائنات التي تغلف البدائل. سيجد الانعكاس هذا كمرشح محتمل لإنشاء عدد صحيح من سلسلة بشكل غير صحيح مقابل فك الرمز ، و ValueOF و Menstructor أعضاء - وكلهم مناسبة لمعظم تحويلات السلسلة التعسفية حيث لا تتحكم حقًا في بيانات الإدخال الخاصة بك ولكنك تريد فقط ذلك تعرف إذا كان ذلك ممكنًا.

لعلاج ما سبق ، فإن البحث عن الأساليب التي ترمي استثناءات هي بداية جيدة لأن قيم الإدخال غير الصالحة التي تنشئ مثيلات من هذه الكائنات ينبغي رمي استثناء. لسوء الحظ ، تختلف التطبيقات فيما إذا تم الإعلان عن الاستثناءات كما تم فحصها أم لا. integer.valueof (سلسلة) يلقي رقمًا محددًا على سبيل المثال ، ولكن لم يتم العثور على استثناءات pattern.compile () أثناء البحث عن الانعكاس. مرة أخرى ، ليس فشل هذا النهج الديناميكي "المتقاطع" الذي أعتقد أنه تطبيق غير قياسي للغاية لإعلانات الاستثناء في أساليب إنشاء الكائنات.

إذا كان أي شخص يرغب في مزيد من التفاصيل حول كيفية تنفيذ ما سبق ، فأخبرني ، لكنني أعتقد أن هذا الحل أكثر مرونة/قابلية التوسيع مع رمز أقل دون فقدان الأجزاء الجيدة من السلامة. بالطبع ، من الأفضل دائمًا "معرفة بياناتك" ، ولكن كما يجد الكثير منا ، فإننا في بعض الأحيان متلقيون فقط من المحتوى غير المُدار وعلينا أن نبذل قصارى جهدنا لاستخدامه بشكل صحيح.

هتافات.

لذلك ، هذا منشور قديم ، لكنني أعتقد أنه يمكنني المساهمة بشيء.

يمكنك دائمًا فعل شيء مثل هذا:

package com.dyna.test;  

import java.io.File;  
import java.lang.reflect.Constructor;  

public class DynamicClass{  

  @SuppressWarnings("unchecked")  
  public Object castDynamicClass(String className, String value){  
    Class<?> dynamicClass;  

    try  
    {  
      //We get the actual .class object associated with the specified name  
      dynamicClass = Class.forName(className);  



    /* We get the constructor that received only 
     a String as a parameter, since the value to be used is a String, but we could
easily change this to be "dynamic" as well, getting the Constructor signature from
the same datasource we get the values from */ 


      Constructor<?> cons =  
        (Constructor<?>) dynamicClass.getConstructor(new Class<?>[]{String.class});  

      /*We generate our object, without knowing until runtime 
 what type it will be, and we place it in an Object as 
 any Java object extends the Object class) */  
      Object object = (Object) cons.newInstance(new Object[]{value});  

      return object;  
    }  
    catch (Exception e)  
    {  
      e.printStackTrace();  
    }  
    return null;  
  }  

  public static void main(String[] args)  
  {   
    DynamicClass dynaClass = new DynamicClass();  

    /* 
 We specify the type of class that should be used to represent 
 the value "3.0", in this case a Double. Both these parameters 
 you can get from a file, or a network stream for example. */  
    System.out.println(dynaClass.castDynamicClass("java.lang.Double", "3.0"));  

    /* 
We specify a different value and type, and it will work as 
 expected, printing 3.0 in the above case and the test path in the one below, as the Double.toString() and 
 File.toString() would do. */  
    System.out.println(dynaClass.castDynamicClass("java.io.File", "C:\\testpath"));  
  }  

بطبيعة الحال ، هذا ليس صب ديناميكي حقًا ، كما هو الحال في اللغات الأخرى (Python على سبيل المثال) ، لأن Java عبارة عن لانج مكتوب بشكل ثابت. ومع ذلك ، يمكن أن يحل هذا بعض الحالات الهامشية حيث تحتاج فعليًا إلى تحميل بعض البيانات بطرق مختلفة ، اعتمادًا على بعض المعرفات. أيضًا ، يمكن أن يكون الجزء الذي تحصل فيه على مُنشئ مع معلمة سلسلة أكثر مرونة ، من خلال تمرير تلك المعلمة من مصدر البيانات نفسه. IE من ملف ، ستحصل على توقيع المنشئ الذي تريد استخدامه ، وقائمة القيم المراد استخدامها ، وبهذه الطريقة يمكنك إقرانها ، على سبيل المثال ، المعلمة الأولى هي سلسلة ، مع الكائن الأول ، يلقيها كسلسلة ، الكائن التالي هو عدد صحيح ، وما إلى ذلك ، ولكن بعض الشيء على طول تنفيذ البرنامج الخاص بك ، ستحصل الآن على كائن ملف أولاً ، ثم مزدوج ، إلخ.

وبهذه الطريقة ، يمكنك حساب هذه الحالات ، وصنع صب "ديناميكي" إلى حد ما.

آمل أن يساعد هذا أي شخص لأن هذا يستمر في الظهور في عمليات البحث في Google.

لقد شعرت مؤخرًا أنني اضطررت إلى القيام بذلك أيضًا ، ولكن بعد ذلك وجدت طريقة أخرى ربما تجعل الكود الخاص بي يبدو رائعًا ، ويستخدم OOP أفضل.

لدي العديد من فئات الأخوة التي ينفذ كل منها طريقة معينة doSomething(). من أجل الوصول إلى هذه الطريقة ، يجب أن يكون لدي مثيل لتلك الفئة أولاً ، لكنني أنشأت فئة فائقة لجميع فصول الأخوة الخاصة بي والآن يمكنني الوصول إلى الطريقة من الفئة الفائقة.

أدناه أظهر طريقتان طريقتان بديلتين لـ "الصب الديناميكي".

// Method 1.
mFragment = getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
switch (mUnitNum) {
case 0:
    ((MyFragment0) mFragment).sortNames(sortOptionNum);
    break;
case 1:
    ((MyFragment1) mFragment).sortNames(sortOptionNum);
    break;
case 2:
    ((MyFragment2) mFragment).sortNames(sortOptionNum);
    break;
}

وطريقة استخدامي حاليا ،

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);

فقط اعتقدت أنني سأقوم بنشر شيء وجدته مفيدًا للغاية ويمكن أن يكون ممكنًا لشخص يعاني من احتياجات مماثلة.

كانت الطريقة التالية هي طريقة كتبت لتطبيق Javafx الخاص بي لتجنب الاضطرار إلى الإلقاء وتجنب الكتابة إذا كان الكائن X مثيلًا من عبارات الكائن B في كل مرة يتم فيها إرجاع وحدة التحكم.

public <U> Optional<U> getController(Class<U> castKlazz){
    try {
        return Optional.of(fxmlLoader.<U>getController());
    }catch (Exception e){
        e.printStackTrace();
    }
    return Optional.empty();
}

كان إعلان الطريقة للحصول على وحدة التحكم

public <T> T getController()

باستخدام Type u تم تمريره في طريقتي عبر كائن الفئة ، يمكن إعادة توجيهه إلى الطريقة ، احصل على وحدة تحكم لإخبارها بنوع الكائنات المراد عودتها. يتم إرجاع كائن اختياري في حالة توفير الفئة الخاطئة ويحدث استثناء في هذه الحالة سيتم إرجاع اختياري فارغ والذي يمكننا التحقق منه.

هذا هو ما تبدو عليه الدعوة النهائية للطريقة (إذا كانت موجودة من الكائن الاختياري الذي تم إرجاعه يأخذ المستهلك

getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());

جرب هذا من أجل الصب الديناميكي. سوف تعمل!!!

    String something = "1234";
    String theType = "java.lang.Integer";
    Class<?> theClass = Class.forName(theType);
    Constructor<?> cons = theClass.getConstructor(String.class);
    Object ob =  cons.newInstance(something);
    System.out.println(ob.equals(1234));
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top