Java知识速记-高级

Java知识速记-高级

1.枚举

介绍

枚举类型是jdk1.5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。

使用

  • 常量
public enum Color {
       RED, GREEN, BLUE; //分号可以不加
}
  • 添加构造器
    public enum ColorConstruct {
        RED(1), GREEN(2), BLANK(3), YELLO(4);  //有方法必须加分号
        private int index;
        private ColorConstruct(int index) {
            this.index = index;
        }
        public int getIndex() {
            return index;
        }
    }
  • switch
    @Test
    public void test9() {
        ColorConstruct construct = ColorConstruct.RED;
        switch (construct){
            case RED:
                System.out.println("red");
                break; 
            case GREEN:
                System.out.println("green");
                break;
        }
    }


  • 常用方法
方法名称作用
values()以数组形式返回枚举类型的所有成员
valueOf()将普通字符串转换为枚举实例
compareTo()比较两个枚举成员在定义时的顺序
ordinal()获取枚举成员的索引位置

测试

    @Test
    public void test9() {
        System.out.println(ColorConstruct.values().length); //4
        System.out.println(ColorConstruct.values()[0].getIndex()); //1
        System.out.println(ColorConstruct.RED.toString()); //RED
        System.out.println(ColorConstruct.RED); //RED
        System.out.println(ColorConstruct.valueOf("RED")); //RED
        System.out.println(ColorConstruct.valueOf("RED") == ColorConstruct.RED); //true
        System.out.println(ColorConstruct.RED.ordinal()); //0
        System.out.println(ColorConstruct.RED.compareTo(ColorConstruct.YELLO)); //-3
    }
}

结果

4
1
RED
RED
RED
true
0
-3

2. String类

2.1 java.lang.String类的使用

1.概述

String:字符串,使用一对" "引起来表示。

  1. String声明为final的,不可被继承
  2. String实现了Serializable接口:表示字符串是支持序列化的。
    实现了Comparable接口:表示String可以比较大小
  3. String内部定义了final char[] value用于存储字符串数据
  4. 通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。
  5. 字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。

2.String的不可变性

2.1 说明
  1. 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
  2. 当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
2.2 举例

在这里插入图片描述

3.String实例化的不同方式

3.1 方式说明

方式一:通过字面量定义的方式

  • 此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。

方式二:通过new + 构造器的方式

  • 此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
3.2 代码举例
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");

System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
3.3 面试题

String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”

4. 字符串拼接方式赋值的对比

4.1 说明
  1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  2. 只要其中一个是变量,结果就在堆中。
  3. 如果拼接的结果调用intern()方法,返回值就在常量池中
  4. equals在String中比较的是内容值

5.常用方法

int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1

替换:
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

6. String与其它结构的转换

6.1 与基本数据类型、包装类之间的转换

String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)

  • 代码
    @Test
    public void test1(){
        String str1 = "123";
//        int num = (int)str1;//错误的
        int num = Integer.parseInt(str1);

        String str2 = String.valueOf(num);//"123"
        String str3 = num + "";

        System.out.println(str1 == str3);
    }

6.2 与字符数组之间的转换
  • String --> char[]:调用String的toCharArray()
  • char[] --> String:调用String的构造器
  • 代码
@Test
public void test2(){
    String str1 = "abc123";  //题目: a21cb3

    char[] charArray = str1.toCharArray();
    for (int i = 0; i < charArray.length; i++) {
        System.out.println(charArray[i]);
    }

    char[] arr = new char[]{'h','e','l','l','o'};
    String str2 = new String(arr);
    System.out.println(str2);
}
6.3 与字节数组之间的转换

编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器

编码:字符串 -->字节 (看得懂 —>看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 —> 看得懂

说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。

@Test
public void test3() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
    System.out.println(Arrays.toString(bytes));

    byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
    System.out.println(Arrays.toString(gbks));

    System.out.println("******************");

    String str2 = new String(bytes);//使用默认的字符集,进行解码。
    System.out.println(str2);

    String str3 = new String(gbks);
    System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!


    String str4 = new String(gbks, "gbk");
    System.out.println(str4);//没出现乱码。原因:编码集和解码集一致!


}

