java: Làm cách nào để thực hiện truyền động một biến từ kiểu này sang kiểu khác?


85

Tôi muốn thực hiện ép kiểu động cho một biến Java, kiểu ép kiểu được lưu trữ trong một biến khác.

Đây là quá trình đúc thường xuyên:

 String a = (String) 5;

Đây là những gì tôi muốn:

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

Điều này có thể không, và nếu có thì làm thế nào? Cảm ơn!

Cập nhật

Tôi đang cố gắng điền vào một lớp HashMapmà tôi đã nhận được.

Đây là hàm tạo:

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());         
        }
    }

}

Vấn đề ở đây là một số biến của lớp là kiểu Double, và nếu số 3 được nhận, nó sẽ thấy nó như vậy Integervà tôi có vấn đề về kiểu.


Điều đó không có ý nghĩa gì. Bạn muốn tên biến là một kiểu để truyền một chuỗi thành một chuỗi? Gì?
cletus

3
Tôi không biết câu trả lời nhưng tôi sợ điều này có thể trở thành địa ngục bảo trì ... Chỉ tự học Java nhưng tôi sẽ tránh những tình huống yêu cầu cách tiếp cận như thế này. Tôi khá chắc chắn rằng bất cứ điều gì bạn làm có thể được thực hiện theo cách tốt hơn ... chỉ với 2 xu của tôi.
Sejanus

được rồi tôi sẽ cung cấp thêm thông tin về những gì tôi đang cố gắng đạt được.
ufk

cũng cập nhật câu trả lời của tôi bên dưới!
user85421

Câu trả lời:


14

Về bản cập nhật của bạn, cách duy nhất để giải quyết vấn đề này trong Java là viết mã bao gồm tất cả các trường hợp với rất nhiều ifelseinstanceofbiểu thức. Những gì bạn cố gắng làm có vẻ như được sử dụng để lập trình với các ngôn ngữ động. Trong ngôn ngữ tĩnh, những gì bạn cố gắng làm là gần như không thể và người ta có thể chọn một cách tiếp cận hoàn toàn khác cho những gì bạn cố gắng làm. Ngôn ngữ tĩnh không linh hoạt như ngôn ngữ động :)

Các ví dụ hay về phương pháp hay nhất của Java là câu trả lời của BalusC (tức là ObjectConverter) và câu trả lời của Andreas_D (tức là Adapter) bên dưới.


Điều đó không có ý nghĩa, trong

String a = (theType) 5;

kiểu static abị ràng buộc Stringnên không có ý nghĩa gì khi có một kiểu động cho kiểu static này.

Tái bút: Dòng đầu tiên của ví dụ của bạn có thể được viết Class<String> stringClass = String.class;nhưng bạn không thể sử dụng stringClassđể ép kiểu các biến.


Tôi hy vọng rằng cập nhật mà tôi đã đăng sẽ giải thích những gì tôi đang cố gắng làm. tôi đến từ nền tảng php vì vậy có thể điều này không thể đạt được trong java.
ufk

Chính xác, trong Java, bạn không thể năng động như vậy, hãy xem bản cập nhật của tôi.
akuhn

Câu trả lời Xem BalusC của bên dưới, đây là chiều dài (và đau) mà bạn phải đi ...
akuhn

Tôi biết điều này là muộn nhưng tôi nghĩ ý của anh ấy là [thetype] a = (thetype) some_object; đó là vô nghĩa vì bạn chỉ có thể làm Object a = some_object tốt
George Xavier

120

Có, có thể sử dụng Reflection

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

nhưng điều đó không có ý nghĩa nhiều vì đối tượng kết quả phải được lưu trong một biến Objectkiểu. Nếu bạn cần biến thuộc một lớp nhất định, bạn chỉ có thể ép kiểu đến lớp đó.

Nếu bạn muốn lấy một lớp nhất định, Numberví dụ:

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);

