ฉันได้เรียน Foo<T>
generics, ในวิธีการของFoo
ผมต้องการที่จะได้รับเช่นชั้นชนิดแต่ฉันก็ไม่สามารถเรียกT
T.class
เป็นวิธีที่ต้องการเพื่อรับรอบโดยใช้T.class
อะไร
import com.fasterxml.jackson.core.type.TypeReference;
new TypeReference<T>(){}
ฉันได้เรียน Foo<T>
generics, ในวิธีการของFoo
ผมต้องการที่จะได้รับเช่นชั้นชนิดแต่ฉันก็ไม่สามารถเรียกT
T.class
เป็นวิธีที่ต้องการเพื่อรับรอบโดยใช้T.class
อะไร
import com.fasterxml.jackson.core.type.TypeReference;
new TypeReference<T>(){}
คำตอบ:
คำตอบสั้น ๆ คือไม่มีวิธีค้นหาชนิด runtime ของพารามิเตอร์ประเภททั่วไปใน Java ฉันขอแนะนำให้อ่านบทเกี่ยวกับการลบประเภทในJava Tutorialสำหรับรายละเอียดเพิ่มเติม
วิธีแก้ปัญหายอดนิยมสำหรับสิ่งนี้คือการส่งผ่านClass
พารามิเตอร์ประเภทลงในตัวสร้างของประเภททั่วไปเช่น
class Foo<T> {
final Class<T> typeParameterClass;
public Foo(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public void bar() {
// you can access the typeParameterClass here and do whatever you like
}
}
typeParameterClass
โดยไม่มีการกำหนดค่าเริ่มต้นในตัวสร้างนั้นดีมาก ไม่จำเป็นต้องตั้งค่าเป็นครั้งที่สอง
ฉันกำลังมองหาวิธีที่จะทำสิ่งนี้ด้วยตัวเองโดยไม่เพิ่มการพึ่งพาพิเศษให้กับ classpath หลังจากการสอบสวนฉันพบว่ามันเป็นไปได้ตราบใดที่คุณมีซูเปอร์ไทป์ทั่วไป นี่เป็นสิ่งที่โอเคสำหรับฉันเนื่องจากฉันทำงานกับเลเยอร์DAOกับเลเยอร์ supertype ทั่วไป ถ้าสิ่งนี้เหมาะกับสถานการณ์ของคุณก็เป็นวิธีที่ประณีตที่สุด IMHO
ยาชื่อสามัญส่วนใหญ่ที่ใช้ในกรณีที่ฉันเจอมีซูเปอร์ไทป์ทั่วไปเช่นList<T>
สำหรับArrayList<T>
หรือGenericDAO<T>
เพื่อDAO<T>
ฯลฯ
บทความการเข้าถึงประเภททั่วไปที่รันไทม์ใน Javaอธิบายวิธีที่คุณสามารถทำได้โดยใช้จาวาบริสุทธิ์
@SuppressWarnings("unchecked")
public GenericJpaDao() {
this.entityBeanType = ((Class) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]);
}
โครงการของฉันใช้สปริงซึ่งดียิ่งขึ้นเนื่องจากสปริงมีวิธีการใช้งานที่สะดวกในการค้นหาประเภท นี่เป็นวิธีที่ดีที่สุดสำหรับฉันเพราะดูเรียบร้อย ฉันเดาว่าถ้าคุณไม่ได้ใช้ Spring คุณสามารถเขียนวิธีอรรถประโยชน์ของคุณเอง
import org.springframework.core.GenericTypeResolver;
public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{
@Autowired
private SessionFactory sessionFactory;
private final Class<T> genericType;
private final String RECORD_COUNT_HQL;
private final String FIND_ALL_HQL;
@SuppressWarnings("unchecked")
public AbstractHibernateDao()
{
this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
}
อย่างไรก็ตามมีช่องโหว่เล็ก ๆ น้อย ๆ : ถ้าคุณกำหนดFoo
คลาสของคุณเป็นนามธรรม นั่นหมายความว่าคุณต้องยกระดับชั้นเรียนเป็น:
Foo<MyType> myFoo = new Foo<MyType>(){};
(สังเกตเครื่องหมายวงเล็บปีกกาคู่ท้าย)
ตอนนี้คุณสามารถดึงข้อมูลชนิดของT
at runtime:
Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
อย่างไรก็ตามโปรดทราบว่าmySuperclass
จะต้องเป็น superclass ของการกำหนดคลาสจริงกำหนดประเภทสุดท้ายสำหรับT
ของการกำหนดชั้นเรียนจริงการกำหนดประเภทสุดท้ายสำหรับ
นอกจากนี้ยังไม่หรูหรามาก แต่คุณต้องตัดสินใจว่าคุณต้องการnew Foo<MyType>(){}
หรือnew Foo<MyType>(MyType.class);
ในรหัสของคุณ
ตัวอย่างเช่น:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
/**
* Captures and silently ignores stack exceptions upon popping.
*/
public abstract class SilentStack<E> extends ArrayDeque<E> {
public E pop() {
try {
return super.pop();
}
catch( NoSuchElementException nsee ) {
return create();
}
}
public E create() {
try {
Type sooper = getClass().getGenericSuperclass();
Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];
return (E)(Class.forName( t.toString() ).newInstance());
}
catch( Exception e ) {
return null;
}
}
}
แล้ว:
public class Main {
// Note the braces...
private Deque<String> stack = new SilentStack<String>(){};
public static void main( String args[] ) {
// Returns a new instance of String.
String s = stack.pop();
System.out.printf( "s = '%s'\n", s );
}
}
TypeLiteral
a
และb
สร้างด้วยวิธีนี้จะขยายคลาสเดียวกัน แต่ไม่มีคลาสอินสแตนซ์ที่เหมือนกัน a.getClass() != b.getClass()
วิธีการมาตรฐาน / วิธีแก้ปัญหา / การแก้ปัญหาคือการเพิ่มclass
วัตถุไปยังตัวสร้างเช่น:
public class Foo<T> {
private Class<T> type;
public Foo(Class<T> type) {
this.type = type;
}
public Class<T> getType() {
return type;
}
public T newInstance() {
return type.newInstance();
}
}
ลองนึกภาพคุณมีนามธรรม superclass ที่เป็นทั่วไป:
public abstract class Foo<? extends T> {}
แล้วคุณมีคลาสที่สองที่ขยาย Foo ด้วยแถบทั่วไปที่ขยาย T:
public class Second extends Foo<Bar> {}
คุณสามารถรับคลาสBar.class
ในคลาสFoo ได้โดยเลือกType
(จากคำตอบ bert bruynooghe) และอนุมานโดยใช้Class
อินสแตนซ์:
Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);
คุณต้องทราบว่าการดำเนินการนี้ไม่เหมาะดังนั้นจึงควรเก็บค่าที่คำนวณไว้เพื่อหลีกเลี่ยงการคำนวณหลายอย่างในที่นี้ หนึ่งในการใช้งานทั่วไปคือการใช้ DAO ทั่วไป
การใช้งานขั้นสุดท้าย:
public abstract class Foo<T> {
private Class<T> inferedClass;
public Class<T> getGenericClass(){
if(inferedClass == null){
Type mySuperclass = getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
String className = tType.toString().split(" ")[1];
inferedClass = Class.forName(className);
}
return inferedClass;
}
}
ค่าที่ส่งคืนคือ Bar.class เมื่อเรียกใช้จากคลาส Foo ในฟังก์ชันอื่นหรือจากคลาสบาร์
toString().split(" ")[1]
นั่นคือปัญหาให้หลีกเลี่ยง"class "
นี่คือวิธีการแก้ปัญหาการทำงาน:
@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
try {
String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
Class<?> clazz = Class.forName(className);
return (Class<T>) clazz;
} catch (Exception e) {
throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
}
}
หมายเหตุ: สามารถใช้เป็นซูเปอร์คลาสเท่านั้น
Child extends Generic<Integer>
)หรือ
new Generic<Integer>() {};
) คุณไม่สามารถทำได้เนื่องจากการลบประเภท ดูเพิ่มเติม Stack มากเกินคำถามgenerics ชวา - ประเภทการลบ - เมื่อใดและสิ่งที่เกิดขึ้น
เส้นทางที่ดีกว่าคลาสอื่น ๆ ที่แนะนำคือการส่งผ่านวัตถุที่สามารถทำสิ่งที่คุณจะทำกับคลาสเช่นสร้างอินสแตนซ์ใหม่
interface Factory<T> {
T apply();
}
<T> void List<T> make10(Factory<T> factory) {
List<T> result = new ArrayList<T>();
for (int a = 0; a < 10; a++)
result.add(factory.apply());
return result;
}
class FooFactory<T> implements Factory<Foo<T>> {
public Foo<T> apply() {
return new Foo<T>();
}
}
List<Foo<Integer>> foos = make10(new FooFactory<Integer>());
ฉันมีปัญหานี้ในระดับทั่วไปที่เป็นนามธรรม ในกรณีนี้การแก้ปัญหานั้นง่ายกว่า:
abstract class Foo<T> {
abstract Class<T> getTClass();
//...
}
และต่อมาในชั้นเรียนที่ได้รับ:
class Bar extends Foo<Whatever> {
@Override
Class<T> getTClass() {
return Whatever.class;
}
}
ฉันมีวิธีแก้ปัญหา (น่าเกลียด แต่มีประสิทธิภาพ) สำหรับปัญหานี้ซึ่งฉันใช้เมื่อเร็ว ๆ นี้:
import java.lang.reflect.TypeVariable;
public static <T> Class<T> getGenericClass()
{
__<T> ins = new __<T>();
TypeVariable<?>[] cls = ins.getClass().getTypeParameters();
return (Class<T>)cls[0].getClass();
}
private final class __<T> // generic helper class which does only provide type information
{
private __()
{
}
}
มันเป็นไปได้:
class Foo<T> {
Class<T> clazz = (Class<T>) DAOUtil.getTypeArguments(Foo.class, this.getClass()).get(0);
}
คุณต้องใช้สองฟังก์ชันจากhibernate-generic-dao / blob / master / dao / src / main / java / com / googlecode / genericdao / dao / DAOUtil.javaจำศีล-genericdao
สำหรับคำอธิบายเพิ่มเติมโปรดดูที่สะท้อนให้เห็นถึงข้อมูลทั่วไป
ฉันพบวิธีทั่วไปและเรียบง่ายในการทำเช่นนั้น ในชั้นเรียนของฉันฉันสร้างวิธีที่ส่งกลับประเภททั่วไปตามตำแหน่งในนิยามของชั้นเรียน สมมติว่านิยามคลาสเป็นแบบนี้:
public class MyClass<A, B, C> {
}
ตอนนี้เรามาสร้างคุณลักษณะบางอย่างเพื่อคงประเภทไว้:
public class MyClass<A, B, C> {
private Class<A> aType;
private Class<B> bType;
private Class<C> cType;
// Getters and setters (not necessary if you are going to use them internally)
}
จากนั้นคุณสามารถสร้างวิธีการทั่วไปที่ส่งกลับชนิดตามดัชนีของคำจำกัดความทั่วไป:
/**
* Returns a {@link Type} object to identify generic types
* @return type
*/
private Type getGenericClassType(int index) {
// To make it use generics without supplying the class type
Type type = getClass().getGenericSuperclass();
while (!(type instanceof ParameterizedType)) {
if (type instanceof ParameterizedType) {
type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
} else {
type = ((Class<?>) type).getGenericSuperclass();
}
}
return ((ParameterizedType) type).getActualTypeArguments()[index];
}
ในที่สุดในนวกรรมิกเพียงเรียกวิธีการและส่งดัชนีสำหรับแต่ละประเภท รหัสที่สมบูรณ์ควรมีลักษณะดังนี้:
public class MyClass<A, B, C> {
private Class<A> aType;
private Class<B> bType;
private Class<C> cType;
public MyClass() {
this.aType = (Class<A>) getGenericClassType(0);
this.bType = (Class<B>) getGenericClassType(1);
this.cType = (Class<C>) getGenericClassType(2);
}
/**
* Returns a {@link Type} object to identify generic types
* @return type
*/
private Type getGenericClassType(int index) {
Type type = getClass().getGenericSuperclass();
while (!(type instanceof ParameterizedType)) {
if (type instanceof ParameterizedType) {
type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
} else {
type = ((Class<?>) type).getGenericSuperclass();
}
}
return ((ParameterizedType) type).getActualTypeArguments()[index];
}
}
ตามที่อธิบายไว้ในคำตอบอื่น ๆ ในการใช้ParameterizedType
วิธีการนี้คุณจำเป็นต้องขยายชั้นเรียน แต่ดูเหมือนว่าจะมีงานพิเศษเพื่อสร้างชั้นเรียนใหม่ที่ขยายขอบเขต ...
ดังนั้นการทำให้คลาสเป็นนามธรรมมันบังคับให้คุณขยายออกไปดังนั้นจึงเป็นไปตามข้อกำหนดของคลาสย่อย (ใช้ @Getter ของ lombok)
@Getter
public abstract class ConfigurationDefinition<T> {
private Class<T> type;
...
public ConfigurationDefinition(...) {
this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
...
}
}
ตอนนี้เพื่อขยายโดยไม่ต้องกำหนดคลาสใหม่ (หมายเหตุ {} ที่ส่วนท้าย ... ขยายออกไป แต่อย่าเขียนทับอะไรเลยยกเว้นว่าคุณต้องการ)
private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
ฉันสมมติว่าเนื่องจากคุณมีคลาสทั่วไปคุณจะมีตัวแปรดังนี้:
private T t;
(ตัวแปรนี้จำเป็นต้องใช้ค่าที่ตัวสร้าง)
ในกรณีนั้นคุณสามารถสร้างวิธีการต่อไปนี้:
Class<T> getClassOfInstance()
{
return (Class<T>) t.getClass();
}
หวังว่ามันจะช่วย!
public <T> T yourMethodSignature(Class<T> type) {
// get some object and check the type match the given type
Object result = ...
if (type.isAssignableFrom(result.getClass())) {
return (T)result;
} else {
// handle the error
}
}
หากคุณกำลังขยายหรือใช้คลาส / อินเทอร์เฟซใด ๆ ที่ใช้ชื่อสามัญคุณอาจได้รับประเภททั่วไปของคลาสแม่ / อินเตอร์เฟสโดยไม่ต้องแก้ไขคลาส / อินเตอร์เฟสใด ๆ ที่มีอยู่เลย
อาจมีความเป็นไปได้สามอย่าง
กรณีที่ 1 เมื่อคลาสของคุณขยายคลาสที่ใช้ Generics
public class TestGenerics {
public static void main(String[] args) {
Type type = TestMySuperGenericType.class.getGenericSuperclass();
Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
for(Type gType : gTypes){
System.out.println("Generic type:"+gType.toString());
}
}
}
class GenericClass<T> {
public void print(T obj){};
}
class TestMySuperGenericType extends GenericClass<Integer> {
}
กรณีที่ 2 เมื่อคลาสของคุณกำลังใช้อินเทอร์เฟซที่ใช้ Generics
public class TestGenerics {
public static void main(String[] args) {
Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
for(Type type : interfaces){
Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
for(Type gType : gTypes){
System.out.println("Generic type:"+gType.toString());
}
}
}
}
interface GenericClass<T> {
public void print(T obj);
}
class TestMySuperGenericType implements GenericClass<Integer> {
public void print(Integer obj){}
}
กรณีที่ 3 เมื่อส่วนต่อขยายของคุณขยายส่วนต่อประสานที่ใช้ Generics
public class TestGenerics {
public static void main(String[] args) {
Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
for(Type type : interfaces){
Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
for(Type gType : gTypes){
System.out.println("Generic type:"+gType.toString());
}
}
}
}
interface GenericClass<T> {
public void print(T obj);
}
interface TestMySuperGenericType extends GenericClass<Integer> {
}
นั่นคือตรงไปตรงมาสวย หากคุณต้องการจากภายในชั้นเดียวกัน:
Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
// You have the instance of type 'T' in typeClass variable
System.out.println( "Class instance name: "+ typeClass.getName() );
} catch (ClassNotFoundException e) {
System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
}
ที่จริงแล้วฉันคิดว่าคุณมีเขตข้อมูลในคลาสประเภท T ของคุณหากไม่มีเขตข้อมูลชนิด T จุดของการมีประเภททั่วไปคืออะไร ดังนั้นคุณสามารถทำอินสแตนซ์ของฟิลด์นั้นได้
ในกรณีของฉันฉันมี
รายการ <T> รายการ;ในชั้นเรียนของฉันและฉันจะตรวจสอบว่าประเภทชั้นเรียนเป็น "สถานที่" โดย
if (items.get (0) instanceof Locality) ...
แน่นอนว่าจะใช้งานได้ก็ต่อเมื่อจำนวนคลาสที่เป็นไปได้ทั้งหมดถูก จำกัด
คำถามนี้เก่า แต่ตอนนี้ดีที่สุดคือใช้ google Gson
Google
viewModel
ตัวอย่างที่จะได้รับที่กำหนดเอง
Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);
คลาสชนิดทั่วไป
class GenericClass<T>(private val rawType: Class<*>) {
constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))
fun getRawType(): Class<T> {
return rawType as Class<T>
}
}
ฉันต้องการส่ง T.class ไปยังวิธีที่ใช้ Generics
วิธีreadFileอ่านไฟล์. csv ที่ระบุโดย fileName ด้วย fullpath สามารถมีไฟล์ csv ที่มีเนื้อหาต่างกันดังนั้นฉันต้องผ่านคลาสไฟล์โมเดลเพื่อที่ฉันจะได้รับวัตถุที่เหมาะสม เนื่องจากนี่คือการอ่านไฟล์ csv ฉันต้องการจะทำในลักษณะทั่วไป ด้วยเหตุผลบางอย่างหรือไม่มีวิธีการแก้ปัญหาใดที่ได้ผลสำหรับฉัน ฉันต้องใช้
Class<? extends T> type
เพื่อทำให้มันใช้งานได้ ฉันใช้ไลบรารี opencsv สำหรับการแยกวิเคราะห์ไฟล์ CSV
private <T>List<T> readFile(String fileName, Class<? extends T> type) {
List<T> dataList = new ArrayList<T>();
try {
File file = new File(fileName);
Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
CSVReader csvReader = new CSVReader(headerReader);
// create csv bean reader
CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
.withType(type)
.withIgnoreLeadingWhiteSpace(true)
.build();
dataList = csvToBean.parse();
}
catch (Exception ex) {
logger.error("Error: ", ex);
}
return dataList;
}
นี่คือวิธีที่ readFile วิธีการที่เรียกว่า
List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
ฉันใช้วิธีแก้ปัญหาสำหรับสิ่งนี้:
class MyClass extends Foo<T> {
....
}
MyClass myClassInstance = MyClass.class.newInstance();