回调对象引用外部类方法

huangapple go评论64阅读模式
英文:

Callback object referencing outer class methods

问题

如果将回调对象传递给事件处理程序则回调类定义内部的类方法和属性如何在回调类中访问例如在下面的代码中

    class HomeFragment : Fragment() {
        ...

        private fun showAlert(msg: String) {
            AlertDialog
                .Builder(context)
                .setMessage(msg)
                .show()
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            button_update.setOnClickListener {
                showAlert("Sample")
            }
        }
    //    或者下面这个例子
        private val message = "World"

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            button_update.setOnClickListener(object: View.OnClickListener {
                val message = "Hello"

                override fun onClick(v: View?) {
                    showAlert(message)
                }
            })
        }
        ....
    
    }

`showAlert` 是 `HomeFragment` 的一个私有成员方法事件处理程序是 `OnClickListener` 接口的一个实例这两者是不同的对象但是回调对象可以引用容器类的方法

两个问题
1. 回调将持有外部类的什么样的引用
2. 变量和方法如何被解析而无需指定任何特殊属性

由于这种模式在 Android 中很常见我想知道这对容器类的引用会产生什么影响是否会导致任何类型的内存泄漏例如类似于上面代码的情况这里回调也是容器类的一个成员存在循环引用但我已经看过使用这种模式的代码不一定是点击处理程序这只是一个示例)。

    private val handler = object: View.OnClickListener {
        val message = "Hello"

        override fun onClick(v: View?) {
            showAlert(message)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        button_update.setOnClickListener(handler)
    }

可能之前已经回答过这个问题我找到了一个类似的[问题][1]但是答案只涵盖了部分问题即如何捕获调用上下文

  [1]: https://stackoverflow.com/questions/44557653/object-reference-in-callback-method
英文:

If an callback object is passed to an event handler how contained class methods and properties are accessible within the callback class definition, for example in the below code

class HomeFragment : Fragment() {
...
private fun showAlert(msg: String) {
AlertDialog
.Builder(context)
.setMessage(msg)
.show()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_update.setOnClickListener {
showAlert("Sample")
}
}
//    or the one below
private val message = "World"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_update.setOnClickListener(object: View.OnClickListener {
val message = "Hello"
override fun onClick(v: View?) {
showAlert(message)
}
})
}
....
}

showAlert is a private Member of HomeFragment, the event handler is an instance of OnClickListener interface these are two different objects but somehow the callback object is able to reference the container class method.

Two questions

  1. what kind of reference the callback will hold of the outer class,
  2. how the variables and methods are resolved without a need to specify any special properties

Since this pattern is common in android and I wonder how this would affect the references of the container class, and will this cause any memory leaks of any sort. For ex, similar to above code, here the callback is also a member of container class with circular reference but i have seen code with this pattern (not necessarily click handlers this is just for example)

private val handler = object: View.OnClickListener {
val message = "Hello"
override fun onClick(v: View?) {
showAlert(message)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_update.setOnClickListener(handler)
}

Probably this is answered earlier, Only question i found is this question, but the answer only covers part of the question that is how the calling context is captured.

答案1

得分: 1

有所谓的“顶级类型”,这些类型在源文件的“顶级”定义。然后有所谓的“内部类型”,这些类型基本上可以在任何其他地方定义。具体来说,要么是“类型内部”,要么是“执行上下文内”。例如,在以下代码中,Inner 是一个内部类型:

class Outer { 
    class Inner {} 
}

MethodInner 则是通过“在执行上下文内”的方式定义的一个内部类型:

class Outer {
    void method() {
        class ThisIsAMethodInner {}
    }
}

方法内部类根据定义捕获状态。类型内部类也是如此,除非您使用 static 关键字选择退出。

您应该始终将类型内部类声明为静态*

如果将内部类型声明为静态,实际上您所做的只是使自己能够访问封闭类代码的私有成员,并且这是一个命名空间的事情。在 class Outer { static class Inner {} } 中,您可以在任何其他地方编写 new Outer.Inner(); - OuterInner 命名空间的一部分,大致就是这样。简单明了。

对于非静态内部类,情况就好像:

  1. 存在一个私有且最终的实例字段,具有您看不到的隐藏名称,但它确实存在,并且是外部类类型。
  2. 内部类型具有的所有构造函数都会“升级”,在最前面增加一个额外参数,类型为 Outer,并且用于初始化该字段。
  3. 调用这些构造函数的语法有些奇怪。不是 newInner(outerInstance, otherParams); - 不,它是 outerInstance.new Inner(otherParams);
  4. 如果在上下文中有任何有效的 this 可用,适合作为外部实例,那么就隐含了那个版本的 this。换句话说,在仅需要 Outer x = this; 能够编译的地方,您可以只写 new Inner()(而不是 outerInstance.new Inner())。

您会遇到所有这些弊端,而且它们是很多的!您的内部类型将阻止外部类型的垃圾回收。在没有外部实例可用的情况下,无法创建内部实例。内部的泛型在内部是可用的,这使得对事物的推理变得困难,语法很快变得非常复杂。因此,通常更好的做法是明确地声明外部类型的任何字段,并使内部类型成为静态的。

当我说“就好像”的时候,我的意思是确实如此。使用 javap 或任何其他类文件反编译工具,您会发现我所描述的__确实__是它的工作原理。javac 使所有这些事情变为可能。试试吧!您会看到那些隐藏的字段以及非静态内部的所有构造函数上的额外参数。您会看到在调用非静态内部的 INVOKESPECIAL(这是 JVM 的术语,表示“调用构造函数”)时,外部的引用总是传递到堆栈上。

*) 在您了解足够的 Java 知识以知道何时可以不遵循此规则之前,请始终遵循这一规则。