nhưng vẫn không có ích gì khi làm như vậy, bạn chỉ có thể truyền đến Number.

Đúc một đối tượng KHÔNG thay đổi bất cứ điều gì; nó chỉ là cách trình biên dịch xử lý nó.
Lý do duy nhất để làm điều gì đó như vậy là để kiểm tra xem đối tượng có phải là một thể hiện của lớp đã cho hay của bất kỳ lớp con nào của nó hay không, nhưng điều đó sẽ tốt hơn nếu sử dụng instanceofhoặc Class.isInstance().

Cập nhật

theo cập nhật cuối cùng của bạn , vấn đề thực sự là bạn có một Integertrong của bạn HashMapmà nên được chỉ định cho a Double. Những gì bạn có thể làm trong trường hợp này, là kiểm tra loại trường và sử dụng các xxxValue()phương pháp củaNumber

...
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);
...

(không chắc liệu tôi có thích ý tưởng có loại sai trong Map)


22

Bạn sẽ cần phải viết đại loại ObjectConvertercho việc này. Điều này có thể làm được nếu bạn có cả đối tượng mà bạn muốn chuyển đổi và bạn biết lớp đích mà bạn muốn chuyển đổi sang. Trong trường hợp cụ thể này, bạn có thể lấy lớp mục tiêu bằng cách Field#getDeclaringClass().

Bạn có thể tìm thấy ở đây một ví dụ về như vậy ObjectConverter. Nó sẽ cung cấp cho bạn ý tưởng cơ sở. Nếu bạn muốn có nhiều khả năng chuyển đổi hơn, chỉ cần thêm nhiều phương thức hơn vào nó với đối số và kiểu trả về mong muốn.


@BalusC - Tôi thấy mã ObjectConverter thú vị, bạn có thể vui lòng mô tả các trường hợp sử dụng cho nó không?
srini.venigalla

Nó hữu ích trong các trường hợp khi quy ước hơn cấu hình được ưu tiên và loại nguồn không khớp với loại đích. Tôi đã sử dụng nó cách đây 2-3 năm trong khung ORM và MVC (thuần túy cho mục đích sở thích) của tôi. Cũng xem văn bản hàng đầu của bài viết blog.
BalusC

12

Bạn có thể thực hiện việc này bằng cách sử dụng Class.cast()phương thức này, phương thức này tự động truyền tham số được cung cấp thành kiểu của cá thể lớp mà bạn có. Để lấy thể hiện lớp của một trường cụ thể, bạn sử dụng getType()phương thức trên trường được đề cập. Tôi đã đưa ra một ví dụ bên dưới, nhưng lưu ý rằng nó bỏ qua tất cả việc xử lý lỗi và không nên được sử dụng khi chưa sửa đổi.

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);
    }
}

1
Điều gì sẽ xảy ra nếu kiểu nhập không phải là siêu kiểu của kiểu trường? Sau đó, bạn sẽ thực sự cần phải chuyển đổi theo chương trình.
BalusC

7

Bạn có thể viết một castMethod đơn giản như hình dưới đây.

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

Trong phương pháp của bạn, bạn nên sử dụng nó như

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());          
    }    
}

}

Vâng, tôi nghĩ câu trả lời này là những gì người hỏi muốn. Anh ấy / Cô ấy vừa nhận được một Lớp <?> Và muốn chuyển đổi một thể hiện thành lớp "?". Theo mặc định java không hỗ trợ điều này. Nhưng sử dụng <T> chúng ta có thể làm điều này.
ruiruige1991

@ ruiruige1991 Điều này là sai. T trong trường hợp đó là một chung. Generics không làm gì trong thời gian chạy. (T) blah sẽ chỉ là (Đối tượng) blah trong thời gian chạy vì xóa kiểu. Tóm lại, generics -> compile-time, và không có tác dụng trong thời gian chạy. Vì dynamic -> thời gian chạy và generics -> thời gian biên dịch, generic là vô dụng.
George Xavier

