泛型的多种应用

本篇文章主要介绍泛型的应用。

对象与泛型

泛型是.NET Framework 2.0
版类库就已经提供的语法,主要用于提高代码的可重用性、类型安全性和效率。

目录

  • 1.对象
    1.1 匿名类与对象
    1.2 静态类成员与伴生对象
  • 2.泛型
    2.1 型变
    2.2 类型投影
    2.3 泛型函数
    2.4 泛型约束

泛型的定义

1.对象

下面定义了一个普通类和一个泛型类,我们可以明确看到泛型类和普通类最大的区别就是多了一个<T>。

1.1 匿名类与对象表达式

Java中有匿名类这个概念,指的是在创建类时无需指定类的名字。在Kotlin中也有功能相似的“匿名类”,叫做对象,举个例子:

Java匿名类

public class Login {

    private String userName;

    public Login(String userName) {
        this.userName = userName;
    }

    public void printlnUserName() {
        System.out.println(userName);
    }
}

public class JavaActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        printlnUserName(new Login("Czh") {
            @Override
            public void printlnUserName() {
                super.printlnUserName();
            }
        });
    }

    public void printlnUserName(Login login) {
        login.printlnUserName();
    }
}

Kotlin实现上面的代码,要用关键字object创建一个继承自某个(或某些)类型的匿名类的对象,如下所示:

class KotlinActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //object是一个对象,该对象继承自上面的Login
        printlnUserName(object : Login("Czh") {
            override fun printlnUserName() {
            }    
        })
    }

    fun printlnUserName(login: Login) {
        login.printlnUserName()
    }
}

对象object还可以实现接口,如下所示:

//View.OnClickListener是一个interface
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
    }
})

对象和类一样,只能有一个父类,但可以实现多个接口,多个超类型跟在冒号:后面用逗号,分隔。
如果只想建立一个对象,不继承任何类,不实现任何接口,可以这样写:

fun foo(){
    val abc = object {
            var a = 1
            var b = 2
    }
    Toast.makeText(this, "${abc.a}${abc.b}", Toast.LENGTH_SHORT).show()
}

运行代码,查看结果:

图片 1

请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是
Any。在匿名对象中添加的成员将无法访问。如下所示:

class User {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun getUserName() = object {
        val userName = "Czh"
    }

    // 公有函数,所以其返回类型是 Any
    fun getAge() = object {
        val age = 22
    }

    fun get() {
        getUserName().userName
        //getAge().age //编译错误
    }
}
  • 内部类访问作用域内的变量

就像 Java
匿名内部类一样,Java可以用final声明变量,使匿名内部类可以使用来自包含它的作用域的变量。如下所示:

final int age = 22;
printlnUserName(new Login() {
    @Override
    public void printlnUserName() {
        //因为age用final声明,所以不能修改
        if (age == 22){
            return;
        }
  }
});

而Kotlin在匿名对象中可以任意访问或修改变量age,如下所示:

var age = 22
printlnUserName(object : Login() {
    override fun printlnUserName() {
        age = 23
        Toast.makeText(this@MainActivity, "$age", Toast.LENGTH_SHORT).show()
    }
})

运行代码,查看结果:

图片 2

所以,这个<T>就标记了,这个类是泛型类。其中这个T,也可以写成A,B,C,D或其他字符。

1.2 伴生对象

Java中有静态类成员,而Kotlin中没有,要实现像静态类成员的功能,就要用到伴生对象。

Java静态成员:

class User {
    static User instance = new User();

    public void printlnUser() {
    }
}
//调用
User.instance.printlnUser()

Kotlin类内部的对象声明可以用 companion 关键字标记:

class User {
    companion object {
        var instance = User()
    }

    fun printlnUser() {
    }
}
//调用
User.instance.printlnUser()

public class Generic
{
    public String Name;
}

public class Generic<T>
{
    public T Name;
}

泛型

泛型,顾名思义,就是泛指的类型。好比男人,女人,白人,黑人,可以泛称为【人】。

2.1型变

Java泛型

public class Box<T> {
    public T value;

    public Food(T t) {
        value = t;
    }
}

new Box<String>("123");
new Box<Integer>(1);

对应的Kotlin泛型

class Box<T>(t: T) {
    var value = t
}
var box: Box<String> = Box("123")
var box2: Box<Int> = Box(123)

