泛型趣谈

本文深入探讨Java泛型带来的好处,包括规约、泛型擦除、方法泛型、链式调用以及使用问号表示的通配符类型。通过具体示例,展示了泛型在设计接口、方法及实现链式调用时的独特优势,并介绍了如何灵活运用通配符类型进行对象操作。

Java中的泛型带来了什么好处?规约。就像接口定义一样,可以帮助对于泛型类型和对象的使用上,保证类型的正确性。如果没有泛型的约束,程序员大概需要在代码里面使用大量的类型强制转换语句,而且需要非常清楚没有标注的对象实际类型,这是容易出错的、恼人的。但是话说回来,泛型可不只有规约,还有很多有趣的用法,容我一一道来。

 

泛型擦除

Java的泛型在编译阶段实际上就已经被擦除了(这也是它和C#泛型最本质的区别),也就是说,对于使用泛型的定义,对于编译执行的过程,并没有任何的帮助(有谁能告诉我为什么按着泛型擦除来设计?)。所以,单纯利用泛型的不同来设计接口,会遇到预期之外的问题,比如说:

1
2
3
4
public interface Builder<K,V> {
     public void add(List<K> keyList);
     public void add(List<V> valueList);
}

想这样设计接口?仅仅靠泛型类型的不同来设计重载接口?那是痴人说梦。但是如果代码变成这样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GenericTypes {
 
     public static String method(List<String> list) {
         System.out.println( "invoke method(List<String> list)" );
         return "" ;
     }
 
     public static int method(List<Integer> list) {
         System.out.println( "invoke method(List<Integer> list)" );
         return 1 ;
     }
 
     public static void main(String[] args) {
         method( new ArrayList<String>());
         method( new ArrayList<Integer>());
     }
}

这个情况就有点特殊了,Sun的Javac编译器居然可以通过编译,而其它不行,这个例子来自IcyFenix的文章,有兴趣不妨移步参阅IcyFenix的文章以及下面的讨论

 

方法泛型

在JDK的java.util.List接口里面,定义了这样一个方法:

1
2
3
public interface List<E> extends Collection<E> {
     <T> T[] toArray(T[] a);
}

事实上,这个方法泛型T表示的是任意类型,它可是和此例中的接口/类泛型E毫不相干啊。

如果我去设计方法,我可能写成这样:

1
<T> T[] toArray();

其实这个T[ ] a参数的作用也容易理解:

  1. 确定了数组类型;
  2. 如果给定的数组a能够容纳得下结果,就会把结果放进a里面(JDK的注释有说明“If the list fits in the specified array, it is returned therein.”),同时也把a返回。

如果没有这个T[ ] a参数的话,光光定义一个方法泛型<T>是没有任何意义的,因为这个T是什么类型完全是无法预料的,例如:

1
2
3
4
5
6
7
8
9
public class Builder {
     public <E> E call(){
         return null ;
     }
     
     public static void main(String[] args) {
         String s = new Builder().call(); // ①
         Integer i = new Builder().call(); // ②
         new Builder().<String>call(); // ③
1
2
     }
}

可以看到,call()方法返回的是类型E,这个E其实没有任何约束,它可以表示任何对象,但是代码上不需要强制转换就可以赋给String类型的对象s(①),也可以赋给Integer的对象i(②),甚至,你可以主动告知编译器返回的对象类型(③)。

 

链式调用

看看如下示例代码:

1
2
3
4
5
6
7
8
9
public class Builder<S> {
     public <E> Builder<E> change(S left, E right){
         // 省略实现
     }
     
     public static void main(String[] args) {
         new Builder<String>().change( "3" , 3 ).change( 3 , 3 .0f).change( 3 .0f, 3 .0d);
     }
}

同样一个change方法,接收的参数变来变去的,上例中方法参数从String-int变到int-float,再变为float-double,这样的泛型魔法在设计链式调用的方法的时候,特别是定义DSL语法的时候特别有用。

 

使用问号 

其实问号帮助表示的是“通配符类型”,通配符类型 List<?> 与原始类型 List 和具体类型 List<String>都不相同,List<?>表示这个list内的每个元素的类型都相同,但是这种类型具体是什么我们却不知道。注意,List<?>和List<Object>可不相同,由于Object是最高层的超类,List<Object>表示元素可以是任何类型的对象,但是List<?>可不是这个意思。

来看一段有趣的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Wrapper<E> {
     private E e;
     public void put(E e) {
         this .e = e;
     }
     
     public E get(){
         return e;
     }
}
 
public class Builder {
     public void check(Wrapper<?> wrapper){
         System.out.println(wrapper.get()); // ①
         wrapper.put( new Object()); // ② wrong!
         wrapper.put(wrapper.get()); // ③ wrong!
         wrapper.put( null ); // ④ right!
     }
}

Wrapper的类定义里面指定了它包装了一个类型为E的对象,但是在另一个使用它的类Builder里面,指定了Wrapper的泛型参数是?,这就意味着这个被包装的对象的类型是完全不可知的:

  • 现在我可以调用Wrapper的get方法把对象取出来看看(①),
  • 但是我不能放任意类型确定的对象进去,Object也不行(②),
  • 即便是从wrapper里面get出来也不行(编译器太不聪明了是吧?③)
  • 可奇葩的是,放一个null是可以被允许的,因为null根本就不是任何一个类型的对象(④,注意,不能放int这类的原语类型,虽然它不是对象,但因为它会被自动装箱成Integer,从而变成具体类型,所以是会失败的)

现在思考一下,如果要表示这个未知对象是某个类的子类,上面代码的Wrapper定义不变,但是check方法写成:

1
public void check(Wrapper<? extends String> wrapper){
1
wrapper.put( new String());
1
}

这样呢?

……

依然报错,因为new String()确实是String的子类(或它自己)的对象,一点都没错,但是它可不见得和同为String子类(或它自己)的“?”属于同一个类型啊!

如果写成这样呢(注意extends变成了super)?

1
public void check(Wrapper<? super String> wrapper){
1
wrapper.put( new String());
1
}

这次对了,为什么呢?

……

因为wrapper要求put的参数“?”必须是String的父类(或它自己),而不管这个类型如何变化,它一定是new String()的父类(或它自己)啊!

 

泛型递归

啥,泛型还能递归?当然能。而且这也是一种好玩的泛型使用:

1
2
3
4
5
6
7
8
class Wrapper<E extends Wrapper<E>> implements Comparable<Wrapper<E>> {
 
     @Override
     public int compareTo(Wrapper<E> wrapper) {
         return 0 ;
     }
 
}

好玩吧?泛型也能递归。这个例子指的是,一个对象E由包装器Wrapper所包装,但是,E也必须是一个包装器,这正是包装器的递归;同时,包装器也实现了一个比较接口,使得两个包装器可以互相比较大小。

 

别晕!泛型只不过是一个普普通通的语言特性,但是也挺有趣的。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接《四火的唠叨》

【四旋翼无人机】具备螺旋桨倾斜机构的全驱动四旋翼无人机:建模与控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的全驱动四旋翼无人机展开研究,重点探讨其系统建模与控制策略,结合Matlab代码与Simulink仿真实现。文章详细分析了无人机的动力学模,特别是引入螺旋桨倾斜机构后带来的全驱动特性,使其在姿态与位置控制上具备更强的机动性与自由度。研究涵盖了非线性系统建模、控制器设计(如PID、MPC、非线性控制等)、仿真验证及动态响应分析,旨在提升无人机在复杂环境下的稳定性和控制精度。同时,文中提供的Matlab/Simulink资源便于读者复现实验并进一步优化控制算法。; 适合人群:具备一定控制理论基础和Matlab/Simulink仿真经验的研究生、科研人员及无人机控制系统开发工程师,尤其适合从事飞行器建模与先进控制算法研究的专业人员。; 使用场景及目标:①用于全驱动四旋翼无人机的动力学建模与仿真平台搭建;②研究先进控制算法(如模预测控制、非线性控制)在无人机系统中的应用;③支持科研论文复现、课程设计或毕业课题开发,推动无人机高机动控制技术的研究进展。; 阅读建议:建议读者结合文档提供的Matlab代码与Simulink模,逐步实现建模与控制算法,重点关注坐标系定义、力矩分配逻辑及控制闭环的设计细节,同时可通过修改参数和添加扰动来验证系统的鲁棒性与适应性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值