5

Nó hoạt động và thậm chí có một mẫu chung cho cách tiếp cận của bạn: mẫu Bộ điều hợp . Nhưng tất nhiên, (1) nó không hoạt động để truyền các nguyên thủy java cho các đối tượng và (2) lớp phải có khả năng thích ứng (thường bằng cách triển khai một giao diện tùy chỉnh).

Với mẫu này, bạn có thể làm điều gì đó như:

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

và phương thức getAdapter trong lớp Wolf:

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
}

Đối với bạn ý tưởng đặc biệt - điều đó là không thể. Bạn không thể sử dụng giá trị Chuỗi để truyền.


2

Vấn đề của bạn không phải là thiếu "động đúc". Truyền Integertới hoàn toàn Doublekhông thể thực hiện được. Có vẻ như bạn muốn cung cấp cho Java một đối tượng của một kiểu, một trường thuộc kiểu có thể không tương thích và bằng cách nào đó nó sẽ tự động tìm ra cách chuyển đổi giữa các kiểu.

Loại điều này không phù hợp với một ngôn ngữ được đánh máy mạnh như Java và IMO vì những lý do rất tốt.

Bạn thực sự đang cố gắng làm gì? Tất cả những gì sử dụng phản chiếu trông khá tanh.


@name: liên quan đến chỉnh sửa mà bạn tiếp tục đề xuất: lưu ý rằng tôi đang nói không phải về các giá trị nguyên thủy mà là về các lớp trình bao bọc (được biểu thị bằng cách viết hoa và kiểu dáng dưới dạng mã) và việc truyền giữa các lớp đó chắc chắn là không thể.
Michael Borgwardt

1

Đừng làm điều này. Thay vào đó, chỉ cần có một hàm tạo được tham số hóa đúng. Dù sao thì tập hợp và kiểu của các tham số kết nối đã được cố định, vì vậy không có ích gì khi thực hiện việc này một cách tự động.


1

Đối với những gì đáng giá, hầu hết các ngôn ngữ kịch bản (như Perl) và các ngôn ngữ thời gian biên dịch không tĩnh (như Pick) đều hỗ trợ chuyển đổi Chuỗi động thời gian chạy tự động sang chuyển đổi đối tượng (tương đối tùy ý). Điều này cũng CÓ THỂ được thực hiện trong Java mà không làm mất tính an toàn của kiểu chữ và những thứ tốt nhất mà các ngôn ngữ được định kiểu tĩnh cung cấp mà KHÔNG có tác dụng phụ khó chịu của một số ngôn ngữ khác làm những điều xấu xa với tính năng ép kiểu động. Một ví dụ Perl thực hiện một số phép toán đáng ngờ:

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

Trong Java, điều này được thực hiện tốt hơn (IMHO) bằng cách sử dụng một phương pháp mà tôi gọi là "ép kiểu chéo". Với truyền chéo, phản xạ được sử dụng trong bộ đệm ẩn được tải chậm của các hàm tạo và phương thức được phát hiện động thông qua phương thức tĩnh sau:

Object fromString (String value, Class targetClass)

Thật không may, không có phương thức Java nào được tích hợp sẵn như Class.cast () sẽ thực hiện điều này đối với Chuỗi sang BigDecimal hoặc Chuỗi thành Số nguyên hoặc bất kỳ chuyển đổi nào khác mà không có phân cấp lớp hỗ trợ. Về phần tôi, quan điểm là cung cấp một cách hoàn toàn năng động để đạt được điều này - mà tôi không nghĩ rằng tài liệu tham khảo trước là cách tiếp cận đúng - phải viết mã mọi chuyển đổi. Nói một cách đơn giản, việc triển khai chỉ là ép kiểu từ chuỗi nếu nó hợp pháp / có thể.

Vì vậy, giải pháp là phản ánh đơn giản tìm kiếm các Thành viên công khai của một trong hai:

