面向对象编程学习笔记

banner

前言

如题,这是一篇记录本人Java学习的博文。

说实话,本人一向对系统性学习程序语言抱有一定抵触。

究其原因的话,一方面来说,在没有具体想要实现的需求的情况下,学习开发、写码是一个很枯燥无趣的事;而另一方面,众所周知,学代码是怎么也绕不开做算法题目这一环节的,由于九年义务教育以及后续三年的高中生活实在是对本人精神造成了巨大伤害,导致本人对机械式的做题有着本能的厌烦,这些原因都导致了虽然本人对计算机抱有一定兴趣,但是一直也没有真的投入精力、也不想真的投入精力去深入研究程序语言细节。

那我现在又是为什么想不开,开始学开发呢?

其中也没什么故事可言,简而言之:生活所迫。

这年头万物转码,不学点程序实在是难混。

而要学语言,能选的其实有很多,但考虑到应用的广泛性,以及自己能承受的难度,本人最终还是选择了Java。一来各大公司都有Java的应用,二来它不需要考虑指针、内存的问题,语法也算是整洁,没有C语言那么难,应该还是hold的住的。

不多说了,开始刷题。

力扣人变身


Java 语言基础

这一部分,可能不会写太多,简单记录而已。毕竟主要知识点还是靠多敲代码多应用,就能记在脑中了,写下来用处不会很大。下面的大部分都是网上复制来的,象征性放在这里,如果哪里有侵权,可以联系本人,会予以删除。

Java变量分类

  1. 按数据类型分类

    Java中数据类型分为两大类:基本数据类型(primitive type)和引用数据类型(reference type)。

    其中:

    基本数据类型有:数值型(byte, short, int, long, float, double)、字符型(char)和布尔型(boolean)。

    引用数据类型有:类(class)、接口(interface)和数组([])

    都是基础的知识,细节略了。

  2. 按声明位置分类

    如果按照声明位置分类,变量可分为:成员变量和局部变量。

    其中:

    成员变量有实例变量和类变量。

    局部变量有形参、方法局部变量和代码块局部变量

Java变量使用规则

变量必须先声明,后使用;

变量都定义在其作用域内。在作用域内,它是有效的,出了作用域,就失效了;

同一个作用域内,不可以声明两个同名的变量;

基本数据类型变量间运算规则

  1. 自动类型转换

byte 、char 、short –> int –> long –> float –> double

结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。
特别的:当byte、char、short三种类型的变量做运算时,结果为int型
说明:此时的容量大小指的是,表示数的范围的大和小。比如:float容量要大于long的容量

  1. 强制类型转换(只涉及7种基本数据类型):自动类型提升运算的逆运算。需要使用强转符:()

