JavaSE 进阶-javase进阶(一)https://developer.aliyun.com/article/1469541
泛型
什么是泛型:
泛型就相当于标签
形式:<>
jdk1.5之后,用泛型来解决元素类型不确定的数据保存操作,
例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
没有泛型的集合
public static void main(String[] args) { ArrayList al = new ArrayList(); al.add(98); al.add(18); al.add(38); al.add(88); al.add("丽丽"); for (Object o : al) { System.out.print(o+" "); } }
如果不使用泛型的话,有缺点:
一般我们在使用的时候基本上往集合里随意放值,因为底层是一个obj类型的数组,所以什么都能放,不方便管理。
在jdk1.5以后开始,使用泛型加入泛型的优点:在编译的时候就会对类型进行检查,不是泛型的就无法添加到这个集合
public static void main(String[] args) { ArrayList<Integer> al = new ArrayList(); al.add(98); al.add(18); al.add(38); al.add(88); for (Integer o : al) { System.out.print(o+" "); } }
总结:
- JDK1.5之后
- 泛型实际就是一个<>引起来的参数类型,这个参数类型具体在使用的时候才会确定类型
- 使用了泛型后,可以确定集合的类型,在编译的时候就可以检查出来
- 使用泛型可能觉得麻烦,实际上使用泛型才会简单,后续的便利操作会简单许多
- 泛型对应的类型都是引用类型不能是基本类型
泛型类和泛型接口
声明泛型类时传入类型实参
创建泛型类对象时,传入类型实参
类型实参为空时,默认为Object类型
继承泛型类:
1.泛型父类不传入类型实参,默认为Object类型
2.泛型父类传入类型实参,子类既可以是泛型类,也可以不是
3.泛型父类传入类型实参,则子类必须是泛型类,且子类的类型形参列表必须包含父类的类型形参列表
泛型类的定义和实例化,如果实例化的时候不明确指定泛型,那么默认为Object类型
package TestGeneric.GenericTest; public class test01<e> { /* * 这是一个普通类 * test01<a>就是一个泛型类 * <>里面就是一个参数类型,但是这个类型是什么?,不确定,相当于一个占位 * 但是现在确定的是这个类型一定是引用类型,而不是基本类型 * */ int age; String name; e sex; public void a(e n){ } public void b(e[] n){ } static class Test{ public static void main(String[] args) { test01 gt1 = new test01(); gt1.a("abc"); gt1.a(17); gt1.a(9.8); gt1.b(new String[]{"a","b","A"}); test01<String> gt2 = new test01<>(); gt2.sex ="男"; gt2.a("abc"); gt2.b(new String[]{"a","b","A"}); } } }
继承:父类指定泛型接口
当父类指定了泛型类型,子类就不许要再指定了,可以直接使用
static class SubGeneric extends test01<Integer>{ } static class Demo{ public static void main(String[] args) { SubGeneric sgt = new SubGeneric(); sgt.a(19); } }
继承:父类不指定
如果父类不指定泛型,那么子类在实例化的时候需要指定
static class SubGeneric2<e> extends test01<e>{ }
泛型类可以定义多个参数类型
泛型类的构造器写法:
不同泛型的引用类型不可以互相赋值
泛型如果不指定,就会被擦除,例子就是
泛型类中的静态方法不能使用类的泛型
不能直接使用泛型数组的创建,如果非要创建
泛型方法
- 什么是泛型方法,不是带泛型的方法就是泛型方法,泛型方法有要求:这个方法的泛型参数类型要和当前类的泛型方法的泛型无关
- 换个角度:泛型方法对应的那个泛型参数类型和当前所在的这个类,是否为泛型类,泛型是啥,无关
- 泛型方法定义的时候,前面要加上t,原因如果不加的话,会把t当作一种数据类型,然而代码中没有t类型那么就会报错
- t的类型实在调用方法的时候确定的
- 泛型方法是否是静态方法?可以是
代码类型:
public class test02<e> { public void a(e e){ } public static <T> void b(T t){ } static class Demo{ public static void main(String[] args) { test02<String> t2 = new test02(); t2.a("1"); t2.b("abc"); } }
泛型参数存在继承的情况
前面两个引用类型,都可以赋值,为什么list不行?
其实我们用的Arraylist,他的底层是一个Obj类型的数组,我们的泛型负责在编译的时候限制类型,
例子:两个类,a,b两类,a是b的父类,两者是继承关系,但是 G ,G两者不存在继承关系,应为他们都是引用Arraylist所以是同级关系
通配符
当我想要重载不同泛型的list时,常见的重载无法让我们完成需求,于是我们需要通配符
在没有通配符的情况下,下面a方法,相当于重复定义
通配符是什么?
发现:两个类,a,b两类,a是b的父类,两者是继承关系,但是 G ,G两者不存在继承关系,应为他们都是引用Arraylist所以是同级关系但是使用了通配符后,我们发现G变成了G ,G的父类
使用通配符
无边界通配符( ?),固定上边界通配符(?extends 上界类),固定下边界通配符(?super 下界类)
学习主要就是为了方便查看API文档
使用小细节
public void a(List<?> list){ // 遍历 for (Object o : list) { System.out.println(o); } // 数据的写入操作 // list.add("abc");--》出错,不能随意的写入 // 读取操作 Object s = list.get(0); }
泛型受限
并列关系,我们如何限制类型,
- 用extends的是本类和类所有的子类:定义上限
- 用super是本类和本类的父类,定义下限
List<Object> a = new ArrayList<>(); List<Person> b = new ArrayList<>(); List<Student> c = new ArrayList<>();
A,B,C三个泛型的类型不相同,Person是Student的父类
linkedlist
LinkedList 常用方法:
- 增加:addFirst(E e),addLast(E e),offer(E e),offerFirst(E e),offerLast(E e)
- 删除:poll(),pollFirst(),pollLast(),removeFirst(),removeLast()
- 修改:set(int index, E element)
- 查看:element(),getFirst(),getLast(),indexOf(Object o),lastIndexOf(Object o),peek(),peekFirst(),peekLast()
- 判断:
示例代码:
// 现有一个linkedlist集合对象 public static void main(String[] args) { LinkedList<String> list = new LinkedList(); // 能否重复添加数据 list.add("aaaa"); list.add("bbbb"); list.add("aaaa"); list.add("cccc"); System.out.println(list); // 收尾添加 list.addFirst("ffff"); list.addLast("zzzz"); System.out.println(list); // 添加元素到尾端 list.offer("ll"); System.out.println(list); list.offerFirst("pp"); list.offerLast("rr"); // 删除首个元素 System.out.println(list); System.out.println("删除"+list.poll());//删除头上的元素,并且返回删除的元素 System.out.println("删除"+list.pollFirst()); System.out.println("删除"+list.pollLast()); System.out.println(list); //查找头尾元素 System.out.println(list.peek()); System.out.println(list.peekLast()); // list.clear(); // 方法的举例 System.out.println(list); System.out.println(list.pollLast());//如果没有返回null,不影响程序执行 System.out.println(list.removeFirst());//Exception in thread "main" java.util.NoSuchElementException,会报错, // 遍历 System.out.println("----------"); // 1 for (int i=0; i<list.size();i++){ System.out.println(list.get(i)); } // 2 for (String s : list) { System.out.println(s); } // 3 Iterator it = list.iterator(); while (it.hasNext()){ System.out.println(it.next()); } // 可能源码看到,这个迭代器相对好,内存上的节省 for ( Iterator it1 = list.iterator();it1.hasNext();){ System.out.println(it1.next()); }
为什么有功能相同的方法:以
pollFirst(),pollLast(),
removeFirst(),removeLast()
为例:
removeFirst是早版本的
pollFirst是jdk1.6版本的
实测区别:
// 方法的举例 System.out.println(list); System.out.println(list.pollLast());//如果没有返回null,不影响程序执行 System.out.println(list.removeFirst());//Exception in thread "main" java.util.NoSuchElementException,会报错,
同样是空的集合,pollFirst删除第一个如果没有返回null,无报错,removeFirst会报错没有数据
相比之下1.6之后的方法提高了健壮性,其他类似方法与这一对一致,
遍历方式:
源码看到,这个迭代器相对好,内存上的节省,例子:
for ( Iterator it1 = list.iterator();it1.hasNext();){ System.out.println(it1.next()); }
linkedlist的原理
对比学习:
Arraylist数据结构: |
Linledlist数据结构: |
物理结构:紧密结构 |
物理结构:跳转结构 |
逻辑结构:线性表(数组) |
逻辑结构:线性表(链表) |
Linkedlist是双向链表:
简要底层原理图:
模拟一个linkedList
首先是我们的节点类
package linkedListPrc; import javax.xml.soap.Node; public class Test02 {//节点类 // 三个属性 // 上一个元素的地址 private Test02 pre; // 当前存放的元素 private Object obj; // 下一个元素的地址 private Test02 next; public Test02 getPre() { return pre; } public void setPre(Test02 pre) { this.pre = pre; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public Test02 getNext() { return next; } public void setNext(Test02 next) { this.next = next; } public Test02() { } }
之后就是我们的mylist源码
public class MyLinkedList { // 链中一个定有一个首节点 Test02 first; // 链中一个有一个尾节点 Test02 last; // 计数器 int count =0; // 提供一个构造器 public MyLinkedList(){ } // 元素添加方法 public void add(Object o){ if (first ==null){//证明添加的元素是第一个 // 那接下来我们需要吧添加的元素封装成一个node Test02 n = new Test02(); n.setPre(null); n.setObj(o); n.setNext(null); first = n; last = n; }else {//证明以及不是链中的第一个节点了 // 先把添加的元素封装成一个node对象 Test02 n = new Test02(); n.setPre(last); n.setObj(o); // 当前链中的最后一个节点的下一个元素要指向n last.setNext(n); // 将最后一个节点变成n last = n; } count++; } public int getSize(){ return count; } // 通过下标得到元素 public Object getelByindex(int index){ if (index>count){ System.out.println("这个索引上没有数据,检查插入的元素是否达到查找索引的数量"); } // 获取链表的头元素 Test02 n = first; for (int i = 0;i< index;i++){ // 一路next找到想要的元素 n = n.getNext(); } return n.getObj(); } }
linkdelist部分源码解读
public class LinkedList<E>{ //长度 transient int size = 0; //首位 transient Node<E> first; //尾位 transient Node<E> last; //封装节点对象方法 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } //首元素 transient Node<E> first; //尾元素 transient Node<E> last; //空构造器 public LinkedList() { } //添加元素操作 public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } void linkLast(E e) { //添加的元素e final Node<E> l = last; //将链表中的last节点给l,如果是第一个元素的话l为null final Node<E> newNode = new Node<>(l, e, null); //将元素封装为一个node的具体对象 last = newNode; //将链表的为节点指向新创建的对象 if (l == null)如果添加的是第一个节点 first = newNode;//将链表的first节点只想为新节点 else //如果添加的不是第一个节点 l.next = newNode;//将l的下一个指向新的节点 size++; //集合中元素数量加1 modCount++; } //查找方法 public E get(int index) { checkElementIndex(index); return node(index).item; } //二分查找的方式遍历链表 Node<E> node(int index) { if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } }
迭代器
迭代器的源码,以及两个经典方法
面试题:迭代器对应的关系
hashnext与next方法的具体实现
增强for循环起始底层也是用迭代器完成的
用debug查看增强for循环,会发现,执行的下一步还是会进到iterator里
listirerator
加入字符串,比如我们想想要在代码中遍历到cc后新增一个kk
List list = new ArrayList(); list.add("aa"); list.add("bb"); list.add("cc"); list.add("dd"); list.add("ee"); Iterator iterator = list.iterator(); while (iterator.hasNext()){ if ("cc".equals(iterator.next())){ list.add("kk"); } }
控制台发生并发修改异常
出错原因:
迭代器和lsit同时操作集合,
如何解决:事情让一个迭代器做,新迭代器:listiterator,迭代和添加操作都用listiterator来操作
ListIterator iterator = list.listIterator(); while (iterator.hasNext()){ if ("cc".equals(iterator.next())){ iterator.add("kk"); } }
set接口
特点:无序,唯一,这里的无序是相对list来说的,不是随机,
API:相对于list,没有索引的相关方法,
那么证明我们的遍历方法有:
(1)迭代器
(2)增强for循环
hashset实现类使用
无序,唯一,我们会发现重复存放的元素,输出的时候只会有一个,那如何验证呢,add()是一个bool返回值方法,所以我们输出add方法来看看去呗
我们可以发现,第一次添加hello,的返回结果时true成功的,第二次重复的时候就是false失败的,
public static void main(String[] args) { HashSet<String> hs = new HashSet<>(); System.out.println(hs.add("hello")); //true hs.add("hello"); hs.add("hi"); hs.add("html"); hs.add("apple"); hs.add("hello"); System.out.println(hs.add("hello"));//false System.out.println(hs); System.out.println(hs.size()); }
自定义引用类型的数据:
public static void main(String[] args) { HashSet<Student> hs = new HashSet<>(); hs.add(new Student("小丽",19)); hs.add(new Student("小hu",29)); hs.add(new Student("小Li",39)); hs.add(new Student("小tian",49)); hs.add(new Student("小丽",19)); System.out.println(hs.toString()); }
我自定义的类型不满足,唯一,无序的特点,为什么呢?
HashSet简要原理图
原因:我们的Student类没有重写hashCode和equals方法
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && name.equals(student.name); } @Override public int hashCode() { return Objects.hash(name, age); }
重写之后,就符合HashSet的无序,唯一的特性了
hashSet原理:
底层与hashmap十分相似,下图是属性和构造器的分析
collection总结图
Map接口
无序,唯一
HashMap
特点:无序。唯一,
特点是按照key进行总结的,因为底层key寻找哈希表的结构(数组+链表)
哈希表原理:如放入这个集合数据的对应的类,必须重写HashCode和equals这两个方法,否则结果就不符合唯一,无序的特点
- 添加:put(K key, V value), putAll(Map m)
- 修改:
- 删除:clear(),remove(Object key)
- 查看:entrySet(), keySet(),size(),values()
- 判断: containsKey(Object key), containsValue(Object value), equals(Object o), get(Object key),isEmpty()
public static void main(String[] args) { /* * 添加:put(K key, V value), putAll(Map<? extends K,? extends V> m) * 修改: * 删除:clear(),remove(Object key) * 查看:entrySet(), keySet(),size(),values() * 判断: containsKey(Object key), containsValue(Object value), equals(Object o), get(Object key),isEmpty() * */ // 思考为什么键值对,键的值是一样的,但是value缺是第一个的值 HashMap<String,Integer> map = new HashMap<>(); map.put("lili",123123); System.out.println(map.put("nana", 12345)); map.put("feifei",34567); System.out.println(map.put("nana", 123123)); // 清空方法 // map.clear(); // map.remove("feifei");按照key移除 System.out.println(map.size()); System.out.println(map); System.out.println(map.containsKey("lili")); System.out.println(map.containsValue(123123)); HashMap<String,Integer> map2 = new HashMap<>(); map.put("lili",123123); System.out.println(map.put("nana", 12345)); map.put("feifei",34567); System.out.println(map.put("nana", 123123)); System.out.println(map==map2); System.out.println(map.equals(map2));//底层重写了equals比较集合中的值是否一致 System.out.println("判断是否为空"+map.isEmpty()); System.out.println(map.get("nana")); System.out.println(map.keySet());//查看集合中所有的k System.out.println(map.values());//查看集合中所有的v //通过k来遍历出v Set<String> set = map.keySet(); for (String s : set) { System.out.print(map.get(s)+" "); } System.out.println(); //entrySet 得到的是一对数据 Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); for (Map.Entry<String, Integer> entry : entrySet) { System.out.println(entry); } }
HashMap和Hashtable
HashMap是1.2开始的,效率高,线程不安全 Key可以是空值,并且null值也遵照唯一特点
Hashtable是1.0开始的,效率低,效率安全,对于Hashtable的key不能为null值
如果我想按照加入顺序来输出,我们可以有一个LinkedHashMap
LinkedHashMap
特点,唯一,有序,按照输入顺序输出
小结
JavaSE 进阶-javase进阶(三)https://developer.aliyun.com/article/1469544