วิธีการส่ง type เป็นพารามิเตอร์ method ใน Java


85

ใน Java คุณจะส่งประเภทเป็นพารามิเตอร์ (หรือประกาศเป็นตัวแปร) ได้อย่างไร?

ฉันไม่ต้องการส่งผ่านอินสแตนซ์ของประเภท แต่เป็นประเภทนั้นเอง (เช่น int, String ฯลฯ )

ใน C # ฉันสามารถทำได้:

private void foo(Type t)
{
    if (t == typeof(String)) { ... }
    else if (t == typeof(int)) { ... }
}

private void bar()
{
    foo(typeof(String));
}

มีวิธีใน Java โดยไม่ผ่านอินสแตนซ์ประเภท t หรือไม่?
หรือฉันต้องใช้ค่าคงที่หรือ enum ของตัวเอง?
หรือมีวิธีที่ดีกว่านี้?

แก้ไข: นี่คือข้อกำหนดสำหรับ foo:
ตามประเภท t จะสร้างสตริง xml แบบสั้นที่แตกต่างกัน
โค้ดใน if / else จะมีขนาดเล็กมาก (หนึ่งหรือสองบรรทัด) และจะใช้ตัวแปรคลาสส่วนตัวบางตัว

java  types 

คุณสามารถผ่านประเภท Class เช่น private void foo (Class c) และใช้เช่น foo (String.class)
— Michael Bavin

คำตอบ:


109

คุณสามารถผ่านClass<T>ใน

private void foo(Class<?> cls) {
    if (cls == String.class) { ... }
    else if (cls == int.class) { ... }
}

private void bar() {
    foo(String.class);
}

อัปเดต : วิธี OOP ขึ้นอยู่กับข้อกำหนดการทำงาน ทางออกที่ดีที่สุดคือการกำหนดอินเทอร์เฟซfoo()และการใช้งานที่เป็นรูปธรรมสองอย่างที่นำไปใช้foo()จากนั้นเพียงแค่เรียกร้องfoo()ให้นำไปใช้ที่คุณอยู่ในมือ อีกวิธีหนึ่งอาจเป็นวิธีMap<Class<?>, Action>ที่คุณสามารถโทรติดต่อactions.get(cls)ได้ actions.get(cls).foo()นี้ได้อย่างง่ายดายที่จะนำมารวมกับอินเตอร์เฟซที่ใช้งานและคอนกรีต:


ฉันจะเพิ่มรายละเอียดของข้อกำหนดในคำถาม

ฉันตัดสินใจที่จะใช้เวอร์ชันพื้นฐานในตอนนี้เนื่องจากประเภทที่เป็นปัญหามักจะเป็นแบบดั้งเดิม (int, string ฯลฯ ) อย่างไรก็ตามฉันจะคำนึงถึงวิธีการโอ้อวดอย่างแน่นอน ขอบคุณ.

Stringไม่ใช่ดั้งเดิมใน Java;)
— BalusC

@ Padawan - ยังไม่พอที่จะไปต่อ คำตอบจาก BalusC โดยใช้ Map ก็เพียงพอแล้ว คุณมีสิทธิ์ที่จะยอมรับมัน
— duffymo

นอกจากนี้คุณสามารถใช้Class<?> clsในสิ่งอื่นนอกเหนือจากการเปรียบเทียบ ในขณะที่คุณไม่สามารถเขียน: cls value = (cls) aCollection.iterator().next();คุณสามารถเรียก cls.cast (aCollection.iterator (). next ()); Class javadoc
— dlamblin

17

ฉันมีคำถามที่คล้ายกันดังนั้นฉันจึงหาคำตอบที่รันได้ทั้งหมดด้านล่าง สิ่งที่ฉันต้องทำคือส่งคลาส (C) ไปยังอ็อบเจ็กต์ (O) ของคลาสที่ไม่เกี่ยวข้องและให้อ็อบเจ็กต์นั้น (O) ส่งอ็อบเจ็กต์คลาส (C) ใหม่กลับมาหาฉันเมื่อฉันขอ

ตัวอย่างด้านล่างแสดงวิธีการดำเนินการนี้ มีคลาส MagicGun ที่คุณโหลดด้วยประเภทย่อยของคลาส Projectile (Pebble, Bullet หรือ NuclearMissle) สิ่งที่น่าสนใจคือคุณโหลดด้วยประเภทย่อยของ Projectile แต่ไม่ใช่วัตถุจริงประเภทนั้น MagicGun สร้างวัตถุจริงเมื่อถึงเวลาถ่ายภาพ

ผลลัพธ์

You've annoyed the target!
You've holed the target!
You've obliterated the target!
click
click

รหัส

import java.util.ArrayList;
import java.util.List;

