个人学习 | Java基础知识查缺补漏(异常,String、集合框架、泛型)

本文主要是介绍个人学习 | Java基础知识查缺补漏(异常,String、集合框架、泛型),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

个人学习 | Java基础查缺补漏(异常,String、集合框架、泛型)

  • 写在前面
  • 异常处理
    • Throwable
    • Error 和 Exception
    • 编译时异常和运行时异常
    • 异常处理
      • try-catch-finally
      • throws + 异常类型
      • 手动抛出异常对象:throw
      • 异常小结
  • String、StringBuffer和StringBulide
    • String的不可变性
      • String的内存结构
    • 可变字符序列:StringBuffer、StringBuilder
  • 集合框架:Collection和Map
    • Collection接口
      • 添加方法
      • Collection子接口:List
        • 主要实现类:ArrayList类
        • 主要实现类:LinkedList类
        • 主要实现类:Vector类
      • Collection子接口:Set
        • HashSet类
        • LinkedHashSet类
        • TreeSet类
    • Map接口
      • Map中key-value特点
      • 常用方法
      • HashMap类
      • LinkedHashMap类
      • TreeMap类
      • Hashtable类
      • Properties类
    • 重写hashCode() 方法的基本原则
    • 重写equals()方法的基本原则
  • 泛型(Generic)
    • 核心思想
    • 自定义泛型结构
      • 自定义泛型方法
    • 通配符
      • 使用注意点
      • 有限制的通配符

写在前面

我最近重新开始回到Java开发的学习路径中,希望能够在秋招前尽快掌握基本的Java开发技术。这几天利用尚硅谷的Java教学视频从头过了一遍Java基础教程,在原有的基础上补充了很多知识点。

该文章内容为尚硅谷出品的Java教程中摘抄的内容,并非本人原创。

异常处理

Throwable

java.lang.Throwable类是Java程序执行过程中发生的异常事件对应的类的根父类。
Throwable中的常用方法:

  • public void printStackTrace():打印异常的详细信息。包含了异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得使用printStackTrace。
  • public String getMessage():获取发生异常的原因。

Error 和 Exception

Throwable可分为两类:Error和Exception。分别对应着java.lang.Errorjava.lang.Exception两个类。

Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。

  • 例如:StackOverflowError(栈内存溢出)和OutOfMemoryError(堆内存溢出,简称OOM)。

Exception:其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。例如:

  • 空指针访问
  • 试图读取不存在的文件
  • 网络连接中断
  • 数组角标越界

编译时异常和运行时异常

Java程序的执行分为编译时过程和运行时过程。有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。
在这里插入图片描述

因此,根据异常可能出现的阶段,可以将异常分为:

  • 编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx异常,并明确督促程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)。
  • 运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了xx异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。
    • java.lang.RuntimeException类及它的子类都是运行时异常。比如:ArrayIndexOutOfBoundsException数组下标越界异常,ClassCastException类型转换异常。
      异常类型

异常处理

try-catch-finally

整体执行过程

当某段代码可能发生异常,不管这个异常是编译时异常(受检异常)还是运行时异常(非受检异常),我们都可以使用try块将它括起来,并在try块下面编写catch分支尝试捕获对应的异常对象。

  • 如果在程序运行时,try块中的代码没有发生异常,那么catch所有的分支都不执行。

  • 如果在程序运行时,try块中的代码发生了异常,根据异常对象的类型,将从上到下选择第一个匹配的catch分支执行。此时try中发生异常的语句下面的代码将不执行,而整个try…catch之后的代码可以继续运行。

  • 如果在程序运行时,try块中的代码发生了异常,但是所有catch分支都无法匹配(捕获)这个异常,那么JVM将会终止当前方法的执行,并把异常对象“抛”给调用者。如果调用者不处理,程序就挂了。
    执行过程
    finally的使用
    在这里插入图片描述

  • 因为异常会引发程序跳转,从而会导致有些语句执行不到。而程序中有一些特定的代码无论异常是否发生,都需要执行。例如,数据库连接、输入流输出流、Socket连接、Lock锁的关闭等,这样的代码通常就会放到finally块中。所以,我们通常将一定要被执行的代码声明在finally中。

    • 唯一的例外,使用 System.exit(0) 来终止当前正在运行的 Java 虚拟机。
  • 不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。

  • finally语句和catch语句是可选的,但finally不能单独使用。

try{} finally{}

例子:确保资源关闭