注意:方法局部内部类甚至会捕获其前面声明的所有(实际上是)final 局部变量,这很酷,但在该上下文中无法选择退出。

注意2:匿名内部类,例如 ClickListener listener = new ClickListener() { public void onClick(Event evt) { ... }},是一个方法局部内部类,并且捕获所有相关作用域。

英文:

There are so-called 'top level types' which are types defined at the 'top level' of a source file. Then there are so-called 'inner types' which are types pretty much defined anywhere else. Specifically, either 'in types', or 'in execution contexts'. For example, Inner is an inner type in:

class Outer { class Inner {} }

and MethodInner is an inner type by way of 'inside an execution context' in:

class Outer {
void method() {
class ThisIsAMethodInner{}
}
}

method-inner classes capture state by definition. type-inner classes do so UNLESS you opt out of it, by using the static keyword.

You should always make type-inner types static*

If you make an inner type static, all you've really done is given yourself access to private stuff from your enclosing class code, and it's a namespace thing. In class Outer { static class Inner {}}, you could write anywhere else new Outer.Inner(); - Outer is a part of Inner's namespace and that's about all there is to it. Simple. Easy.

With a non-static inner, it's exactly as if:

  1. There is a private and final instance field with a hidden name that you can't see, but it is there, and it is of your outer class's type.
  2. All constructors that Inner has are 'upgraded' with an additional parameter, all the way at the front, of type Outer, and it is used to initialize that field.
  3. The syntax to invoke those constructors is wonky. It's not newInner(outerInstance, otherParams); - no, it is outerInstance.new Inner(otherParams);
  4. If there is any valid take on this available in context that would fit as outer instance, than that version of this is implied. In other words, you CAN write just new Inner() (vs. outerInstance.new Inner()), but only in places where Outer x = this; would compile.

You get allllllllll the downsides of this, and they are legion! Your inner WILL prevent garbage collection of the outer. It is IMPOSSIBLE to make instances of inner without having an instance of outer available. The generics of outer are available in inner which makes reasoning about things hard and the syntax gets very hairy very quickly. Hence why it is usually a much better idea to make any field of the outer type explicit, and make the inner type static.

When I say 'it is exactly as if', I mean it. Use javap or any other class file decompiler and you will find that what I described is literally how it works. javac makes all that stuff happen. Try it! you'll see those hidden fields and the extra parameter on all the constructors. You'll see that a ref to the outer is always passed on stack whenever the INVOKESPECIAL (which is JVM-ese for 'call a constructor') of a non-static inner is invoked.

*) Until you know enough java to know when you can disregard this rule.

NB: Note that method-local inners even capture all (effectively) final local vars declared before it, which is kinda cool, but you can't opt out of it in that context.

NB2: Anonymous inner classes, such as ClickListener listener = new ClickListener() { public void onClick(Event evt) { ... }} are a method local inner class and capture all relevant scope.

huangapple
  • 本文由 发表于 2020年9月3日 08:47:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/63715282.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定