泛型
什么是泛型?
个人间接: 泛型是一种广泛通用的类型;
JDK1.5之前, 可以用Object类来表示一种广泛通用的类型;
packagealgorithm.list;publicclassMyLinkedList{privateListNode head;privateint size =0;//publicvoidinsertHead(int data){//插入链表的头部 data就是插入的数据ListNode newNode =newListNode(data);//如果原来就有数据呢?
newNode.next = head;//栈内存的引用
head = newNode;//插入O(1)}publicvoidinsertNth(int data,int position){//插入链表的中间 假设定义在第N个插入 O(n)if(position ==0){//这个表示插入在头部了insertHead(data);}else{ListNode cur = head;for(int i =1; i < position ; i++){
cur = cur.next;//一直往后遍历 p=p->next; ->是c++里面的往后找指针}ListNode newNode =newListNode(data);//
newNode.next = cur.next;//新加的点指向后面 保证不断链
cur.next = newNode;//把当前的点指向新加的点}}/*int a = 1;
int b = a;
int a = 2;*/publicvoiddeleteHead(){//O(1)
head = head.next;}publicvoiddeleteNth(int position){//O(n)if(position ==0){deleteHead();}else{ListNode cur = head;for(int i =1; i < position ; i ++){
cur = cur.next;}
cur.next = cur.next.next;//cur.next 表示的是删除的点,后一个next就是我们要指向的}}publicListNodefind(int data){//O(n)ListNode cur = head;while(cur !=null){if(cur.value == data)break;
cur = cur.next;}return cur;}publicvoidprint(){ListNode cur = head;while(cur !=null){System.out.print(cur.value +" ");
cur = cur.next;}System.out.println();}}classListNode{int value;//值ListNode next;//下一个的指针ListNode(int value){this.value = value;this.next =null;}}
像上面设计的链表中, 结点的值只能存放整形值, 如果需要表示通用类型, 则可以把结点存储的类型改为Object, 即如下所示:
classListNode{Object value;//值ListNode next;//下一个的指针ListNode(int value){this.value = value;this.next =null;}}
这样这个链表就可以存放整形,字符串等; 但是这样也存在一些问题,
publicstaticvoidmain(String[] args){MyLinkedList myList =newMyLinkedList();
myList.insertHead(5);
myList.insertHead("XXX");ListNode node =myList.get(5);Object val = node.getValue();int intVal =(int)val;//更直接的List testList =newArrayList();
testList.add("1");
testList.add("XXX")
testList.add(2);Object val2 = testList.get(2);int intVal2 =(int)val2;}
这里存在一个强转的过程,容器中存在多种形式的值, 整形,字符串等, 在转换的过程中可能会发生强转异常;
因此,在JDK1.5后, 因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型;
个人理解: Object 可以称为泛型,但是不规范;
泛型的规范使用-自定义泛型结构
/**
* 自定义泛型类
*
*/publicclassOrderTest<T>{String orderName;int orderId;//类的内部结构就可以使用类的泛型, 然后创建OrderTest时, 需要指定T orderT;publicOrderTest(){};publicOrderTest(String orderName,int orderId,T orderT){this.orderName = orderName;this.orderId = orderId;this.orderT = orderT;}//如下的三个方法都不是泛型方法publicTgetOrderT(){return orderT;}publicvoidsetOrderT(T orderT){this.orderT = orderT;}@OverridepublicStringtoString(){return"Order{"+"orderName='"+ orderName +'\''+", orderId="+ orderId +", orderT="+ orderT +'}';}//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。//换句话说,泛型方法所属的类是不是泛型类都没有关系。//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。publicstatic<E>List<E>copyFromArrayToList(E[] arr){ArrayList<E> list =newArrayList<>();for(E e : arr){
list.add(e);}return list;}}
- 泛型一般用于描述一种通用的算法、数据结构,对象类型其实并不关心。
- 可以用一个代号 T 来代表目标类型。 在Java语言里,泛型的规范写法:
publicclassSample<T>{}
- 在类的定义里,T代表一个通用的类型,把T称为类型参数
- 定义泛型类后,如果属性类型为T, 则泛型属性;
- 创建类实例时, 如果不指定参数类型, 则体现为Object,泛型属性值可以随便设置,如果制定了参数类型, 如String, 那么泛型属性值就只能为String,如果使用了其他类型,编译不通过;
publicstaticvoidmain(String[] args){OrderTest<Object> orderTest =newOrderTest<>();
orderTest.setName(1);
orderTest.setName("你好");OrderTest<String> test =newOrderTest<>();//报错//test.setName(1);
test.setName("nihao");}
再来看一下, 开头的链表代码的改动, 因为要结点的值设计为泛型, ListNode应该是一个泛型类;
即:
classListNode<T>{T value;//值ListNode next;//下一个的指针ListNode(T value){this.value = value;this.next =null;}}
再看看链表:
由于结点是一个泛型类,想要整个链表插入值,查询值等操作, 由于值是泛型,所以整个链表也是一个泛型类;
publicclassMyLinkedList<T>{privateListNode<T> head;privateint size =0;////结点值得类型是泛型,所以publicvoidinsertHead(T data){//插入链表的头部 data就是插入的数据ListNode newNode =newListNode(data);//如果原来就有数据呢?
newNode.next = head;//栈内存的引用
head = newNode;//插入O(1)}publicvoidinsertNth(T data,int position){//插入链表的中间 假设定义在第N个插入 O(n)if(position ==0){//这个表示插入在头部了insertHead(data);}else{ListNode cur = head;for(int i =1; i < position ; i++){
cur = cur.next;//一直往后遍历 p=p->next; ->是c++里面的往后找指针}ListNode newNode =newListNode(data);//
newNode.next = cur.next;//新加的点指向后面 保证不断链
cur.next = newNode;//把当前的点指向新加的点}}/*int a = 1;
int b = a;
int a = 2;*/publicvoiddeleteHead(){//O(1)
head = head.next;}publicvoiddeleteNth(int position){//O(n)if(position ==0){deleteHead();}else{ListNode cur = head;for(int i =1; i < position ; i ++){
cur = cur.next;}
cur.next = cur.next.next;//cur.next 表示的是删除的点,后一个next就是我们要指向的}}publicvoidfind(T data){//O(n)ListNode cur = head;while(cur !=null){if(cur.value == data)break;
cur = cur.next;}}publicvoidprint(){ListNode cur = head;while(cur !=null){System.out.print(cur.value +" ");
cur = cur.next;}System.out.println();}publicstaticvoidmain(String[] args){MyLinkedList<String> linkedList =newMyLinkedList<>();
linkedList.insertHead("name1");
linkedList.insertHead("name2");}}classListNode<T>{T value;//值ListNode next;//下一个的指针ListNode(T value){this.value = value;this.next =null;}}
除了T之外, 还有集中泛型符号:
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类) T代表在调用时的指定类型
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的java类型 一般用在通配
个人认为: E,T,K,V其实都可以用,只不过字面以上不符合我们使用场景,
例如向上面自定义的MyLinkedList的元素就设置为了T, 而不是E; 但是使用E更加规范,符合字面意思;
一些泛型使用:
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3> 上面自定义MyLinkedList可以改成下面这样, 相应的,在创建实例, 也需要一一指定每个泛型的具体类型;
publicclassMyLinkedList<T,K,V>{//...省略publicstaticvoidmain(String[] args){MyLinkedList<String,String,String> linkedList =newMyLinkedList<>();
linkedList.insertHead("name1");
linkedList.insertHead("name2");}}
- 泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass<>(){}
//错误例子如下:publicMyLinkedList<T,K,V>(){}publicMyLinkedList<>(){}//正确如下:publicMyLinkedList(){}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
//以下代码报错publicstaticvoidmain(String[] args){MyLinkedList<Integer> list =newMyLinkedList<>();MyLinkedList<String> list1 =newMyLinkedList<>();
list = list1;}//以下代码不报错publicstaticvoidmain(String[] args){MyLinkedList<Integer> list =newMyLinkedList<>();MyLinkedList<Integer> list1 =newMyLinkedList<>();
list = list1;}
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。 经验:泛型要使用一路都用。要不用,一路都不要用。
publicstaticvoidmain(String[] args){MyLinkedList<Object,Object,Object> linkedList1 =newMyLinkedList<>();
linkedList1.insertHead("1");
linkedList1.insertHead("nihao");
linkedList1.insertHead(1);}
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- dk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
publicstaticvoidmain(String[] args){//报错MyLinkedList<int> list =newMyLinkedList<>();//不报错MyLinkedList<Integer> list2 =newMyLinkedList<>();}
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
异常类不能是泛型的
继承中的泛型
存在几种情况:
- 子类不保留父类的泛型:按需实现 - 没有类型擦除- 具体类型
- 子类保留父类的泛型:泛型子类 - 全部保留- 部分保留
classFather<T1, T2>{}// 子类不保留父类的泛型// 1)没有类型擦除classSon1extendsFather{// 等价于class Son extends Father<Object,Object>{}}// 2)具体类型classSon2extendsFather<Integer,String>{}// 子类保留父类的泛型// 1)全部保留classSon3<T1, T2>extendsFather<T1, T2>{}// 2)部分保留classSon4<T2>extendsFather<Integer, T2>{}
泛型通配符
通配符:?
publicstaticvoidmain(String[] args){MyLinkedList<?> list =newMyLinkedList<>();}
- 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object
- 不可以写入list中的元素。 因为不知道c的元素类型,不能向其中添加对象
- 唯一的例外是null,它是所有类型的成员
- MyLinkedList<?> 是所有的泛型MyLinkedList的父类,
- 调用get时,返回的是一个Object;
通配符使用注意
- 不能用在定义泛型类上
//以下报错publicclassMyLinkedList<?>{}
- 不能用在创建对象上。
publicstaticvoidmain(String[] args){//以下编译通过,正确//JVM 在创建对象时可以认为是默认ObjectMyLinkedList<?> list =newMyLinkedList<>();//以下编译不通过, 错误;//JVM 创建对象 不知道具体是啥类型;MyLinkedList<?> linkedList =newMyLinkedList<?>();}
通配符指定上限上限
- 上限 extends:使用时指定的类型必须是继承某个类,或者实现某个接口
- 下限 下限super:使用时指定的类型不能小于操作的类
例子:
- <?extends Number> (无穷小, Number]: 只允许泛型为Number及Number子类的引用调用
- <? super Number>(无穷大,Number) : 只允许泛型为Number或者Number的父类引用调用;
JDK的一些泛型
ArrayList
Arrayu
publicclassArrayList<E>extendsAbstractList<E>implementsList<E>,RandomAccess,Cloneable,java.io.Serializable{transientObject[] elementData;// non-private to simplify nested class accesspublicvoidadd(E e){//....try{ArrayList.this.add(i, e);//....}catch(IndexOutOfBoundsException ex){thrownewConcurrentModificationException();}}publicvoidadd(int index,E element){
elementData[index]= element;
size++;}}
插入值的参数类型为E, 容器时一个Object数组;
publicclassHashMap<K,V>extendsAbstractMap<K,V>implementsMap<K,V>,Cloneable,Serializable{//存储数据的是一个Node数组, 也是泛型transientNode<K,V>[] table;//存储数据的结点staticclassNode<K,V>implementsMap.Entry<K,V>{finalint hash;finalK key;V value;Node<K,V> next;Node(int hash,K key,V value,Node<K,V> next){this.hash = hash;this.key = key;this.value = value;this.next = next;}publicfinalKgetKey(){return key;}publicfinalVgetValue(){return value;}publicfinalVsetValue(V newValue){V oldValue = value;
value = newValue;return oldValue;}}publicVput(K key,V value){returnputVal(hash(key), key, value,false,true);}}publicstaticvoidmain(String[] args){HashMap<String,Integer> hashMap =newHashMap<>();}
main方法创建了一个HashMap, K 为 String类型, V为Integer类型, 因此Node的K也为String, V也为Integer类型; 可以称为嵌套泛型;
HashSet
publicclassHashSet<E>extendsAbstractSet<E>implementsSet<E>,Cloneable,java.io.Serializable{staticfinallong serialVersionUID =-5024744406713321676L;privatetransientHashMap<E,Object> map;publicHashSet(){
map =newHashMap<>();}publicbooleanadd(E e){return map.put(e, PRESENT)==null;}}
HashSet内部实际是一个HashMap, 只不过定义属性的时候, HashMap<E,Object>, V为Object类型, Key的参数类型为 E ;
publicstaticvoidmain(String[] args){HashSet<String> objects =newHashSet<>();}
上面main方法, HashSet的E泛型 的具体类型为String, 属性map的HashMap<E, Object>的E 泛型的具体类型也成了String。
版权归原作者 ~玄霄- 所有, 如有侵权,请联系我们删除。