public class PassAClass {
    public static void main(String[] args) {
        MagicGun gun = new MagicGun();
        gun.loadWith(Pebble.class);
        gun.loadWith(Bullet.class);
        gun.loadWith(NuclearMissle.class);
        //gun.loadWith(Object.class);   // Won't compile -- Object is not a Projectile
        for(int i=0; i<5; i++){
            try {
                String effect = gun.shoot().effectOnTarget();
                System.out.printf("You've %s the target!\n", effect);
            } catch (GunIsEmptyException e) {
                System.err.printf("click\n");
            }
        }
    }
}

class MagicGun {
    /**
     * projectiles holds a list of classes that extend Projectile. Because of erasure, it
     * can't hold be a List<? extends Projectile> so we need the SuppressWarning. However
     * the only way to add to it is the "loadWith" method which makes it typesafe. 
     */
    private @SuppressWarnings("rawtypes") List<Class> projectiles = new ArrayList<Class>();
    /**
     * Load the MagicGun with a new Projectile class.
     * @param projectileClass The class of the Projectile to create when it's time to shoot.
     */
    public void loadWith(Class<? extends Projectile> projectileClass){
        projectiles.add(projectileClass);
    }
    /**
     * Shoot the MagicGun with the next Projectile. Projectiles are shot First In First Out.
     * @return A newly created Projectile object.
     * @throws GunIsEmptyException
     */
    public Projectile shoot() throws GunIsEmptyException{
        if (projectiles.isEmpty())
            throw new GunIsEmptyException();
        Projectile projectile = null;
        // We know it must be a Projectile, so the SuppressWarnings is OK
        @SuppressWarnings("unchecked") Class<? extends Projectile> projectileClass = projectiles.get(0);
        projectiles.remove(0);
        try{
            // http://www.java2s.com/Code/Java/Language-Basics/ObjectReflectioncreatenewinstance.htm
            projectile = projectileClass.newInstance();
        } catch (InstantiationException e) {
            System.err.println(e);
        } catch (IllegalAccessException e) {
            System.err.println(e);
        }
        return projectile;
    }
}

abstract class Projectile {
    public abstract String effectOnTarget();
}

class Pebble extends Projectile {
    @Override public String effectOnTarget() {
        return "annoyed";
    }
}

class Bullet extends Projectile {
    @Override public String effectOnTarget() {
        return "holed";
    }
}

class NuclearMissle extends Projectile {
    @Override public String effectOnTarget() {
        return "obliterated";
    }
}

class GunIsEmptyException extends Exception {
    private static final long serialVersionUID = 4574971294051632635L;
}

2
ฉันเข้าใจว่าคุณกำลังพยายามสาธิตการส่งผ่านประเภทไปยังวิธีการและฉันชอบตัวอย่างโดยทั่วไป แต่คำถามง่ายๆที่ทำให้เกิดขึ้นคือทำไมคุณไม่โหลดปืนเวทย์มนตร์ด้วยอินสแตนซ์ของกระสุนปืนใหม่แทนที่จะทำให้เรื่องยุ่งยากเช่น นี้? List<Class<? extends Projectile>> projectiles = new ArrayList<Class<? extends Projectile>>()นอกจากนี้ยังไม่กดราคาคำเตือนแก้ไขให้กับ คลาส Projectile ควรเป็นอินเทอร์เฟซและซีเรียลไม่จำเป็นสำหรับตัวอย่างของคุณ
— dlamblin

1
เหตุผลเดียวที่ฉันคิดได้คือการอ่านชื่อคลาสอาจมีประโยชน์ pastebin.com/v9UyPtWTแต่จากนั้นคุณสามารถใช้ enum pastebin.com/Nw939Js1
— dlamblin

10

โอ้ แต่มันน่าเกลียดไม่ใช่รหัสเชิงวัตถุ ช่วงเวลาที่คุณเห็น "if / else" และ "typeof" คุณน่าจะนึกถึงความหลากหลาย นี่เป็นวิธีที่ผิดที่จะไป ฉันคิดว่ายาชื่อสามัญเป็นเพื่อนของคุณที่นี่

คุณวางแผนที่จะจัดการกับประเภทใดบ้าง?

อัพเดท:

ถ้าคุณแค่พูดถึง String และ int นี่เป็นวิธีหนึ่งที่คุณอาจทำได้ เริ่มต้นด้วยอินเทอร์เฟซ XmlGenerator (เพียงพอกับ "foo"):

package generics;

public interface XmlGenerator<T>
{
   String getXml(T value);
}

และการนำ XmlGeneratorImpl ไปใช้อย่างเป็นรูปธรรม:

    package generics;

public class XmlGeneratorImpl<T> implements XmlGenerator<T>
{
    private Class<T> valueType;
    private static final int DEFAULT_CAPACITY = 1024;