可以看出Java跟Kotlin定义泛型的方法都是差不多的,不同的是Java中的泛型有通配符,而Kotlin没有。举个例子:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;//编译错误

Java编译器不认为List<String>是List<Object>的子类,所以编译不通过。那我们换种写法:

List<String> strings = new ArrayList<String>();
List<Object> objects = new ArrayList<Object>();
objects.addAll(strings);//编译通过

为什么调用addAll()方法就能编译通过呢,看一下他的源码:

boolean addAll(Collection<? extends E> c);

Java泛型提供了问号?通配符,上面的<? extends E>代表此方法接受 E
或者 E 的
一些子类型对象的集合。所以可以通过addAll()方法把List<String>赋值给List<Object>。

Kotlin的泛型没有提供通配符,取而代之的是outin修饰符。先举个例子:

//用out修饰T
class Box<out T> {
}

图片 3

(红色波浪线标记处为编译错误)

//用in修饰T
class Box<in T> {
}

图片 4

(红色波浪线标记处为编译错误)

对比上面两段代码可以看出,用out来修饰T,只能消费T类型,不能返回T类型;
用in来修饰T,只能返回T类型,不能消费T类型。简单来说就是 in 是消费者, out
是生产者。

但类型只能是一个类型。 那么泛型和类型之间是什么关系呢?

2.2 类型投影

上面说到了outin修饰符,如果我们不用他们来修饰泛型,会出现这种情况:

class Box<T> {
}

图片 5

编译不通过,因为Array<T>对于类型T是不可变的,所以Box<Any>和Box<String>谁也不是谁的子类型,所以编译不通过。对于这种情况,我们还是可以用outin修饰符来解决,但不是用来修饰Box<T>,如下所示:

fun test(strs: Box<Any>) {
    var objects: Box<in String> = strs
    //编译通过
}

fun test2(strs: Box<String>) {
    var objects: Box<out Any> = strs
    //编译通过
}

上面的解决方式叫做类型投影,Box<out Any>相当于 Java 的 Box<?
extends Object>、Box<in String>相当于 Java 的 Box<? super
Object>。

其实很简单,泛型在定义的时候,是泛指类型;在使用的时候,就需要被指定,到底使用哪个类型。

2.3 泛型函数

不仅类可以有类型参数。函数也可以有。类型参数要放在函数名称之前:

fun <T> singletonList(item: T): List<T> {
    // ……
}

//调用
val l = singletonList<Int>(1)
singletonList(l)

类似于Java的泛型方法:

public <T> T singletonList(T item) {
    // ……
}

//调用
singletonList(1);

即,使用时,就不在是泛指类型,而是特定类型。

2.4 泛型约束

泛型约束能够限制泛型参数允许使用的类型,如下所示:

Kotlin代码

fun <T : Comparable<T>> sort(list: List<T>) {
}

sort(1) //编译错误
sort(listOf(1)) //编译通过

上述代码把泛型参数允许使用的类型限制为 List<T>

Java中也有类似的泛型约束,对应的代码如下:

public static <T extends Comparable> List<T> sort(List<T> list){
}

如果没有指定泛型约束,Kotlin的泛型参数默认类型上界是Any,Java的泛型参数默认类型上界是Object


好比,定义时,定义了一个人。但在使用时,必须明确指定,到底是黑人还是白人。

总结

本篇文章对比了Java匿名类、静态类与Kotlin对象的写法和两种语言中对泛型的使用。相对来说,Kotlin还是在Java的基础上作了一些改进,增加了一些语法糖,更灵活也更安全。

参考文献:
Kotlin语言中文站、《Kotlin程序开发入门精要》

推荐阅读:
从Java到Kotlin(一)为什么使用Kotlin
从Java到Kotlin(二)基本语法
从Java到Kotlin(三)类和接口
从Java到Kotlin(五)函数与Lambda表达式
从Java到Kotlin(六)扩展与委托
从Java到Kotlin(七)反射和注解
从Java到Kotlin(八)Kotlin的其他技术
Kotlin学习资料汇总


更多精彩文章请扫描下方二维码关注微信公众号”AndroidCzh“:这里将长期为您分享原创文章、Android开发经验等!
QQ交流群: 705929135

图片 6

泛型的使用

