泛型说明
泛型是什么?我们在哪里会遇到? 比如在一些集合类里面,我们可以看到对于键值的参数化限制。作用就是指定了键值的类型。 当然也有未知类型的时候指定泛型,这种比较灵活,根据传入的具体参数决定具体参数类型。
一般具有一些比较规范的泛型类型标记符。
E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number(数值类型) ? - 表示不确定的 java 类型
这种标记符可以用在类,接口,方法中,我们可以称之为泛型类,泛型接口,泛型方法。
使用泛型的好处
1:在代码编译时期对数据类型进行检查
package java_practice;
import java.util.ArrayList;
public class GenericDemo {
public static void main(String args[])
{
ArrayList list = new ArrayList();
list.add("jgdaabc");
list.add(123);
for(int i =0;i<list.size();i++)
{
String item = (String)list.get(i);
System.out.println(item);
}
}
}
这个程序没有使用泛型,也就是没有对参数类型进行限制,集合中可以添加任意类型,但是如果我们后面的需求是String类型的话,我们需要转换。这样转换虽然在编译上没有报错,但是运行的时候便会抛出异常。 Integer类型是无法转换为String类型的。
其实我们可以去简单修改
package java_practice;
import java.util.ArrayList;
public class GenericDemo {
public static void main(String args[])
{
ArrayList list = new ArrayList();
list.add("jgdaabc");
list.add(123);
for(int i =0;i<list.size();i++)
{
String item = list.get(i).toString();
System.out.println(item);
}
}
}
我们也可以这样
package java_practice;
import java.util.ArrayList;
public class GenericDemo {
public static void main(String args[])
{
ArrayList list = new ArrayList();
list.add("jgdaabc");
list.add(123);
for(int i =0;i<list.size();i++)
{
String item = String.valueOf(list.get(i));
System.out.println(item);
}
}
}
关于这三种方式的区别,本文不做探讨。只是给出了解决办法。
但是我要说明的就是没有泛型的情况下,如果我们错误进行存储的话,倏然类型不可以转换,但是编译通过了。这样就可能在运行的时候抛出异常,但是如果我们很好的使用泛型,这样可以在编译的时候就可以避免这种错误。
ArrayList<String> list = new ArrayList<String>();
给集合添加String类型进行限制,所以泛型为String,这样定义的话说明了集合中存储的元素只能是String类型。所以如果存储其它类型的话,就会在编译的时候进行检查。 2:让程序更加灵活 但是其实并不是说,我们使用泛型的时候类型一定是固定的。 简单举个例子
package java_practice;
import java.util.ArrayList;
public class GenericDemo<T> {
public GenericDemo(T t) {
System.out.println(t);
}
public static void main(String args[])
{
GenericDemo genericDemo = new GenericDemo("hello");
new GenericDemo(123);
}
}
T具体的类型由参入的参数决定
3:消除强制转换 其实道理还是和第一点的一样 提前将泛型写明,可以对后续的类型需求更加清楚。在其它的方面也是这样。
泛型类
class 类名 <T>{
private T value1;
public T setValue(T value1)
{
this.value = value;
}
public T getValue()
{
return value;
}
}
大致的基本可以这样去定义,当然只要符合规范,自己也是可以去设定一些方法等的。类在实例化的时候一定要传入具体的参数。
一个例子
package java_practice;
import java.util.ArrayList;
import java.util.HashMap;
public class GenericDemo<T> {
private T name;
private T age;
public void setName(T name) {
this.name = name;
}
public void setAge(T age) {
this.age = age;
}
public T getName() {
return name;
}
public T getAge() {
return age;
}
public GenericDemo(T t) {
System.out.print(t);
}
public void print_demo() {
System.out.println("我的名字叫" + name + ",今年" + age + "岁");
}
public static void main(String args[]) {
GenericDemo demo_1 = new GenericDemo<>("hello,");
demo_1.setAge(19);
demo_1.setName("兰舟千帆");
demo_1.print_demo();
}
}
使用泛型给我们带来了一定的便捷。属性的确定可以根据传入参数的类型进行确定。
泛型接口
定义一个接口
public interface 类名<T>{
public default T 方法名()
{
.....
}
public T demo_1();
}
实现接口类
public class 类名<T> implements 接口类名<T>
{
@Override
public T demo() {
.....
}
@Override
public T demo_1() {
......
}
}
default关键字使得接口中的方法有方法体。这是最近的一个新的特性。 被default修饰的方法不再是抽象方法,我们甚至可以不去实现。
用泛型修饰方法的话,其实是代替了方法的返回类型。如果用泛型修饰后,又用其它的类型指定后只是冲突的。
引入类型通配符
这个我是查看了许多文章,然后其中说明的一个比较好的,我摘录一下具体的内容。
不变,协变,逆变
网上查看了许多资料就找到一篇用这几个名词。说实话,这样去描述类型转换或者继承我还是第一次见
逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(?)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类); f(?)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;** f(?)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;** f(?)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系
注:该段摘录自java泛型 通配符详解及实践
这样的数学公式表达可能对于一些读者不是很友好。 简单来说,逆变就是比如A是B的父类,但是A可以继承B的类型匹配。。这样其实看起来有点逆向关系,所以叫做逆变,当然一般情况下,是不支持逆变的。 协变就是如果A是B的父类,B可以继承A的属性,也可以认为就是类型关系。 不变就是无论AB是和关系,都不能进行类型匹配。
这里的A或者B可以理解为Number或者Integer.然后f就代表了对应关系,A,B满足转型的条件的话,如果对应的数组是支持这种关系的,也就成立。
但是泛型是不变的,那就说明即使你的类型参数的转换满足了这种关系,也是绝对转换不了的。(以不变应万变)比如这样是不可以的。
用通配符支持协变和逆变(开挂行为)
解决上面问题的办法就是采用上边界通配符 加这个上边界通配符的作用就是说明了list被限制为继承number的任意类型。加了这个之后编译通过了,但是又带来了新的问题,既然是任意类型了,那么就没法再添加数据了,也就是无法添加一个确定的类型。除了null这个特殊的。null是不确定的,再mysql里面也经常说到null都不等于null。 上边界通配符的特点就是上界<? extends T>不能往里存,只能往外取。 既然有上边界通配符,那当然相对的也有下边界通配符。我们可以通过上边界通配符的特点发现,其实也就是帮助了支持协变,那么下边界通配符就是支持逆变。 这里的list2的类型就是Integer或者其父类
var foo = 'bar';
我们来看一个具体的说明的实例
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GenericTest {
public static void main(String[] args) throws Exception {
class Animal {}
class Bird extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
List<Animal> tmpList = new ArrayList<>();
tmpList.add(1);
tmpList.add(2);
tmpList.add(3);
List<? extends Animal> extendsList = tmpList;
extendsList.add(new Bird('a'));
extendsList.set(1, new Bird('b'));
extendsList.remove(new Bird('a'));
extendsList.contains(new Bird('b'));
Animal extendsGet = extendsList.get(1);
List<? super Animal> superList = tmpList;
superList.add(new Bird('a'));
superList.set(1, new Bird('b'));
superList.remove(new Bird(1));
superList.contains(1);
Animal superGet = superList.get(1);
}
但是我觉得两行代码就足够理解了。 可以看出采用上边界通配符修饰是不能够添加数据的。但是下边界可以。 什么时候使用向上转,和向下转?
in"类型: “in”类型变量向代码提供数据。 如copy(src,dest) src参数提供要复制的数据,因此它是“in”类型变量的参数。 "out"类型: “out”类型变量保存接收数据以供其他地方使用.如复制示例中,copy(src,dest),dest参数接收数据,因此它是“out”参数。 “in”,“out” 准则 “in” 类型使用 上边界通配符? extends. “out” 类型使用 下边界通配符? super. 如果即需要 提供数据(in), 又需要接收数据(out), 就不要使用通配符.
泛型方法
public void setName(T name) {
this.name = name;
}
这个叫泛型方法吗?并不叫
public T getName() {
return name;
}
这个也不叫泛型方法 这样可以
public <T> void getAge(T t)
{
...
}
public<T>void show(T t)
{
...
}
如果再泛型类中声明了泛型方法,泛型方法使用的泛型类型T可以与类中的T不是同一种类型,也就是T不等于T。
泛型方法与可变参数
public <T> void printfMsg(T... args)
{
for(T t:args)
{
System.out.println(t);
}
}
调用赋值
demo_1.printfMsg(1,2,3,4,5,6,7,8,9);
输出
泛型上界下界
其实还是和通配符的道理一样。上界就是<? extends 指定类型> 来举一个例子。字迹琢磨出来一个无聊的栗子,来吃个栗子
public <T>void show_insert(GenericDemo<? extends Number> t) {
System.out.println(t);
}
我是如何调用这个方法给这个t赋值呢?
GenericDemo<Integer> demo2 = new GenericDemo<>(2);
demo_1.show_insert(demo2);
相应的下界也是一样的道理
public <T>void show_insert(GenericDemo<? super Integer> t) {
System.out.println(t);
}
GenericDemo<Integer> demo2 = new GenericDemo<>(2);
demo_1.show_insert(demo2);
super以后你这个传入必须是指定类型或者是其父类型。
泛型数组
一般类型的数组直接指定泛型去操作是行不通的。可以用集合类型的数组,但是这样用的话,会有套娃的风险
其实这样做不一定有实用的价值,只是sun其实给出了这部分的有关说明。目前,对集合采用这样的操作自己不是怎么去用。
但是总说来。泛型合理使用还是对代码的优化很有帮助的。
自己以后要是遇到这方面的事情会再说明。就先菜到这里吧!该文是自己的一些认识,如果有不足或者说的不对的地方,还请指正。
|