泛型
什么是泛型?
个人间接: 泛型是一种广泛通用的类型;
JDK1.5之前, 可以用Object类来表示一种广泛通用的类型;
package algorithm.list;
public class MyLinkedList {
private ListNode head;
private int size = 0; //
public void insertHead(int data){ //插入链表的头部 data就是插入的数据
ListNode newNode = new ListNode(data);
//如果原来就有数据呢?
newNode.next = head; //栈内存的引用
head = newNode;
//插入O(1)
}
public void insertNth(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 = new ListNode(data);
//
newNode.next = cur.next; //新加的点指向后面 保证不断链
cur.next = newNode; //把当前的点指向新加的点
}
}
/*int a = 1;
int b = a;
int a = 2;*/
public void deleteHead(){//O(1)
head = head.next;
}
public void deleteNth(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就是我们要指向的
}
}
public ListNode find(int data){//O(n)
ListNode cur = head;
while(cur != null){
if(cur.value == data) break;
cur = cur.next;
}
return cur;
}
public void print(){
ListNode cur = head;
while(cur != null){
System.out.print(cur.value + " ");
cur = cur.next;
}
System.out.println();
}
}
class ListNode{
int value; //值
ListNode next; //下一个的指针
ListNode(int value){
this.value = value;
this.next = null;
}
}
像上面设计的链表中, 结点的值只能存放整形值, 如果需要表示通用类型, 则可以把结点存储的类型改为Object, 即如下所示:
class ListNode{
Object value; //值
ListNode next; //下一个的指针
ListNode(int value){
this.value = value;
this.next = null;
}
}
这样这个链表就可以存放整形,字符串等; 但是这样也存在一些问题,
public static void main(String[] args) {
MyLinkedList myList = new MyLinkedList();
myList.insertHead(5);
myList.insertHead("XXX");
ListNode node =myList.get(5);
Object val = node.getValue();
int intVal = (int)val;
//更直接的
List testList = new ArrayList();
testList.add("1");
testList.add("XXX")
testList.add(2);
Object val2 = testList.get(2);
int intVal2 = (int)val2;
}
这里存在一个强转的过程,容器中存在多种形式的值, 整形,字符串等, 在转换的过程中可能会发生强转异常;
因此,在JDK1.5后, 因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型;
个人理解: Object 可以称为泛型,但是不规范;
泛型的规范使用-自定义泛型结构
/**
* 自定义泛型类
*
*/
public class OrderTest<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型, 然后创建OrderTest时, 需要指定
T orderT;
public OrderTest(){
};
public OrderTest(String orderName,int orderId,T orderT){
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
- 泛型一般用于描述一种通用的算法、数据结构,对象类型其实并不关心。
- 可以用一个代号 T 来代表目标类型。
在Java语言里,泛型的规范写法:
public class Sample <T>
{
}
- 在类的定义里,T代表一个通用的类型,把T称为类型参数
- 定义泛型类后,如果属性类型为T, 则泛型属性;
- 创建类实例时, 如果不指定参数类型, 则体现为Object,泛型属性值可以随便设置,如果制定了参数类型, 如String, 那么泛型属性值就只能为String,如果使用了其他类型,编译不通过;
public static void main(String[] args) {
OrderTest<Object> orderTest = new OrderTest<>();
orderTest.setName(1);
orderTest.setName("你好");
OrderTest<String> test = new OrderTest<>();
//报错
//test.setName(1);
test.setName("nihao");
}
再来看一下, 开头的链表代码的改动, 因为要结点的值设计为泛型, ListNode应该是一个泛型类;
即:
class ListNode<T> {
T value; //值
ListNode next; //下一个的指针
ListNode(T value){
this.value = value;
this.next = null;
}
}
再看看链表:
由于结点是一个泛型类,想要整个链表插入值,查询值等操作, 由于值是泛型,所以整个链表也是一个泛型类;
public class MyLinkedList<T> {
private ListNode<T> head;
private int size = 0; //
//结点值得类型是泛型,所以
public void insertHead(T data){ //插入链表的头部 data就是插入的数据
ListNode newNode = new ListNode(data);
//如果原来就有数据呢?
newNode.next = head; //栈内存的引用
head = newNode;
//插入O(1)
}
public void insertNth(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 = new ListNode(data);
//
newNode.next = cur.next; //新加的点指向后面 保证不断链
cur.next = newNode; //把当前的点指向新加的点
}
}
/*int a = 1;
int b = a;
int a = 2;*/
public void deleteHead(){//O(1)
head = head.next;
}
public void deleteNth(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就是我们要指向的
}
}
public void find(T data){//O(n)
ListNode cur = head;
while(cur != null){
if(cur.value == data) break;
cur = cur.next;
}
}
public void print(){
ListNode cur = head;
while(cur != null){
System.out.print(cur.value + " ");
cur = cur.next;
}
System.out.println();
}
public static void main(String[] args) {
MyLinkedList<String> linkedList = new MyLinkedList<>();
linkedList.insertHead("name1");
linkedList.insertHead("name2");
}
}
class ListNode<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可以改成下面这样, 相应的,在创建实例, 也需要一一指定每个泛型的具体类型;
public class MyLinkedList<T, K, V> {
//...省略
public static void main(String[] args) {
MyLinkedList<String, String, String> linkedList = new MyLinkedList<>();
linkedList.insertHead("name1");
linkedList.insertHead("name2");
}
}
- 泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass<>(){}
//错误例子如下:
public MyLinkedList<T,K,V>() {
}
public MyLinkedList<>() {
}
//正确如下:
public MyLinkedList() {
}
-
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
-
泛型不同的引用不能相互赋值。尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
//以下代码报错
public static void main(String[] args) {
MyLinkedList<Integer> list = new MyLinkedList<>();
MyLinkedList<String> list1 = new MyLinkedList<>();
list = list1;
}
//以下代码不报错
public static void main(String[] args) {
MyLinkedList<Integer> list = new MyLinkedList<>();
MyLinkedList<Integer> list1 = new MyLinkedList<>();
list = list1;
}
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
经验:泛型要使用一路都用。要不用,一路都不要用。
public static void main(String[] args) {
MyLinkedList<Object, Object, Object> linkedList1 = new MyLinkedList<>();
linkedList1.insertHead("1");
linkedList1.insertHead("nihao");
linkedList1.insertHead(1);
}
-
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
-
dk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
-
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
public static void main(String[] args) {
//报错
MyLinkedList<int> list = new MyLinkedList<>();
//不报错
MyLinkedList<Integer> list2 = new MyLinkedList<>();
}
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
异常类不能是泛型的
继承中的泛型
存在几种情况:
- 子类不保留父类的泛型:按需实现
- 没有类型擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
class Father<T1, T2> {}
// 子类不保留父类的泛型
// 1)没有类型擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{}
}
// 2)具体类型
class Son2 extends Father<Integer, String> {}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {}
泛型通配符
通配符:?
public static void main(String[] args) {
MyLinkedList<?> list = new MyLinkedList<>();
}
- 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object
- 不可以写入list中的元素。 因为不知道c的元素类型,不能向其中添加对象
- 唯一的例外是null,它是所有类型的成员
- MyLinkedList<?> 是所有的泛型MyLinkedList的父类,
- 调用get时,返回的是一个Object;
通配符使用注意
- 不能用在定义泛型类上
//以下报错
public class MyLinkedList<?> {
}
- 不能用在创建对象上。
public static void main(String[] args) {
//以下编译通过,正确
//JVM 在创建对象时可以认为是默认Object
MyLinkedList<?> list = new MyLinkedList<>();
//以下编译不通过, 错误;
//JVM 创建对象 不知道具体是啥类型;
MyLinkedList<?> linkedList = new MyLinkedList<?>();
}
通配符指定上限上限
- 上限
extends:使用时指定的类型必须是继承某个类,或者实现某个接口 - 下限
下限super:使用时指定的类型不能小于操作的类
例子:
- <?extends Number> (无穷小, Number]: 只允许泛型为Number及Number子类的引用调用
- <? super Number>(无穷大,Number) : 只允许泛型为Number或者Number的父类引用调用;
JDK的一些泛型
ArrayList
Arrayu
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
transient Object[] elementData; // non-private to simplify nested class access
public void add(E e) {
//....
try {
ArrayList.this.add(i, e);
//....
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(int index, E element) {
elementData[index] = element;
size++;
}
}
插入值的参数类型为E, 容器时一个Object数组;
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
//存储数据的是一个Node数组, 也是泛型
transient Node<K,V>[] table;
//存储数据的结点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K 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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
}
public static void main(String[] args) {
HashMap<String, Integer> hashMap = new HashMap<>();
}
main方法创建了一个HashMap, K 为 String类型, V为Integer类型, 因此Node的K也为String, V也为Integer类型; 可以称为嵌套泛型;
HashSet
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
HashSet内部实际是一个HashMap, 只不过定义属性的时候, HashMap<E,Object>, V为Object类型, Key的参数类型为 E ;
public static void main(String[] args) {
HashSet<String> objects = new HashSet<>();
}
上面main方法, HashSet的E泛型 的具体类型为String, 属性map的HashMap<E, Object>的E 泛型的具体类型也成了String。