泛型类跟普通类的使用方式一样,都需要实例化对象,再由对象来调用内部的属性或方法。

下面代码实例化了泛型Generic,实例化时,还指定了该泛型Generic的指定类型为String。

所以要给泛型Generic的属性Name赋值,就需要赋值字符串类型的值。

public static void Excute()
{
    Generic<String> gs = new Generic<String>();
    gs.Name = "Kiba518";
}

下面代码定义了一个Int类型的泛型Generic。

public static void Excute()
{
    Generic<int> gs = new Generic<int>();
    gs.Name = 518;
}

泛型的默认值

泛型的默认值,如下面代码所示。需要使用default(T)来赋值。

不管泛型到底是String,int,bool或者是一个Class类型,都可以被自动赋值。

public static void Excute()
{
    Generic<int> gs = new Generic<int>();
    gs.Name = 518;
    Generic<Task> gsTask = new Generic<Task>();
    gsTask.Name = new Task(()=> {
        Console.WriteLine("Kiba518");
    });
}

public class Generic<T>
{
    public T Name = default(T); 
}

泛型的约束

在泛型类中,有个特别的约束可供我们使用。

当我们不显示的声明时,这个约束不存在。但当我们显示的声明的时候,这个约束就会执行。

下面,我们来看看这个特别的约束。

public static void Excute()
{ 
    Generic<FanXing> gFanXing = new Generic<FanXing>();
    Generic<Base> gFanXingBase = new Generic<Base>();
    //Generic<string> gs = new Generic<string>(); 这样定义会报错
} 
public class Generic<T> where T : Base
{
    public T Name = default(T); 
} 
public class Base  
{
    public string Name { get; set; }
}
public class FanXing : Base
{
    public new string Name { get; set; }
}

如上面代码所示,【where T : Base】就是这个特别的约束。

当显示声明这个约束的时候,定义会限制泛型的类型。

什么是限制泛型的类型呢?

很简单,泛型T,是泛指某一个类型。我们在定义泛型类时,还需显示的指定类型,此时我们显示指定的类型,要受这个限制。

这个限制就是指【where T : Base】。

它的限制是,要求我们指定的类型T必须是Base,或者该类型继承自Base,如FanXing类。

泛型的函数

在C#中,泛型不仅可以用于类,还可以直接用于函数。

具体使用方式如下:

 public static void Excute()
 {
     GenericFunc gf = new GenericFunc();
     gf.FanXingFunc<FanXing>(new FanXing() { Name="Kiba518"});
 }
 public class GenericFunc
 {
     public void FanXingFunc<T>(T obj)
     {
         Console.WriteLine(obj.GetType());
     }
 }

很简单,调用泛型函数的时候,指定泛型函数的[指定类型]即可。

但是,这里我们发现一个问题,那就是,在泛型函数里,使用泛型对象的时候,我们发现对象都是object类型的。

那我们如果想使用泛型对象里的属性和方法时,要怎么办呢?

也很简单,反射就可以了。

下面我们添加一个反射函数GetPropertyValue,专门用来获取属性。

public class GenericFunc
{
    public void FanXingFunc<T>(T obj)
    { 
        var name = GetPropertyValue(obj, "Name");
        Console.WriteLine(name); 
    }
    public object GetPropertyValue(object obj, string name)
    {
        object drv1 = obj.GetType().GetProperty(name).GetValue(obj, null);
        return drv1;
    }
}

输出结果如下:

图片 7

这样我们就得到了我们想要的结果,如果想使用泛型类里的函数,道理也一样,只需要用反射来调用即可。

结语

看到这里,有些同学可能会觉得泛型很复杂,连使用其对象下的属性,都得反射,太繁琐了,还不如不用呢。

有这样想法的同学,心里想想就好了,如果对老司机这么说,他肯定会内心默默的微笑,然后对你说,你想的没错。

然后,你就没有然后了。

泛型的应用,开篇已经说了,主要用在提高代码的可重用性、类型安全性和效率上。

如果只是定义一个类,调用一个属性,那泛型的存在就是鸡肋。

但事实上,我们的系统永远只有更复杂,更复杂,更复杂。因此泛型才有了用武之地。

C#语法——委托,架构的血液

C#语法——元组类型

C#语法——await与async的正确打开方式


注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website