การพิจารณาว่าออบเจ็กต์เป็นประเภทดั้งเดิมหรือไม่


114

ฉันมีObject[]อาร์เรย์และฉันกำลังพยายามค้นหาอาร์เรย์ที่เป็นแบบดั้งเดิม ผมเคยลองใช้Class.isPrimitive()แต่ดูเหมือนว่าฉันทำอะไรผิด:

int i = 3;
Object o = i;

System.out.println(o.getClass().getName() + ", " +
                   o.getClass().isPrimitive());

พิมพ์ java.lang.Integer, falseพิมพ์

มีวิธีที่ถูกต้องหรือทางเลือกอื่นหรือไม่?


12
ในระยะสั้น: int.class.isPrimitive()ผลตอบแทนtrue; อัตราผลตอบแทนInteger.class.isPrimitive() false
Patrick

คำตอบ:


166

ประเภทในObject[]จะไม่เป็นแบบดั้งเดิมจริง ๆ - เพราะคุณมีข้อมูลอ้างอิง! นี่คือประเภทของiคือintในขณะที่ชนิดของวัตถุอ้างอิงโดยoเป็นInteger (เนื่องจากอัตโนมัติมวย)

ดูเหมือนว่าคุณต้องค้นหาว่าประเภทนั้นเป็น "Wrapper for Primitive" หรือไม่ ฉันไม่คิดว่าจะมีอะไรในไลบรารีมาตรฐานสำหรับสิ่งนี้ แต่ง่ายต่อการเขียนโค้ด:

import java.util.*;

public class Test
{
    public static void main(String[] args)        
    {
        System.out.println(isWrapperType(String.class));
        System.out.println(isWrapperType(Integer.class));
    }

    private static final Set<Class<?>> WRAPPER_TYPES = getWrapperTypes();

    public static boolean isWrapperType(Class<?> clazz)
    {
        return WRAPPER_TYPES.contains(clazz);
    }

    private static Set<Class<?>> getWrapperTypes()
    {
        Set<Class<?>> ret = new HashSet<Class<?>>();
        ret.add(Boolean.class);
        ret.add(Character.class);
        ret.add(Byte.class);
        ret.add(Short.class);
        ret.add(Integer.class);
        ret.add(Long.class);
        ret.add(Float.class);
        ret.add(Double.class);
        ret.add(Void.class);
        return ret;
    }
}

ฉันรู้สึกว่ามันใช้งานได้กับเครื่องห่อแบบดั้งเดิม แต่ก็ใช้ได้ผลในjava.lang.<type>.TYPEที่สุดซึ่งแน่นอนว่าเป็นแบบดั้งเดิม ดูเหมือนว่าฉันจะไม่สามารถหลีกเลี่ยงการตรวจสอบแต่ละประเภททีละรายการได้ขอบคุณสำหรับวิธีแก้ปัญหาที่ดี
drill3r

3
ฉันสงสัยว่าค่าใช้จ่ายในการใช้ HashSet นั้นดีกว่างบ IF หรือไม่
NateS

9
@NateS: ฉันเชื่อว่ามันอ่านง่ายกว่าซึ่งเป็นเหตุผลว่าทำไมฉันถึงใช้คำสั่งนั้นแทนคำสั่ง "if" จนกว่าจะพิสูจน์ได้ว่าค่าโสหุ้ยของชุดนั้นเป็นคอขวดที่แท้จริง
Jon Skeet

1
@mark: นั่นเป็นบริบทที่เฉพาะเจาะจงมากและควรได้รับการปฏิบัติเช่นนี้ autoboxing ใช้กับ enums หรือไม่? ไม่เป็นประเภทอ้างอิงอยู่แล้ว ไม่เป็นโมฆะ? ไม่เพราะเป็นประเภทอ้างอิง ... รายการต่อไป การเรียกพวกมันว่าดึกดำบรรพ์ทำให้ความหมายของคำนี้อ่อนแอลงอย่างมากและฉันก็ไม่เห็นประโยชน์อะไรเลย
Jon Skeet

