Irgendwann war das ja mal ein (hauptsächlich) Java-Forum. Und ich hänge gerade an etwas, wo ich entweder tierisch auf dem Schlauch stehe, oder ich irgendeine trickreiche Lösung übersehen habe.
Hier ist eine Klase, die eine Methode hat, die ein float akzeptiert. Und eine Method Reference auf diese Methode kann an eine Funktion übergeben werden, die einen BiConsumer<Example, Byte> erwartet:
import java.util.function.BiConsumer;
public class _TypedConsumerExample
{
static class Example
{
void setX(float x)
{
System.out.println("float " + x);
}
}
public static void main(String[] args)
{
doSomethingByte(Example::setX);
}
static void doSomethingByte(BiConsumer<Example, Byte> consumer)
{
}
}
Warum das möglich ist, kann man recht leicht begründen: Der Consumer akzeptiert ein Byte, und das kann man über auto-unboxing in ein float packen. (Der umgekehrte Fall, ein Float a ein byte zu geben, funktioniert entsprechend nicht).
Die Frage ist: Kann man ihn irgendwie auf den genauen (geboxten) Typ festpinnen? Ziel wäre, dass man dort nur eine set(byte) Method reference übergeben kann, aber keine set(float)…
OK, mit static <U extends Byte> void doSomethingByte(BiConsumer<Example, U> consumer)
scheint es sich erstmal wie gewünscht zu verhalten (muss noch genauer schauen), aber das extends Byte generiert erstmal eine Warnung (weil Byte eben final ist), und… es sieht komisch aus
Ja, entweder überladen, oder einen Parameter-Check:
import java.util.function.BiConsumer;
public class TypeConsumerExample {
static class Example1 {
@Deprecated
void setX(int x) {
throw new RuntimeException("Not implemented: use setX(float x) instead (1)");
}
void setX(float x) {
System.out.println("float " + x);
}
}
static class Example2 {
void setX(Object x) {
if (x.getClass() != Float.class) {
throw new RuntimeException("Not implemented: use setX(float x) instead (2)");
}
System.out.println("float " + x);
}
}
public static void main(String[] args) {
doSomethingByte1(Example1::setX);
doSomethingByte2(Example2::setX);
}
static void doSomethingByte1(BiConsumer<Example1, Float> consumer) {
Example1 example = new Example1();
consumer.accept(example, 3.14f);
// example.setX(3); // This will cause a runtime exception due to the deprecated method
}
static void doSomethingByte2(BiConsumer<Example2, Float> consumer) {
Example2 example = new Example2();
consumer.accept(example, 1.23f);
example.setX(4); // This will cause a runtime exception if the argument is not a Float
}
}
Btw.: Keine Underscores in Klassennamen verwenden und statt setX heißt es mittlerweile einfach nur noch x Get und set entfällt bei den Gettern.
Die Example-Klasse sollte in diesem Fall als gegeben angesehen werden. Das ist das, was der Benutzer halt hat (und nicht verändern kann/will). Und er soll die Möglichkeit haben, eine Methode seiner Klasse - so wie sie ist - möglichst typsicher an die doSomethingByte-Methode zu übergeben.
(Es gibt halt noch doSomethingShort/Int/Float/Double, und diese doSomething...-Methoden speichern halt intern diesen Consumer, und füttern ihn später mit einem Byte/byte/Short/short…).
Ich hab’s jetzt tatsächlich mit diesem extends Byte gelöst…
/**
* Handle the specified property with the given setter.
*
* @param <U> The value type
* @param propertyName The property name
* @param setter The setter
* @return This handle
*/
// Note: The 'extends' part is necessary for some type safety.
@SuppressWarnings("all")
public <U extends Byte> Handle<T> withByte(String propertyName,
BiConsumer<T, U> setter)
{
return withImpl(propertyName, setter);
}
Das bewirkt, dass die gewünschten Kombinationen funktionieren…
public class _TypedConsumerDemo
{
static class Example
{
void setByte(byte b)
{
}
void setByteObject(Byte b)
{
}
void setFloat(float f)
{
}
void setFloatObject(Float f)
{
}
}
public static void main(String[] args) throws Exception
{
ObjectPlyTarget magic = new ObjectPlyTarget();
Handle<Example> x = magic.register("vertex", Example::new);
x.withByte("x", Example::setByte); // Geht
x.withByte("x", Example::setByteObject); // Geht
//x.withFloat("x", Example::setByte); // Geht nicht
//x.withFloat("x", Example::setByteObject); // Geht nicht
//x.withByte("x", Example::setFloat); // Geht nicht
//x.withByte("x", Example::setFloatObject); // Geht nicht
x.withFloat("x", Example::setFloat); // Geht
x.withFloat("x", Example::setFloatObject); // Geht
}
}
Das ist noch in einem experimental-package, aber scheint so weit alles zu passen…
Ja, weil du in Example eine Graduierung vorgenommen hast (byte b, float f usw.). Gäbe es byte b nicht, bestünde wieder das Problem. Das ist eigentlich nicht so weit von meinem entfernt. Aber gut
Was du sagst ergibt keinen Sinn, und der Code, den du gepostet hast, war ein Witz. Du kannst gerne versuchen, irgendwas davon irgendwie zu rechtfertigen, aber ich erwarte nichts.