1. ArrayList
- ArrayList集合的特点: 长度可以变化,只能存储引用数据类型。
ArrayList<Integer> res = new ArrayList<>(); //right
ArrayList<int> res1 = new ArrayList<int>(); //error:Type argument cannot be of primitive type
- ArrayList的remove方法有两种方式:remove(int index)和remove(Object o)。前者是按照指定下标删除元素,后者是按照指定值删除元素。如果使用的是自定义对象,务必要保证该对象已经正确地重写了equals()方法,否则可能会无法删除指定元素。
2. static 关键字
- 当 static 修饰成员变量时,该变量称为静态变量; static 修饰成员方法时,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法。但是不推荐这种方式去访问。因为静态变量或者静态方法直接通过类名访问即可,完全没有必要用对象去访问。
- 无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。
- static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。
- 无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。
3. Override注解
- @Override:重写注解。这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错。
4. 构造方法
-
构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
-
构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子)
class Student extends Person { private double score; public Student() { //super(); // 调用父类无参构造方法,默认就存在,可以不写,必须在第一行 System.out.println("子类无参"); } public Student(double score) { //super(); // 调用父类无参构造方法,默认就存在,可以不写,必须在第一行 this.score = score; System.out.println("子类有参"); } }
4. super 和 this 关键字
- super代表的是父类对象的引用,this代表的是当前对象的引用。
this.成员变量 --> 本类的 super.成员变量 --> 父类的 this.成员方法名() --> 本类的 super.成员方法名() --> 父类的 super(...) -- 调用父类的构造方法,根据参数匹配确认 this(...) -- 调用本类的其他构造方法,根据参数匹配确认
- 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
- java支持在构造函数中调用其他的构造函数,通过this(…)实现。this(…)可以调用本类中的其他构造方法,根据参数去确定调用哪个构造方法。
- super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
Student(String name) { super(name); this(name, "man"); //error:Call to 'this()' must be first statement in constructor body }
- 静态方法中不能使用super和this关键字。因为静态方法是存储在静态区内的,静态区会随着类加载器一起加载到内存当中,这时候,只是加载到内存当中,但是并没有真正的去运行,此时也就没有产生实例化的对象。而this是表示当前对象的。super表示父类的对象,这时候,连实例化的对象都没有产生,所以this和super也就不存在。
5. 多态的定义和前提
- 多态: 是指同一行为,具有多个不同表现形式。
- 前提:
- 有继承或者实现关系
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
- 多态的运行特点
- 调用成员变量时:编译看左边,运行看左边
- 调用成员方法时:编译看左边,运行看右边
Fu f = new Zi(); //编译看左边的父类中有没有name这个属性,没有就报错 //在实际运行的时候,把父类name属性的值打印出来 System.out.println(f.name); //编译看左边的父类中有没有show这个方法,没有就报错 //在实际运行的时候,运行的是子类中的show方法 f.show();
- 多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
6. 多态的转型
- 多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
- 向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。如:父类类型 变量名 = new 子类类型();
- 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。如:子类类型 变量名 = (子类类型) 父类变量名;
7. 导包
- 什么时候需要导包?
情况一:在使用Java中提供的非核心包中的类时
情况二:使用自己写的其他包中的类时 - 什么时候不需要导包?
情况一:在使用Java核心包(java.lang)中的类时
情况二:在使用自己写的同一个包中的类时
8. 访问修饰符
- Java提供了四种访问权限,分别是public、protected、private、默认(不加权限修饰符,就是默认权限)。使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限。
- 访问修饰符修饰类
-
除内部类之外,只能使用public和(default)来进行修饰类。
- public修饰: 用这个修饰符修饰的类可以被所有其它类进行访问,如果不同包只需要使用import导包语句将这个类导入就可以使用。
- default修饰: 不使用修饰符的类只能被本包中的其它类进行访问,不能被不同包中的类访问。
protected、private不能进行对类的修饰,在idea里面使用编译会报错。(如:Modifier ‘protected’ not allowed her)
protected: 这个修饰符不能修饰类,原因就是这会破坏Java封装的原则。
private: 这个修饰符修饰类的时候会让整个类变得没有意义,因为这不能被其它任何类所访问。 -
对于内部类,可以被所有修饰符修饰,其作用范围和成员属性、成员方法一致。具体规则如下:
-
public | protected | 默认 | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中的类 | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
9. final 关键字
- Java提供了final 关键字,表示修饰的内容不可变。可以用于修饰类、方法和变量。具体含义如下:
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,有且仅能被赋值一次。
- final成员变量的初始化时机
- 显式初始化(在定义成员变量的时候立马赋值)(常用);
- 构造方法初始化(在构造方法中赋值一次)(不常用);
- 被final修饰的常量名称,一般都有书写规范,所有字母都大写。
10. 抽象类
- 抽象方法:没有方法体的方法
定义格式:修饰符 abstract 返回值类型 方法名 (参数列表);
具体示例:public abstract void run();
- 抽象类:用abstract修饰符修饰的类
定义格式:修饰符 abstract class 类名字 {
}
具体示例:public abstract class Animal {
public abstract void run();
}
- 抽象类的细节
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
- 抽象类存在的意义是为了被子类继承。
理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
11. 接口
- 接口中,只包含抽象方法和常量。
- 接口中的抽象方法默认会自动加上public abstract修饰,无需自己手写!! 按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。
- 在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
public interface InterF { // 抽象方法! // public abstract void run(); void run(); // public abstract String getName(); String getName(); // public abstract int add(int a , int b); int add(int a , int b); // 它的最终写法是: // public static final int AGE = 12 ; int AGE = 12; //常量 String SCHOOL_NAME = "程序员"; }
- 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口声明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
- 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?
可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
12. 内部类
- 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。
- 什么时候使用内部类
一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用。
人里面有一颗心脏。
汽车内部有一个发动机。
为了实现更好的封装。 - 内部类的分类(按定义的位置来分)
- 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
外部类.内部类 变量 = new 外部类().new 内部类();
Outer.Inner oi = new Outer().new Inner(); - 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
外部类.内部类 变量 = new 外部类.内部类构造器;
Outer01.Inner01 in = new Outer01.Inner01(“张三”); - 局部内部类,类定义在方法内
class 外部类名 { 数据类型 变量名; 修饰符 返回值类型 方法名(参数列表) { // … class 内部类 { // 成员变量 // 成员方法 } } }
- 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。
- 匿名内部类必须继承一个父类或者实现一个父接口。
- 匿名内部类格式
new 父类名或者接口名(){ // 方法重写 @Override public void method() { // 执行语句 }
- 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 匿名内部类的使用方式
- 以实现接口为例
public interface Swim { public abstract void swimming(); } public class Demo07 { public static void main(String[] args) { // 使用匿名内部类 new Swim() { @Override public void swimming() { System.out.println("自由泳..."); } }.swimming(); //接口 变量 = new 实现类(); Swim s2 = new Swim() { @Override public void swimming() { System.out.println("蛙泳..."); } }; //多态,走子类的重写方法 s2.swimming(); } }
- 以实现接口为例
- 成员内部类的细节
- 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。
- 内部类访问外部类对象的格式是:外部类名.this。
public class Test { public static void main(String[] args) { Outer.inner oi = new Outer().new inner(); oi.method(); } } class Outer { // 外部类 private int a = 30; // 在成员位置定义一个类 class inner { private int a = 20; public void method() { int a = 10; /*请在?地方填上相应代码,以达到输出的内容*/ System.out.println(???); // 10 答案:a System.out.println(???); // 20 答案:this.a System.out.println(???); // 30 答案:Outer.this.a } } }
13. 匿名内部类
-
特点
- 定义一个没有名字的内部类
- 这个类实现了父类,或者父类接口
- 匿名内部类会创建这个没有名字的类的对象
-
使用场景
- 通常在方法的形式参数是接口或者抽象类时,将匿名内部类作为参数传递。代码如下:
// 定义一个方法,模拟请一些人去游泳 public static void goSwimming(Swim s) { s.swimming(); } interface Swim { public abstract void swimming(); } public class Demo07 { public static void main(String[] args) { // 普通方式传入对象 // 创建实现类对象 Student s = new Student(); goSwimming(s); // 匿名内部类使用场景:作为方法参数传递 Swim s3 = new Swim() { @Override public void swimming() { System.out.println("蝶泳..."); } }; // 传入匿名内部类 goSwimming(s3); // 完美方案: 一步到位 goSwimming(new Swim() { public void swimming() { System.out.println("大学生, 蛙泳..."); } }); goSwimming(new Swim() { public void swimming() { System.out.println("小学生, 自由泳..."); } }); } }
- 通常在方法的形式参数是接口或者抽象类时,将匿名内部类作为参数传递。代码如下:
14. Object 类
- Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类。、
- Object类中的常见方法
public String toString() //返回该对象的字符串表示形式(可以看做是对象的内存地址值) public boolean equals(Object obj) //比较两个对象地址值是否相等;true表示相同,false表示不相同 protected Object clone() //对象克隆(默认浅拷贝)
- toString()
- 在通过输出语句输出一个对象时,默认调用的就是toString()方法;
// 创建学生对象
Student s1 = new Student(“itheima” , “14”) ;
// 直接输出对象s1
System.out.println(s1); - 输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
- toString方法的作用:以良好的格式,更方便的展示对象中的属性值;
- 一般情况下Jdk所提供的类都会重写Object类中的toString方法;
- 在通过输出语句输出一个对象时,默认调用的就是toString()方法;
- equals()
- 默认情况下equals方法比较的是对象的地址值;
- 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法;
- clone()
- 把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制。
- Object类默认的是浅拷贝;(即:基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值)
- 如果类中包含引用数据类型,那么一般需要进行对象clone时,都要重写Obejct类中的clone()方式,实现为深拷贝(即:基本数据类型拷贝过来,字符串复用,引用数据类型重新创建新的);
15. Objects 类
- Objects类是被final修饰的,因此该类不能被继承。
- Objects类提供了一些对象常见操作的方法。比如判断对象是否相等,判断对象是否为null等等。
- Objects类中的常见方法:
public static String toString(Object o) // 获取对象的字符串表现形式 public static boolean equals(Object a, Object b) // 比较两个对象是否相等 public static boolean isNull(Object obj) // 判断对象是否为null public static boolean nonNull(Object obj) // 判断对象是否不为null
16. 装箱与拆箱
- 基本类型对应的包装类
基本类型 | 对应的包装类(位于java.lang包中) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
- 基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:
- 装箱:从基本类型转换为对应的包装类对象。
- 拆箱:从包装类对象转换为对应的基本类型。
//装箱:基本数值---->包装对象 Integer i = new Integer(4);//使用构造函数函数 Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法 //拆箱:包装对象---->基本数值 int num = i.intValue();
- 自动装箱与自动拆箱
- 由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4); i = i + 5; //等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5; //加法运算完成后,再次装箱,把基本数值转成对象。
- 由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
- 经验建议
- 获取Integer对象的时候不要自己new,而是采取直接赋值或者静态方法valueOf的方式。因为在实际开发中,-128~127之间的数据,用的比较多。如果每次使用都是new对象,那么太浪费内存了。所以,提前把这个范围之内的每一个数据都创建好对象,如果要用到了不会创建新的,而是返回已经创建好的对象。
17. 集合
1) 数组和集合的区别
- 数组可以存基本数据类型和引用数据类型;
- 集合只能存引用数据类型,如果要存基本数据类型, 需要存对应的包装类;
2)集合类体系结构
- 集合有两个基本接口:Collection和Map
3) Collection集合(接口)
- Collection集合是单例集合的顶层接口, 它表示一组对象, 这些对象也称为Collection的元素;JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现。
- Collection集合的遍历(3种)
-
迭代器遍历
- 1.迭代器遍历完毕,指针不会复位
- 2.当循环结束之后,迭代器的指针已经指向了最后没有元素的位置,如果继续使用next() 报错NoSuchElementException
- 3.循环中只能用一次next方法
- 4.迭代器遍历时,不能用集合的方法进行增加或者删除;
//创建集合对象 Collection<String> c = new ArrayList<>(); //添加元素 c.add("hello"); c.add("world"); //Iterator<E> iterator():通过集合的iterator()方法获取集合中元素的迭代器 Iterator<String> it = c.iterator(); //循环元素的判断和获取 while (it.hasNext()) { System.out.println(it.next()); }
-
增强for
- 增强for 内部原理是一个Iterator迭代器,只有实现Iterable接口的类才可以使用迭代器和增强for。
for(集合/数组中元素的数据类型 变量名 : 集合/数组名) { // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可 }
-
lambda表达式
/*lambda遍历(推算)*/ //lambda: ()-> {} //coll.forEach((String s)->{System.out.println(s);}); coll.forEach(s->System.out.println(s));
-
4) List集合(接口)
-
概述
- List 集合是一个存取有序的集合,用户可以精确控制列表中每个元素的插入位置, 用户可以通过整数索引访问元素, 并搜索列表中的元素。与Set集合不同, 列表通常允许重复的元素。
-
特点
- 存取有序
- 允许重复
- 有索引 (下标)
-
List集合的遍历方式
-
迭代器
Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); //coll.remove(s); //error: ConcurrentModificationException; 不能使用集合的删除方式在遍历中删除元素;(普通迭代器没有添加方法add) it.remove(); //right, 使用迭代器方法删除元素 }
-
列表迭代器
//1. 列表迭代器创建时,里面的指针默认也是指向0索引的元素; //2. 列表迭代器比普通迭代器额外多了一个方法:在遍历的过程中,可以添加元素(迭代器遍历过程中,不能用集合的方法添加元素,可以用列表迭代器的方法添加元素) //3. List接口才有listIterator方法,Collector接口没有该方法 //4. 列表迭代器还可以通过it2.hasPrevious()和 it2.previous()该两个方法进行反向遍历,但由于迭代器默认创建时在0索引位置,需要先next移动迭代器到末尾,才可以反向使用,局限性较大。 ListIterator it2 = coll.listIterator(); while (it2.hasNext()) { System.out.println(it2.next()); it2.add("add test"); //right, 可以用列表迭代器的方法添加元素 } it2.hasPrevious(); it2.previous();
-
增强for
//快速生成增强for代码块快捷方式: 变量名 + '.' + for for (String s : list) { System.out.println(s); }
-
匿名内部类
//底层原理:forEach底层会进行一次遍历集合,依次得到每一个元素,然后得到的每一个元素传递给accept方法 list.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });
-
lambda
/*lambda遍历(推算)*/ //lambda: ()-> {} //list.forEach((String s)->{System.out.println(s);}); list.forEach(s->System.out.println(s));
-
普通for循环
for (int i = 0; i < list.size(); i++) { //i:依次表示集合中的每一个索引 String s = list.get(i); System.out.println(s); }
-
-
总结:
- 1.遍历过程中,当需要删除元素时,使用普通迭代器;
- 2.遍历过程中,当需要增加元素时,使用列表迭代器;(普通迭代器不能增加元素)
- 3.如果仅仅是遍历,使用增加for或者lambda;
- 4.如果想遍历的过程中,操作索引,使用普通for;
- 5.相较于List接口,Collection接口不支持列表迭代器遍历;同时也没有索引,因此不支持普通for循环遍历;
5) Set 集合(接口)
- 不可以存储重复元素
- 没有索引, 不能使用普通for循环遍历
- Set 集合的遍历方式
- 迭代器遍历
- 增强for遍历
- lambda遍历
5.1) TreeSet 集合(具体实现)
- TreeSet 是Set接口的具体实现;
- 不可以存储重复元素
- 没有索引
- 可以将元素按照规则进行排序
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
- 自然排序 Comparable 和 比较器排序 Comparator
- 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
- 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
- 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序。
- 两种方式中关于返回值的规则
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
/*1.自然排序*/
public class Student implements Comparable<Student>{
....
@Override
public int compareTo(Student o) {
//按照对象的年龄进行排序
//主要判断条件: 按照年龄从小到大排序
int result = this.age - o.age;
//次要判断条件: 年龄相同时,按照姓名的字母顺序排序
result = result == 0 ? this.name.compareTo(o.getName()) : result;
return result;
}
}
//创建集合对象(自然排序)
TreeSet<Student> ts = new TreeSet<>();
/*2.比较器排序*/
public class Teacher {
...
}
//创建集合对象
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
//主要条件
int result = o1.getAge() - o2.getAge();
//次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});
5.2) HashSet 集合(具体实现)
- HashSet 是Set接口的具体实现;
- 不可以存储重复元素;
- 没有索引,不能使用普通for循环遍历;
- 底层数据结构是哈希表;
- 节点个数少于等于8个(数组 + 链表实现哈希表)
- 节点个数多于8个(数组 + 红黑树实现哈希表)
- 以上是JDK1.8以后的哈希表实现方式
- HashSet 集合存储自定义类型元素, 要想实现元素的唯一, 要求必须重写hashCode方法和equals方法。
6) Map集合(接口)
- 概述
- interface Map<K,V> K:键的类型;V:值的类型
- 特点
- 双列集合, 一个键对应一个值
- 键不可以重复, 值可以重复。
- Map集合的遍历
//方法一:获取所有key的集合,通过遍历key的集合,获取到每一个key, 再根据key获取对应值 for (String key: map.keySet()) { System.out.println(key + ": " + map.get(key)); } //方法二:获取所有键值对对象的集合,遍历键值对对象的集合,得到每一个键值对对象, 再根据键值对对象获取键和值 Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); //键值对对象集合 for (Map.Entry entry: entrySet) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
6.1) HashMap集合 (具体实现)
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法, 才能包装保证键的唯一, 否则默认会使用默认的hashCode和equals进行判断。
6.2) TreeMap集合(具体实现)
- TreeMap底层是红黑树结构
- 依赖自然排序或者比较器排序, 对键进行排序
- 如果键存储的是自定义对象, 需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则。
- 自然排序 Comparable 和 比较器排序 Comparator
-
自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
-
比较器排序: 创建TreeMap对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序。
//自然排序 public class Student implements Comparable<Student> { ... } Map<Student, String> studentMap = new TreeMap<>(); //比较器排序 Map<Student, String> stdMap = new TreeMap<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getAge() - o2.getAge(); } });
-
6.3) 区别
- 相同点:
- 均是Map接口的具体实现,集成了Map集合的基本特点;
- 均是键不可以重复, 值可以重复;
- 不同点:
- 底层存储结构不。HashMap底层是哈希表结构,TreeMap底层是红黑树结构;
- 判断重复的依据不同。在HashMap中判断key是否重复的依据是根据hash值和equals比较,但是在TreeMap中,判断key是否重复的依据是根据 comparaTo 是否为0,如果为0,TreeMap 就认为key是重复的。