import java.util.InputMismatchException;
import java.util.Scanner;public class TestFinally {public static void main(String[] args) {Scanner input = new Scanner(System.in);try {System.out.print("请输入第一个整数:");int a = input.nextInt();System.out.print("请输入第二个整数:");int b = input.nextInt();int result = a/b;System.out.println(a + "/" + b +"=" + result);} catch (InputMismatchException e) {System.out.println("数字格式不正确,请输入两个整数");}catch (ArithmeticException e){System.out.println("第二个整数不能为0");} finally {System.out.println("程序结束,释放资源");input.close();}}@Testpublic void test1(){FileInputStream fis = null;try{File file = new File("hello1.txt");fis = new FileInputStream(file);//FileNotFoundExceptionint b = fis.read();//IOExceptionwhile(b != -1){System.out.print((char)b);b = fis.read();//IOException}}catch(IOException e){e.printStackTrace();}finally{try {if(fis != null)fis.close();//IOException} catch (IOException e) {e.printStackTrace();}	}}
}

throws + 异常类型

如果在编写方法体的代码时,某句代码可能发生某个编译时异常,不处理编译不通过,但是在当前方法体中可能不适合处理无法给出合理的处理方式,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
抛出机制
具体方式:在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

手动抛出异常对象:throw

Java 中异常对象的生成有两种方式:

  • 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,那么针对当前代码,就会在后台自动创建一个对应异常类的实例对象并抛出。
  • 由开发人员手动创建new 异常类型([实参列表]);,如果创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,但是一旦throw抛出,就会对程序运行产生影响了。
    使用格式
throw new 异常类名(参数);

throw语句抛出的异常对象,和JVM自动创建和抛出的异常对象一样。

  • 如果是编译时异常类型的对象,同样需要使用throws或者try…catch处理,否则编译不通过。
  • 如果是运行时异常类型的对象,编译器不提示。
  • 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
throw new String("want to throw");

使用注意点:
无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被try…catch合理的处理,都会导致程序崩溃。

throw语句会导致程序执行流程被改变,throw语句是明确抛出一个异常对象,因此它下面的代码将不会执行

如果当前方法没有try…catch处理这个异常对象,throw语句就会代替return语句提前终止当前方法的执行,并返回一个异常对象给调用者。

异常小结

在这里插入图片描述

String、StringBuffer和StringBulide

String的不可变性

  • java.lang.String类代表字符串。
  • 字符串是常量,用双引号表示。它们的值在创建之后不能更改。
  • 字符串String类型本身是final声明的,意味着我们不能继承String。
  • String对象的字符内容是存储在一个字符数组value[]中的。String data = "abc"等效于char[] data = {'a', 'b', 'c'}

String

//jdk8中的String源码:
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[]; //String对象的字符内容是存储在此数组中/** Cache the hash code for the string */private int hash; // Default to 0
  • private意味着外面无法直接获取字符数组,而且String没有提供value的get和set方法。
  • final意味着字符数组的引用不可改变,而且String也没有提供方法来修改value数组某个元素值
  • 因此字符串的字符数组内容也不可变的,即String代表着不可变的字符序列。即,一旦对字符串进行修改,就会产生新对象
  • 在JDK9后,String底层使用byte[]数组存储。

String的内存结构

由于字符串对象设计为不可变,那么所以字符串有常量池来保存很多常量对象。JDK6中,字符串常量池在方法区。JDK7开始,就移到堆空间,直到目前JDK17版本。

内存结构分配的例子:
内存结构

字符串常量存储在字符串常量池,目的是共享。
字符串非常量对象存储在堆中。

可变字符序列:StringBuffer、StringBuilder

由于String对象是不可变对象,虽然可以共享常量对象,但是对于频繁字符串的修改和拼接操作,效率极低,空间消耗也比较高。因此,JDK又在java.lang包提供了可变字符序列StringBuffer和StringBuilder类型。

  • java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。比如:
//情况1
String s = new String("我喜欢学习");
StringBuffer buffer = new StringBuffer("我喜欢学习");
buffer.append("数学")

在这里插入图片描述

  • StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样。

区分String、StringBuffer和StringBuilder:

  • String:不可变的字符序列; 底层使用char[]数组存储(JDK8.0中)
  • StringBuffer:可变的字符序列;线程安全(方法有synchronized修饰),效率低;底层使用char[]数组存储(JDK8.0中)
  • StringBuilder:可变的字符序列; jdk1.5引入,线程不安全的,效率高;底层使用char[]数组存储(JDK8.0中)
  • StringBuilder、StringBuffer的API是完全一致的,并且很多方法与String相同。

集合框架:Collection和Map

Java 集合可分为 Collection 和 Map 两大体系:

  • Collection接口:用于存储一个一个的数据,也称单列数据集合
    • List子接口:用来存储有序的、可以重复的数据(主要用来替换数组,"动态"数组)
      • 实现类:ArrayList(主要实现类)、LinkedList、Vector
  • Set子接口:用来存储无序的、不可重复的数据(类似于高中讲的"集合")
    • 实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet
  • Map接口:用于存储具有映射关系“key-value对”的集合,即一对一对的数据,也称双列数据集合。(类似于高中的函数、映射。(x1,y1),(x2,y2) —> y = f(x) )
    • HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
    • 集合框架全图如下:
      集合框架
  • Collection接口继承树 Collection
  • Map接口继承树Map

Collection接口

  • JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)去实现。
  • Collection 接口是 List和Set接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 集合。方法如下:

添加方法

  • add(E obj):添加元素对象到当前集合中。
  • addAll(Collection other):添加other集合中的所有元素对象到当前集合中,即this = this ∪ other

注意:当other是一个Collection类型的变量时,coll.addAll(other)coll.add(other)的区别

在这里插入图片描述

Collection子接口:List

  • 鉴于Java中数组用来存储数据的局限性,我们通常使用java.util.List替代数组
  • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
  • JDK API中List接口的实现类常用的有:ArrayListLinkedListVector

List接口方法

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。

  • 插入元素
    • void add(int index, Object ele):在index位置插入ele元素
    • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
  • 获取元素
    • Object get(int index):获取指定index位置的元素
    • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
  • 获取元素索引
    • int indexOf(Object obj):返回obj在集合中首次出现的位置
    • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  • 删除和替换元素
    • Object remove(int index):移除指定index位置的元素,并返回此元素
    • Object set(int index, Object obj):设置指定index位置的元素为obj

注意:在JavaSE中List名称的类型有两个,一个是java.util.List集合接口,一个是java.awt.List图形界面的组件,别导错包了。

主要实现类:ArrayList类
  • ArrayList 是 List 接口的主要实现类

  • 本质上,ArrayList是对象引用的一个”变长”数组

  • Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合

主要实现类:LinkedList类
  • 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。这是由底层采用链表(双向链表)结构存储数据决定的。
  • 特有方法:
    • void addFirst(Object obj)
    • void addLast(Object obj)
    • Object getFirst()
    • Object getLast()
    • Object removeFirst()
    • Object removeLast()
主要实现类:Vector类
  • Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
  • 在各种List中,最好把ArrayList作为默认选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
  • 特有方法:
    • void addElement(Object obj)
    • void insertElementAt(Object obj,int index)
    • void setElementAt(Object obj,int index)
    • void removeElement(Object obj)
    • void removeAllElements()

Collection子接口:Set

  • Set接口是Collection的子接口,Set接口相较于Collection接口没有提供额外的方法
  • Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。
  • Set集合支持的遍历方式和Collection集合一样:foreach和Iterator。
  • Set的常用实现类有:HashSet、TreeSet、LinkedHashSet。
HashSet类
  • HashSet 是 Set 接口的主要实现类,大多数时候使用 Set 集合时都使用这个实现类。

  • HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存储、查找、删除性能。

  • HashSet 具有以下特点

    • 不能保证元素的排列顺序
    • HashSet线程不安全
    • 集合元素可以是 null
  • HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法得到的哈希值相等,并且两个对象的 equals() 方法返回值为true。

  • 对于存放在Set容器中的对象,对应的类一定要重写hashCode()和equals(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

  • HashSet集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。

HashSet中添加元素的过程:

  • 第1步:当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法得到该对象的 hashCode值,然后根据 hashCode值,通过某个散列函数决定该对象在 HashSet 底层数组中的存储位置。

  • 第2步:如果要在数组中存储的位置上没有元素,则直接添加成功。

  • 第3步:如果要在数组中存储的位置上有元素,则继续比较:

    • 如果两个元素的hashCode值不相等,则添加成功;
    • 如果两个元素的hashCode()值相等,则会继续调用equals()方法:
      • 如果equals()方法结果为false,则添加成功。
      • 如果equals()方法结果为true,则添加失败。

    第2步添加成功,元素会保存在底层数组中。
    第3步两种添加成功的操作,由于该底层数组的位置已经有元素了,则会通过链表的方式继续链接,存储。

LinkedHashSet类
  • LinkedHashSet 是 HashSet 的子类,不允许集合元素重复。

  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以添加顺序保存的。

  • LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
    底层结构

TreeSet类
  • TreeSet 是 SortedSet 接口的实现类,TreeSet 可以按照添加的元素的指定的属性的大小顺序进行遍历。
  • TreeSet底层使用红黑树结构存储数据
  • 新增的方法如下: (了解)
    • Comparator comparator()
    • Object first()
    • Object last()
    • Object lower(Object e)
    • Object higher(Object e)
    • SortedSet subSet(fromElement, toElement)
    • SortedSet headSet(toElement)
    • SortedSet tailSet(fromElement)
  • TreeSet特点:不允许重复、实现排序(自然排序或定制排序)
  • TreeSet 两种排序方法:自然排序定制排序。默认情况下,TreeSet 采用自然排序。
    • 自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
      • 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
      • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
    • 定制排序:如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
      • 利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
      • 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
  • 因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象
  • 对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 或compare(Object o1,Object o2)方法比较返回值。返回值为0,则认为两个对象相等。

Map接口

  • Map与Collection并列存在。用于保存具有映射关系的数据:key-value

    • Collection集合称为单列集合,元素是孤立存在的(理解为单身)。
    • Map集合称为双列集合,元素是成对存在的(理解为夫妻)。
  • Map 中的 key 和 value 都可以是任何引用类型的数据。但常用String类作为Map的“键”。

  • Map接口的常用实现类:HashMapLinkedHashMapTreeMap和``Properties。其中,HashMap是 Map 接口使用频率最高`的实现类。
    Map

Map中key-value特点

这里主要以HashMap为例说明。HashMap中存储的key、value的特点如下:

  • Map 中的 key用Set来存放不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
    在这里插入图片描述

  • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value,不同key对应的value可以重复。value所在的类要重写equals()方法。

  • key和value构成一个entry。所有的entry彼此之间是无序的不可重复的

常用方法

  • 添加、修改操作:
    • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
    • void putAll(Map m):将m中的所有key-value对存放到当前map中
  • 删除操作:
    • Object remove(Object key):移除指定key的key-value对,并返回value
    • void clear():清空当前map中的所有数据
  • 元素查询的操作:
    • Object get(Object key):获取指定key对应的value
    • boolean containsKey(Object key):是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • int size():返回map中key-value对的个数
    • boolean isEmpty():判断当前map是否为空
    • boolean equals(Object obj):判断当前map和参数对象obj是否相等
  • 元视图操作的方法:
    • Set keySet():返回所有key构成的Set集合
    • Collection values():返回所有value构成的Collection集合
    • Set entrySet():返回所有key-value对构成的Set集合

HashMap类

  • HashMap是 Map 接口使用频率最高的实现类。
  • HashMap是线程不安全的。允许添加 null 键和 null 值。
  • 存储数据采用的哈希表结构,底层使用一维数组+单向链表+红黑树进行key-value数据的存储。与HashSet一样,元素的存取顺序不能保证一致。
  • HashMap 判断两个key相等的标准是:两个 key 的hashCode值相等,通过 equals() 方法返回 true。
  • HashMap 判断两个value相等的标准是:两个 value 通过 equals() 方法返回 true。

LinkedHashMap类

  • LinkedHashMap 是 HashMap 的子类
  • 存储数据采用的哈希表结构+链表结构,在HashMap存储结构的基础上,使用了一对双向链表记录添加元素的先后顺序,可以保证遍历元素时,与添加的顺序一致。
  • 通过哈希表结构可以保证键的唯一、不重复,需要键所在类重写hashCode()方法、equals()方法。

TreeMap类

  • TreeMap存储 key-value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 key-value 对处于有序状态
  • TreeSet底层使用红黑树结构存储数据
  • TreeMap 的 Key 的排序:
    • 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
    • 定制排序:创建 TreeMap 时,构造器传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。

Hashtable类

  • Hashtable是Map接口的古老实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
  • Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构(数组+单向链表),查询速度快。
  • 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
  • Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
  • 与HashMap不同,Hashtable 不允许使用 null 作为 key 或 value。

Hashtable和HashMap的区别

  1. HashMap 底层是一个哈希表**(jdk7:数组+链表;jdk8:数组+链表+红黑树),是一个线程不安全的集合,执行效率高。Hashtable 底层也是一个哈希表(数组+链表)**,是一个线程安全的集合,执行效率低
  2. HashMap集合:可以存储null的键、null的值,Hashtable集合,不能存储null的键、null的值
  3. Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了。所以HashMap是Map的主要实现类,Hashtable是Map的古老实现类。
  4. Hashtable的子类Properties(配置文件)依然活跃在历史舞台。Properties集合是一个唯一和IO流相结合的集合。

Properties类

  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件

  • 由于属性文件里的 key、value 都是字符串类型,所以 Properties 中要求 key 和 value 都是字符串类型

  • 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

重写hashCode() 方法的基本原则

  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

注意:如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

重写equals()方法的基本原则

  • 重写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。

  • 推荐:开发中直接调用Eclipse/IDEA里的快捷键自动重写equals()和hashCode()方法即可。

  • 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

首先,选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
其次,31只占用5bits,相乘造成数据溢出的概率较小。
再次,31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
最后,31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

例子:自定义日期类

import java.util.Objects;public class MyDate {private int year;private int month;private int day;public MyDate(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;MyDate myDate = (MyDate) o;return year == myDate.year &&month == myDate.month &&day == myDate.day;}@Overridepublic int hashCode() {return Objects.hash(year, month, day);}@Overridepublic String toString() {return "MyDate{" +"year=" + year +", month=" + month +", day=" + day +'}';}
}

泛型(Generic)

核心思想

把一个集合中的内容限制为一个特定的数据类型
在这里插入图片描述
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常,即把不安全的因素在编译期间就排除了,而不是运行期。既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。

  • 在创建集合对象的时候,可以指明泛型的类型。

    具体格式为:List list = new ArrayList();

  • JDK7.0时,有新特性,可以简写为:

    List list = new ArrayList<>(); //类型推断

  • 泛型,也称为泛型参数,即参数的类型,只能使用引用数据类型进行赋值。(不能使用基本数据类型,可以使用包装类替换)

  • 集合声明时,声明泛型参数。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。如果没有指明的话,看做是Object类型。

自定义泛型结构

使用场景
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口。

  • 泛型要使用一路都用。要不用,一路都不要用。

注意

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
  2. JDK7.0 开始,泛型的简化操作:ArrayList flist = new ArrayList<>();
  3. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
  4. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
  • 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
  1. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型;
  2. 异常类不能是带泛型的。

自定义泛型方法

如果我们定义类、接口时没有使用<泛型参数>,但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>。

  • 泛型方法的格式:
[访问权限]  <泛型>  返回值类型  方法名([泛型标识 参数名称])  [抛出的异常]{}
  • 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系。
  • 泛型方法中的泛型参数在方法被调用时确定。
  • 泛型方法可以根据需要,声明为static的。

例子:

//编写一个泛型方法,实现任意引用类型数组指定位置元素交换。
public static <E> void method( E[] arr,int a,int b){E temp = arr[a];arr[a] = arr[b];arr[b] = temp;
}//编写一个泛型方法,接收一个任意引用类型的数组,并反转数组中的所有元素  
public static <E> void method1( E[] arr){for(int min = 0,max = arr.length - 1;min < max; min++,max--){E temp = arr[min];arr[min] = arr[max];arr[max] = temp;}
}

通配符

当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ?

比如:List<?>Map<?,?>List<?>List<String>List<Object>等各种泛型List的父类。

使用注意点

注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?

public static <?> void test(ArrayList<?> list){
}

注意点2:编译错误:不能用在泛型类的声明上

class GenericTypeClass<?>{
}

注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象

ArrayList<?> list2 = new ArrayList<?>();

有限制的通配符

  • <?> 允许所有泛型的引用调用

  • 通配符指定上限:<? extends 类/接口 >

    • 使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
  • 通配符指定下限:<? super 类/接口 >

    • 使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即>=
  • 说明:

    <? extends Number>     //(无穷小 , Number]
    //只允许泛型为Number及Number子类的引用调用<? super Number>      //[Number , 无穷大)
    //只允许泛型为Number及Number父类的引用调用<? extends Comparable>
    //只允许泛型为实现Comparable接口的实现类的引用调用
    

这篇关于个人学习 | Java基础知识查缺补漏(异常,String、集合框架、泛型)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/244831

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关