深入了解JAVA泛型
什么是泛型
泛型的概念:java泛型(generics)是jdk1.5中引入的一个新特性,泛型提供了编译时的类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。
泛型的本质就是类型参数化,也就是所操作的数据类型被指定为一个参数。
使用泛型的好处:
1 在编译期间提供了类型检查
2 取数据时无须进行类型装换
泛型类、接口
泛型类
语法:
class 类名称 <泛型标识,泛型标识,泛型标识,...> { private 泛型标识 变量名; // ... }
常用的泛型标识:t、e、k、v
使用语法:
类名 <具体的数据类型> 对象名 = new 类名<具体的数据类型>();
jdk 1.7 之后,后面的 <> 中的具体的数据类型可以省略不写。
定义一个简单的泛型类:
/** * 泛型类 t:类型形参,在类创建对象时,指定具体的数据类型 * @author rainszj * 2020/3/19 */ public class genericdemo01<t> { private t value; public genericdemo01() { } public genericdemo01(t value) { this.value = value; } @override public string tostring() { return "genericdemo01{" + "value=" + value + '}'; } public t getvalue() { return value; } public void setvalue(t value) { this.value = value; } }
测试一下:
public class test { public static void main(string[] args) { // 在创建对象时指定具体的数据类型 genericdemo01<string> genericdemo01 = new genericdemo01<>("java"); // 泛型类不支持基本数据类型,但可以使用基本类型对应的包装类 genericdemo01<integer> genericdemo02 = new genericdemo01<>(1); // 在泛型类对象时,不指定具体的数据类型,将会使用object类型来接收 // 同一个泛型类,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板 // class com.rainszj.genericdemo01 system.out.println(genericdemo01.getclass()); // class com.rainszj.genericdemo01 system.out.println(genericdemo02.getclass()); // true system.out.println(genericdemo01.getclass() == genericdemo02.getclass()); } }
注意事项:
泛型类,如果没有指定具体的数据类型,按object类型来接收
泛型的类型参数只能是类类型,也就是引用数据类型,不能是基本数据类型
泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
/** * 抽奖池 * * @author rainszj * 2020/3/19 */ public class productgetter<t> { // 奖品 private t product; private arraylist<t> list = new arraylist<>(); /** * 添加奖品 * * @param product */ public void addproduct(t product) { list.add(product); } /** * 抽取随机奖品 * * @return */ public t getproduct() { return list.get(new random().nextint(list.size())); } @override public string tostring() { return "productgetter{" + "product=" + product + '}'; } } public static void main(string[] args) { productgetter<string> productgetter1 = new productgetter<>(); // 奖品类型 礼物 string[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"}; // 添加奖品 for (int i = 0, length = products1.length; i < length; i++) { productgetter1.addproduct(products1[i]); } // 获取奖品 string product1 = productgetter1.getproduct(); system.out.println("恭喜您抽中了," + product1.tostring()); productgetter<integer> productgetter2 = new productgetter<>(); // 奖品类型 money integer[] products2 = {1000, 3000, 10000, 500}; for (integer money : products2) { productgetter2.addproduct(money); } integer product2 = productgetter2.getproduct(); system.out.println("恭喜您抽中了," + product2.tostring()); }
从泛型类派生子类
子类也是泛型类,子类的泛型标识 t 要和父类的泛型标识 t 保持一致,或者是包含关系,子类的泛型标识包含父类的泛型标识
class childgeneric<t> extends parentgeneric<t> class childgeneric<t, e> extends parentgeneric<t>
子类不是泛型类,父类要明确泛型的数据类型
class childgeneric extends parentgeneric<string>
泛型接口
语法:
interface 接口名称 <泛型标识,泛型标识,...> { 泛型标识 方法名(); }
实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型
public class apple implements generic<string> {}
实现类也是泛型类,实现类和接口的泛型类型要一致,或者是包含关系,实现类的泛型标识包含泛型接口的泛型标识
public class apple<k> implements generic<k> {} public class apple<k, v> implements generic<k> {}
定义一个泛型接口
public interface generic<k> { k getkey(); }
实现其中方法:
/** * 泛型接口的实现类,是一个泛型类, * 那么要保证实现接口的泛型类的泛型标识包含泛型接口的泛型标识 */ public class pair<k, v> implements generic<k> { private k key; private v value; public pair() { } public pair(k key, v value) { this.key = key; this.value = value; } @override public k getkey() { return key; } public v getvalue() { return value; } @override public string tostring() { return "pair{" + "key=" + key + ", value=" + value + '}'; } }
测试:
public class mytest { public static void main(string[] args) { pair<string, integer> pair = new pair<>("数学", 100); system.out.println(pair.tostring()); // pair{key=数学, value=100} } }
泛型方法
普通泛型方法
泛型类,是在实例化类时指明泛型的具体类型。
泛型方法,是在调用方法时,指明泛型的具体类型。
语法:
修饰符 <t,e,...> 返回值类型 方法名(形参列表) { // 方法体... }
public
与返回值中间 <t,e,...>
(泛型列表)非常重要,可以理解为声明此方法为泛型方法。
只有声明了 <t,e,...>
的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法
<t>
表明该方法将使用泛型类型 t,此时才可以在方法中使用泛型类型 t。
public class productsetter<t> { private t product; private random random= new random(); private arraylist<t> list = new arraylist<>(); public void addproduct(t product) { list.add(product); } /** * @param list * @param <e> 泛型方法的类型,是在调用泛型方法时确定的 * @return */ public <e> e getproduct(arraylist<e> list) { return list.get(random.nextint(list.size())); } public t getproduct() { return list.get(random.nextint(list.size())); } @override public string tostring() { return "productsetter{" + "product=" + product + '}'; } }
测试:
public static void main(string[] args) { productsetter<string> productsetter = new productsetter<>(); string[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"}; for (int i = 0; i < products1.length; i++) { productsetter.addproduct(products1[i]); } system.out.println(productsetter.getproduct()); arraylist<string> list1 = new arraylist<>(); list1.add("华硕电脑"); list1.add("苹果电脑"); list1.add("华为电脑"); string product1 = productsetter.getproduct(list1); system.out.println(product1 + "\t" + product1.getclass().getsimplename()); // 华为电脑 string arraylist<integer> list2 = new arraylist<>(); list2.add(1); list2.add(2); list2.add(3); integer product2 = productsetter.getproduct(list2); system.out.println(product2 + "\t" + product2.getclass().getsimplename()); // 2 integer }
静态泛型方法
public static <t, e, k> void pringtype(t k1, e k2, k k3) { system.out.println(k1 + "\t" + k1.getclass().getsimplename()); system.out.println(k2 + "\t" + k2.getclass().getsimplename()); system.out.println(k3 + "\t" + k3.getclass().getsimplename()); } // 方法的调用 productsetter.pringtype(1, "hello", false);
// 输出结果
1 integer
hello string
false boolean
注意:
// 在泛型类中无法添加静态的 带有泛型成员方法,但可以添加静态的 泛型方法 public class test<t> { // 带有泛型的成员方法 // 错误 public static t getkey(t key) { return key; } // 泛型方法 // 正确 public static <e> e getkey(e key) { return key; } }
泛型方法中的可变参数
public class mytest { public static void main(string[] args) { mytest.print(1, 2, 3); } /** * 泛型方法中的可变长参数 * @param value * @param <e> */ public static <e> void print(e ... value) { for (int i = 0; i < value.length; i++) { system.out.println(value[i]); } } }
总结:
泛型方法能使方法独立于类而产生变化。
如果 static
方法要使用泛型能力,就必须使其成为泛型方法。
类型通配符
类型通配符一般是使用 ?
代替具体的类型实参。
类型通配符是类型实参,而不是类型形参。
我们先来定义一个简单的泛型类:
public class box<t> { private t width; public static void showbox(box<number> box) { number width = box.getwidth(); system.out.println(width); } public t getwidth() { return width; } public void setwidth(t width) { this.width = width; } }
main方法:
public static void main(string[] args) { box<number> box1 = new box<number>(); box1.setwidth(100); showbox(box1); }
当我们在 main 方法中增加这一段代码时,就会报错
box<integer> box2 = new box<>(); box2.setwidth(200); showbox(box2);
虽然 integer 类继承自 number 类,但在类型通配符中不存在继承这一概念!
也许你会使用方法的重载,但是 在同一个泛型类中,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板,所以无法通过方法的重载,传递不同的泛型类型。
这时可以使用类型通配符 ?
,来代表具体的类型实参!
public static void showbox(box<?> box) { object width = box.getwidth(); system.out.println(width); }
类型通配符的上限
在我们上面的showbox()代码中,发现 box.getwidth()
得到的还是object类型,这和我们不使用类型通配符,得到的结果是一样的。这时我们可以使用类型通配符的上限。
语法:
类/接口 <? entends 实参类型>
要求该泛型的类型,只能是实参类型,或者是实参类型的子类类型。
public static void showbox(box<? extends number> box) { number width = box.getwidth(); system.out.println(width); } public static void main(string[] args) { box<integer> box2 = new box<>(); box2.setwidth(200); showbox(box2); }
使用类型通配符的下限,无法得知该类型具体是指定的类型,还是该类型的子类类型,因此无法在 list 集合中执行添加该类或者该类子类的操作!
public static void showanimal(list<? extends cat> list) { // 错误 list.add(new cat()); list.add(new minicat()); }
类型通配符的下限
语法
类/接口 <? super 实参类型>
要求该泛型的类型,只能是实参类型,或者是实参类型的父类类型。
下面通过 treeset 集合中的一个构造方法来进一步理解 类型通配符的下限
public treeset(comparator<? super e> comparator) { this(new treemap<>(comparator)); }
首先是一个animal类,只有一个 name 属性
public class animal { private string name; public animal(string name) { this.name = name; } public string getname() { return name; } public void setname(string name) { this.name = name; } @override public string tostring() { return "animal{" + "name='" + name + '\'' + '}'; } }
然后它的一个子类,cat添加一个属性:age
public class cat extends animal { private int age; public cat(string name, int age) { super(name); this.age = age; } public int getage() { return age; } public void setage(int age) { this.age = age; } @override public string tostring() { return "cat{" + "age=" + age + '}'; } }
最后是 cat 的子类,minicat,再添加一个属性 level
public class minicat extends cat { private int level; public minicat(string name, int age, int level) { super(name, age); this.level = level; } public int getlevel() { return level; } public void setlevel(int level) { this.level = level; } @override public string tostring() { return "minicat{" + "level=" + level + '}'; } }
测试,首先我们要在mytest类通过静态内部类的方式,实现比较的接口,在构造treeset时,传递比较器
public class mytest { public static void main(string[] args) { // 正常 // treeset<cat> animals = new treeset<cat>(new comparator1()); // 正常 treeset<cat> animals = new treeset<cat>(new comparator2()); // 报错 // treeset<cat> animals = new treeset<cat>(new comparator3()); list<cat> list = arrays.aslist(new cat("a", 12), new cat("c", 9), new cat("b", 20)); animals.addall(list); animals.foreach(system.out::println); } public static class comparator1 implements comparator<animal> { @override public int compare(animal o1, animal o2) { return o1.getname().compareto(o2.getname()); } } public static class comparator2 implements comparator<cat> { @override public int compare(cat o1, cat o2) { return o1.getage() - o2.getage(); } } public static class comparator3 implements comparator<minicat> { @override public int compare(minicat o1, minicat o2) { return o1.getlevel() - o2.getlevel(); } } }
结论:
通过以上的比较,我们可以看出,类型通配符的下限,只能传递实参类型的或者实参类型的父类类型。
我们每次比较使用的都是 cat 类型,但在 comparator1
比较的是 animal 中的 name 属性,这是因为 我们在初始化 cat 对象的时候,一定会先初始化 animal 对象,也就是创建子类对象的时候,一定会先创建父类对象,所以才可以进行比较。
如果是使用 类型通配符的上限,在创建对象时,比较的是该类的子类对象中的属性,就会造成空指针异常!也就是comparator3
无法使用的原因, 所以在 treeset
中才会使用 <? super e>
,类型通配符的下限。
类型擦除
泛型是java 1.5 引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前的代码兼容。那是因为,泛型信息只存在编译阶段,在进入 jvm 之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除。
无限类型擦除
先定义一个泛型类:
public class erasure<t> { private t key; public t getkey() { return key; } public void setkey(t key) { this.key = key; } }
输出结构:
public static void main(string[] args) { erasure<integer> erasure = new erasure<>(); class<? extends erasure> cls = erasure.getclass(); field[] fields = cls.getdeclaredfields(); for (field field : fields) { system.out.println(field.getname() + ":" + field.gettype().getsimplename()); // key:object } }
可以发现在编译完成后的字节码文件中,t --> object 类型
有限类型擦除
还是刚才的泛型类,只不过加了泛型的上限
public class erasure<t extends number> {// ...}
测试不变,输出结果:
key:number
当我们指定了泛型的上限时,它会将我们的泛型擦除为上限类型
同样对泛型方法,也是一样的道理
// 泛型方法 public <e extends list> e test(e t) { return t; }
method[] methods = cls.getdeclaredmethods(); for (method method : methods) { system.out.println(method.getname() + ":" + method.getreturntype().getsimplename()); } // 输出结果 // getkey:number // test:list // setkey:void
桥接方法
泛型接口
public interface info<t> { t test(t value); }
泛型接口的实现类
public class infoimpl implements info<integer> { @override public integer test(integer value) { return value; } }
测试
public static void main(string[] args) { class cls = infoimpl.class; method[] methods = cls.getdeclaredmethods(); for (method method : methods) { system.out.println(method.getname() + ":" + method.getreturntype().getsimplename()); } } // 输出结果: // test:integer // test:object
原本 infoimpl 中只是实现了 info 接口中的一个方法,但通过反射却拿到了两个方法。其中返回值为 object 的方法就是桥接方法。
在编译完成后,类型擦除的结果是这样的:
public interface info { object test(object value); }
public class infoimpl implements info { public integer test(integer value) { return value; } // 桥接方法:保持接口和类的实现关系 @override public object test(object value) { return (integer)value; } }
泛型数组
开发中,一般常用的是泛型集合
泛型数组的创建:
可以声明带泛型的数组引用,但是不能直接创建带泛型数组对象。
可以通过 java.lang.reflect.array
的 newinstance(class<t>, int)
创建 t[ ] 数组。
// 可以创建带泛型的数组引用 arraylist<string>[] arraylists1 = new arraylist[3]; // 无法创建带泛型的数组对象 arraylist<string>[] arraylists2 = new arraylist<string>[3];
简单使用 java.lang.reflect.array
的 newinstance(class<t>, int)
创建 t[ ] 数组。 封装一个泛型数组
public class genericarray<t> { private t[] array; public genericarray(class cls, int length) { this.array = (t[]) array.newinstance(cls, length); } public void put(int index, t item) { this.array[index] = item; } public t get(int index) { return this.array[index]; } public t[] getarray() { return this.array; } public static void main(string[] args) { genericarray<string> ga = new genericarray<>(string.class, 3); ga.put(0, "白虎"); ga.put(1, "青龙"); ga.put(2, "朱雀"); system.out.println(arrays.tostring(ga.getarray())); } }
泛型和反射
反射常用的泛型类:
class
constructor
通过反射创建对象,带泛型和不带泛型
class<cat> catclass1 = cat.class; try { constructor<cat> c1 = catclass1.getconstructor(); cat cat = c1.newinstance(); } catch (exception e) { e.printstacktrace(); } class catclass2 = cat.class; try { constructor c2 = catclass2.getconstructor(); object cat2 = c2.newinstance(); } catch (exception e) { e.printstacktrace(); }
以上就是深入了解java泛型的详细内容,更多关于java泛型的资料请关注其它相关文章!
上一篇: Python基础-day01-8
下一篇: Unity Shader实现翻书效果