首页 > 解决方案 > Increase visibility of interface

问题描述

I have two packages:

1. com.test.hidden:

Contains an protected interface MyListener and a public class FancyClass with a public method for registering MyListener objects:

package com.test.hidden;
public class FancyClass {
    public void registerListener(MyListener listener) {
        // ...
    }
}

package com.test.hidden;
interface MyListener {
    void doSomething();
}

2. com.test.myapp:

Here I have an instance of FancyClass and I want to register a new MyListener object to it.

Obviously this will not work since MyListener is not visible in com.test.myapp. My intuition was to create a new interface in com.test.hidden that extends MyListener and is public:

package com.test.hidden;
public interface MyListenerWrapper extends MyListener {
    @Override
    void doSomething();
}

package com.test.myapp
import com.test.hidden.FancyClass
import com.test.hidden.MyListenerWrapper

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        val fc = FancyClass()
        fc.registerListener(object : MyListenerWrapper {
            override fun doSomething() {
                // ...
            }
        })
    }
}

But I get an IllegalAccessError:

java.lang.IllegalAccessError: Illegal class access: 'com.test.myapp.MainActivity' attempting to access 'com.test.hidden.MyListener' (declaration of 'com.test.myapp.MainActivity' appears in /data/app/com.test.myapp-ZzNlHhuBCCdAEV4QsZHspw==/base.apk)
        at com.test.myapp.MainActivity.onCreate(MainActivity.kt:15)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2899)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3054)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1814)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:280)
        at android.app.ActivityThread.main(ActivityThread.java:6710)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

When I change the visibility of MyListener to public it works just fine, so I suspect Java does not like increasing the visibility of interfaces in subinterfaces. Why doesn't this work and how to solve this?

标签: javakotlininterfacevisibility

解决方案


Kotlin 不喜欢在公共方法中公开内部 API,如果这样做,实际上会导致编译器错误。如果您从 Kotlin 调用代码,这也会影响 Java 代码,但它会在运行时而不是在编译时引发异常。

至于解决方案,Kotlin 有internal,但这是一个非常特殊的关键字:与 Java 的包 private (没有修饰符)不同, internal 使它在模块中的任何地方都可见。

这也是一种解决方案:转换为 Kotlin 并使用internal. 如果你正在制作一个库,它在模块之外是不可见的,但你至少可以在内部声明它。

第二个是将接口转换为类并使用sealed class. 请注意,这需要在同一个文件中声明所有子项。有关密封类的更多信息,请参阅此内容。

最后的解决方案:公开。我强烈建议您使用它而不是其他替代方案,主要是因为它是最简单的选择,并且不需要骇人听闻的设计。此外,如果您打算在其他地方访问接口,则必须使用特定的实现而不是通用超类,这并不总是可行的(尽管取决于您的使用)。

还有只使用 Java 的选项,但我假设你更喜欢混合,因为你已经在这样做了。但正如迈克尔所说,你真的应该避免这种设计:这是糟糕的设计。如果我是你,无论如何我都会公开。


推荐阅读