2016-12-13 6 views
1

ich eine Klasse, die wie folgt aussieht ...Kann nicht instanziiert den Typ T

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

Ich möchte eine generische Art verwenden, so dass es jede Art von RecordReader durch den Benutzer, wie so angegeben zurückkehren konnten :

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

Wie der Post-Titel schon sagt, ich werde gesagt, dass ich „die Art T. nicht instanziiert“ Bei anderen Stack Exchange-Posts habe ich festgestellt, dass dies aufgrund der Funktionsweise von Generika nicht möglich ist. Was ich nicht erfassen konnte, ist eine intuitive Erklärung, warum das der Fall ist. Ich lerne am besten durch Verstehen, so dass es wirklich hilfreich wäre, wenn jemand es anbieten könnte.

Ich bin auch an der Best Practice interessiert, um das zu erreichen, was ich hier machen möchte. Sollte der Konstruktor für LegionInputFormat eine Klasse RecordReader akzeptieren, diese speichern und später darauf verweisen, um eine neue Instanz zu erstellen? Oder gibt es eine bessere Lösung?

(Zusätzliche Hintergrund -.. Kontext ist hier Hadoop, aber ich bezweifle es wichtig ist, ich bin ein ziemlich erreicht Daten Wissenschaftler, aber ich bin ziemlich neu in Java)

+0

[Typ Erasure] (https://docs.oracle.com/javase/tutorial/java/generics/erasure.html). –

+0

Ich bin mir nicht sicher, warum das das verhindert? Also wird zur Kompilierzeit 'T' durch' LegionRecordReader' oder irgendeine andere 'RecordReader'-Klasse ersetzt, die ich spezifiziere ... Warum verhindert das, dass' neues T zurückgeben 'zu Kompilierzeit 'neues LegionRecordReader' zurückgibt? –

+1

Zur Kompilierzeit wird 'T' durch' java.lang.Object' ersetzt. ** Nicht ** 'LegionRecordReader'. –

Antwort

0

In Ihrem zweiten Codebeispiel kann der Compiler nicht wissen, ob T einen Konstruktor hat, der recordDelimiterBytes als Argument akzeptiert. Dies ist so, weil jede Klasse eine separate Kompilierungseinheit ist. Wenn also LegionInputFormat kompiliert wird, weiß der Compiler nur, dass T eine RecordReader<NullWritable, LegionRecord> ist. Es weiß nicht, welche konkreten Typen für T verwendet werden, und es muss davon ausgegangen werden, dass jemand zu einem späteren Zeitpunkt in jeder Klasse bleiben kann, die sich erweitert RecordReader<NullWritable, LegionRecord>. Wir können dem Compiler etwas über T mit extends sagen, aber es gibt keine Möglichkeit in Java, dass wir spezifizieren können, dass T einen Konstruktor T(byte[]) hat (oder was auch immer der Typ recordDelimiterBytes ist).

Ich habe die folgende Lösung ein paar Mal verwendet, und obwohl es Unterklassen erstellen erfordert, bin ich ziemlich glücklich damit. Die Arbeit ist immer noch in der generischen Klasse. Es ist jetzt abstrakt deklariert:

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

Für Instanziierung es erfordert, dass Sie eine konkrete Unterklasse mit diesen wenigen Zeilen schreiben:

public class LegionInputFormat extends InputFormat<LegionRecordReader> { 

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

} 

In der Unterklasse kennen wir die konkrete Klasse von T und damit die Klasse Konstruktor/en, damit wir es instanziieren können. Obwohl nicht ganz so einfach, wie Sie vielleicht gehofft haben, halte ich die Lösung für schön und sauber.

In meinem eigenen Code, den ich die Gelegenheit ergriffen haben, zu erklären, die Factory-Methode als Rückkehr Typ T: müssen

protected abstract T constructRecordReader(byte[] recordDelimiterBytes); 

Dazu einfach in der Umsetzung folgen:

protected LegionRecordReader constructRecordReader(byte[] recordDelimiterBytes) { 

In dieser Fall es sogar ein paar Zeichen kürzer wird. Auf der anderen Seite, in Ihrem Fall scheinen Sie es nicht zu brauchen, so dass Sie vielleicht lieber mit der schwächeren Rückgabetyp von RecordReader<NullWritable, LegionRecord> bleiben.

0

Wie die Post-Titel schon sagt, Mir wird gesagt, dass ich den Typ T nicht instantiieren kann. Bei anderen Stack Exchange-Posts habe ich festgestellt, dass dies aufgrund der Funktionsweise von Generika nicht möglich ist.

Dies liegt daran, Generika in Java sind rein eine Kompilierzeit-Funktion; Der Compiler verwirft die Generika (das heißt "type erasure"), so dass es zur Laufzeit keine Typvariable T gibt, so dass Sie new T(...) nicht tun können.

Sie können dies in Java tun, indem Sie ein Class<T> Objekt an die Methode übergeben, die eine Instanz T erstellen muss, und dann eine Instanz über reflection erstellen.

+0

"zur Laufzeit gibt es keine Typvariable T" Das hilft mir wirklich, die Typ-Lösch-Erklärung anzuklicken. Wenn T zur Kompilierzeit durch 'XRecordReader' ersetzt wird, warum wird es dann nicht zur Kompilierzeit in' return new T' ersetzt? –

+1

Es wird nicht durch 'XRecordReader' zur Kompilierzeit, sondern durch' Object' ersetzt. Zum Beispiel sieht eine 'Liste ' nur wie eine rohe 'Liste' aus, die zur Laufzeit irgendein 'Objekt' enthält. – Jesper

+1

@JohnChrysostom Das würde bedeuten, dass für jedes "T", das damit verwendet wird, eine separate Version der Methode erstellt werden müsste, d. H. Es wird eine Vorlage. Beim Löschen benötigen Sie nur 1 Version der Methode. Es ist einfach eine Design-Wahl, die auch rückwärtskompatibel ist. –

Verwandte Themen