运算符

  • 算术运算符

    算术运算符:+ - + - * / % (前)++ (后)++ (前)-- (后)-- +

    典型代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    //除号:/
    int num1 = 12;
    int num2 = 5;
    int result1 = num1 / num2;
    System.out.println(result1);//2
    // %:取余运算
    //结果的符号与被模数的符号相同
    //开发中,经常使用%来判断能否被除尽的情况。
    int m1 = 12;
    int n1 = 5;
    System.out.println("m1 % n1 = " + m1 % n1);

    int m2 = -12;
    int n2 = 5;
    System.out.println("m2 % n2 = " + m2 % n2);

    int m3 = 12;
    int n3 = -5;
    System.out.println("m3 % n3 = " + m3 % n3);

    int m4 = -12;
    int n4 = -5;
    System.out.println("m4 % n4 = " + m4 % n4);
    //(前)++ :先自增1,后运算
    //(后)++ :先运算,后自增1
    int a1 = 10;
    int b1 = ++a1;
    System.out.println("a1 = " + a1 + ",b1 = " + b1);

    int a2 = 10;
    int b2 = a2++;
    System.out.println("a2 = " + a2 + ",b2 = " + b2);

    int a3 = 10;
    ++a3;//a3++;
    int b3 = a3;
    //(前)-- :先自减1,后运算
    //(后)-- :先运算,后自减1

    int a4 = 10;
    int b4 = a4--;//int b4 = --a4;
    System.out.println("a4 = " + a4 + ",b4 = " + b4);

    (前) + + :先自增1,后运算 (后)++ :先运算,后自增1

    (前) - - :先自减1,后运算 (后)– :先运算,后自减1

    连接符:+ :只能使用在String与其他数据类型变量之间使用。

  • 赋值运算符

    赋值运算符:= += -= *= /= %=

    典型代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    int i2,j2;
    //连续赋值
    i2 = j2 = 10;
    //***************
    int i3 = 10,j3 = 20;
    int num1 = 10;
    num1 += 2;//num1 = num1 + 2;
    System.out.println(num1);//12

    int num2 = 12;
    num2 %= 5;//num2 = num2 % 5;
    System.out.println(num2);

    short s1 = 10;
    //s1 = s1 + 2;//编译失败
    s1 += 2;//结论:不会改变变量本身的数据类型
    System.out.println(s1);
  • 逻辑运算符

    逻辑运算符:& && | || ! ^

    典型代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //区分& 与 &&
    //相同点1:& 与 && 的运算结果相同
    //相同点2:当符号左边是true时,二者都会执行符号右边的运算
    //不同点:当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。
    //开发中,推荐使用&&
    boolean b1 = true;
    b1 = false;
    int num1 = 10;
    if(b1 & (num1++ > 0)){
    System.out.println("我现在在北京");
    }else{
    System.out.println("我现在在南京");
    }

    System.out.println("num1 = " + num1);


    boolean b2 = true;
    b2 = false;
    int num2 = 10;
    if(b2 && (num2++ > 0)){
    System.out.println("我现在在北京");
    }else{
    System.out.println("我现在在南京");
    }

    System.out.println("num2 = " + num2);
  • 位运算符

    高级操作,玩不来,不学这个了

  • 三元运算符

    三元运算符:(条件表达式) ? 表达式1 : 表达式2

    典型代码:

    1
    2
    3
    4
    5
    6
    7

    // 获取两个整数的较大值

    int a = 3;
    int b = 6;
    int c = 10;
    int max = (a > b) ? a : b;

流程控制

  • if-else结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    结构一:
    if(条件表达式){
    执行表达式
    }

    结构二:二选一
    if(条件表达式){
    执行表达式1
    }else{
    执行表达式2
    }

    结构三:n选一
    if(条件表达式){
    执行表达式1
    }else if(条件表达式){
    执行表达式2
    }else if(条件表达式){
    执行表达式3
    }
    ...
    else{
    执行表达式n
    }
  • switch-case结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    switch(表达式){
    case 常量1:
    执行语句1;
    //break;
    case 常量2:
    执行语句2;
    //break;
    ...
    default:
    执行语句n;
    //break;
    }
  • 循环结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //for循环结构
    for(①;②;④){

    }
    //执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②

    //while循环结构

    while(②){
    ③;
    ④;
    }
    //执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②

    //do-while循环结构

    do{
    ③;
    ④;
    }while(②);
    //执行过程:① - ③ - ④ - ② - ③ - ④ - ... - ②

面向对象(Object Oriented)

此处开始,才是正式的面向对象的内容。

面型对象编程,是从英文Object Oriented Programming翻译而来,可以简称为OOP。

坦白来说,“面向对象”一词,无论是从英文层面还是中文层面来看,其具体含义都很不明晰。毕竟“对象”这一用词十分抽象,读了也不知所云。如今网上有很多解释面向对象的帖子,比较著名的有怎么洗衣服、冰箱装大象这种莫名其妙的例子,在本人看来都写的十分无趣且不明所以,当然本人也没什么能力写出简单易懂的描述性文字,在我看来,不去深究OOP的每一个词的含义,而是把它当成一个整体,通过不停的写面向对象类型的代码,反而更容易理解其含义。

