目录
一、泛型的上界
1.语法
在我们定义泛型类时,有时需要对传入的类型变量做一定的约束,这时,我们可以通过类型边界来约束。
这时就需要引入泛型的上界这个概念了。
泛型上界语法:
class 泛型类名称<类型形参 extends 类型边界>{
......
}
例如:
这行代码是指:只接受Number的子类作为T的类型实参。也就是说:在这里我们能够传递的类型参数只能是Number的子类或本身。
l1能正常运行是因为Integer是Number的子类。
l2不能正常运行是因为String不是Number的子类。
如果没有指定类型边界T,可以看作T extends Object。
当然,我们的泛型上界也有复杂的结构,例如:
这里是指:E实现了Comparable接口,这个接口也是E类型的。当然,E只能是Comparable的子类或者本身。
二、泛型方法
1.语法
方法限定符<类型形参列表>返回值类型 方法名称(形参列表){
......
}
如果我们想要将某个泛型方法设置为静态的,那么如下所示:
public class Util{
public static <E>void swap(E[]array,int i,int j){
E t=array[i];
array[i]=array[j];
array[j]=t;
}
}
也就是说,如果想将泛型方法设置为静态,那么需要在static后用<>声明泛型类型参数。
当然,如果是静态泛型方法的话,不用new就可以直接使用。
在使用时,会采用两种情况:1.类型推导。2.不使用类型推导
我们先来看第一种情况:类型推导。
与前文提到过的类型推导相似。根据上下文编译器可以自行推导出类型时,便可以不写类型。
如:
public static void main(String[] args) {
Integer[] a={};
swap(a,0,9);
String[] b={};
swap(b,0,8);
}
此处编译器能够根据上下文推导出实参类型,故可以不写类型。
我们再来看第二种情况:不使用类型推导。
public static void main(String[] args) {
Integer[] a={};
Util.<Integer>swap(a,0,9);
String[] b={};
Util.<String>swap(b,0,8);
}
当然,这两种方式都能够使用。在平时写代码时根据自己所写的代码加以判断,选择更适合的一种就可。
三、通配符
1.关于通配符
如果存在这样一个关系:Student是Person的子类,那么List<Student>应该也是List<Person>的子类。这样的关系看似是正确的。但是在泛型中并不支持这样的父子类关系。
泛型中的T是确定的类型,一旦进行传递就定了下来。
那么,如果我们要让上面的关系实现就需要引入一个比泛型更为灵巧或者不确定的概念:通配符。
当然,通配符用于在泛型中使用,是泛型的一大助力。
通配符 ?
那么下面,我们来看一个例子:
class Message<T>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message){
this.message=message;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<String>message=new Message();
message.setMessage("Hello,Welcome to my world");
fun(message);
}
public static void fun(Message<String>temp){
System.out.println(temp.getMessage());
}
}
目前我们的代码是没有问题的,但是如果我们将String换做Integer就会出现问题了。
此时,我们就需要一个解决方法:
可以接收所有的泛型类型,但是又不能够让用户随意进行更改,这种情况下我们就需要派我们的通配符 ? 登场了。
此时,我们就解决了我们想要解决的问题。
那么关于通配符,我们又引出两个子通配符:
?extends 类 :通配符的上界
? super类 :通配符的下界
2.通配符的上界
我们首先来看通配符的上界:? extends类。
<? extends 上界>
例如:
<? extends Number>可以传入的实参类型是Number或者Number的子类。
class Biology{
}
class Animal extends Biology{
}
class BirdsAndBirds extends Animal{
}
class Bird extends BirdsAndBirds{
}
class Message1<T>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo1 {
public static void main(String[] args) {
Message<Animal>message=new Message<>();
message.setMessage(new Animal());
fun(message);
Message<Animal>message1=new Message<>();
message1.setMessage(new BirdsAndBirds());
fun(message1);
}
public static void fun(Message<?extends Biology>temp){
System.out.println(temp.getMessage());
}
}
我们可以观察到:在上面代码中只能读取数据而不能存储数据。
这是因为,temp接收的是Biology和它的子类,这时去存储的元素应该是哪个子类无法确定,所以编译器会进行报错。
3.通配符的下界
<? super 下界>
例如:
<? super Integer>可以传入的实参类型是Integer或者Integer的父类类型。
class Biology{
}
class Animal extends Biology{
}
class BirdsAndBirds extends Animal{
}
class Bird extends BirdsAndBirds{
}
class Message1<T>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo1 {
public static void main(String[] args) {
Message<Animal>message=new Message<>();
message.setMessage(new Animal());
fun(message);
Message<Biology>message1=new Message<>();
message1.setMessage(new Biology());
fun(message1);
}
public static void fun(Message<?super Animal>temp){
temp.setMessage(new Bird());
temp.setMessage(new Animal());
Biology biology=temp.getMessage();
System.out.println(temp.getMessage());
}
}
因为不知道我们所取出的数据类型是什么,所以上面代码会报错。
由此可见:通配符的下界,不能进行读取数据,但是可以写入数据。(写入数据时,只能写入父类,不能写入子类)。
四、包装类
1.基本数据类型所对应的包装类
在Java中,存在这样一类特殊的类。
由于基本数据类型并不是继承自Object,为了在泛型代码中可以支持基本数据类型,Java为每个基本数据类型都配对了一个包装类。
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
2.装箱和拆箱(装包和拆包)
装箱和拆箱在某些书中又被称为装包和拆包。
装箱操作:将基本数据类型变成对应的包装类型。
拆箱操作:将包装类型变成对应的基本数据类型。
例如:
装箱操作:
int i=10;
Integer li=Integer.valueOf(i);
Integer ij=new Integer(i);//新建一个Integer类型对象,将i的值放入对象的某个属性中。
拆箱操作:
int j=li.intValue();//将Integer对象中的值取出,放到对应的基本数据类型中。
3.自动装箱和自动拆箱(自动装包和自动拆包)
在敲写代码的过程中往往少不了拆箱装箱的使用,当然拆箱装箱也带来了不少代码量,为了减轻开发者的代码量负担。于是,Java提供了自动装箱和自动拆箱的操作。
例如:
自动装箱:
int i=10;
Integer ii=i;
Integer lj=(Integer)i;
自动拆箱:
int j=ii;
int k=(int)i;