2.2 String、StringBuffer、StringBuilder三者的对比

  1. String:不可变的字符序列;底层使用char[]存储
  2. StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
  3. StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

1.StringBuffer、StringBuilder中的常用方法

增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
*遍历:for() + charAt() / toString()

3. 注解

1. 注解的理解

① jdk 5.0 新增的功能

② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。

③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。

2. 注解的使用示例

@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已经过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告

3. 如何自定义注解

① 注解声明为:@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义注解没成员,表明是一个标识作用。
在这里插入图片描述
说明:

  • 如果注解有成员,在使用注解时,需要指明成员的值。
  • 自定义注解必须配上注解的信息处理流程(使用反射)才意义。
  • 自定义注解通过都会指明两个元注解:Retention、Target

4. 元注解 :对现有的注解进行解释说明的注解。

jdk 提供的4种元注解:

  • Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为\RUNTIME,只声明为RUNTIME生命周期的注解,才能通过反射获取。
  • Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
    出现的频率较低
  • Documented:表示所修饰的注解在被javadoc解析时,保留下来。
  • Inherited:被它修饰的 Annotation 将具继承性。
    —>类比:元数据的概念:String name = “Tom”;

5. 如何获取注解信息:通过发射来进行获取、调用。

前提: 要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME.

6.JDK8中注解的新特性:可重复注解、类型注解

6.1 可重复注解:

① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。

6.2 类型注解:

ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

示例

@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {

    String value() default "hello";
}

4 java集合

1.数组与集合

1.集合与数组存储数据概述:

  • 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
  • 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)

2.数组存储的特点:

  • 一旦初始化以后,其长度就确定了。
  • 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
    比如:String[] arr;int[] arr1;Object[] arr2;

3.数组存储的弊端:

  • 一旦初始化以后,其长度就不可修改。
  • 数组中提供的方法非常限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
  • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
  • 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

4.集合存储的优点:

  • 解决数组存储数据方面的弊端。

2 Collection接口

在这里插入图片描述

1. Collection接口常用方法

add(Object obj):添加元素
addAll(Collection coll),
size():查看集合的大小
isEmpty(),clear();
contains(Object obj):判断当前集合中是否包含obj,我们在判断时,会调用obj对象所在类的equals()方法.
containsAll(Collection coll):判断形参coll中的所有元素是否都存在于当前集合中。
remove(Object obj):移除元素
removeAll(Collection coll):移除两集合交集的部分
retainsAll(Collection coll):获取交集,并返回当前集合
equals(Object obj):要想返回True,需要当前集合和形参集合的元素都相同
hasCode():返回当前对象的哈希值
toArray():集合–>数组
数组 —>集合:List list = Arrays.asList(new String[]{“AA”, “BB”, “CC”});
iterator();

2.Collection集合与数组间的转换

  • 集合 —>数组:toArray() :注意此方法转换得到的类型是Object,可以加上参数转成需要的类型。
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
    System.out.println(arr[i]);
}
  • 数组 —>集合:调用Arrays类的静态方法asList(T … t)
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);

List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1

List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2

3.使用Collection集合存储对象

向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().方法
否则就是使用==进行判断

3 Iterator接口与foreach循环

1.遍历Collection的两种方式:

① 使用迭代器Iterator ② foreach循环(或增强for循环)

2.java.utils包下定义的迭代器接口:Iterator

2.1说明:

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。

2.2作用:遍历集合Collectiton元素
2.3如何获取实例:coll.iterator()返回一个迭代器实例
2.4遍历的代码实现:
Iterator iterator = coll.iterator();
//hasNext():判断是否还下一个元素
while(iterator.hasNext()){
    //next():①指针下移 ②将下移以后集合位置上的元素返回
    System.out.println(iterator.next());
}
2.5图示说明:

