泛型进阶之通配符
通配符
通配符其实就是一个问号(?),多用于泛型中。
1.1 通配符所要解决的问题
首先先来看一段代码:
class Message<T> {private T message ;public T getMessage() {return message;}public void setMessage(T message) {this.message = message;}
}
public class Test2 {public static void main1(String[] args) {Message<String> message = new Message<>() ;message.setMessage("666");fun(message) ;}public static void fun(Message<String> temp){System.out.println(temp.getMessage());}
}
上面代码本身没有语法错误也可以正常编译运行,打印666,不过存在一定的局限性,当前的fun方法的参数被明确指定为了Message<String>类型,意味着它只能接收Message类中泛型为String类型的对象。
如果需要输出Message类的其他类型的对象,如Message<Integer>,就需要重载一个参数为对应类型fun方法。这么做代码的复用性就会变得很低了。
我们需要的解决方案:可以接收所有的泛型类型,但是又不能让用户随意修改。这种情况就需要使用通配符进行处理了:
修改代码如下:
//可以接受任意类型,但是由于不知道类型不能进行删改public static void fun(Message<?> temp){System.out.println(temp.getMessage());}
注意此时如果在fun方法内部对当前message的值进行设置或者修改都是行不通的,如图: 这是因为当前的方法参数不确定类型,不能随意进行删改。
总结:使用了通配符之后,既使当前方法可以接收任意类型的数据功能,也使当前的形参在方法中无法随意修改。
public static void main(String[] args) {Message<String> message = new Message<>() ;message.setMessage("Java");fun(message) ;Message<Integer> message1 = new Message<>() ;message1.setMessage1(666);fun(message);}//可以接受任意类型,但是由于不知道类型不能进行删改public static void fun(Message<?> temp){System.out.println(temp.getMessage());}
运行结果如下:
在“?”(通配符)的基础上又产生了两个子通配符:
?extends 类名:设置通配符上界
?super 类名:设置通配符下0界
1.2 通配符上界
语法:
<? extends 上界>
如<? extends Number>(当前传入的类型只能是Number或者Number的子类,如:Integer、Float、Double……)
通配符上界不仅能实现指定传入数据为数字类型,也可以指定实现指定传入类型为自定义类型。下面,我们就通过代码来实现:输出Fruit和Fruit子类的信息。
首先,要实现的关系图如下:
class Plate{@Overridepublic String toString() {return "一个盘子";}
}
class Food extends Plate{public String toString() {return "一种食物";}
}
class Fruit extends Food {public String toString() {return "一种水果";}
}
class Apple extends Fruit {public String toString() {return "一个苹果";}
}
class Banana extends Fruit {public String toString() {return "一个香蕉";}
}
注意:这里为了使输出的信息更加清晰,重写了toString方法。
message类实现输出当前传入对象信息:
class Message<T> {private T message ;public T getMessage() {return message;}public void setMessage(T message) {this.message = message;}
}
funExtend方法实现输出信息功能:
public static void funExtend(Message<? extends Fruit> temp){System.out.println(temp.getMessage());//对于通配符的上界来说 是不可以修改元素的//temp.setMessage(new Banana());// temp.setMessage(new Apple());//向上转型Fruit fruit = temp.getMessage();}
由于只是给通配符加上了上界,本质上还是通配符,所以在当前funExtend方法中仍然无法修改参数的值。
测试代码:
public static void main(String[] args) {Message<Apple> message = new Message<>() ;message.setMessage(new Apple());Message<Banana> message1 = new Message<>() ;message1.setMessage(new Banana());funExtend(message);funExtend(message1);}
我们要实现的是:输出Fruit和Fruit子类的信息 。如果我们在测试用例中传入一个Food对象(即想要输出Food对象的信息)又会发生什么情况呢?
可以看到,代码报错!!!这就是通配符上界的作用:帮助我们检查当前检查的类型是否是该类或者该类的子类。像这样的检查的机制,在Java中还有很多,如之前讲过的:final(指定当前方法不能被重写,重写则报错)、注解@Override(检查该方法是否已经重写,如果没有重写会报错)……
1.2 通配符下界
语法:
<? super 下界>
<? super Integer>(代表可传入的类型可以是Integer和Integer的父类)
与上界相同可以传入自定义类型,这里的内容与通配符上界基本相同,不同的是:1、传入类型变为了当前的类或者当前类的父类。2、以通配符下界为参数的方法,是可以进行修改的。
代码:
class Plate{@Overridepublic String toString() {return "一个盘子";}
}
class Food extends Plate{public String toString() {return "一种食物";}
}
class Fruit extends Food {public String toString() {return "一种水果";}
}
class Apple extends Fruit {public String toString() {return "一个苹果";}
}
class Banana extends Fruit {public String toString() {return "一个香蕉";}
}
funSuper方法 :
//下界一般用来写入public static void funSuper(Message<? super Fruit> temp) {System.out.println(temp.getMessage());//此时可以修改添加的是Fruit或者Fruit的子类temp.setMessage(new Banana());//向上转型temp.setMessage(new Apple());//Fruit fruit = temp.getMessage();//此时不知道是那个父类System.out.println(temp.getMessage());}
注意:此时写入是不报错的,因此泛型下界一般用来写入。
测试:
public static void main(String[] args) {Message<Food> message = new Message<>() ;message.setMessage(new Food());Message<Fruit> message1 = new Message<>() ;message1.setMessage(new Fruit());funSuper(message);funSuper(message1);}
结果:
可以看到在funSuper方法中修改的对象信息也能被正常输出。