实际上,面向过程的模式才是人类更易于理解、且更贴近本能思考的方法,而这也是编程最初的样子,然而,随着代码量的增加,面向对象这一模式由于其代码更容易复用,可以很好的解决代码“耦合”问题,同时还带来了其他好处,这一模式才逐渐开始替代原有的面向过程编程。也就是说,OOP确实不符合“正常”的人的思维模式,它是业界通过多年的发展而“进化”出来的,因此,本人认为在初学时完全没有深究其定义的需要,照着教程写就是了,只要在面试前背一背八股文混过面试就好。

类与对象(Class and Object)

支持面向对象编程语言通常利用继承其他类达到代码重用和可扩展性的特性。而类有两个主要的概念:

类(Class):定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。

对象(Object):是类的实例(Instance)。

封装(Encapsulation)

封装指的是将一个类中的方法或变量隐藏起来,只暴露我们想让其暴露的内容。至于为什么要这么做,原因有很多,这里不想赘述了,用最形而上的语言来说就是编程的发展将其引导到了这一方向,我们其实不用太在意原因,就这么用就好了。

在Java中,封装性是如何体现的?

简单来说,就是将类成员声明为private类型,然后用this关键字在内部进行调用。

1
2
3
4
5
6
7
private double radius;
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return radius;
}

继承(Inheritance)

这里就直接拿维基百科的定义了:

继承性(Inheritance)是指,在某种情况下,一个类会有“子类”。子类比原本的类(称为父类)要更加具体化。例如,“犬”这个类可能会有它的子类“中华田园犬”和“牧羊犬”。在这种情况下,“大黄”可能就是中华田园犬的一个实例。子类会继承父类的属性和行为,并且也可包含它们自己的。我们假设“犬”这个类有一个方法(行为)叫做“吠()”和一个属性叫做“毛色”。它的子类(前例中的中华田园犬和牧羊犬)会继承这些成员。这意味着程序员只需要将相同的代码写一次。

  • 子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。

  • 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。

多态(Polymorphism)

多态的定义很抽象,不多写了。主要功能就是可以在实例化一个子类对象时,将其赋至其父类变量。

1
2
//用父类Map将子类HashMap进行引用
Map<Integer> map = new HashMap<Integer>();

这么说也还是很抽象,实际上,多态只有在应用时才能感受到便捷之处,本人不才,没法用文字描述清楚。

Object类

对于Java来说,Object类是Java中所有类的父类,类似于二叉树中的根节点,定义了一些通用的方法。

  1. 如果我们没显式的声明一个类的父类的话,则此类继承于java.lang.Object类。

  2. 所的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。

  3. 意味着,所的java类具有java.lang.Object类声明的功能。

  4. java.lang.Object类中定义的一些方法。

对于Object类来说,需要注意的是其中的一些方法:

  • equals()方法

    1
    2
    3
    public boolean equals(Object obj) {
    return (this == obj);
    }
    1. Object类中默认定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。

    2. String、Date、File、包装类等都重写了Object类中的equals()方法。==重写equals()方法以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容”是否相同。

    3. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的”实体内容”是否相同。那么,我们就需要对Object类中的equals()进行重写。

    4. 重写的原则:比较两个对象的变量是否相同。

  • toString()方法

    1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()方法。

    2. Object类中toString()的定义:

    1
    2
    3
    public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    可以看出,默认我们打印一个对象时,输出的是内存地址,想要输出变量内容,则需重写该方法。

    1. String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回”实体内容”信息。

    2. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的”实体内容”。

    1
    2
    3
    4
    5
    //自动实现
    @Override
    public String toString() {
    return "Customer [name=" + name + ", age=" + age + "]";
    }

集合(Collection)

集合是为了解决数组存储数据方面的弊端而开发的接口。

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

  • Collection接口:单列数据,定义了存取一组对象的方法的集合

    • List:元素有序、可重复的集合

    • Set:元素无序、不可重复的集

  • Map接口:双列数据,保存具有映射关系“key-value对”的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