2
@NateS HashSetอนุญาตให้เข้าถึงใน O (1) ในขณะที่แถวของifคำสั่งหรือswitchคำสั่งต้องการ O (# ของ wrapper) ในกรณีที่เลวร้ายที่สุด ในทางปฏิบัติเป็นเรื่องที่น่าสงสัยหากifข้อความสำหรับจำนวนคงที่ของ 9 wrapper อาจไม่เร็วกว่าการเข้าถึงแบบแฮชหลังจากทั้งหมด
Karl Richter

83

คอมมอนส์-lang ClassUtilsมีความเกี่ยวข้องวิธีการ

เวอร์ชันใหม่มี:

boolean isPrimitiveOrWrapped = 
    ClassUtils.isPrimitiveOrWrapper(object.getClass());

เวอร์ชันเก่ามีwrapperToPrimitive(clazz)วิธีการซึ่งจะส่งคืนการติดต่อแบบดั้งเดิม

boolean isPrimitiveOrWrapped = 
    clazz.isPrimitive() || ClassUtils.wrapperToPrimitive(clazz) != null;

1
สิ่งนี้ไม่ได้ถูกเพิ่มจนกว่า v3.1ลิงก์ของคุณจะแสดง 2.5 API ฉันได้แก้ไขแล้ว
javamonkey79

8
Spring ยังมีคลาสClassUtilsดังนั้นหากคุณใช้ Spring อยู่แล้วจะสะดวกกว่า
Sergey


17

สำหรับผู้ที่ชอบโค้ดสั้น ๆ

private static final Set<Class> WRAPPER_TYPES = new HashSet(Arrays.asList(
    Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class));
public static boolean isWrapperType(Class clazz) {
    return WRAPPER_TYPES.contains(clazz);
}

1
ทำไม Void.class? คุณห่อโมฆะได้อย่างไร?
Shervin Asgari

2
@Shervin void.class.isPrimitive()กลับมาจริง
assylias

1
โมฆะว่างเปล่าและค่าเดียวที่ถูกต้องสำหรับ a Voidคือnull;) มันมีประโยชน์สำหรับการสร้างCallable<Void>ซึ่งเป็น Callable ซึ่งไม่ส่งคืนอะไรเลย
Peter Lawrey

8

ตั้งแต่ Java 1.5 ขึ้นไปมีคุณลักษณะใหม่ที่เรียกว่า auto-boxing คอมไพเลอร์ทำสิ่งนี้เอง เมื่อเห็นโอกาสมันจะแปลงประเภทดั้งเดิมเป็นคลาส Wrapper ที่เหมาะสม

สิ่งที่อาจเกิดขึ้นที่นี่คือเมื่อคุณประกาศ

Object o = i;

คอมไพเลอร์จะรวบรวมคำสั่งนี้ว่า

Object o = Integer.valueOf(i);

นี่คือการชกมวยอัตโนมัติ สิ่งนี้จะอธิบายผลลัพธ์ที่คุณได้รับ หน้านี้จากข้อมูลจำเพาะ Java 1.5 จะอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับการชกมวยอัตโนมัติ


6
ไม่เป็นความจริงทั้งหมด มันไม่ได้สร้างจำนวนเต็มใหม่ แต่เรียกว่า Integer.valueOf (int) ซึ่งทำแคชอินสแตนซ์จำนวนเต็ม
Steve Kuo

1
@SteveKuo Integer.valueOf(int)จะส่งคืนค่าแคชเมื่ออาร์กิวเมนต์เป็น "a byte" เท่านั้น (อ่าน: ระหว่าง -128, 127 รวมทั้งสองอย่าง) มิฉะนั้นจะโทรnew Integer(int). ดู: developer.classpath.org/doc/java/lang/… , hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…
Dragas


6

ฉันคิดว่าสิ่งนี้เกิดขึ้นเนื่องจากการชกมวยอัตโนมัติ

int i = 3;
Object o = i;
o.getClass().getName(); // prints Integer

คุณสามารถใช้วิธียูทิลิตี้ที่ตรงกับคลาสมวยเฉพาะเหล่านี้และให้หากคลาสใดคลาสหนึ่งเป็นคลาสดั้งเดิม

public static boolean isWrapperType(Class<?> clazz) {
    return clazz.equals(Boolean.class) || 
        clazz.equals(Integer.class) ||
        clazz.equals(Character.class) ||
        clazz.equals(Byte.class) ||
        clazz.equals(Short.class) ||
        clazz.equals(Double.class) ||
        clazz.equals(Long.class) ||
        clazz.equals(Float.class);
}