STRING_CLASS_ARRAY = (Lớp mới [] {String.class});

a) Thành viên thành viên = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b) Thành viên thành viên = targetClass.getConstructor (STRING_CLASS_ARRAY);

Bạn sẽ thấy rằng tất cả các nguyên thủy (Integer, Long, v.v.) và tất cả các khái niệm cơ bản (BigInteger, BigDecimal, v.v.) và thậm chí cả java.regex.Pattern đều được đề cập thông qua cách tiếp cận này. Tôi đã sử dụng điều này với thành công đáng kể trong các dự án sản xuất có một lượng lớn đầu vào giá trị Chuỗi tùy ý, nơi cần phải kiểm tra nghiêm ngặt hơn. Trong cách tiếp cận này, nếu không có phương thức nào hoặc khi phương thức được gọi, một ngoại lệ sẽ được ném ra (vì đó là giá trị không hợp lệ, chẳng hạn như đầu vào không phải số cho BigDecimal hoặc RegEx không hợp lệ cho Mẫu), điều này cung cấp kiểm tra cụ thể cho logic vốn có của lớp đích.

Có một số nhược điểm của điều này:

1) Bạn cần hiểu rõ về phản xạ (điều này hơi phức tạp và không dành cho người mới). 2) Một số lớp Java và thực sự là các thư viện của bên thứ 3 (đáng ngạc nhiên là) không được mã hóa đúng cách. Có nghĩa là, có những phương thức lấy một đối số chuỗi đơn làm đầu vào và trả về một thể hiện của lớp đích nhưng nó không như bạn nghĩ ... Hãy xem xét lớp Integer:

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

Phương thức trên thực sự không liên quan gì đến Số nguyên vì các đối tượng bao bọc các int nguyên thủy. Reflection sẽ thấy đây là một ứng cử viên khả dĩ để tạo một Số nguyên từ một Chuỗi không chính xác so với các Thành viên giải mã, valueof và constructor - tất cả đều phù hợp với hầu hết các chuyển đổi Chuỗi tùy ý mà bạn thực sự không có quyền kiểm soát dữ liệu đầu vào của mình mà chỉ muốn biết nếu nó có thể là một số nguyên.

Để khắc phục điều trên, việc tìm kiếm các phương thức ném Ngoại lệ là một khởi đầu tốt vì các giá trị đầu vào không hợp lệ tạo ra các thể hiện của các đối tượng như vậy nên ném Ngoại lệ. Thật không may, việc triển khai khác nhau tùy thuộc vào việc các Ngoại lệ có được khai báo là đã kiểm tra hay không. Ví dụ: Integer.valueOf (String) ném một NumberFormatException đã kiểm tra, nhưng không tìm thấy ngoại lệ Pattern.compile () trong khi tra cứu phản chiếu. Một lần nữa, không phải là thất bại của phương pháp tiếp cận "truyền chéo" động này, tôi nghĩ rất nhiều như một cách triển khai không chuẩn cho các khai báo ngoại lệ trong các phương pháp tạo đối tượng.

Nếu có ai muốn biết thêm chi tiết về cách thực hiện ở trên, hãy cho tôi biết nhưng tôi nghĩ giải pháp này linh hoạt hơn nhiều / có thể mở rộng và ít mã hơn mà không làm mất đi các phần tốt của type-safe. Tất nhiên, tốt nhất là "biết dữ liệu của bạn" nhưng như nhiều người trong chúng ta thấy, đôi khi chúng ta chỉ là người nhận nội dung không được quản lý và phải cố gắng hết sức để sử dụng nó đúng cách.

Chúc mừng.


1

Vì vậy, đây là một bài viết cũ, tuy nhiên tôi nghĩ tôi có thể đóng góp một chút gì đó cho nó.

Bạn luôn có thể làm điều gì đó như sau:

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"));  
  }  

