精华内容
下载资源
问答
  • java集合类说明

    2013-06-26 11:45:11
    1、Collection 和 Collections的区别。 Collection是集合类的上级接口,继承与他的接口主要有Set和List. Collections针对集合类的一个...集合框架(Collection Framework)泛指java.util若干个类和接口,如C...

    1Collection Collections的区别。

    Collection是集合类的上级接口,继承与他的接口主要有SetList.

    Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

    2Collection框架。

    集合框架(Collection Framework)泛指java.util包的若干个类和接口Collection,List,ArrayList,LinkedList,Vector(自动增长数组),HashSet,HashMap.

    集合框架中的类主要封装的是典型的数据结构,如动态数组,链表,堆栈,集合,哈希表等.

    集合框架类似编程中经常用到的工具类,使得编码只需要专注于业务层的实现,不需要从底层实现相关细节,如:“数据结构的封装典型算法的实现”。

    3Collection框架中实现比较要实现什么接口

    Comparable/comparator

    4List, Set, Map是否继承自Collection接口。

    ListSet是,Map不是

    5ListMapSet三个接口,存取元素时,各有什么特点。

    List以特定次序来持有元素,可有重复元素。

    Set有自己的内部排序,无法拥有重复元素。

    Map保存key-value键值对。

    6ListMap的区别

    List是存储单列数据的集合,Map是存储键值对双列数据的集合,List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,但值是可以重复的。

    7、说出ArrayList,Vector,LinkedList的存储性能和特性。

    ArrayListVector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

    LinkedList是线程不安全的LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用。

    8HashMapHashtable的区别。

    HashMapHashtable都实现了Map接口,主要区别在于HashMap的方法不是同步的,Hashtable是同步的,所以在多线程场合要手动同步HashMap,这个区别就像ArrayListVector一样。HashMap允许null(keyvalue都可以,只容许有一个null值的key,可以有多个null值的value)Hashtable不允许null(keyvalue都不可以)Hashtable有一个contains(Object value),功能和containsValue(Object value)功能一样

    总之有三条:

    (1)历史原因Hashtable继承自陈旧的Dictionary类,而HashMapJava1.2引进的Map接口的一个实现。

    (2)同步性: Hashtable是线程安全的,是同步的,而HashMap是线程不安全的,不是同步的,在多个线程访问Hashtable时,不需要为它的方法实现同步,而HashMap 就必须为之提供外同步(Map m = Collections.synchronizedMap(new HashMap()))

    (3)值:只有HashMap可以将空值作为一个表的条目的keyvalue

    9 ArrayListVector的区别

    这两个类都实现了List接口,List接口继承了Collection接口,他们都是有序集合.即存储在这两个集合中元素的位置都是有顺序的,可以按位置索引号来取出某个元素,并且其中的数据是允许重复的.这是与HashSet之类集合的最大不同处,HashSet之类的集合不能按索引号去检索其中的元素.也不允许有重复的元素.本题目本与hashset没有任何关系,但为了说清楚ArrayListVector的功能,我们使用对比方式,有利于说明问题)

    接着说ArrayListVector的区别,主要包括两个方面:

    (1)同步性

    Vector是线程安全的,也就是说它的方法之间线程同步.ArrayList是线程不安全的,它的方法之间的线程数不同步,如果只有一个线程会访问到集合,那么最好用ArrayList,因为它不考虑线程安全,效率会高些.如果有多个线程会访问到集合,那么就使用Vector,因为不需要我们自己去考虑和编写线程安全代码.

    备注:对于VectorArrayList,HashtableHashMap,线程安全是前2,记住VectorHashtable是旧的,java一诞生就提供了的,他们是线程安全的.ArratlistHashMapjava2才提供的,他们是线程不安全的.

    (2)数据增长

    ArrayListVector都有一个初始的容量大小,但存储在他们里面的元素个数超过了容量时,就需要增加ArrayListVector的存储空间,每次要增加时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间里和程序效率之间要取得一定的平衡.Vector增长为原来的一倍,ArrayList增加原来的0.5.

    10、去掉一个Vector集合中重复的元素

    Vector newVector = new Vector();
    for (int i=0; i<vector.size(); i++){
    	Object obj = vector.get(i);
    	if(!newVector.contains(obj){
    		newVector.add(obj);
    	}
    }
    

     

    还有一种简单的方式,HashSet set = new HashSet(vector);

    11.TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常!

    (应该是没有针对问题的确切答案,当前的add方法放入的是哪个对象,就调用哪个对象的compareTo方法,至于这个compareTo方法怎么做,就看当前这个对象的类中是如何编写这个方法的)

    实验代码:

    public class Parent implements Comparable {
    	private int age = 0;
    	public Parent(int age) {
    		this.age = age;
    	}
    	public int compareTo(Object o) {
    		System.out.println("method of parent");
    		Parent o1 = (Parent) o;
    		return age > o1.age ? 1 : age < o1.age ? -1 : 0;
    	}
    }
    public class Child extends Parent {
    	public Child() {
    		super(2);
    	}
    	public int compareTo(Object o) {
    		System.out.println("method of child");
    		return 1;
    	}
    }
    public class TreeSetTest {
    	public static void main(String[] args) {
    		TreeSet set = new TreeSet();
    		set.add(new Child());
    		set.add(new Parent(3));
    		set.add(new Parent(4));
    	}
    }
    
    

     

    12Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?

    Set里的元素是不能重复的,元素重复与否是使用equals()方法进行判断的。

    equals方法(是String类从它的超类Object中继承的)被用来检测两个对象的内容是否相等。== 用于比较引用和比较基本数据类型时具有不同的功能:比较基本数据类型,如果两个值相同,则结果为true;而在比较引用时,如果引用指向内存中的同一对象,结果为true

    13、你所知道的集合类都有哪些?主要方法?

    最常用的集合类是 List Map List 的具体实现包括 ArrayListLinkedList Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。

    Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作""""),其中每个键映射到一个值。

    Collection

    ArrayList/LinkedList/Vector List

    HashSet/TreeSet Set

    HashMap/HashTable/TreeMap Map

    对于set,大概的方法是add,remove, contains;对于map,大概的方法就是put,removecontains等,我记住的一些思想就是List类会有get(int index)这样的方法,因为它可以按顺序取元素,而set类中没有get(int index)这样的方法。Listset都可以迭代出所有元素,迭代时先要得到一个iterator对象,所以,setlist类都有一个iterator方法,用于返回那个iterator对象。map可以返回三个集合,一个是返回所有的key的集合,另外一个返回的是所有value的集合,再一个返回的keyvalue组合成的EntrySet对象的集合,map也有get方法,参数是key,返回值是key对应的value

    展开全文
  • 包是一组类的集合 一个包可以包含若干个类文件,还可以包含若干个包 包作用: 将相关的源代码文件组织在一起 类名的空间管理,利用包来划分名字空间可以避免类名冲突 提供包一级的封装及存取权限 包的命名: 名称...

    一、包
    定义:
    包是一组类的集合
    一个包可以包含若干个类文件,还可以包含若干个包
    包作用:
    将相关的源代码文件组织在一起
    类名的空间管理,利用包来划分名字空间可以避免类名冲突
    提供包一级的封装及存取权限
    包的命名:
    名称“独一无二”
    一般使用小写字母表示
    命名方式建议:将机构的internet域名反序,作为包名的前导(域名是独一无二的)
    不是标识符的字符用下划线替代
    与关键字冲突,后缀下划线
    开头冲突,前缀下划线
    包与目录:包名即是文件夹名,即目录名;目录名不一定是包名;
    静态引入:引入某一静态成员,例如:import static java.lang.Math.PI;
    引入类中所有的静态成员:import static java.lang.Math.*;

    二、类的访问控制权限:
    类 无修饰符(默认) public
    同一包中的类 是 是
    不同包中的类 否 是
    类的成员的访问控制权限:
    共有:对类成员有访问权限即可访问
    保护:只可被同一子类及其子类的方法访问
    私有:只可被同一类的方法访问
    默认:同一包内访问

    三、内存回收机制
    对象的自动回收:
    无用对象:离开了作用域的对象,无引用指向的对象
    java运行时系统通过垃圾收集器周期性地释放无用对象所使用的内存
    java运行时系统会在对对象进行垃圾回收前,自动调用对象的finalize()方法
    垃圾收集器:自动扫描对象的动态内存区,对不再使用的对象做上标记以进行垃圾回收
    作为一个后台线程运行,通常在系统空闲时异步地执行

    finalize()方法
    在类java.lang.Object中声明,因此java中的每一个类都有该方法:
    proctected void finalize() throws throwable
    用于释放资源,
    类可以覆盖(重写)finalize()方法
    finalize()方法有可能在任何时机以任何次序执行

    四、枚举类
    声明枚举类:
    [public] enum 枚举类型名称
    [implements 接口名称列表]
    {
    枚举值;
    变量成员声明及初始化;
    方法声明及方法体;
    }
    特点:
    枚举定义实际上是定义了一个类
    所有枚举类型都隐含继承(扩展)自java.lang.Enum,因此枚举类型不能再继承其他任何类;
    枚举类型的类体中可以包括方法和变量;
    枚举类型的构造方法必须是包内私有或私有的。定义在枚举开头的常量会被自动创建,不能显式地调用枚举类的构造方法。
    枚举类型的默认方法:
    静态的values()方法用于获得枚举类型的枚举值的数组;
    toString()方法返回枚举值的字符串描述;
    valueOf方法将以字符串形式表示的枚举值转化为枚举类型的对象;
    Ordinal方法获得对象在枚举类型中的位置索引。

    五、类的继承
    根据已有类来定义新类,新类拥有已有类的所有功能
    java只支持类的单继承,每个子类只能有一个直接超类(父类)
    超类是所有子类的公共属性及方法的集合,子类则是超类的特殊化
    继承机制可以提高程序的抽象程度,提高代码的可重用性
    继承语法:
    [ClassModifier] class ClassName extends SuperClassName
    {
    //类体
    }
    子类不能直接访问从超类中继承的私有属性及方法,但可使用共有(及保护)方法进行访问

    六、隐藏和覆盖
    属性的隐藏:子类中声明了与超类中相同的成员变量名
    从超类继承的变量将被隐藏
    子类拥有了两个相同名字的变量,一个继承自超类,另一个由自己声明
    当子类执行继承自超类的操做时,处理的是继承自超类的变量,而当子类执行它自己声明的方法时,所操做的就是它自己声明的变量
    本类中声明的方法使用“super.属性"访问从超类继承的属性

    访问超类的静态属性,static,不被继承,可以被所有子类对象访问,公共的

    方法覆盖:如果子类不需要使用从超类继承来的方法的功能,则可以声明自己的同名方法,成为方法覆盖。
    覆盖方法的参数、名称、返回都需一致。
    只需在调用时使用不同的类名或对象名即可区分覆盖方法和被覆盖方法。
    覆盖方法的访问权限可以比被覆盖的宽松,但是不能更为严格。

    注意事项:
    必须覆盖的方法:
    派生类必须覆盖基类中的抽象方法,否则派生类自身也成为抽象类。
    不能覆盖的方法:
    基类中声明为final的终结方法
    基类中声明为static的静态方法
    调用被覆盖的方法:
    super.overriddenMethodName();

    七、Object类
    所有类的直接或间接超类,处在类层次最高点;
    包含了所有java类的公共属性。
    Object类的主要方法:
    public final Class getClass() 获取当前对象所属的类信息,返回Class对象。
    public String toString() 返回表示当前对象本身有关信息的字符串对象。
    public boolean equals(Object obj) 比较两个对象引用能否指向同一对象,是则返回true,否则返回false。
    public Object clone() 复制当前对象,并返回这个副本。
    public int hashCode() 返回该对象的哈希代码值
    protected void finalize() throw Throwable

    相等和同一
    两个对象具体有相同的类型,及相同的属性值,则称二者相等(equal)。
    如果两个引用变量指向的是同一个对象,则称这两个引用变量同一(identical)。
    同一肯定相等,相等不一定同一。
    ‘==’,判断引用对象是否同一。Object类的equals方法,判断引用对象是否同一。(String类里面重写了equals,所以可以判断相等)
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224222018606.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0Nzg3Njcx,size_16,color_FFFFFF,t_70在这里插入图片描述
    在这里插入图片描述

    instanceof 和 getclass(),都是用来判断是否是一个类型。
    hashCode方法:
    hashCode是一个返回对象散列码的方法,该方法实现的一般规定是:
    在一个java程序的一次执行过程中,如果对象“相等比较”所使用的信息没有被修改的话,同一对象执行hashCode方法每次都应返回同一个整数。在不同的执行中,对象的hashCode方法返回值不必一致。
    如果依照equals方法两个对象是相等的,则在这两个对象上调用hashCode方法应该返回同样的整数结果。
    如果依据equals方法两个对象不相等,并不要求在这两个对象上调用hashCode()方法返回值不同。
    clone方法:
    使用clone方法复制对象:
    覆盖clone方法:在Oject类中被定义为protected,所以需要覆盖为public。
    实现Cloneable接口,赋予一个对象被克隆的能力(cloneability)。
    finalize方法:
    在对象被垃圾回收器回收之前,系统自动调用对象的finalize方法。
    如果要覆盖finalize方法,覆盖方法的最后必须调用super.finalize。
    getClass()方法:
    final方法,返回一个Class对象,用来代表对象所属的类。
    通过Class对象,可以查询类的各种信息:比如名字、超类、实现接口的名字等。
    notify、notifyAll、wait方法,final方法,不能覆盖,这三个方法主要用在多线程程序中。
    八、终结类与终结方法:
    用final修饰的类和方法;
    终结类不能被继承;只能直接使用
    终结方法不能被子类覆盖;只能原样使用
    九、抽象类
    类名前加修饰符abstract;
    可包含常规类能包含的任何成员,包括非抽象方法;
    也可包含常规类能包含的任何成员,包括非抽象方法;
    也可包含抽象方法:用abstract修饰,只有方法原型,没有方法的实现;
    没有具体实例对象的类,不能使用new方法进行实例化,只能用做超类;
    只有当子类实现了抽象超类的所有抽象方法,子类才不是抽象类,才能产生实例;
    抽象方法的优点:
    隐藏具体的细节信息,所有的子类使用的都是相同的方法原型,其中包含了调用该方法时需要了解的全部信息;
    强迫子类完成指定的行为,规定所有子类的“标准”行为。
    十、泛型
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    有限制的泛型:
    在参数“Type”后面使用"extends"关键字并加上类名或接口名,表明参数所代表的类型必须是该类的子类或者实现了该接口。
    注意:,对于实现了某接口的有限制泛型,也是使用extends关键字,而不是implements关键字。
    在这里插入图片描述
    十一、类的组合(和继承一样,也是一种类的重用机制)
    在这里插入图片描述

    展开全文
  • 包是一组类的集合; 一个包可以包含若干个类文件,还可以包含若干个包。 包的作用 将相关的源代码文件组织在一起; 类名的空间管理,利用包来划分名字空间可以避免类名冲突; 提供包一级的封装及存取权限。 包的命名...

    包是一组类的集合;
    一个包可以包含若干个类文件,还可以包含若干个包。

    包的作用

    将相关的源代码文件组织在一起;
    类名的空间管理,利用包来划分名字空间可以避免类名冲突;
    提供包一级的封装及存取权限。

    包的命名

    每个包的名称必须是“第一无二”的;
    Java中包名使用小写字母表示;

    命名方式建议:

    将机构的Internet域名反序,作为包名的前导;(Internet的域名是独一无二的)
    若包名中有任何不可用于标识符的字符,用下划线替代;
    若包名中任何部分与关键字冲突,后缀下划线;
    若包名中的任何部分以数字或其他不能用作标识符起始的字符开头,前缀下划线。
    

    编译单元

    一个Java源代码文件称为一个编译单元,由三部分组成:
    		所属包的声明(省略则属于默认包);
    		import(引入)包的声明,用于导入外部的类;
    		类和接口的声明。
    
    一个编译单元中只能有一个public类,该类名与文件名相同你那个,编译单元中的其他类往往是public类的辅助类,经过编译,每个类都会产生一个class文件。
    

    包的声明

    命名的包:
    
    	package //后面跟包名;
    
    		例如:package Mypackage;
    	
    默认包(未命名的包)
    		不含有包声明的编译单元是默认包的一部本
    

    包与目录

    每个包都对应一个目录,包名就是文件夹名,即目录名;
    目录名不一定是包名。
    

    引入包

    为了使用其他包中所提供的的类,需要使用import语句引入所需要的类。
    系统库里面很多常用的内容都放在java.lang这个包中,java编译器为所有程序自动引入包java.lang。

    import语句格式:
    import package1[.package2…].(classname | );
    package1[.package2…]表明包的层次,对应于文件目录;
    classname指明所要引入的类名;
    如果要引入包中所有的类可以使用

    静态引入

    单一引入是指引入某一个指定的静态成员,例如:import static java.lang.Math.PI;
    全体引入是指引入类中所有的静态成员,例如:import static java.lang.Math.*;
    
    import static java.lang.Math.PI;
    public class Circle{
    	int radius;
    	public double circumference(){
    		return 2*PI*radius;
    	}
    }
    

    这样可以直接使用PI,而不用带其类名。

    类的访问控制权限

    类在不同范围是否可以被访问:

    | 类型 | 无修饰 | public
    | 同一包中的类 | 是 | 是
    | 不同包中的类 | 否 | 是

    类的成员访问控制权限

    公有public:(最宽泛)
    可以被其他访问方法询问(前提是对类成员所属的类有访问权限)
    保护protected:
    可被同一类或同一包或不同包中的子类的方法访问
    私有private:
    只可被同一类的方法访问
    默认default:
    可被同一个包内的访问;又称为“包(package)访问权限”

    公有的接口方法:get方法、set方法

    get方法
    功能是取得属性变量的值
    get方法名以“get”开头,后面是实例变量的名字
    例如:

    public int getRadius(){
    	return radius;
    }
    

    set方法
    功能是修改属性变量的值
    set方法名以“set”开头,后面是实例变量的名字
    例如:

    public void setRadius(int r){
    	radius = r;
    }
    

    this关键字
    如果方法内的局部变量(包括形参)名与实例变量名相同,则方法体内访问实例变量时需要this关键字。
    例如:

    public void setRadius(int radius){
    	this.radius = radius;
    }
    

    实例:

    public class Circle{
        static double PI=3.14159265;
        private int radius;
        public int getRadius(){
            return radius;
        }
        public void setRadius(int radius){
            this.radius=radius;
        }
    }
    
    public class ClassVariableTester{
        public static void main(String[] args){
            Circle x=new Circle();
            int radius= 4 ;
            System.out.println(x.getRadius());
            x.setRadius(radius);
            System.out.println(x.getRadius());
        }
    }
    
    展开全文
  • 集合

    2020-07-09 20:58:27
    Java标准库自带的java.util提供了集合类:Collection,它除Map外所有其他集合类的根接口。Java的java.util主要提供了以下三种类型的集合: List:一种有序列表的集合,例如,按索引排列的Student的List;

    Java集合简介

    集合就是“由若干个确定的元素所构成的整体”。

    在Java中,如果一个Java对象可以在内部持有若干其他Java对象,并对外提供访问接口,我们把这种Java对象称为集合。

    数组有如下限制:

    • 数组初始化后大小不可变;
    • 数组只能按索引顺序存取

    Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口。Java的java.util包主要提供了以下三种类型的集合:

    • List:一种有序列表的集合,例如,按索引排列的StudentList
    • Set:一种保证没有重复元素的集合,例如,所有无重复名称的StudentSet
    • Map:一种通过键值(key-value)查找的映射表集合,例如,根据Studentname查找对应StudentMap

    Java集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List,具体的实现类有ArrayListLinkedList等,二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:

    List<String> list = new ArrayList<>(); // 只能放入String类型
    

    最后,Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。

    由于Java的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:

    • Hashtable:一种线程安全的Map实现;
    • Vector:一种线程安全的List实现;
    • Stack:基于Vector实现的LIFO的栈。

    还有一小部分接口是遗留接口,也不应该继续使用:

    • Enumeration<E>:已被Iterator<E>取代。

    小结

    Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括ListSetMap。Java集合使用统一的Iterator遍历,尽量不要使用遗留接口。

    使用List

    在集合类中,List是最基础的一种集合:它是一种有序列表。

    List的行为和数组几乎完全相同:List内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置,List的索引和数组一样,从0开始。

    在实际应用中,需要增删元素的有序列表,我们使用最多的是ArrayList。实际上,ArrayList在内部使用了数组来存储所有元素。

    考察List<E>接口,可以看到几个主要的接口方法:

    • 在末尾添加一个元素:void add(E e)
    • 在指定索引添加一个元素:void add(int index, E e)
    • 删除指定索引的元素:int remove(int index)
    • 删除某个元素:int remove(Object e)
    • 获取指定索引的元素:E get(int index)
    • 获取链表大小(包含元素的个数):int size()
    ArrayList LinkedList
    获取指定元素 速度很快 需要从头开始查找元素
    添加元素到末尾 速度很快 速度很快
    在指定位置添加/删除 需要移动元素 不需要移动元素
    内存占用 较大

    通常情况下,我们总是优先使用ArrayList。

    List的特点

    使用List时,我们要关注List接口的规范。List接口允许我们添加重复的元素,即List内部的元素可以重复。
    List还允许添加null

    创建List

    除了使用ArrayListLinkedList,我们还可以通过List接口提供的of()方法,根据给定元素快速创建List

    List<Integer> list = List.of(1, 2, 5);
    

    遍历List

    和数组类型相似,我们要遍历一个List,完全可以用for循环根据索引配合get(int)方法遍历:

    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("apple", "pear", "banana");
            for (int i=0; i<list.size(); i++) {
                String s = list.get(i);
                System.out.println(s);
            }
        }
    }
    
    

    但这种方式并不推荐,一是代码复杂,二是因为get(int)方法只有ArrayList的实现是高效的,换成LinkedList后,索引越大,访问速度越慢。

    所以我们要始终坚持使用迭代器Iterator来访问ListIterator本身也是一个对象,但它是由List的实例调用iterator()方法的时候创建的。Iterator对象知道如何遍历一个List,并且不同的List类型,返回的Iterator对象实现也是不同的,但总是具有最高的访问效率。
    Iterator对象有两个方法:boolean hasNext()判断是否有下一个元素,E next()返回下一个元素。因此,使用Iterator遍历List代码如下:

    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("apple", "pear", "banana");
            for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
                String s = it.next();
                System.out.println(s);
            }
        }
    }
    
    

    Java的for each循环本身就可以帮我们使用Iterator遍历。把上面的代码再改写如下:

    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("apple", "pear", "banana");
            for (String s : list) {
                System.out.println(s);
            }
        }
    }
    
    

    List和Array转换

    List变为Array有三种方法,
    第一种是调用toArray()方法直接返回一个Object[]数组:

    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("apple", "pear", "banana");
            Object[] array = list.toArray();
            for (Object s : array) {
                System.out.println(s);
            }
        }
    }
    

    这种方法会丢失类型信息,所以实际应用很少。

    第二种方式是给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array中:

    public class Main {
        public static void main(String[] args) {
            List<Integer> list = List.of(12, 34, 56);
            Integer[] array = list.toArray(new Integer[3]);
            for (Integer n : array) {
                System.out.println(n);
            }
        }
    }
    
    

    最后一种更简洁的写法是通过List接口定义的T[] toArray(IntFunction<T[]> generator)方法

    Integer[] array = list.toArray(Integer[]::new);
    

    Array变为List就简单多了,通过List.of(T...)方法最简单:

    Integer[] array = { 1, 2, 3 };
    List<Integer> list = List.of(array);
    

    对于JDK 11之前的版本,可以使用Arrays.asList(T...)方法把数组转换成List

    小结

    • List是按索引顺序访问的长度可变的有序表,优先使用ArrayList而不是LinkedList
    • 可以直接使用for each遍历List
    • List可以和Array相互转换。

    编写equals方法

    List还提供了boolean contains(Object o)方法来判断List是否包含某个指定元素。此外,int indexOf(Object o)方法可以返回某个元素的索引,如果元素不存在,就返回-1

    因为List内部并不是通过==判断两个元素是否相等,而是使用equals()方法判断两个元素是否相等

    编写equals

    要正确使用Listcontains()indexOf()这些方法,放入的实例必须正确覆写equals()方法,否则,放进去的实例,查找不到。我们之所以能正常放入StringInteger这些对象,是因为Java标准库定义的这些类已经正确实现了equals()方法。

    以Person对象为例,测试一下:

    public class Main {
        public static void main(String[] args) {
            List<Person> list = List.of(
                new Person("Xiao Ming"),
                new Person("Xiao Hong"),
                new Person("Bob")
            );
            System.out.println(list.contains(new Person("Bob"))); // false
        }
    }
    
    class Person {
        String name;
        public Person(String name) {
            this.name = name;
        }
    }
    

    不出意外,虽然放入了new Person("Bob"),但是用另一个new Person("Bob")查询不到,原因就是Person类没有覆写equals()方法。

    equals()方法要求我们必须满足以下条件:

    • 自反性(Reflexive):对于非nullx来说,x.equals(x)必须返回true
    • 对称性(Symmetric):对于非nullxy来说,如果x.equals(y)true,则y.equals(x)也必须为true
    • 传递性(Transitive):对于非nullxyz来说,如果x.equals(y)truey.equals(z)也为true,那么x.equals(z)也必须为true
    • 一致性(Consistent):对于非nullxy来说,只要xy状态不变,则x.equals(y)总是一致地返回true或者false
    • null的比较:即x.equals(null)永远返回false

    我们总结一下equals()方法的正确编写方法:

    • 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
    • instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false
    • 对引用类型用Objects.equals()比较,对基本类型直接用==比较。
      使用Objects.equals()比较两个引用类型是否相等的目的是省去了判断null的麻烦。两个引用类型都是null时它们也是相等的。

    小结

    List中查找元素时,List的实现类通过元素的equals()方法比较两个元素是否相等,因此,放入的元素必须正确覆写equals()方法,Java标准库提供的StringInteger等已经覆写了equals()方法;

    编写equals()方法可借助Objects.equals()判断。

    如果不在List中查找元素,就不必覆写equals()方法。

    使用Map

    通过一个键去查询对应的值。使用List来实现存在效率非常低的问题,因为平均需要扫描一半的元素才能确定,而Map这种键值(key-value)映射表的数据结构,作用就是能高效通过key快速查找value(元素)。

    Map来实现根据name查询某个Student的代码如下:

    public class Main {
        public static void main(String[] args) {
            Student s = new Student("Xiao Ming", 99);
            Map<String, Student> map = new HashMap<>();
            map.put("Xiao Ming", s); // 将"Xiao Ming"和Student实例映射并关联
            Student target = map.get("Xiao Ming"); // 通过key查找并返回映射的Student实例
            System.out.println(target == s); // true,同一个实例
            System.out.println(target.score); // 99
            Student another = map.get("Bob"); // 通过另一个key查找
            System.out.println(another); // 未找到返回null
        }
    }
    
    class Student {
        public String name;
        public int score;
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
    }
    
    

    通过上述代码可知:Map<K, V>是一种键-值映射表,当我们调用put(K key, V value)方法时,就把keyvalue做了映射并放入Map。当我们调用V get(K key)时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的实现类是HashMap

    如果只是想查询某个key是否存在,可以调用boolean containsKey(K key)方法。

    put()方法的签名是V put(K key, V value),如果放入的key已经存在,put()方法会返回被删除的旧的value,否则,返回null
    始终牢记:Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。
    此外,在一个Map中,虽然key不能重复,但value是可以重复的

    遍历Map

    Map来说,要遍历key可以使用for each循环遍历Map实例的keySet()方法返回的Set集合,它包含不重复的key的集合:

    public class Main {
        public static void main(String[] args) {
            Map<String, Integer> map = new HashMap<>();
            map.put("apple", 123);
            map.put("pear", 456);
            map.put("banana", 789);
            for (String key : map.keySet()) {
                Integer value = map.get(key);
                System.out.println(key + " = " + value);
            }
        }
    }
    
    

    同时遍历keyvalue可以使用for each循环遍历Map对象的entrySet()集合,它包含每一个key-value映射:

    public class Main {
        public static void main(String[] args) {
            Map<String, Integer> map = new HashMap<>();
            map.put("apple", 123);
            map.put("pear", 456);
            map.put("banana", 789);
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                String key = entry.getKey();
                Integer value = entry.getValue();
                System.out.println(key + " = " + value);
            }
        }
    }
    

    遍历Map时,不可假设输出的key是有序的!

    小结

    • Map是一种映射表,可以通过key快速查找value

    • 可以通过for each遍历keySet(),也可以通过for each遍历entrySet(),直接获取key-value

    • 最常用的一种Map实现是HashMap

    编写equals和hashCode

    HashMap之所以能根据key直接拿到value,原因是它内部通过空间换时间的方法,用一个大数组存储所有value,并根据key直接计算出value应该存储在哪个索引。

    我们放入Mapkey是字符串"a",但是,当我们获取Mapvalue时,传入的变量不一定就是放入的那个key对象。
    换句话讲,两个key应该是内容相同,但不一定是同一个对象。测试代码如下:

    public class Main {
        public static void main(String[] args) {
            String key1 = "a";
            Map<String, Integer> map = new HashMap<>();
            map.put(key1, 123);
    
            String key2 = new String("a");
            map.get(key2); // 123
    
            System.out.println(key1 == key2); // false
            System.out.println(key1.equals(key2)); // true
        }
    }
    

    因为在Map的内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的,即正确使用Map必须保证:作为key的对象必须正确覆写equals()方法。

    我们经常使用String作为key,因为String已经正确覆写了equals()方法。但如果我们放入的key是一个自己写的类,就必须保证正确覆写了equals()方法。

    通过key计算索引的方式就是调用key对象的hashCode()方法,它返回一个int整数。HashMap正是通过这个方法直接定位key对应的value的索引,继而直接返回value

    因此,正确使用Map必须保证:

    • 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true
    • 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:
      • 如果两个对象相等,则两个对象的hashCode()必须相等;
      • 如果两个对象不相等,则两个对象的hashCode()尽量不要相等。

    即对应两个实例ab

    • 如果ab相等,那么a.equals(b)一定为true,则a.hashCode()必须等于b.hashCode()
    • 如果ab不相等,那么a.equals(b)一定为false,则a.hashCode()b.hashCode()尽量不要相等。

    在正确实现equals()的基础上,我们还需要正确实现hashCode(),即上述3个字段分别相同的实例,hashCode()返回的int必须相同:

    public class Person {
        String firstName;
        String lastName;
        int age;
    
        @Override
        int hashCode() {
            int h = 0;
            h = 31 * h + firstName.hashCode();
            h = 31 * h + lastName.hashCode();
            h = 31 * h + age;
            return h;
        }
    }
    

    注意到String类已经正确实现了hashCode()方法,我们在计算PersonhashCode()时,反复使用31*h,这样做的目的是为了尽量把不同的Person实例的hashCode()均匀分布到整个int范围。

    和实现equals()方法遇到的问题类似,如果firstNamelastNamenull,上述代码工作起来就会抛NullPointerException。为了解决这个问题,我们在计算hashCode()的时候,经常借助Objects.hash()来计算:

    int hashCode() {
        return Objects.hash(firstName, lastName, age);
    }
    

    所以,编写equals()hashCode()遵循的原则是:
    equals()用到的用于比较的每一个字段,都必须在hashCode()中用于计算;equals()中没有使用到的字段,绝不可放在hashCode()中计算。

    另外注意,对于放入HashMapvalue对象,没有任何要求。

    小结

    要正确使用HashMap,作为key的类必须正确覆写equals()hashCode()方法;

    一个类如果覆写了equals(),就必须覆写hashCode(),并且覆写规则是:

    • 如果equals()返回true,则hashCode()返回值必须相等;

    • 如果equals()返回false,则hashCode()返回值尽量不要相等。

    实现hashCode()方法可以通过Objects.hashCode()辅助方法实现。

    使用EnumMap

    因为HashMap是一种通过对key计算hashCode(),通过空间换时间的方式,直接定位到value所在的内部数组的索引,因此,查找效率非常高。

    如果作为key的对象是enum类型,那么,还可以使用Java集合库提供的一种EnumMap,它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。

    我们以DayOfWeek这个枚举类型为例,为它做一个“翻译”功能:

    public class Main {
        public static void main(String[] args) {
            Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
            map.put(DayOfWeek.MONDAY, "星期一");
            map.put(DayOfWeek.TUESDAY, "星期二");
            map.put(DayOfWeek.WEDNESDAY, "星期三");
            map.put(DayOfWeek.THURSDAY, "星期四");
            map.put(DayOfWeek.FRIDAY, "星期五");
            map.put(DayOfWeek.SATURDAY, "星期六");
            map.put(DayOfWeek.SUNDAY, "星期日");
            System.out.println(map);
            System.out.println(map.get(DayOfWeek.MONDAY));
        }
    }
    
    

    小结

    如果Mapkeyenum类型,推荐使用EnumMap,既保证速度,也不浪费空间。

    使用EnumMap的时候,根据面向抽象编程的原则,应持有Map接口。

    TreeMap

    还有一种Map,它在内部会对Key进行排序,这种Map就是SortedMap。注意到SortedMap是接口,它的实现类是TreeMap
    Map
    SortedMap保证遍历时以Key的顺序来进行排序。例如,放入的Key"apple""pear""orange",遍历的顺序一定是"apple""orange""pear",因为String默认按字母排序:

    public class Main {
        public static void main(String[] args) {
            Map<String, Integer> map = new TreeMap<>();
            map.put("orange", 1);
            map.put("apple", 2);
            map.put("pear", 3);
            for (String key : map.keySet()) {
                System.out.println(key);
            }
            // apple, orange, pear
        }
    }
    
    

    使用TreeMap时,放入的Key必须实现Comparable接口。StringInteger这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。

    如果作为Keyclass没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:

    public class Main {
        public static void main(String[] args) {
            Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
                public int compare(Person p1, Person p2) {
                    return p1.name.compareTo(p2.name);
                }
            });
            map.put(new Person("Tom"), 1);
            map.put(new Person("Bob"), 2);
            map.put(new Person("Lily"), 3);
            for (Person key : map.keySet()) {
                System.out.println(key);
            }
            // {Person: Bob}, {Person: Lily}, {Person: Tom}
            System.out.println(map.get(new Person("Bob"))); // 2
        }
    }
    
    class Person {
        public String name;
        Person(String name) {
            this.name = name;
        }
        public String toString() {
            return "{Person: " + name + "}";
        }
    }
    

    注意到Comparator接口要求实现一个比较方法,它负责比较传入的两个元素ab,如果a<b,则返回负数,通常是-1,如果a==b,则返回0,如果a>b,则返回正数,通常是1TreeMap内部根据比较结果对Key进行排序。

    另外,注意到Person类并未覆写equals()hashCode(),因为TreeMap不使用equals()hashCode()

    我们来看一个稍微复杂的例子:这次我们定义了Student类,并用分数score进行排序,高分在前:

    public class Main {
        public static void main(String[] args) {
            Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() {
                public int compare(Student p1, Student p2) {
                    return p1.score > p2.score ? -1 : 1;
                }
            });
            map.put(new Student("Tom", 77), 1);
            map.put(new Student("Bob", 66), 2);
            map.put(new Student("Lily", 99), 3);
            for (Student key : map.keySet()) {
                System.out.println(key);
            }
            System.out.println(map.get(new Student("Bob", 66))); // null?,相等时没有返回0
        }
    }
    
    class Student {
        public String name;
        public int score;
        Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
        public String toString() {
            return String.format("{%s: score=%d}", name, score);
        }
    }
    
    

    直接借助Integer.compare(int, int)也可以返回正确的比较结果。

    小结

    • SortedMap在遍历时严格按照Key的顺序遍历,最常用的实现类是TreeMap
    • 作为SortedMapKey必须实现Comparable接口,或者传入Comparator
    • 要严格按照compare()规范实现比较逻辑,否则,TreeMap将不能正常工作。

    使用Properties

    配置文件的特点是,它的Key-Value一般都是String-String类型的,因此我们完全可以用Map<String, String>来表示它。

    因为配置文件非常常用,所以Java集合库提供了一个Properties来表示一组“配置”。由于历史遗留原因,Properties内部本质上是一个Hashtable,但我们只需要用到Properties自身关于读写配置的接口。

    读取配置文件

    Properties读取配置文件非常简单。Java默认配置文件以.properties为扩展名,每行以key=value表示,以#课开头的是注释。以下是一个典型的配置文件:

    # setting.properties
    
    last_open_file=/data/hello.txt
    auto_save_interval=60
    

    可以从文件系统读取这个.properties文件:

    String f = "setting.properties";
    Properties props = new Properties();
    props.load(new java.io.FileInputStream(f));
    
    String filepath = props.getProperty("last_open_file");
    String interval = props.getProperty("auto_save_interval", "120");
    

    可见,用Properties读取配置文件,一共有三步:

    • 创建Properties实例;
    • 调用load()读取文件;
    • 调用getProperty()获取配置。

    调用getProperty()获取配置时,如果key不存在,将返回null。我们还可以提供一个默认值,这样,当key不存在的时候,就返回默认值。

    也可以从classpath读取.properties文件,因为load(InputStream)方法接收一个InputStream实例,表示一个字节流,它不一定是文件流,也可以是从jar包中读取的资源流:

    Properties props = new Properties();
    props.load(getClass().getResourceAsStream("/common/setting.properties"));
    

    试试从内存读取一个字节流:

    import java.io.*;
    import java.util.Properties;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            String settings = "# test" + "\n" + "course=Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
            ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
            Properties props = new Properties();
            props.load(input);
    
            System.out.println("course: " + props.getProperty("course"));
            System.out.println("last_open_date: " + props.getProperty("last_open_date"));
            System.out.println("last_open_file: " + props.getProperty("last_open_file"));
            System.out.println("auto_save: " + props.getProperty("auto_save", "60"));
        }
    }
    
    

    如果有多个.properties文件,可以反复调用load()读取,后读取的key-value会覆盖已读取的key-value:

    Properties props = new Properties();
    props.load(getClass().getResourceAsStream("/common/setting.properties"));
    props.load(new FileInputStream("C:\\conf\\setting.properties"));
    

    使用Set

    Set用于存储不重复的元素集合,它主要提供以下几个方法:

    • 将元素添加进Set<E>boolean add(E e)
    • 将元素从Set<E>删除:boolean remove(Object e)
    • 判断是否包含元素:boolean contains(Object e)
    public class Main {
        public static void main(String[] args) {
            Set<String> set = new HashSet<>();
            System.out.println(set.add("abc")); // true
            System.out.println(set.add("xyz")); // true
            System.out.println(set.add("xyz")); // false,添加失败,因为元素已存在
            System.out.println(set.contains("xyz")); // true,元素存在
            System.out.println(set.contains("XYZ")); // false,元素不存在
            System.out.println(set.remove("hello")); // false,删除失败,因为元素不存在
            System.out.println(set.size()); // 2,一共两个元素
        }
    }
    
    

    Set实际上相当于只存储key、不存储valueMap。我们经常用Set用于去除重复元素。

    因为放入Set的元素和Mapkey类似,都要正确实现equals()hashCode()方法,否则该元素无法正确地放入Set

    最常用的Set实现类是HashSet,实际上,HashSet仅仅是对HashMap的一个简单封装。

    Set接口并不保证有序,而SortedSet接口则保证元素是有序的:

    • HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
    • TreeSet是有序的,因为它实现了SortedSet接口。

    使用TreeSet和使用TreeMap的要求一样,添加的元素必须正确实现Comparable接口,如果没有实现Comparable接口,那么创建TreeSet时必须传入一个Comparator对象。

    小结

    Set用于存储不重复的元素集合:

    • 放入HashSet的元素与作为HashMapkey要求相同;
    • 放入TreeSet的元素与作为TreeMapKey要求相同;
    • 利用Set可以去除重复元素;

    遍历SortedSet按照元素的排序顺序遍历,也可以自定义排序算法。

    使用Queue

    队列(Queue)是一种经常使用的集合。Queue实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。它和List的区别在于,List可以在任意位置添加和删除元素,而Queue只有两个操作:

    • 把元素添加到队列末尾;
    • 从队列头部取出元素。

    在Java的标准库中,队列接口Queue定义了以下几个方法:

    • int size():获取队列长度;
    • boolean add(E)/boolean offer(E):添加元素到队尾;
    • E remove()/E poll():获取队首元素并从队列中删除;
    • E element()/E peek():获取队首元素但并不从队列中删除。

    对于具体的实现类,有的Queue有最大队列长度限制,有的Queue没有。注意到添加、删除和获取队列元素总是有两个方法,这是因为在添加或获取元素失败时,这两个方法的行为是不同的。我们用一个表格总结如下:

    throw Exception 返回false或null
    添加元素到队尾 add(E e) boolean offer(E e)
    取队首元素并删除 E remove() E poll()
    取队首元素但不删除 E element() E peek()

    注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。

    LinkedList即实现了List接口,又实现了Queue接口,但是,在使用的时候,如果我们把它当作List,就获取List的引用,如果我们把它当作Queue,就获取Queue的引用:

    // 这是一个List:
    List<String> list = new LinkedList<>();
    // 这是一个Queue:
    Queue<String> queue = new LinkedList<>();
    

    小结

    队列Queue实现了一个先进先出(FIFO)的数据结构:

    • 通过add()/offer()方法将元素添加到队尾;
    • 通过remove()/poll()从队首获取元素并删除;
    • 通过element()/peek()从队首获取元素但不删除。
      要避免把null添加到队列。

    使用PriorityQueue

    PriorityQueueQueue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()poll()方法,返回的总是优先级最高的元素。

    要使用PriorityQueue,我们就必须给每个元素定义“优先级”。我们以实际代码为例,先看看PriorityQueue的行为:

    public class Main {
        public static void main(String[] args) {
            Queue<String> q = new PriorityQueue<>();
            // 添加3个元素到队列:
            q.offer("apple");
            q.offer("pear");
            q.offer("banana");
            System.out.println(q.poll()); // apple
            System.out.println(q.poll()); // banana
            System.out.println(q.poll()); // pear
            System.out.println(q.poll()); // null,因为队列为空
        }
    }
    
    

    我们放入的顺序是"apple"、"pear"、"banana",但是取出的顺序却是"apple"、"banana"、"pear",这是因为从字符串的排序看,"apple“排在最前面,”pear"排在最后面。

    因此,放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。

    如果我们要放入的元素并没有实现Comparable接口怎么办?PriorityQueue允许我们提供一个Comparator对象来判断两个元素的顺序。我们以银行排队业务为例,实现一个PriorityQueue

    public class Main {
        public static void main(String[] args) {
            Queue<User> q = new PriorityQueue<>(new UserComparator());
            // 添加3个元素到队列:
            q.offer(new User("Bob", "A1"));
            q.offer(new User("Alice", "A2"));
            q.offer(new User("Boss", "V1"));
            System.out.println(q.poll()); // Boss/V1
            System.out.println(q.poll()); // Bob/A1
            System.out.println(q.poll()); // Alice/A2
            System.out.println(q.poll()); // null,因为队列为空
        }
    }
    
    class UserComparator implements Comparator<User> {
        public int compare(User u1, User u2) {
            if (u1.number.charAt(0) == u2.number.charAt(0)) {
                // 如果两人的号都是A开头或者都是V开头,比较号的大小:
                return u1.number.compareTo(u2.number);
            }
            if (u1.number.charAt(0) == 'V') {
                // u1的号码是V开头,优先级高:
                return -1;
            } else {
                return 1;
            }
        }
    }
    
    class User {
        public final String name;
        public final String number;
    
        public User(String name, String number) {
            this.name = name;
            this.number = number;
        }
    
        public String toString() {
            return name + "/" + number;
        }
    }
    

    小结

    PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。
    PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。

    使用Deque

    如果把条件放松一下,允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue),学名Deque

    Java集合提供了接口Deque来实现一个双端队列,它的功能是:

    • 既可以添加到队尾,也可以添加到队首;
    • 既可以从队首获取,又可以从队尾获取。
    Queue Deque
    添加元素到队尾 add(E e)/offer(E e) addLast(E e)/offerLast(E e)
    取队首元素并删除 E remove()/E poll() E removeFirst()/ E pollFirst()
    取队首元素但不删除 E element()/E peek() E getFirst()/E peekFirst()
    添加元素到队首 addFirst()/addLast()
    取队尾元素并删除 E removeLast()/E pollLast()
    取队尾元素但不删除 E getLast()/E peekLast()

    Deque是一个接口,它的实现类有ArrayDequeLinkedList
    我们发现LinkedList真是一个全能选手,它即是List,又是Queue,还是Deque。但是我们在使用的时候,总是用特定的接口来引用它,这是因为持有接口说明代码的抽象层次更高,而且接口本身定义的方法代表了特定的用途。

    // 不推荐的写法:
    LinkedList<String> d1 = new LinkedList<>();
    d1.offerLast("z");
    // 推荐的写法:
    Deque<String> d2 = new LinkedList<>();
    d2.offerLast("z");
    

    小结

    Deque实现了一个双端队列(Double Ended Queue),它可以:

    • 将元素添加到队尾或队首:addLast()/offerLast()/addFirst()/offerFirst()
    • 从队首/队尾获取元素并删除:removeFirst()/pollFirst()/removeLast()/pollLast()
    • 从队首/队尾获取元素但不删除:getFirst()/peekFirst()/getLast()/peekLast()
    • 总是调用xxxFirst()/xxxLast()以便与Queue的方法区分开;
    • 避免把null添加到队列。

    使用Satck

    栈(Stack)是一种后进先出(LIFO:Last In First Out)的数据结构。
    Stack是这样一种数据结构:只能不断地往Stack中压入(push)元素,最后进去的必须最早弹出(pop)来:

    Stack只有入栈和出栈的操作:

    • 把元素压栈:push(E)
    • 把栈顶的元素“弹出”:pop(E)
    • 取栈顶元素但不弹出:peek(E)

    在Java中,我们用Deque可以实现Stack的功能:

    • 把元素压栈:push(E)/addFirst(E)
    • 把栈顶的元素“弹出”:pop(E)/removeFirst()
    • 取栈顶元素但不弹出:peek(E)/peekFirst()

    为什么Java的集合类没有单独的Stack接口呢?因为有个遗留类名字就叫Stack,出于兼容性考虑,所以没办法创建Stack接口,只能用Deque接口来“模拟”一个Stack了。

    当我们把Deque作为Stack使用时,注意只调用push()/pop()/peek()方法,不要调用addFirst()/removeFirst()/peekFirst()方法,这样代码更加清晰。

    Stack的作用

    Stack在计算机中使用非常广泛,JVM在处理Java方法调用的时候就会通过栈这种数据结构维护方法调用的层次

    我们再来看一个Stack的用途:对整数进行进制的转换就可以利用栈。

    计算中缀表达式

    小结

    栈(Stack)是一种后进先出(LIFO)的数据结构,操作栈的元素的方法有:

    • 把元素压栈:push(E)
    • 把栈顶的元素“弹出”:pop(E)
    • 取栈顶元素但不弹出:peek(E)
    • 在Java中,我们用Deque可以实现Stack的功能,注意只调用push()/pop()/peek()方法,避免调用Deque的其他方法。
      最后,不要使用遗留类Stack

    使用Iterator

    编译器把for each循环通过Iterator改写为了普通的for循环:

    for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
         String s = it.next();
         System.out.println(s);
    }
    

    我们把这种通过Iterator对象遍历集合的模式称为迭代器。

    Iterator对象是集合对象自己在内部创建的,它自己知道如何高效遍历内部的数据集合,调用方则获得了统一的代码,编译器才能把标准的for each循环自动转换为Iterator遍历。

    如果我们自己编写了一个集合类,想要使用for each循环,只需满足以下条件:

    • 集合类实现Iterable接口,该接口要求返回一个Iterator对象;
    • Iterator对象迭代集合内部数据。

    这里的关键在于,集合类通过调用iterator()方法,返回一个Iterator对象,这个对象必须自己知道如何遍历该集合。
    一个简单的Iterator示例如下,它总是以倒序遍历集合:

    import java.util.*;
    
    public class Main {
        public static void main(String[] args) {
            ReverseList<String> rlist = new ReverseList<>();
            rlist.add("Apple");
            rlist.add("Orange");
            rlist.add("Pear");
            for (String s : rlist) {
                System.out.println(s);
            }
        }
    }
    
    class ReverseList<T> implements Iterable<T> {
    
        private List<T> list = new ArrayList<>();
    
        public void add(T t) {
            list.add(t);
        }
    
        @Override
        public Iterator<T> iterator() {
            return new ReverseIterator(list.size());
        }
    
        class ReverseIterator implements Iterator<T> {
            int index;
    
            ReverseIterator(int index) {
                this.index = index;
            }
    
            @Override
            public boolean hasNext() {
                return index > 0;
            }
    
            @Override
            public T next() {
                index--;
                return ReverseList.this.list.get(index);
            }
        }
    }
    
    

    在编写Iterator的时候,我们通常可以用一个内部类来实现Iterator接口,这个内部类可以直接访问对应的外部类的所有字段和方法。例如,上述代码中,内部类ReverseIterator可以用ReverseList.this获得当前外部类的this引用,然后,通过这个this引用就可以访问ReverseList的所有字段和方法。

    小结

    Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有:

    • 对任何集合都采用同一种访问模型;
    • 调用者对集合内部结构一无所知;
    • 集合类返回的Iterator对象知道如何迭代。

    Java提供了标准的迭代器模型,即集合类实现java.util.Iterable接口,返回java.util.Iterator实例。

    使用Collections

    Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。

    addAll()方法可以给一个Collection类型的集合添加若干元素。因为方法签名是Collection,所以我们可以传入ListSet等各种集合类型。

    创建空集合

    Collections提供了一系列方法来创建空集合:

    • 创建空ListList<T> emptyList()
    • 创建空MapMap<K, V> emptyMap()
    • 创建空SetSet<T> emptySet()

    要注意到返回的空集合是不可变集合,无法向其中添加或删除元素。
    此外,也可以用各个集合接口提供的of(T...)方法创建空集合。例如,以下创建空List的两个方法是等价的:

    List<String> list1 = List.of();
    List<String> list2 = Collections.emptyList();
    

    创建单元素集合

    Collections提供了一系列方法来创建一个单元素集合:

    • 创建一个元素的ListList<T> singletonList(T o)
    • 创建一个元素的MapMap<K, V> singletonMap(K key, V value)
    • 创建一个元素的SetSet<T> singleton(T o)

    要注意到返回的单元素集合也是不可变集合,无法向其中添加或删除元素。

    实际上,使用List.of(T...)更方便,因为它既可以创建空集合,也可以创建单元素集合,还可以创建任意个元素的集合:

    排序

    Collections可以对List进行排序。因为排序会直接修改List元素的位置,因此必须传入可变List

    public class Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("apple");
            list.add("pear");
            list.add("orange");
            // 排序前:
            System.out.println(list);
            Collections.sort(list);
            // 排序后:
            System.out.println(list);
        }
    }
    
    

    洗牌

    Collections提供了洗牌算法,即传入一个有序的List,可以随机打乱List内部元素的顺序,效果相当于让计算机洗牌:

    public class Main {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            for (int i=0; i<10; i++) {
                list.add(i);
            }
            // 洗牌前:
            System.out.println(list);
            Collections.shuffle(list);
            // 洗牌后:
            System.out.println(list);
        }
    }
    
    

    不可变集合

    Collections还提供了一组方法把可变集合封装成不可变集合:

    • 封装成不可变ListList<T> unmodifiableList(List<? extends T> list)
    • 封装成不可变SetSet<T> unmodifiableSet(Set<? extends T> set)
    • 封装成不可变MapMap<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)

    这种封装实际上是通过创建一个代理对象,拦截掉所有修改方法实现的。我们来看看效果:

    public class Main {
        public static void main(String[] args) {
            List<String> mutable = new ArrayList<>();
            mutable.add("apple");
            mutable.add("pear");
            // 变为不可变集合:
            List<String> immutable = Collections.unmodifiableList(mutable);
            immutable.add("orange"); // UnsupportedOperationException!
        }
    }
    
    

    然而,继续对原始的可变List进行增删是可以的,并且,会直接影响到封装后的“不可变”List

    因此,如果我们希望把一个可变List封装成不可变List,那么,返回不可变List后,最好立刻扔掉可变List的引用,这样可以保证后续操作不会意外改变原始对象,从而造成“不可变”List变化了。

    线程安全集合

    Collections还提供了一组方法,可以把线程不安全的集合变为线程安全的集合:

    • 变为线程安全的ListList<T> synchronizedList(List<T> list)
    • 变为线程安全的SetSet<T> synchronizedSet(Set<T> s)
    • 变为线程安全的MapMap<K,V> synchronizedMap(Map<K,V> m)

    小结

    Collections类提供了一组工具方法来方便使用集合类:

    • 创建空集合;
    • 创建单元素集合;
    • 创建不可变集合;
    • 排序/洗牌等操作。
    展开全文
  • 文章目录1.概述2. 字段表集合在class文件中位置3. Java中一个Field字段应该包含那些信息?4. field字段访问标志5. 字段数据类型表示和字段名称表示6....字段表集合是指由若干个字段表(field_info)组成
  • 难点:关于输入输出 1、字符流与字节流区别在于(D )。 前者带有缓冲,后者没有 前者块读写,后者字节读写 ...2、在编写Java Application程序时,若需要使用到标准输入输出流的若干个类,导入时...
  • JAVA中的包

    2016-02-03 22:06:04
    相当于一个文件夹,一个包中放了若干个类,不同的包中可以存在同名的类,类的名称应该加上.类名。 2、的导入 程序中的类存放在不同的里,类跨访问时需要在程序的前面加上的导入,
  • 7.1集合简介

    2020-07-05 10:49:58
    概念:集合是若干个确定元素所构成整体。 如果一个Java对象可以在内部持有若干其他Java对象,并对外提供访问接口,我们把这种Java对象称为集合。 在java中数组就是一个集合,但数组有其局限性: 数组初始化后...
  • Collection集合框架

    2015-04-11 19:50:16
    集合框架(Collection Framework)泛指java.util包的若干个类和接口.如Collection,List,ArrayList,LinkedList,Vector(自动增长数组),HashSet,HashMap等. 集合框架中类主要封装的是典型数据结构,如动态数组,链表...
  • 一般情况下都是若干个.class文件能实现一组功能,这时候便可以把这些.class文件打包成.jar文件 举例:比如说当需要使用集合类的时候,咱们需要import java.uitl.*; 对应的就是一个jar(.jar文件)它里面就是一堆.class...
  • 前言:在Java当中,如果有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体 。 Java集合是java提供的工具,包含了常用的...
  • 一、集合的概念 集合一种工具,容器,可以存储数量不等的若干对象 。代替数组。 二、Collection集合体系 Collection接口 ... //将o添加到指定的集合中 boolean addAll(Collection c);将 集合c 中的所有
  • Java虚拟机不和包括Java在内任何语言绑定,只与"Class文件"这种特定二进制文件所关联,Class文件中包含了Java虚拟机指令集合符号表以及若干其它辅助信息。Java虚拟机作为一通用、机器无关执行平台,任何...
  • Java学习之路——集合

    2021-01-17 21:30:33
    Java学习之路——集合 概述 集合的定义 什么是集合(Collection)?...Java标准库自带的java.util提供了集合类:Collection,它除Map外所有其他集合类的根接口。Java 的java.util主要提供了以下三种类
  • 1.5java

    2021-03-15 07:46:58
    【定义】一组类的集合,一个包可以包含若干个类文件,还可以包含若干个包 (包是一级类的组织,和访问控制的机制) 包的作用 将相关的源代码文件组织在一起 类名的空间管理,利用包来划分命名空间,便可以避免类名...
  • java及编译单元

    2018-05-30 22:05:00
    包是一组类的集合,一个包可以包含若干个类文件还可以包含若干个包 包的作用:  将相关的源代码文件组织在一起;  类名的空间管理,利用包来划分名字空间可以避免类名冲突;  提供包一级的封装及存取权限; ...
  • 不同的集合有不同的遍历的方法,这在使用中极其不方便的,那么能不能使用同一种遍历的方法,遍历所有集合,即用一个类中的若干方法可以操作不同集合中的各种数据.那么不同的集合他的实现方式不同,存储数据的结构也不同...
  • java 集合(一)

    2014-10-12 10:34:00
    集合框架(Collection Framework)泛指java.util包的若干个类和接口.如Collection,List,ArrayList,LinkedList,Vector(自动增长数组),HashSet,HashMap等. 集合框架中类主要封装的是典型数据结构,如动态数组,链表,...
  • JAVA面试题之集合

    2012-12-10 17:31:24
    答:集合框架(Collection Framework)泛指java.util包的若干个类和接口.如Collection,List,ArrayList,LinkedList,Vector(自动增长数组),HashSet,HashMap等.集合框架中类主要封装的是典型数据结构,如动态数组,链表...
  • 同学打包代码

    2012-06-22 13:48:26
    本程序中至少有三个类,男生类,女生类,以及这两个类的基类——学生类。三个类至少具有的属性分别: 学生类的属性:姓名(不会出现同名的情况),性别,魅力,体重,花费的金钱。 男生类的属性:继承而来的属性...
  • Java基础知识——集合

    千次阅读 2020-02-19 18:01:44
    在Java中,如果一Java对象可以在内部持有若干其他Java对象,并对外提供访问接口,我们把这种Java对象称为集合,故Java数组也可以看作一种集合。那么即然有了数组,为什么Java还要提供集合类呢,原因:一、数组...

空空如也

空空如也

1 2 3 4 5 ... 18
收藏数 354
精华内容 141
关键字:

包是若干个类的集合