JavaSE 进阶-javase进阶(二)

简介: JavaSE 进阶-javase进阶

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+" ");
        }
    }

总结:

  1. JDK1.5之后
  2. 泛型实际就是一个<>引起来的参数类型,这个参数类型具体在使用的时候才会确定类型
  3. 使用了泛型后,可以确定集合的类型,在编译的时候就可以检查出来
  4. 使用泛型可能觉得麻烦,实际上使用泛型才会简单,后续的便利操作会简单许多
  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>{
            
    }

泛型类可以定义多个参数类型

泛型类的构造器写法:

不同泛型的引用类型不可以互相赋值

泛型如果不指定,就会被擦除,例子就是

泛型类中的静态方法不能使用类的泛型

不能直接使用泛型数组的创建,如果非要创建

泛型方法

  1. 什么是泛型方法,不是带泛型的方法就是泛型方法,泛型方法有要求:这个方法的泛型参数类型要和当前类的泛型方法的泛型无关
  2. 换个角度:泛型方法对应的那个泛型参数类型和当前所在的这个类,是否为泛型类,泛型是啥,无关
  3. 泛型方法定义的时候,前面要加上t,原因如果不加的话,会把t当作一种数据类型,然而代码中没有t类型那么就会报错
  4. t的类型实在调用方法的时候确定的
  5. 泛型方法是否是静态方法?可以是

代码类型:

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

目录
相关文章
|
开发框架 分布式计算 Java
【面试题精讲】JavaSe和JavaEE的区别
【面试题精讲】JavaSe和JavaEE的区别
|
Java 程序员 开发工具
黑马程序员Java零基础视频教程笔记-Java入门
黑马程序员Java零基础视频教程笔记-Java入门
75 0
|
3月前
|
Java
javaSE思维导图
这篇文章是关于Java SE基础的总结,包含了思维导图,以帮助读者更好地理解和记忆Java SE的知识点。
javaSE思维导图
|
3月前
|
Java Linux 编译器
JavaSE基础1
JavaSE基础
52 4
|
3月前
|
存储 Java
JavaSE基础2
JavaSE基础
40 1
|
6月前
|
Java 编译器 数据安全/隐私保护
一文让你深入了解JavaSE的知识点(上)
一文让你深入了解JavaSE的知识点
|
6月前
|
安全 Java 程序员
一文让你深入了解JavaSE的知识点(下)
一文让你深入了解JavaSE的知识点(下)
|
6月前
|
存储 人工智能 算法
JavaSE 进阶-javase进阶(一)
JavaSE 进阶-javase进阶
61 0
|
6月前
|
安全 算法 Java
JavaSE 进阶-javase进阶(三)
JavaSE 进阶-javase进阶
58 0
|
6月前
|
缓存 NoSQL Java
JavaSE面试题(一)
JavaSE面试题(一)
JavaSE面试题(一)