大家好,欢迎来到IT知识分享网。
开发和学习中需要时刻和数据打交道,如何组织这些数据是我们编程中重要的内容。我们一般通过“容器”来容纳和管理数据。
事实上,数组就是一种容器,可以在其中放置对象或基本类型数据。数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?在写程序时是无法确定的。因此,在这里就不能使用数组。
基于数组并不能满足对于“管理和组织数据的需求”,所以需要一种更强大、更灵活、容量随时可扩的容器来装载对象。 这就是容器,也叫集合(Collection)。以下是容器的接口层次结构图:
为了更好的理解容器,我们需要先了解一下泛型。
一、泛型(Generics)
1.泛型简介
1.1 泛型的基本概念:
泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。 可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
参数化类型,通俗来讲就是:1. 把类型当作是参数一样传递。2. <数据类型> 只能是引用类型。
1.2泛型的好处
在不使用泛型的情况下,可以使用 Object 类型来实现任意的参数类型,但是在使用时需要强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
总结一下,就是使用泛型主要是两个好处:1.代码可读性更好【不用强制转换】。2.程序更加安全【只要编译时期没有警告,运行时期就不会出现 ClassCastException 异常】
1.3 类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。泛型主要用于编译阶段,编译后生成的字节码 class 文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。 类型参数在编译后会被替换成 Object,运行时虚拟机并不知道泛型。泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
2.泛型的使用
2.1 泛型的定义
泛型字符可以是任何标识符,一般采用这几个标记:E、T、K、V、N、?。
泛型标记 |
对应单词 |
说明 |
E |
Element |
在容器中使用,表示容器中的元素 |
T |
Type |
表示普通的Java类 |
K |
Key |
表示键,例如Map中的键Key |
V |
Value |
表示值 |
N |
Number |
表示数值类型 |
? |
表示不确定的Java类型 |
2.2 泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后添加一个或多个类型参数声明,如:<T>、<T,K,V>
语法结构:
public class 类名<泛型表示符号>{
}
示例:
package cn.pxy.generics; public class Generic<T> { private T flag; public void setFlag(T flag) { this.flag=flag; } public T getFlag() { return this.flag; } }
package cn.pxy.generics; public class Test { public static void main(String[] args) { Generic<String> generic=new Generic<>(); generic.setFlag("admin"); String flag=generic.getFlag(); System.out.println(flag); Generic<Integer> generic1=new Generic<>(); generic1.setFlag(100); Integer flag1=generic1.getFlag(); System.out.println(flag1); } }
运行结果:
2.3泛型接口
泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
语法结构
public interface 接口名<泛型表示符号> {
}
示例:
package cn.pxy.generics; //接口 public interface Igeneric<T> { T getName(T name); }
package cn.pxy.generics; //接口实现类 public class IgenericImpl implements Igeneric<String>{ public String getName (String name) { return name; } }
package cn.pxy.generics; //测试 public class Test2 { public static void main(String[] args) { IgenericImpl igeneric=new IgenericImpl(); String name=igeneric.getName("liqi"); System.out.println(name); Igeneric<String> igeneric1=new IgenericImpl(); String name1=igeneric1.getName("pxyxss"); System.out.println(name1); } }
运行结果:
2.4 泛型方法
泛型类中所定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
泛型方法是指将方法的参数类型定义成泛型,以便在调用时接收不同类型的参数。类型参数可以有多个,用逗号隔开,如:<K,V>。定义时,类型参数一般放到返回值前面。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型来。
2.4.1非静态方法:
语法结构:
public<泛型表示符号> void getName(泛型表示符号 name){ }
public<泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){ }
示例:
package cn.pxy.generics; public class MethodGeneric { public <T> void setName(T name) { System.out.println(name); } public <T> T getName(T name) { return name; } }
package cn.pxy.generics; public class Test3 { public static void main(String[] args) { MethodGeneric methodGeneric=new MethodGeneric(); methodGeneric.setName("胖咸鱼"); methodGeneric.setName(); MethodGeneric methodGeneric2=new MethodGeneric(); String name=methodGeneric2.getName("pxy"); Integer name1=methodGeneric2.getName(123); System.out.println(name1); System.out.println(name); } }
运行结果:
2.4.2静态方法
静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
语法结构:
public static <泛型表示符号> void setName(泛型表示符号 name){ }
public static <泛型表示符号> 泛型表示符号 setName(泛型表示符号 name){ }
示例:
package cn.pxy.generics; public class MethodGeneric { public static <T> void setFlag(T flag) { System.out.println(flag); } public static <T> T getFlag(T flag) { return flag; } }
package cn.pxy.generics; public class Test4 { public static void main(String[] args) { MethodGeneric.setFlag("胖咸鱼");; MethodGeneric.setFlag(); String flag=MethodGeneric.getFlag("pxy"); System.out.println(flag); Integer flag1=MethodGeneric.getFlag(); System.out.println(flag1); } }
运行结果:
2.4.3泛型方法与可变参数
在泛型方法中,泛型也可以定义可变参数类型。
语法结构:
public <泛型表示符号> void showMsg(泛型表示符号是。。。 agrs){ }
示例:
package cn.pxy.generics; public class MethodGeneric { public <T> void method(T... args) { for(T t:args) { System.out.println(t); } } }
package cn.pxy.generics; public class Test5 { public static void main(String[] args) { MethodGeneric methodGeneric=new MethodGeneric(); String[] arr=new String[] {"a","b","c"}; Integer[] arr2=new Integer[] {1,2,3}; methodGeneric.method(arr); methodGeneric.method(arr2); } }
运行结果:
2.5通配符和上下限定
2.5.1无界通配符
“?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用,可以解决当具体类型不确定的问题。
语法结构:
public void showFlag(Generic<?> generic){ }
示例:
package cn.pxy.generics; public class Generic<T> { private T flag; public void setFlag(T flag) { this.flag=flag; } public T getFlag() { return this.flag; } }
package cn.pxy.generics; public class ShowMsg { public void showFlag(Generic<?> generic) { System.out.println(generic.getFlag()); } }
package cn.pxy.generics; public class Test6 { public static void main(String[] args) { ShowMsg showMsg=new ShowMsg(); Generic<Integer> generic=new Generic<>(); generic.setFlag(20); showMsg.showFlag(generic); Generic<Number> generic1=new Generic<>(); generic1.setFlag(50); showMsg.showFlag(generic1); Generic<String> generic2=new Generic<>(); generic2.setFlag("胖咸鱼"); showMsg.showFlag(generic2); } }
运行结果:
2.5.2通配符的上限限定
上限限定表示通配符的类型是T类以及T类的子类或者T接口以及T接口的子接口。该方式同样适用于与泛型的上限限定。
语法结构:
public void showFlag(Generic<? extends Number>generic){ }
示例:
package cn.pxy.generics; public class ShowMsg { public void showFlag(Generic<? extends Number> generic) { System.out.println(generic.getFlag()); } }
package cn.pxy.generics; public class Test6 { public static void main(String[] args) { ShowMsg showMsg=new ShowMsg(); Generic<Integer> generic=new Generic<>(); generic.setFlag(20); showMsg.showFlag(generic); Generic<Number> generic1=new Generic<>(); generic1.setFlag(50); showMsg.showFlag(generic1); } }
运行结果:
2.5.3通配符的下限限定
下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的符接口。注:该方法不适用泛型类。
语法结构:
public void showFlag(Generic<? super Integer> generic){ }
示例:
package cn.pxy.generics; public class ShowMsg { public void showFlag(Generic<? super Integer> generic) { System.out.println(generic.getFlag()); } }
package cn.pxy.generics; public class Test6 { public static void main(String[] args) { ShowMsg showMsg=new ShowMsg(); Generic<Integer> generic=new Generic<>(); generic.setFlag(20); showMsg.showFlag(generic); Generic<Number> generic1=new Generic<>(); generic1.setFlag(50); showMsg.showFlag(generic1); } }
3.总结
泛型主要用于编译阶段,编译后生成的字节码 class 文件不包含泛型中的类型信息。 类型参数在编译后会被替换成 Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错误的:
1. 基本类型不能用于泛型。
Test<int> t;这样写法是错误,可以使用对应的包装类;Test<Integer> t ;
2. 不能通过类型参数创建对象。
T elm = new T(); 运行时类型参数 T 会被替换成 Object,无法创建 T 类型的对象,容易引起误解,所以在 Java 中不支持这种写法。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/123738.html