    public static void main(String [] args)
    {
        Integer x = 42;
        String y = "foobar";

        XmlGenerator<Integer> intXmlGenerator = new XmlGeneratorImpl<Integer>(Integer.class);
        XmlGenerator<String> stringXmlGenerator = new XmlGeneratorImpl<String>(String.class);

        System.out.println("integer: " + intXmlGenerator.getXml(x));
        System.out.println("string : " + stringXmlGenerator.getXml(y));
    }

    public XmlGeneratorImpl(Class<T> clazz)
    {
        this.valueType = clazz;
    }

    public String getXml(T value)
    {
        StringBuilder builder = new StringBuilder(DEFAULT_CAPACITY);

        appendTag(builder);
        builder.append(value);
        appendTag(builder, false);

        return builder.toString();
    }

    private void appendTag(StringBuilder builder) { this.appendTag(builder, false); }

    private void appendTag(StringBuilder builder, boolean isClosing)
    {
        String valueTypeName = valueType.getName();
        builder.append("<").append(valueTypeName);
        if (isClosing)
        {
            builder.append("/");
        }
        builder.append(">");
    }
}

หากฉันรันสิ่งนี้ฉันจะได้รับผลลัพธ์ดังต่อไปนี้:

integer: <java.lang.Integer>42<java.lang.Integer>
string : <java.lang.String>foobar<java.lang.String>

ฉันไม่รู้ว่านี่คือสิ่งที่คุณคิดไว้หรือเปล่า


ตอนนี้มีเพียง int และสตริง วิธีที่จะทำสิ่งนี้คืออะไร?

4
"เมื่อใดก็ตามที่คุณพบว่าตัวเองเขียนโค้ดของฟอร์ม" หากวัตถุเป็นประเภท T1 ให้ทำบางอย่าง แต่ถ้าเป็นประเภท T2 ให้ทำอย่างอื่น "ตบตัวเอง" javapractices.com/topic/TopicAction.do?Id=31
— พูล

@ The Feast: ขอบคุณสำหรับลิงค์ ฉันคิดว่าฉันเข้าใจสิ่งที่กำลังพูด แต่ที่นี่ฉันไม่มีตัวอย่างของวัตถุใด ๆ ฉันต้องการให้ foo สร้างสตริงที่แตกต่างกันสำหรับสตริง int เทียบกับสตริง ฉันจะต้องสร้างสองอินสแตนซ์ของออบเจ็กต์ที่ได้มาจากอ็อบเจ็กต์อื่นเพื่อทำแบบนั้นหรือไม่ มันอาจจะมากเกินไปสำหรับกรณีนี้?

ฉันตัดสินใจที่จะใช้เวอร์ชันพื้นฐานในตอนนี้เนื่องจากประเภทที่เป็นปัญหามักจะเป็นแบบดั้งเดิม (int, string ฯลฯ ) อย่างไรก็ตามฉันจะคำนึงถึงวิธีการโอ้อวดอย่างแน่นอน ขอบคุณ.

1
@ Padawan ขอโทษฉันไม่ได้ตั้งใจที่จะยอมแพ้ - ความคิดเห็นของ duffymo ทำให้ฉันนึกถึงคำพูดนั้นทั้งหมด!
— สระว่ายน้ำ

9

คุณควรผ่านClass...

private void foo(Class<?> t){
    if(t == String.class){ ... }
    else if(t == int.class){ ... }
}

private void bar()
{
   foo(String.class);
}

ทำไมคุณถึงต้องผ่านชั้นเรียนไม่ใช่ประเภท? มีเหตุผลบางประการที่ลายเซ็นเช่นmethod_foo(Type fooableType)มีประโยชน์น้อยกว่าmethod_foo(Class<?> fooableClass)?
— roberto tomás

5

หากคุณต้องการส่งผ่านประเภทที่เทียบเท่าใน Java จะเป็น

java.lang.Class

หากคุณต้องการใช้วิธีการพิมพ์ที่ไม่เหมาะสมคุณก็ใช้

java.lang.Object

และตัวดำเนินการที่เกี่ยวข้อง

instanceof

เช่น

private void foo(Object o) {

  if(o instanceof String) {

  }

}//foo

อย่างไรก็ตามใน Java มีประเภทดั้งเดิมซึ่งไม่ใช่คลาส (เช่น int จากตัวอย่างของคุณ) ดังนั้นคุณต้องระวัง

คำถามที่แท้จริงคือสิ่งที่คุณต้องการบรรลุที่นี่จริง ๆ มิฉะนั้นจะตอบยาก:

หรือมีวิธีที่ดีกว่านี้?


0

คุณสามารถส่งผ่านอินสแตนซ์ของ java.lang.Class ที่แสดงถึงประเภทเช่น

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