Tất nhiên, đây không thực sự là truyền động, như trong các ngôn ngữ khác (ví dụ: Python), vì java là một ngôn ngữ được nhập tĩnh. Tuy nhiên, điều này có thể giải quyết một số trường hợp ngoài lề trong đó bạn thực sự cần tải một số dữ liệu theo những cách khác nhau, tùy thuộc vào một số định danh. Ngoài ra, phần mà bạn nhận được một hàm tạo với tham số Chuỗi có thể được làm cho linh hoạt hơn, bằng cách truyền tham số đó từ cùng một nguồn dữ liệu. Tức là từ một tệp, bạn nhận được chữ ký phương thức khởi tạo mà bạn muốn sử dụng và danh sách các giá trị sẽ được sử dụng, theo cách bạn ghép nối, chẳng hạn, tham số đầu tiên là Chuỗi, với đối tượng đầu tiên, truyền nó dưới dạng Chuỗi, đối tượng tiếp theo là một Số nguyên, v.v., nhưng một số trong quá trình thực thi chương trình của bạn, bây giờ bạn nhận được đối tượng Tệp trước tiên, sau đó là Đôi, v.v.

Bằng cách này, bạn có thể giải thích cho những trường hợp đó và thực hiện quá trình truyền "động" một cách nhanh chóng.

Hy vọng điều này sẽ giúp ích cho mọi người vì điều này liên tục xuất hiện trong các tìm kiếm của Google.


0

Gần đây tôi cảm thấy mình cũng phải làm điều này, nhưng sau đó đã tìm ra một cách khác có thể làm cho mã của tôi trông gọn gàng hơn và sử dụng OOP tốt hơn.

Tôi có nhiều lớp anh chị em mà mỗi lớp thực hiện một phương pháp nhất định doSomething(). Để truy cập phương thức đó, trước tiên tôi sẽ phải có một thể hiện của lớp đó, nhưng tôi đã tạo một lớp cha cho tất cả các lớp anh em của mình và bây giờ tôi có thể truy cập phương thức từ lớp cha đó.

Dưới đây tôi chỉ ra hai cách thay thế cho "truyền động".

// 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;
}

và phương pháp hiện đang sử dụng của tôi,

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

Bạn không cần phải truyền động. Công thức của bạn về việc có một lớp cha với phương thức doSomething () và các lớp con triển khai phương thức doSomething () là một trong những mục đích chính của OOP, tính đa hình. Đối tượng foo = new Integer ("1"); foo.toString () trả về 1. Mặc dù nó được gán cho Đối tượng, nó là một Số nguyên và do đó hoạt động như vậy. Chạy phương thức toString sẽ chạy triển khai Integer. Tính đa hình.
George Xavier

0

Tôi chỉ nghĩ rằng tôi sẽ đăng một cái gì đó mà tôi thấy khá hữu ích và có thể thực hiện được cho những người có nhu cầu tương tự.

Phương thức sau đây là phương thức tôi đã viết cho ứng dụng JavaFX của mình để tránh phải ép kiểu và cũng tránh việc viết if object x instance của object b mỗi khi bộ điều khiển được trả về.

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

Khai báo phương thức để lấy bộ điều khiển là

public <T> T getController()

Bằng cách sử dụng kiểu U được truyền vào phương thức của tôi thông qua đối tượng lớp, nó có thể được chuyển tiếp tới bộ điều khiển phương thức get để cho nó biết loại đối tượng nào sẽ trả về. Một đối tượng tùy chọn được trả về trong trường hợp cung cấp sai lớp và một ngoại lệ xảy ra trong trường hợp đó một tùy chọn trống sẽ được trả về mà chúng tôi có thể kiểm tra.

Đây là cuộc gọi cuối cùng đến phương thức trông như thế nào (nếu hiện diện của đối tượng tùy chọn được trả về sẽ lấy một Người tiêu dùng

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

0

Hãy thử điều này cho Truyền động. Nó sẽ hoạt động !!!

    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));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.