Kotlin函数参数:如何定义一个可以接受尾随Lambda或接口作为参数的函数?

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

Kotlin function parameter: how to define a function which can have a trailing lambda or interface as a parameter?

问题

I found two similar codes:

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)
binding.playButton.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_titleFragment_to_gameFragment)
}

Java code from an Android view class:

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

The question is: how can I create such a function where I can use a trailing lambda or interface as a parameter? I get a type mismatch.

interface One {
    fun a(): Int
}

class OneImp : One {
    override fun a(): Int {
        return 4
    }
}

fun test(one: One) {
    val a = one
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
   val a = OneImp()
   test(a)   //works fine
   test {
        a //error
   }
}

Error:

Type mismatch.
Required: TitleFragment.One
Found: ()  TitleFragment.OneImp

UPDATE:

After the answer of @Jenea Vranceanu, I have found my error in testing SAM (I used interface from kotlin file, while all the code should be in java). Solution will be: (before kotlin v1.4 releases) create a java file:

public class Mine {
    public interface One {
        int a();
    }

    public class OneImpl implements One {
        @Override
        public int a() {
            return 4;
        }
    }

    public void test(One one) {}
}

Then I can use both function argument and lambda. In a kotlin file now:

Mine().test { 4 }
val b = Mine().OneImpl()
Mine().test(b)

PS. If he adds it to his answer I will delete it from here.

英文:

I found two similar codes:

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)
binding.playButton.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_titleFragment_to_gameFragment)
}

Java code from android view class:

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

The question is: how can I create such function where I can use trailing lambda or interface as parameter?
I get type mismatch.

    interface One {
        fun a(): Int
    }

    class OneImp : One {
        override fun a(): Int {
            return 4
        }
    }

    fun test(one: One) {
        val a = one
    }

   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
       val a = OneImp()
       test (a)   //works fine
       test {
            a //error
       }
   }

Error:

Type mismatch.
Required:
TitleFragment.One
Found:
() → TitleFragment.OneImp

UPDATE:

After the answer of @Jenea Vranceanu, I have found my error in testing SAM (I used interface from kotlin file, while all the code should be in java).
Solution will be: (before kotlinv v1.4 releases)
create a java file:

public class Mine {
    public interface One {
        int a();
    }

    public class OneImpl implements One {
        @Override
        public int a() {
            return 4;
        }
    }

    public void test(One one) {}
}

Then I can use both function argument and lambda. In kotlin file now:

 Mine().test {4}
 val b = Mine().OneImpl()
 Mine().test (b)

PS. If he adds it to his answer I will delete if from here.

答案1

得分: 2

你对binding.playButton.setOnClickListener的工作方式有一点误解。

在第一个示例中,Navigation 组件创建了一个 View.OnClickListener,然后将其传递给 setOnClickListener(注意括号或圆括号):

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

在Java中,它看起来几乎相同:

binding.getPlayButton().setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

第二个示例的工作方式有一点不同。首先,你通过使用花括号创建了一个 View.OnClickListener,并定义了这个 View.OnClickListener 方法 onClick(View view) 的主体:

binding.playButton.setOnClickListener { /* onClick(View view) 的空主体 */ }

在花括号内部,你可以引用点击的视图:

binding.playButton.setOnClickListener { 
    it.context // `it` 是被点击的视图
}

// 这与以下代码相同
binding.playButton.setOnClickListener { view ->
    view.context
}

为什么它不完全起作用?

这种方式只适用于Java中定义的单一抽象方法(SAM)类,而 View.OnClickListener 是Java中定义的SAM类。Kotlin的SAM类/接口尚不支持此特性。

通过编写:

val a = OneImp()
test {
    a //error
}

你假设 test 函数声明如下:

fun test(one: () -> One) {
    val a = one()
}

请注意,在第二个示例中,你在问题的顶部的 Navigation.findNavController(it).navigate 不会创建 View.OnClickListener 对象。花括号创建了这个对象,由于此接口仅具有一个抽象方法,因此没有必要编写此方法的签名,因此花括号内声明的所有内容都直接进入了 void onClick(View view) 方法中。

更新(官方文档)

Kotlin中的SAM转换。这是官方文档,在底部写着:

> ... 请注意,此功能仅适用于与Java的互操作性;由于Kotlin具有适当的函数类型,因此将函数自动转换为Kotlin接口的实现是不必要的,因此不受支持。

<strike>因此,对于Kotlin接口,永远不会支持这一功能。</strike> 但是似乎我错了关于 "SAM永远不会支持"。它将会支持,但尚不可用。从Kotlin 1.4开始,将支持Kotlin类的SAM转换,现在是一个发布候选版。

更新,带有解决方案

最初未添加替代解决方案。问题者礼貌地要求添加下面的代码,以使答案完整。谢谢!

public class Mine { 
    public interface One { 
        int a(); 
    } 

    public class OneImpl implements One { 
        @Override public int a() { return 4; } 
    } 
    
    public void test(One one) {} 
}
英文:

You have misunderstood a little bit how binding.playButton.setOnClickListener worked in each case.

In the first one, Navigation component creates View.OnClickListener that is passed into setOnClickListener (notice parentheses or round brackets):

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

In Java it would look almost the same:

binding.getPlayButton().setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

The second example works a little bit differently. First, you create View.OnClickListener by using braces and you define the body of this View.OnClickListener method onClick(View view):

binding.playButton.setOnClickListener { /* empty body of onClick(View view) */ }

You have reference to the clicked view inside of braces:

binding.playButton.setOnClickListener { 
    it.context // `it` is the clicked view
}

// It is the same as
binding.playButton.setOnClickListener { view -&gt;
    view.context
}

Why doesn't it work exactly?

This type of defining anonymous classes is possible only with SAM classes defined in Java and View.OnClickListener is a SAM class defined in Java. Kotlin SAM classes/interfaces do not support this feature yet.

By writing:

val a = OneImp()
test {
    a //error
}

You assume that test function declaration looks like this:

fun test(one: () -&gt; One) {
    val a = one()
}

Note that in the second example you have at the top of your question Navigation.findNavController(it).navigate does not create View.OnClickListener object. The braces create this object and as this interface has only one abstract method there is no use to write this method signature so all the content declared inside of braces goes directly into void onClick(View view) method.

Update (official documentation)

SAM conversions in Kotlin. This is the official documentation and at the bottom of it is written:

> ... note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.

<strike>So it will never be supported for Kotlin interfaces as there is no need for it.</strike> As it appears I was wrong about "SAM will never be supported". It will be but it is not yet available. SAM conversions for Kotlin classes will be available starting from Kotlin 1.4 which is now a release candidate.

Update with a solution

The alternative solution was not added initially. Questioner politely requested to add the next code to make the answer complete. Thanks!

public class Mine { 
    public interface One { 
        int a(); 
    } 

    public class OneImpl implements One { 
        @Override public int a() { return 4; } 
    } 
    
    public void test(One one) {} 
}

huangapple
  • 本文由 发表于 2020年7月31日 19:12:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/63190797.html
匿名

发表评论

匿名网友

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

确定