语境

我想为我的JavaFX应用程序创建色彩映射表,以便根据它们的值以不同的 colored颜色 显示网格.定义了两种类型:DiscreteColorMap使用integer个键,ContinuousColorMap使用double个键.两者都必须实现接口ColorMap,以便可以这样调用它:

ColorMap palette1 = new DiscreteColorMap();
ColorMap palette2 = new ContinuousColorMap();

问题

因为这两个类依赖于相同的接口,所以我指定了一个模板(public interface ColorMap<T>)来适应它们:

ColorMap<Integer> palette1 = new DiscreteColorMap();
ColorMap<Double> palette2 = new ContinuousColorMap();

我想要最简单的 colored颜色 映射语法,所以我需要go 掉<Integer><Double>字符串.做到这一点最优雅的方式是什么?

来源

完整的代码可以在这GitHub project中找到.

编辑

我的英语不是很完美^^我用了"摆脱",但这并不清楚:当我实例化我的彩色 map 时,我想让<Integer><Double>消失,这样我就可以写ColorMap palette...而不是ColorMap<Integer> palette....

推荐答案

TL/DR:

有三种方法可以从引用变量的类型中删除类型参数:

  1. 使用var.这只是一个语法速记,var palette = new DiscreteColorMap();在运行时和编译时都与DiscreteColorMap palette = new DiscreteColorMap();相同.这一点在另一个答案中得到了回答.
  2. 使用通配符:ColorMap<?> palette = new DiscreteColorMap();.这会告诉编译器"忘记"用作参数类型的类型.这意味着您将无法调用任何需要类型T的参数的方法,因为编译器无法判断类型是否正确.下面将详细介绍这一点.
  3. (Don't do this.)使用原始类型:ColorMap palette = new DiscreteColorMap();.这将告诉编译器ignore参数的类型(它有效地将T视为Object).由于类型不兼容而导致的任何错误都会在运行时引发,并且不会在编译时捕获,因此强烈建议不要使用这种方法.

此答案的其余部分将使用通配符详细描述第二种 Select .


Java泛型的目的是允许灵活地创建可以处理任何类型的对象(或特定类型的对象)的类,同时保留编译器执行编译时类型判断的能力.这方面的典型例子是属于java.util包的Colltions API.

在您的例子中,您已经定义了一个泛型的ColorMap接口,我从接口的名称中猜测您正在将参数类型T的值映射到 colored颜色 .所以你可能会有这样的症状:

public interface ColorMap<T> {
    public Color get(T value);
}

然后是一些实现.我将使用非生产级的非常基本的实现,只是为了演示这个 idea .有一个用于Integer类型的值:

public class DiscreteColorMap implements ColorMap<Integer> {

    private final Color[] colors ;

    public DiscreteColorMap(Color... colors) {
        this.colors = colors ;
    }

    @Override
    public Color get(Integer value) {
        return colors[value];
    }
}

和一个类型为Double的:

public class ContinuousColorMap implements ColorMap<Double> {

    private final Color start ;
    private final Color end ;

    public ContinuousColorMap(Color start, Color end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Color get(Double value) {
        return start.interpolate(end, value);
    }
}

请注意,as far as the compiler is concernedColorMap<Integer>ColorMap<Double>是不同的类型.

据我所知,您的问题似乎是"我是否可以创建一个DiscreteColorMapContinuousColorMap,并将它们赋给相同类型的引用".答案是"是的",您可以使用通配符以一种非常重要的方式(即,不只是将它们分配给Object个引用)来实现这一点:

ColorMap<?> cm1 = new DiscreteColorMap(Color.RED, Color.GREEN, Color.BLUE);
ColorMap<?> cm2 = new ContinuousColorMap(Color.RED, Color.BLUE);

引用类型ColorMap<?>可以被认为是"某种特定但未知的类型的A ColorMap".(我认为ColorMap<Integer>是"Integer类型的A ColorMap",以此类推.)

您也可以使用bounded个通配符.由于IntegerDouble都是Number的子类,因此可以在类型中指定:

ColorMap<? extends Number> cm1 = new DiscreteColorMap(Color.RED, Color.GREEN, Color.BLUE);
ColorMap<? extends Number> cm2 = new ContinuousColorMap(Color.RED, Color.BLUE);

思考ColorMap<? extends Number>的方式是"某个特定类型的ColorMap,即NumberNumber的子类".这里的Number是表示参数化类型的upper bound.

使用当前的接口和类定义,这是"统一"两个不同 colored颜色 映射类型的最具体方法:它们都是属于Number的子类的某种类型的ColorMap.

这一切是否有用,取决于你想用ColorMap做什么.你当然可以做到

List<ColorMap<? extends Number>> colorMaps = List.of(cm1, cm2);

这里的问题是,在ColorMapconsumes个参数化类型的值中,您拥有的唯一方法(即get(...)方法需要一个类型为T的参数).因为我们列表中每个ColorMap的实际类型是未知的(我们只知道它是Numberspecific个子类),所以编译器不能推断我们正在向ColorMap<? extends Number>的任何给定实例传递正确的值.我们的一个实例特别需要传递一个Integer,另一个特别需要传递一个Double.因为没有可以同时包含这两种情况的值,所以我们不能编写任何如下代码:

Number value = 1;
for (ColorMap<? extends Number> cm : colorMaps) {
    // this won't compile, because cm expects some specific type of Number:
    Color c = cm.get(value);
}

在本例中,下一部分有点做作,但如果ColorMap有一个produced(即返回)类型T的值的方法,那么这个列表可能会有用.目前还不清楚您将如何实现这一点,但如果您向接口添加了一个方法:

public interface ColorMap<T> {
    public Color get(T value);
    public T getValue(Color c);
}

然后您可以执行以下操作:

List<ColorMap<? extends Number>> colorMaps = List.of(cm1, cm2);
Color c = Color.BLUE;
for (ColorMap<? extends Number> cm : colorMaps) {
    Number value = cm.getValue(c);
}

这将进行编译.向编译器保证,我们列表中的每个ColorMap都有一个特定值T,即NumberNumber的子类.因此,每个getValue()方法返回某种类型的Number,并且保证赋值Number value = cm.getValue(c);成功.

如果您稍微更改一下ContinuousColorMap的定义,那么使用公共类型而不使用人工getValue()方法可能是一种很好的方法:

public interface ColorMap<T> {
    public Color get(T value);
}
public class DiscreteColorMap implements ColorMap<Integer> {

    private final Color[] colors ;

    public DiscreteColorMap(Color... colors) {
        this.colors = colors ;
    }

    @Override
    public Color get(Integer value) {
        return colors[value];
    }
}

这一次,让ContinuousColorMap变成ColorMap<Number>:

public class ContinuousColorMap implements ColorMap<Number> {

    private final Color start ;
    private final Color end ;

    public ContinuousColorMap(Color start, Color end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Color get(Number value) {
        return start.interpolate(end, value.doubleValue());
    }
}

现在我们可以做

ColorMap<? super Integer> cm1 = new DiscreteColorMap(Color.RED, Color.GREEN, Color.BLUE);
ColorMap<? super Integer> cm2 = new ContinuousColorMap(Color.RED, Color.BLUE);
List<ColorMap<? super Integer>> colorMaps = List.of(cm1, cm2);

这里,Integer是表示参数化类型的lower bound,我们可以将ColorMap<? super Integer>解释为"某个特定类型的AColorMap,它是IntegerInteger的超类".由于Number是整数的超类,因此将编译cm2的赋值.

对于列表中的每个元素,get(...)方法都需要一些特定的类型,但我们知道该特定类型必须是IntegerInteger的超类.因此,如果我们通过Integer,该调用肯定会成功.因此,我们可以做到

Integer value = 1;
for (ColorMap<? super Integer> cm : colorMaps) {
    // this line will compile and retrieve the correct color when executed:
    Color c = cm.get(value);
}

这可能已经超出了问题的范围,但如果您愿意,您甚至可以编写一个类,根据它们的类型跟踪ColorMap个实例,并给出一个值Number,它将从 colored颜色 映射表中返回所提供的特定类型的数字的 colored颜色 .为此,您可以使用Class<T>类作为"类型令牌":

@SuppressWarnings("unchecked")
public class ColorMaps {

    private final Map<Class<? extends Number>, ColorMap<? extends Number>> colorMaps = new HashMap<>();

    public <N extends Number> void registerColorMap(Class<N> type, ColorMap<N> map) {
        colorMaps.put(type, map);
    }

    public <N extends Number> ColorMap<N> getColorMap(Class<N> type) {
        return (ColorMap<N>) colorMaps.get(type);
    }

    public <N extends Number> Color getColor(N n) {
        Class<N> type = (Class<N>) n.getClass();
        return getColorMap(type).get(n);
    }
}

然后你可以做一些有趣的事情,比如:

ColorMaps colorMaps = new ColorMaps();
colorMaps.registerColorMap(Integer.class, 
    new DiscreteColorMap(Color.RED, Color.GREEN, Color.BLUE));
colorMaps.registerColorMap(Double.class,
    new ContinuousColorMap(Color.RED, Color.BLUE));

List<Number> numbers = List.of(0, 0.5, 1, 1.0, 2);
for (Number n : numbers) {
    System.out.println(n.getClass());
    System.out.println(colorMaps.getColor(n));
}

这段代码将使用DiscreteColorMap来映射列表中的整数(012),并使用ContinuousColorMap来映射列表中的双精度数(0.51.0).

Java相关问答推荐

如何让TaskView总是添加特定的列来进行排序?

Selenium Java:无法访问IFRAME内部的元素

将具有多个未知字段的SON映射到Java POJO

Listview—在Android Java中正确链接项目时出错

尽管类型擦除,instanceof与泛型在Java中如何工作?

当返回Mono<;Something>;时,不会调用Mono<;void>;.flatMap

测试期间未执行开放重写方法

如何使用SpringBoot中的可分页对整数作为字符串存储在数据库中时进行排序

如何对多个字段进行分组和排序?

如何在 spring 数据的MongoDB派生查询方法中使用$EXISTS

如何将Java文档配置为在指定的项目根目录中生成?

与IntArray相比,ArrayList<;Int>;对于大量元素的性能极差

如何读取3个CSV文件并在控制台中按顺序显示?(Java)

无法使用Java PreparedStatement在SQLite中的日期之间获取结果

如何在Java中使用正则表达式拆分字符串

JavaFX复杂项目体系 struct

协同 routine 似乎并不比JVM线程占用更少的资源

AspectJ编织外部依赖代码,重新打包jar并强制依赖用户使用它

spring 更新多项管理关系

由于版本不匹配,从Java 8迁移到Java 17和Spring 6 JUnit4失败