ฉันชอบคำตอบนี้มากที่สุดเพราะควรจะเร็วกว่าการค้นหาแฮช นอกจากนี้ยังมี HashSet น้อยกว่าหนึ่งรายการในหน่วยความจำ (เนื่องจากอาจไม่มาก) สุดท้ายนี้ผู้คนสามารถเพิ่มประสิทธิภาพสิ่งนี้ได้มากขึ้นโดยการจัดลำดับชั้นเรียนที่มีการรับรู้บ่อยขึ้น ซึ่งจะแตกต่างกันไปในทุกแอปพลิเคชัน
bmauter

5
คุณสามารถเปลี่ยนไป.equals ==ชั้นเรียนเป็นเสื้อกล้าม
บุญ

5

คุณต้องจัดการกับการชกมวยอัตโนมัติของ java
ลองใช้รหัส

แบบทดสอบสาธารณะ
{
    โมฆะคงที่สาธารณะ main (String [] args)
    {
        int ผม = 3;
        วัตถุ o = i;
        กลับ;
    }
}
คุณได้รับการทดสอบคลาส test.class และjavap -cให้คุณตรวจสอบ bytecode ที่สร้างขึ้น
เรียบเรียงจาก "test.java"
การทดสอบระดับสาธารณะขยาย java.lang.Object {
การทดสอบสาธารณะ ();
  รหัส:
   0: aload_0
   1: invokespecial # 1; // วิธี java / lang / Object. "" :() V
   4: กลับ

โมฆะคงที่สาธารณะหลัก (java.lang.String []); รหัส: 0: iconst_3 1: istore_1 2: iload_1 3: invokestatic # 2; // วิธี java / lang / Integer.valueOf: (I) Ljava / lang / Integer; 6: astore_2 7: กลับ

}

ดังที่คุณเห็นคอมไพเลอร์ java เพิ่ม
invokestatic # 2; // วิธี java / lang / Integer.valueOf: (I) Ljava / lang / Integer;
เพื่อสร้าง Integer ใหม่จาก int ของคุณจากนั้นเก็บObject ใหม่นั้นไว้ใน o ผ่านทาง astore_2


5
public static boolean isValidType(Class<?> retType)
{
    if (retType.isPrimitive() && retType != void.class) return true;
    if (Number.class.isAssignableFrom(retType)) return true;
    if (AbstractCode.class.isAssignableFrom(retType)) return true;
    if (Boolean.class == retType) return true;
    if (Character.class == retType) return true;
    if (String.class == retType) return true;
    if (Date.class.isAssignableFrom(retType)) return true;
    if (byte[].class.isAssignableFrom(retType)) return true;
    if (Enum.class.isAssignableFrom(retType)) return true;
    return false;
}

3

เพื่อให้คุณเห็นว่าเป็นไปได้ที่ isPrimitive จะคืนค่าจริง (เนื่องจากคุณมีคำตอบเพียงพอที่แสดงให้คุณเห็นว่าเหตุใดจึงเป็นเท็จ):

public class Main
{
    public static void main(final String[] argv)
    {
        final Class clazz;

        clazz = int.class;
        System.out.println(clazz.isPrimitive());
    }
}

สิ่งนี้มีความสำคัญในการสะท้อนเมื่อเมธอดใช้ "int" แทนที่จะเป็น "Integer"

รหัสนี้ใช้งานได้:

import java.lang.reflect.Method;

public class Main
{
    public static void main(final String[] argv)
        throws Exception
    {
        final Method method;

        method = Main.class.getDeclaredMethod("foo", int.class);
    }

    public static void foo(final int x)
    {
    }
}

รหัสนี้ล้มเหลว (ไม่พบวิธีการ):

import java.lang.reflect.Method;

public class Main
{
    public static void main(final String[] argv)
        throws Exception
    {
        final Method method;

        method = Main.class.getDeclaredMethod("foo", Integer.class);
    }

    public static void foo(final int x)
    {
    }
}

2

อย่างที่หลาย ๆ คนได้กล่าวไปแล้วนั้นเกิดจาก autoboxing

คุณสามารถสร้างวิธีการสาธารณูปโภคเพื่อตรวจสอบว่าระดับของวัตถุคือInteger, Doubleฯลฯ แต่มีไม่มีทางทราบได้ว่าอ็อบเจ็กต์ถูกสร้างขึ้นโดยออโต้บ็อกซ์ดั้งเดิมหรือไม่ เมื่อบรรจุกล่องแล้วจะดูเหมือนวัตถุที่สร้างขึ้นอย่างชัดเจน

