Tengo una clase que se ve así ...

public class LegionInputFormat
        extends FileInputFormat<NullWritable, LegionRecord> {

    @Override
    public RecordReader<NullWritable, LegionRecord>
            createRecordReader(InputSplit split, TaskAttemptContext context) {

        /* Skipped code for getting recordDelimiterBytes */

        return new LegionRecordReader(recordDelimiterBytes);
    }
}

Me gustaría usar un tipo genérico para que pueda devolver cualquier tipo de RecordReader especificado por el usuario, así:

public class LegionInputFormat<T extends RecordReader<NullWritable, LegionRecord>>
        extends FileInputFormat<NullWritable, LegionRecord> {

    @Override
    public RecordReader<NullWritable, LegionRecord>
            createRecordReader(InputSplit split, TaskAttemptContext context) {

        /* Skipped code for getting recordDelimiterBytes */

        return new T(recordDelimiterBytes);
    }
}

Como sugiere el título de la publicación, me dicen que "no puedo crear una instancia del Tipo T". De otras publicaciones de Stack Exchange, he deducido que esto no es posible debido a algo con el funcionamiento de los genéricos. Lo que no he podido recopilar es una explicación intuitiva de por qué ese es el caso. Aprendo mejor entendiendo, así que sería muy útil si alguien pudiera ofrecerlo.

También me interesan las mejores prácticas para lograr lo que busco hacer aquí. ¿Debería el constructor de LegionInputFormat aceptar una clase RecordReader, almacenarla y luego hacer referencia a ella más tarde para crear una nueva instancia? ¿O hay una mejor solución?

(Antecedentes adicionales: el contexto aquí es Hadoop, pero dudo que importe. Soy un científico de datos bastante logrado, pero soy bastante nuevo en Java).

1
John Chrysostom 13 dic. 2016 a las 15:57

2 respuestas

La mejor respuesta

En su segundo ejemplo de código, el compilador no tiene forma de saber si T tiene un constructor que acepta recordDelimiterBytes como argumento. Esto es así porque cada clase es una unidad de compilación separada, por lo que cuando se compila LegionInputFormat, el compilador solo sabe que T es un RecordReader<NullWritable, LegionRecord>. No sabe qué tipos concretos se utilizan para T, y tiene que asumir que alguien más adelante puede quedarse en cualquier clase que amplíe RecordReader<NullWritable, LegionRecord>. Podemos decirle algo al compilador sobre T usando extends, pero no hay forma en Java de que podamos especificar que T tiene un constructor T(byte[]) (o cualquier tipo de recordDelimiterBytes es).

He usado la siguiente solución un par de veces y, aunque requiere la creación de subclases, estoy bastante contento con ella. El trabajo todavía está dentro de la clase genérica. Ahora se declara abstracto:

public abstract class InputFormat<T extends RecordReader<NullWritable, LegionRecord>>
        extends FileInputFormat<NullWritable, LegionRecord> {

    private byte[] recordDelimiterBytes;

    @Override
    public RecordReader<NullWritable, LegionRecord> createRecordReader(InputSplit split, TaskAttemptContext context) {

        /* Skipped code for getting recordDelimiterBytes */

        return constructRecordReader(recordDelimiterBytes);
    }

    // factory method for T objects
    protected abstract RecordReader<NullWritable, LegionRecord> constructRecordReader(byte[] recordDelimiterBytes);
}

Para la instanciación, requiere que escriba una subclase concreta con solo estas pocas líneas:

public class LegionInputFormat extends InputFormat<LegionRecordReader> {

    @Override
    protected RecordReader<NullWritable, LegionRecord> constructRecordReader(byte[] recordDelimiterBytes) {
        return new LegionRecordReader(recordDelimiterBytes);
    }

}

En la subclase conocemos la clase concreta de T y, por lo tanto, el constructor o constructores de la clase, por lo que podemos instanciarlo. Aunque no es tan simple como podría haber esperado, considero que la solución es agradable y limpia.

En mi propio código, he aprovechado la oportunidad para declarar el método de fábrica como tipo de retorno T:

protected abstract T constructRecordReader(byte[] recordDelimiterBytes);

Entonces solo tienes que hacer un seguimiento en la implementación:

protected LegionRecordReader constructRecordReader(byte[] recordDelimiterBytes) {

En este caso, incluso se acortan algunos caracteres. Por otro lado, en su caso, parece que no lo necesita, por lo que es posible que prefiera quedarse con el tipo de retorno más débil de RecordReader<NullWritable, LegionRecord>.

0
Ole V.V. 15 dic. 2016 a las 04:11

Como sugiere el título de la publicación, me dicen que "no puedo crear una instancia del Tipo T". De otras publicaciones de Stack Exchange, he deducido que esto no es posible debido a algo con el funcionamiento de los genéricos.

Esto se debe a que los genéricos en Java son puramente una característica en tiempo de compilación; el compilador desecha los genéricos (esto se llama " borrado de tipo ") para que en tiempo de ejecución no exista una variable de tipo T, por lo que no puede hacer new T(...).

Puede hacer esto en Java pasando un objeto Class<T> al método que necesita crear una instancia de T, y luego creando una instancia a través de reflexión.

0
Jesper 13 dic. 2016 a las 13:05