问题

在使用Scala实现JTV客户端界面程序时,我遇到了Scala重载Java类中的泛型方法的问题。

因为界面上的JList使用了自定义的元素类型,我需要自定义ListCellRender来列表对象中元素行的显示。最简单的方法就是直接继承DefaultListCellRenderer,它会将JList中的数据元素渲染为JLabel,我只需要覆盖其getListCellRendererComponent实现元素转JLabel的逻辑即可。

自定义类的结构如下:

1
2
3
class FileRender extends DefaultListCellRenderer{
    override def getListCellRendererComponent(list: JList[_], value: scala.Any, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
  }

编译时会产生错误:

1
2
Error:(441, 9) class FileRender needs to be abstract, since method getListCellRendererComponent in trait ListCellRenderer of type (x$1: javax.swing.JList[_ <: Object], x$2: Object, x$3: Int, x$4: Boolean, x$5: Boolean)java.awt.Component is not defined
  class FileRender extends DefaultListCellRenderer{

原因分析

查看DefaultListCellRenderListCellRender

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class DefaultListCellRenderer extends JLabel
    implements ListCellRenderer<Object>, Serializable
{
    public Component getListCellRendererComponent(
        JList<?> list,
        Object value,
        int index,
        boolean isSelected,
        boolean cellHasFocus)
    {
    ...
    }
...
}

public interface ListCellRenderer<E>
{
    Component getListCellRendererComponent(
        JList<? extends E> list,
        E value,
        int index,
        boolean isSelected,
        boolean cellHasFocus);
}

从错误信息中可以看到,错误的原因在于我们的实现与ListCellRender中方法的泛型参数不匹配。先把第一个参数的泛型参数按错误提示进行修正:

1
2
3
class FileRender extends DefaultListCellRenderer{
    override def getListCellRendererComponent(list: JList[_ <: Object], value: scala.Any, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
  }

再编译时,编译器会提示getListCellRendererComponent未覆盖任何方法。

从错误信息中可以了解到,应该是由于Scala对于Java实现对接口中的泛型参数无法理解。而我们编写的Scala继承Java类之后,Scala编译器不认为Java实现类与接口中的两个方法具有相同的方法签名。无论我们按接口的签名编写,还是按Java实现类的编译都会导致编译失败。

解决

在网上搜索这个问题找到了几篇有价值的贴子:

类似问题

Martin Odersky对这类问题的回复

在这个贴子中,Martin Odersky对这类问题的建议是用Java编写一个实现类,之后再用Scala继承。

同一个问题

而针对我们这个具体的问题,上面这个篇贴子给出的方法更简单。它直接用Scala编写ListCellRender的实现,用它作为DefaultListCellRender的代理类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class FileRender extends ListCellRenderer[FileInfo]{
  val render = (new DefaultListCellRenderer).asInstanceOf[ListCellRenderer[FileInfo]]

  override def getListCellRendererComponent(list: JList[_ <: FileInfo], value: FileInfo, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component = {
    val result = render.getListCellRendererComponent(list,value,index,isSelected,cellHasFocus)
    val label = result.asInstanceOf[JLabel]
    label.setText(value.file.getName)
    label.setIcon(ImageUtils.toImageIcon(value.icon))
    label
  }
}