|----ArrayList:作为List接口的主要实现类,线程不安全的,效率高;底层采用Object[] elementData数组存储
|----LinkedList:对于频繁的插入删除操作,使用此类效率比ArrayList效率高底层采用双向链表存储
|----Vector:作为List的古老实现类,线程安全的,效率低;底层采用Object[]数组存储

|----Set接口:存储无序的、不可重复的数据 -->数学概念上的“集合”
|----HashSet:作为Set接口主要实现类;线程不安全;可以存null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加顺序遍历;对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以按照添加对象的指定属性,进行排序。


|----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类型

集合的子接口

List 接口

  1. 存储的数据特点:

    存储序有序的、可重复的数据。

    • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组。

    • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。

    • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

    • JDK AP中List接口的实现类常用的有:ArrayList、LinkedList和 Vector。

  2. 常用方法:

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

    • void add(int index, Object ele):在index位置插入ele元素

    • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来

    • Object get(int index):获取指定index位置的元素

    • int indexOf(Object obj):返回obj在集合中首次出现的位置

    • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置

    • Object remove(int index):移除指定index位置的元素,并返回此元素

    • Object set(int index, Object ele):设置指定index位置的元素为ele

    • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

  3. 常用实现类:

    1
    2
    3
    4
    5
    |----Collection接口:单列集合,用来存储一个一个的对象
    |----List接口:存储序的、可重复的数据。 -->“动态”数组,替换原的数组
    |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
    |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
    |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

    3.1 ArrayList

    • ArrayList是List接口的典型实现类、主要实现类

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

    • Array Listi的JDK 1.8之前与之后的实现区别?

      • JDK 1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组

      • JDK 1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组

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

3.2 LinkedList

- 对与对于频繁的插入和删除元素操作,建议使用LinkedList类,效率更高

- 新增方法:

    - `void addFirst(Object obj)`

    - `void addLast(Object obj)`

    - `Object getFirst()`

    - `Object getlast)()`

    - `Object removeFirst()`

    - `Object removeLast()`

- Linkedlist:双向链表,内部没有声明数组,而是定义了Node类型的frst和last,用于记录首末元素。同时,定义内部类Node,作为 Linkedlist中保存数据的基本结构。Node除了保存数据,还定义了两个变量:

    - prev:变量记录前一个元素的位置
    
    - next:变量记录下一个元素的位置

Set 接口

  1. 数据存储特点:

    用于存放无序的、不可重复的元素

    以HashSet为例说明:

    • 无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。

    • 不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。

  2. 元素添加过程

    源码比较复杂,这里先不写了。

  3. 常用方法

  4. 常用实现类

    1
    2
    3
    4
    5
    |----Collection接口:单列集合,用来存储一个一个的对象
    |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
    |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
    |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
    |----TreeSet:可以按照添加对象的指定属性,进行排序。

4.1 HashSet

- Hashset是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。

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

- HashSet具有以下特点:
    - 不能保证元素的排列顺序
    
    - HashSet不是线程安全的
    
    - 集合元素可以是null

- HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。

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

4.2 LinkedHashSet

- LinkedhashSet是HashSet的子类

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

- LinkedhashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。

- LinkedhashSet不允许集合元素重复。