ดังนั้นหากคุณไม่ทราบแน่ชัดว่าอาร์เรย์ของคุณจะไม่มีคลาส Wrapper หากไม่มีการใส่กล่องอัตโนมัติก็ไม่มีทางแก้ปัญหาที่แท้จริง


2

ประเภทกระดาษห่อหุ้ม primitve จะไม่ตอบสนองต่อค่านี้ นี่มีไว้สำหรับการแสดงคลาสของดั้งเดิมแม้ว่านอกเหนือจากการสะท้อนแล้วฉันไม่สามารถนึกถึงการใช้งานมันมากเกินไป ตัวอย่างเช่น

System.out.println(Integer.class.isPrimitive());

พิมพ์ "เท็จ" แต่

public static void main (String args[]) throws Exception
{
    Method m = Junk.class.getMethod( "a",null);
    System.out.println( m.getReturnType().isPrimitive());
}

public static int a()
{
    return 1;
}

พิมพ์ "จริง"


2

ฉันมาสาย แต่ถ้าคุณกำลังทดสอบสนามคุณสามารถใช้getGenericType:

import static org.junit.Assert.*;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;

import org.junit.Test;

public class PrimitiveVsObjectTest {

    private static final Collection<String> PRIMITIVE_TYPES = 
            new HashSet<>(Arrays.asList("byte", "short", "int", "long", "float", "double", "boolean", "char"));

    private static boolean isPrimitive(Type type) {
        return PRIMITIVE_TYPES.contains(type.getTypeName());
    }

    public int i1 = 34;
    public Integer i2 = 34;

    @Test
    public void primitive_type() throws NoSuchFieldException, SecurityException {
        Field i1Field = PrimitiveVsObjectTest.class.getField("i1");
        Type genericType1 = i1Field.getGenericType();
        assertEquals("int", genericType1.getTypeName());
        assertNotEquals("java.lang.Integer", genericType1.getTypeName());
        assertTrue(isPrimitive(genericType1));
    }

    @Test
    public void object_type() throws NoSuchFieldException, SecurityException {
        Field i2Field = PrimitiveVsObjectTest.class.getField("i2");
        Type genericType2 = i2Field.getGenericType();
        assertEquals("java.lang.Integer", genericType2.getTypeName());
        assertNotEquals("int", genericType2.getTypeName());
        assertFalse(isPrimitive(genericType2));
    }
}

เอกสารของ Oracleรายการ 8 ชนิดดั้งเดิม


1

นี่เป็นวิธีที่ง่ายที่สุดที่ฉันคิดได้ คลาส Wrapper มีอยู่ในjava.langแพ็กเกจเท่านั้น และนอกเหนือจากการเรียนเสื้อคลุมไม่มีระดับอื่น ๆ ในการมีข้อมูลชื่อjava.lang TYPEคุณสามารถใช้เพื่อตรวจสอบว่าคลาสนั้นเป็นคลาส Wrapper หรือไม่

public static boolean isBoxingClass(Class<?> clazz)
{
    String pack = clazz.getPackage().getName();
    if(!"java.lang".equals(pack)) 
        return false;
    try 
    {
        clazz.getField("TYPE");
    } 
    catch (NoSuchFieldException e) 
    {
        return false;
    }           
    return true;        
}

1
ฉันเห็นด้วย. แต่ ณ ตอนนี้มันเป็นวิธีที่ง่ายที่สุดที่ฉันคิดได้ :)
Rahul Bobhate


1

คุณสามารถระบุได้ว่าวัตถุเป็นประเภท Wrapper หรือไม่โดยอยู่ใต้คำสั่ง:

***objClass.isAssignableFrom(Number.class);***

และคุณยังสามารถกำหนดวัตถุดั้งเดิมได้โดยใช้เมธอด isPrimitive ()


0
public class CheckPrimitve {
    public static void main(String[] args) {
        int i = 3;
        Object o = i;
        System.out.println(o.getClass().getSimpleName().equals("Integer"));
        Field[] fields = o.getClass().getFields();
        for(Field field:fields) {
            System.out.println(field.getType());
        }
    }
}  

Output:
true
int
int
class java.lang.Class
int

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.