接口中的Java 9默认方法:从哪个模块调用它们?

时间:2018-03-06 14:55:29

标签: java interface java-9

假设您有moduleA和moduleB。 ModuleA定义了一个接口(例如用于服务),ModuleB有一个实现接口的具体类(提供服务)。

现在,如果接口有一个默认方法,并且你在moduleB中的类(来自另一个模块)上调用它,那么这个调用应该在moduleA或moduleB中执行吗? 显然它来自moduleA ......理由是什么?

示例:假设您有一个执行此操作的代码:

InputStream is = this.getClass().getResourceAsStream(fullPath);

如果此代码位于moduleB中服务的实现中,则将打开流。但是如果代码位于moduleA中的默认方法中,那么当在moduleB上调用服务时,您需要在moduleB中拥有一个“open”资源(因此看起来调用认为它来自“outside”moduleB)。

想了解原因。

感谢

用一个例子编辑我的问题。 假设你在moduleA中有这段代码:

public interface PropertiesProvider {
    public default Properties get(String domain) {
        Class clazz =this.getClass();
        System.out.println(" CLASS " +clazz);
        InputStream is = clazz.getResourceAsStream(domain) ;
        if (is != null) {
            Properties props = new Properties();
            try {
                props.load(is);
                return props;
            } catch (IOException e) {
                //log
            }
        }
        return null;
    }

}

并在moduleB

public class PropertiesProviderImpl implements PropertiesProvider {}

如果从ModuleA调用该服务,则调用该调用来自类PropertiesProviderImpl查找资源但如果未打开则不加载该资源

如果将代码复制到PropertiesProviderImpl中,则调用会追溯到同一个类找到ressource并加载它,即使它没有“打开”

所以我的问题是:为什么调用后的差异来自同一个类? (不同之处在于,在一种情况下,该方法是从接口中的默认方法继承的类型)

2 个答案:

答案 0 :(得分:3)

查看 getResourceAsStream If this class is in a named Module then this method will attempt to find the resource in the module.

的文档

在第一种情况下,您的代码(在moduleA中)会看到Type,但无法看到实现Type的类,因为它在moduleB中}。在第二种情况下,您的代码可以看到"实现" Type

请看下面的参考文献,最重要的句子是:

  

在模块化设置中,Class :: forName 的调用将继续有效,只要包含提供程序类的包对于上下文类加载器是已知的。但是,通过反射newInstance方法调用提供程序类的构造函数将不起作用:可能从类路径加载提供程序,在这种情况下,它将位于未命名的模块中,或者它可能在某个命名模块中,但在任何一种情况下,框架本身都在java.xml模块中。该模块仅依赖于并因此读取基本模块,并且因此框架无法访问任何其他模块中的提供者类

[...]

  

,修改反射API只是为了假设任何反映某种类型的代码都在一个模块中,该模块可以读取定义该类型的模块。

[答案很长] reflective-readability

  

框架是一种使用反射在运行时加载,检查和实例化其他类的工具[...]

     

鉴于在运行时发现的类,框架必须能够访问其构造函数之一才能实例化它。但事实上,通常情况并非如此。

     

平台的流式XML解析器,例如,加载并实例化由系统属性javax.xml.stream.XMLInputFactory命名的XMLInputFactory服务的实现(如果已定义),优先于通过ServiceLoader类可发现的任何提供者。忽略异常处理和安全性检查,代码粗略地读取:

String providerName
    = System.getProperty("javax.xml.stream.XMLInputFactory");
if (providerName != null) {
    Class providerClass = Class.forName(providerName, false,
                                        Thread.getContextClassLoader());
    Object ob = providerClass.newInstance();
    return (XMLInputFactory)ob;
}
// Otherwise use ServiceLoader
...
  

在模块化设置中,只要包含提供程序类的包对于上下文类加载器是已知的,Class :: forName的调用将继续有效。但是,通过反射newInstance方法调用提供程序类的构造函数将不起作用:提供程序可能从类路径加载,在这种情况下,它将在未命名的模块中,或者它可能在某个命名模块中,但是在任何一种情况下,框架本身都在java.xml模块中。该模块仅依赖于并因此读取基本模块,因此框架将无法访问任何其他模块中的提供者类。

     

为了使框架可以访问提供程序类,我们需要使框架的模块可以读取提供程序的模块。我们可以强制要求每个框架在运行时明确地向模块图添加必要的可读性边缘,如本文档的早期版本,但经验表明这种方法很麻烦并且是迁移的障碍。

     因此,我们修改反射API只是为了假设任何反映某种类型的代码都在一个可以读取定义该类型的模块的模块中。这使得上述示例和其他类似代码可以无变化地工作。这种方法不会削弱强封装:公共类型必须仍然在导出的包中,以便从其定义模块外部访问,无论是从编译代码还是通过反射。

答案 1 :(得分:0)

由于我们没有准确理解之前的反应,因此我们进行了一些额外的测试

在每个测试中,资源文件未打开"

1) 调用clazz.getResouceAsStream的代码是定义服务的默认接口方法。实现接口的类没有定义任何方法。

- > this.getClass()产生实现类,测试无法找到资源

2) 我们在默认方法

中添加了此代码

Object obj = clazz.getConstructor()。newInstance();

是的,它失败了

3)我们更改了代码,因此PropertiesProvider是抽象类,而PropertiesProviderImpl是从它继承的

同样的行为。

所以是的,这意味着如果从中继承或直接调用它,相同的代码将表现不同。 这令人担忧:这意味着语言的内在逻辑将导致复杂的拜占庭行为(我们抛弃C ++的原因)。