4.3 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)`
    
  1. 储存对象所在类的要求

5.1 HashSet/LinkedHashSet:

- 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()

- 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

5.2 TreeSet:

- 自然排序中,比较两个对象是否相同的标准为:compareTo() 返回0。不再是 equals()

- 定制排序中,比较两个对象是否相同的标准为:compare() 返回0。不再是 equals()

Map 接口

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

  • Map中的key和value都可以是任何引用类型的数据

  • Map中的key用set来存放,不允许重复,即同一个Map对象所对应的类,须重写 hashCode() 和 equals() 方法

  • 常用 String类作为Map的“键”

  • key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value

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

1
2
3
4
5
6
7
8
9
10
11
12
13
|----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类型


HashMap的底层: 数组+链表 (JDK 7.0及之前)
数组+链表+红黑树 (JDK 8.0以后)
  1. 常见实现类

1.1 HashMap

- HashMap是Map接口使用频率最高的实现类。

- 允许使用null键和null值,与 HashSet一样,不保证映射的顺序。

- 所有的key构成的集合是set:无序的、不可重复的。所以,key所在的类要重写equals()和 hashCode()

- 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()

- 一个key-value构成一个entry

- 所有的entry构成的集合是Set:无序的、不可重复的

- HashMap判断两个key相等的标准是:两个key通过 equals() 方法返回true,hashCode值也相等

- HashMap判断两个value相等的标准是:两个value通过 equals() 方法返回true

1.2 LinkedHashMap

- LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap。

- 区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node。

- 与Linkedhash Set类似,LinkedHashMap可以维护Map的迭代顺序:迭代顺序与Key-value对的插入顺序一致。

1.3 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

1.4 Hashtable

- Hashtable是个古老的Map实现类,JDK1.0就提供了。不同于 HashMap,Hashtable是线程安全的

- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用

- 与HashMap.不同,Hashtable不允许使用null作为key和value

- 与HashMap一样,Hashtable也不能保证其中Key-value对的顺序

- Hashtable判断两个key相等、两个value相等的标准,与HashMap-致

1.5 Properties

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

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

- 存取数据时,建议使用 `setProperty(String key,String value)` 方法和 `getProperty(String key)` 方法
  1. 存储结构的理解
  • Map中的key:无序的、不可重复的,使用Set存储所的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)

  • Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals()

  • 一个键值对:key-value构成了一个Entry对象。

  • Map中的entry:无序的、不可重复的,使用Set存储所的entry

  1. 常用方法

3.1 添加、删除、修改操作:

- `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中的所有数据

3.2元素查询的操作:

- `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是否相等

3.3 元视图操作的方法:
- Set keySet():返回所有key构成的Set集合

- `Collection values()`:返回所有value构成的Collection集合

- `Set entrySet()`:返回所有key-value对构成的Set集合
  1. 底层内存实现

    长,略

Collections工具类

  1. 作用

    Collections是一个操作Set、Lit和Map等集合的工具类

    Collections中提供了一系列静态的方法对集合元素进行排序、査询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

  2. 常用方法

2.1排序操作

- `reverse(List)`:反转 List 中元素的顺序

- `shuffle(List)`:对 List 集合元素进行随机排序

- `sort(List)`:根据元素的自然顺序对指定 List 集合元素升序排序

- `sort(List,Comparator)`:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序

- `swap(List,int, int)`:将指定 list 集合中的 i 处元素和 j 处元素进行交换

2.2查找、替换
- 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 对象的所旧值

2.3 同步控制

Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

泛型(Generic Type)

  1. 概念

    所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返 回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、 创建对象时确定(即传入实际的类型参数,也称为类型实参)。

    从JDK 5.0以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。

    JDK 5.0改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

  2. 引入背景

    集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

  3. 应用案例

3.1 集合中未应用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test1(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全
// list.add("Tom");

for(Object score : list){
//问题二:强转时,可能出现ClassCastException
int stuScore = (Integer) score;

System.out.println(stuScore);

}

}

3.2 集合中使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//在集合中使用泛型,以ArrayList为例
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("AAA");
list.add("BBB");
list.add("FFF");
list.add("EEE");
list.add("CCC");
//遍历方式一:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("-------------");
//遍历方式二:
for (String str:
list) {
System.out.println(str);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
//在集合中使用泛型的情况:以HashMap为例
public void test2(){
Map<String,Integer> map = new HashMap<>();//jdk7新特性:类型推断
map.put("Tom",26);
map.put("Jarry",30);
map.put("Bruce",28);
map.put("Davie",60);
//嵌套循环
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();

while (iterator.hasNext()){
Map.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+"="+value);
}

}

反射(Reflection)

Class类

类的加载

反射的应用