在这里插入图片描述

2.6 remove()的使用

//测试Iterator中的remove()
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
//内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()

    @Test
    public void test3(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person("Jerry",20));
        coll.add(new String("Tom"));
        coll.add(false);

        //删除集合中"Tom"
        Iterator iterator = coll.iterator();
        while (iterator.hasNext()){
//            iterator.remove();
            Object obj = iterator.next();
            if("Tom".equals(obj)){
                iterator.remove();
//                iterator.remove();
            }

        }
        //遍历集合
        iterator = coll.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

3. jdk5.0新特性–增强for循环:(foreach循环)

在这里插入图片描述
注意:
增强for:s是局部变量。原字符串并没有改变

    @Test
    public void test8(){

        String[] arr = new String[]{"MM","MM","MM"};
        //1.普通for赋值:栈内存指向堆内存,堆指向常量池中的字面量MM,赋值改变了指向
//        for (int i = 0; i < arr.length; i++) {
//            arr[i] = "GG";
//        }
        //2.增强for:s是局部变量。原字符串并没有改变
        for (String s : arr) {
            s="GG";
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

4. Collection子接口:List接口

4.1 概述
  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
  • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  • JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
4.2. 常用方法:(记住)
  • 增:add(Object obj)
  • 删:remove(int index) / remove(Object obj)
  • 改:set(int index, Object ele)
  • 查:get(int index)
  • 插:add(int index, Object ele)
  • 长度:size()
  • 遍历:① Iterator迭代器方式
    ② 增强for循环
    ③ 普通的循环
4.3 常用实现类
  • ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
  • LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
  • Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
4.4 Arraylist与 LinkedList异同点?
  • 是否保证线程安全: ArrayList和 LinkedList都是不同步的,也就是不保证线程安全;
  • 底层数据结构: Arraylist底层使用的是Object数组;LinkedList底层使用的是双向循环链表数据结构;
  • 插入和删除是否受元素位置的影响:ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。LinkedList采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n) 。
  • 是否支持快速随机访问:LinkedList不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  • 内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
4.5 ArrayList 与 Vector区别?
  • Vector是线程安全的,ArrayList不是线程安全的。其中,Vector在关键性的方法前面都加了synchronized关键字,来保证线程的安全性。如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍,这样ArrayList就有利于节约内存空间。
4.6 ArrayList的扩容机制?

ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。默认情况下,新的容量会是原容量的1.5倍。

4.7 Array和ArrayList有什么区别?
  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
  • Array大小是固定的,ArrayList的大小是动态变化的。
  • ArrayList提供了更多的方法和特性,比如: addAll(),removeAll(),iterator()等等。

5. Collection子接口:Set接口

5.1 概述

  • Set接口是Collection的子接口,set接口没有提供额外的方法
  • Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
  • Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法
  • 存储的数据特点:无序的、不可重复的元素

以HashSet为例说明:

  1. 无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。(存储的顺序是随机的)
  2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。(重写equals和hashcode方法)

5.2 常用方法

Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。
add(Object obj):添加元素
addAll(Collection coll),
size():查看集合的大小
isEmpty(),clear();
contains(Object obj):判断当前集合中是否包含obj,我们在判断时,会调用obj对象所在类的equals()方法.
containsAll(Collection coll):判断形参coll中的所有元素是否都存在于当前集合中。
remove(Object obj):移除元素
removeAll(Collection coll):移除两集合交集的部分
retainsAll(Collection coll):获取交集,并返回当前集合
equals(Object obj):要想返回True,需要当前集合和形参集合的元素都相同
hasCode():返回当前对象的哈希值
toArray():集合–>数组
数组 —>集合:List list = Arrays.asList(new String[]{“AA”, “BB”, “CC”});
iterator();

5.3 常用实现类

  • HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
  • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
  • TreeSet:可以照添加对象的指定属性,进行排序。

5.4 hashset的底层

HashSet的实现: HashSet的底层其实就是HashMap,只不过我们HashSet是实现了Set接口并且把数据作为K值,而V值一直使用一个相同的虚值来保存。如源码所示:

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public boolean add(E e) {
        return map.put(e, PRESENT)==null; //PRESENT 为虚值
    }

5.5 存储对象所在类的要求

HashSet/LinkedHashSet

  • 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
  • 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
  • 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
public class person {
    Integer id;
    String name;

    public person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        person person = (person) o;
        return Objects.equals(id, person.id) && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

    @Test
    public void test5(){
        HashSet set = new HashSet();
        person p1 = new person(1001, "AA");
        person p2 = new person(1002, "BB");
        set.add(p1);
        set.add(p2);
        System.out.println(set);//[person{id=1002, name='BB'}, person{id=1001, name='AA'}]
        p1.name = "CC";
        set.remove(p1);
        //先找CC哈希值,然后找对应数组位置,此时CC的哈希值和AA的哈希值不同,则没有删除CC,只是删除了
        //CC哈希值对应的空数组位置
        System.out.println(set);//[person{id=1002, name='BB'}, person{id=1001, name='CC'}]
        set.add(new person(1001,"CC"));
        System.out.println(set);//[person{id=1002, name='BB'}, person{id=1001, name='CC'}, person{id=1001, name='CC'}]
        set.add(new person(1001,"AA"));
        System.out.println(set);//[person{id=1002, name='BB'}, person{id=1001, name='CC'}, person{id=1001, name='CC'}, person{id=1001, name='AA'}]
    }

5.6 TreeSet的使用

  1. 向TreeSet中添加的数据,要求是相同类的对象
  2. 两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)

6 Map

  • Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
    • HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
      • LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
        原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
        对于频繁的遍历操作,此类执行效率高于HashMap。
    • TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序底层使用红黑树。
    • Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
      • Properties:常用来处理配置文件。key和value都是String类型

6.1 存储结构的理解

  • Map中的key:无序的、不可重复的,使用Set存储所的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
  • Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals()
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的entry:无序的、不可重复的,使用Set存储所的entry。

6.3 常用方法

  • 添加:put(Object key,Object value)
  • 删除:remove(Object key)
  • 修改:put(Object key,Object value)
  • 查询:get(Object key)
  • 长度:size()
  • 遍历:keySet() / values() / entrySet()
  • 是否包含key(boolean值): containsKey()
  • 是否包含value(boolean值):containsValue()
  • 判空:isEmpty()
  • 遍历所有的key集:keyset()
  • 遍历所有的value集:values()
  • 遍历所有的key-value:entrySet()
  • entrySet集合中的元素都是entry(Map.Entry或者Map.Entry<String, Integer>)

6.4 HashMap的底层数据结构是什么?

在JDK1.7和JDK1.8中有所差别:

  • 在JDK1.7中,由"数组+链表"组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。
  • 在JDK1.8中,由“数组+链表+红黑树"组成。当链表过长,则会严重影响HashMap 的性能,红黑树搜索时间复杂度是O(logn),而链表是糟糕的O(n)。因此,JDK1.8对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换: 当链表超过8且数据总量超过64才会转红黑树
  • 将链表转换成红黑树前会判断,如果当前数组的长度小于64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。
    在这里插入图片描述

6.5 解决hash冲突的办法有哪些?HashMap用的哪种?

解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。HashMap中采用的是链地址法。

6.6 为什么在解决hash 冲突的时候,不直接用红黑树?而选择先用链表,再转红黑树?

  • 因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于8个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于8个的时候,红黑树搜索时间复杂度是O(logn),而链表是O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。
  • 因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的

6.7 HashMap底层典型属性的属性的说明

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
    • 默认的loadFactor是0.75,0.75是对空间和时间效率的一个平衡选择
  • threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
  • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

6.8 HashMap的put方法流程

以JDK1.8为例,简要流程如下:

  1. 首先根据key的值计算hash值,找到该元素在数组中存储的下标;
  2. 如果数组是空的,则调用resize进行初始化;
  3. 如果没有哈希冲突直接放在对应的数组下标里;
  4. 如果冲突了,且 key已经存在,就覆盖掉value;
  5. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;
  6. 如果冲突后是链表,判断该链表是否大于8,如果大于8并且数组容量小于64,就进行扩容;如果链表节点大于8并且数组的容量大于64,则将这个结构转换为红黑树;否则,链表插入键值对,若key存在,就覆盖掉value。

6.9 一般用什么作为HashMap的key?

  • 一般用Integer、String这种不可变类当HashMap当 key,而且String最为常用。
  • 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就是HashMap 中的键往往都使用字符串的原因。
  • 因为获取对象的时候要用到equals()和 hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的重写了hashCode()以及 equals()方法。

6.10 HashMap为什么线程不安全?

  • 多线程下扩容死循环。JDK1.7中的 HashMap使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。
  • 多线程的put可能导致元素的丢失。多线程同时执行put操作,如果计算出来的索引位置是相同的,那会造成前一个key被后一个 key覆盖,从而导致元素的丢失。此问题在JDK1.7和JDK 1.8中都存在。
  • put和get并发时,可能导致get为null。线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。此问题在JDK1.7和JDK1.8中都存在。

队列

Queue是一个先进先出(FIFO)的队列。
PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。
因此,放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。

7. Collections工具类

1 作用

操作Set,List和Map等集合的工具类

2. 常用方法

  • reverse(List):反转 List 中元素的顺序
  • shuffle(List):对 List 集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
  • sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
  • swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
  • Object min(Collection)
  • Object min(Collection,Comparator)
  • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
  • void copy(List dest,List src):将src中的内容复制到dest中
  • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所旧值

8. 泛型

8.1 简介

  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。
  • 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection< E >,List< E >,ArrayList< E > 这个就是类型参数,即泛型。

8.2 集合中使用泛型

在这里插入图片描述
在这里插入图片描述
集合中使用泛型总结:

  • ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
  • ② 在实例化集合类时,可以指明具体的泛型类型
  • ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
  • 比如:add(E e) —>实例化以后:add(Integer e)
  • ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
  • ⑤ 如果实例化时,没指明泛型的类型。默认类型为java.lang.Object类型。

8.3 自定义泛型类、泛型接口、泛型方法

  • 泛型类
public class Order<T> {

    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型

    T orderT;

    public Order(){
        //编译不通过
//        T[] arr = new T[10];
        //编译通过
        T[] arr = (T[]) new Object[10];
    }

    public Order(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 void show(T orderT){
//        System.out.println(orderT);
//    }

    public void show(){
        //编译不通过
//        try{
//
//
//        }catch(T t){
//
//        }

    }

  • 泛型接口
public interface Order<T> {

    //如下的个方法都不是泛型方法
    public T getOrderT(){

    }

  • 泛型方法
//返回 map 中存放的所有 T 对象
public List<T> list(){
    ArrayList<T> list = new ArrayList<>();
    Collection<T> values = map.values();
    for (T t : values){
        list.add(t);
    }
    return list;

    //泛型方法
    //举例:获取表中一共有多少条记录?获取最大的员工入职时间?
    public <E> E getValue(){

        return null;
    }

}

8.4 泛型在继承上的体现

  • 虽然类A是类B的父类,但是G< A > 和G< B >二者不具备子父类关系,二者是并列关系。
  • 补充:类A是类B的父类,A< G > 是 B< G > 的父类

8.5 通配符

1.通配符的使用
  • 通配符的使用, 通配符:?

  • 类A是类B的父类,G< A >和G< B >是没关系的,二者共同的父类是:G<?>

2. 有限制条件的通配符的使用
  • ? extends A:
    G<? extends A> 可以作为G< A >和G< B >的父类,其中B是A的子类

  • ? super A:
    G<? super A> 可以作为G< A >和G< B >的父类,其中B是A的父类

● 通配符指定上限
上限extends: 使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
● 通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值