当前位置: 首页 > news >正文

哪个网站做简历好物理服务器

哪个网站做简历好,物理服务器,网站策划与运营课程认知,网上能免费做网站发布叼#x1f6eb; JDK和JRE傻傻分不清?#x1f6eb; HelloWorld的输出都经历了啥#xff1f;#x1f6eb; Java的三个版本都是啥#xff1f;#x1f6eb; 关于main方法你都知道啥#xff1f;main方法被声明为private会怎样#xff1f;#x1f6eb; 强制and自动类型转换都… JDK和JRE傻傻分不清? HelloWorld的输出都经历了啥 Java的三个版本都是啥 关于main方法你都知道啥main方法被声明为private会怎样 强制and自动类型转换都是啥? 关于变量和常量你都知道啥? 和、|和||有什么区别 可以修改一个字符串中的值吗? 字符串使用和equals判等的区别? 空串和null串有啥区别? String StringBuilder StringBuffer的异同 关于输入输出你都知道啥? 关于控制路程都需要注意啥? 数组初始化的小细节 类?对象?他们的关系又是什么 关于构造器你都知道什么? 关于类的源文件都需要知道什么? 重载和重写都是什么?区别又是什么 关键字 this和super关键字 instanceof关键字 static关键字 final关键字 权限修饰符的作用范围知多少? 抽象类 抽象方法 interface接口 接口的结构 接口的实现和方法引用 为什么使用接口 方法的参数传递机制是什么? 关于包装类你都知道什么? 包装类基本知识 什么是装箱和拆箱 包装类cache缓存机制 关于迭代器你都知道什么? 什么是迭代器? 迭代器的4个API 如何使用迭代器 Collection集合接口知多少? 为什么不使用数组而是集合?Collection接口的API都有什么?AbstractCollection类知多少? 关于List集合类你都知道什么? List接口三个实现类的异同? ArrayList类源码浅析 LinkedList类源码浅析 vector类源码简析 关于Set集合类你都知道什么? 如何理解Set集合的无序不可重复? Map集合类 HashSet类、HashMap类数据结构及源码浅析 LinkedHashSet类、LinkedHashMap类源码浅析 TreeSet类、TreeMap类源码浅析 JDK和JRE傻傻分不清? 好歹也是学了那么长时间的Java了强烈的直觉告诉我JDK、JRE这俩指的肯定不是一个东西一看书才知道我的直觉是真滴准(夸夸自己)既然不一样那么接下来就扒一扒JDK和JRE的野史吧。   JDK 是指Java Development Kit 也就是我们常说的Java开发工具包是程序员编写Java程序时必须使用的软件。JRE 是指Java Runtime Environment 也就是Java的运行时环境是程序运行时必须依赖的软件。那么我们想要使用Java这门语言进行编程的话究竟需要安装哪个呢答案是我都要一个编译一个运行缺一不可。不过当你下载了JDK之后就会发现在jdk的安装目录下会有一个的jre文件夹也就是意味着我们只需要下载jdk即可。jdk的下载安装配置十分简单点击我仅需四步即可完成   既然是野史就说一说大家可能不知道的知识点在1998~2006年之间开发工具包被称为是SDK(Software Development Kit)后更名JDK在某些场合中任然会见到这个术语同一时间段内Java所出的版本被统称为java 2。看到这个名词不知道大家会不会联想到J2EE猜的没错 J2EE的全称就是Java 2 Platform Enterprise Edition也就是Java 2 平台企业版是在SUN公司领导下多家公司(Oracle、IBM、BEA等)共同制定的一套广泛认可的企业级分布式应用程序开发规范后来强大如J2EE还是被Spring框架所冲击导致这一切的就是那个恐怖如斯的男人——罗德·约翰森(Rod Johnson) HelloWorld的输出都经历了啥 以HelloWorld程序为例介绍java程序开发的几个步骤程序员编写HelloWorld.java文件(源代码文件)计算机使用javac.exe程序将HelloWorld.java文件编译成HelloWorld.class文件(字节码文件)计算机使用java.exe程序将HelloWorld.class文件送到JVM中运行运行的过程中随时向核心类库中调用Java编写好的程序来支撑自己编写程序的运行手动操作编译运行的过程中有几个注意点 1、编写的源代码文件以.java作为拓展名放在C盘以外的盘符下显示文件的拓展名防止文件的拓展名不是.java否则会报javac: 找不到文件的错误2、使用java.exe运行字节码文件的时候HelloWorld后面没有拓展名 Java的三个版本都是啥 Java SE(Java Standard Edition)标准版 Java EE(Java Enterprise Edition)企业版 Java ME(Java Micro Edition)微型版 关于main方法你都知道啥main方法被声明为private会怎样 public static void main(String[] args) public: main方法的访问权限为public方便虚拟机直接调用 static: 静态方法虚拟机不用创建该类的对象即可调用它内部的main方法 void: 没有返回值也就是说返回值为空 String[] args: 在使用命令行执行Java程序的时候会通过命令行参数传递一些数据这些数据就会被名为args的字符串数组所接收 首先根据Java语言的开发规范main方法必须被声明为public当然只是规范而不是必须。如果想要使用private或者protected不将main方法对外开放的话也是可以的而且源代码文件依旧会被正常编译为字节码(.class)文件只不过在运行的时候会报Main method not public的异常错误。  出现上述情况的原因是类加载后main方法被虚拟机所调用执行但是由于main方法并不是public的导致虚拟机无法调用该方法出现异常。从以上的结论中我们就可以推知如果在intellij IDEA中不将main方法的访问权限设置为public的话就会导致main方法左侧不会出现供代码运行的绿色小三角图标  main方法是Java程序的入口所以每个Java程序都必须有一个main方法但并不代表着每个类都必须有main方法 强制and自动类型转换都是啥? Java是一种强类型语言这就意味着我们在编译之前就应该为每一个变量声明一种类型。而在Java中数据类型又被分为基本数据类型和引用数据类型基本数据类型又被分为四类八种除了基本数据类型之外的所有数据都是引用数据类型自动类型转换   下面这张图片中揭示了基本数据类型间的自动转换关系。箭头之间具有传递性实线箭头代表着无精度损失的转换虚线代表转换会伴随着数据精度的损失。其中char转int会将char字符对应的ASCII码赋给int型变量。  表达式(用一个二元运算符连接两个值)中的自动类型转换小范围的值会自动转换为较大范围类型的值参与运算所以说表达式运算的最终结果由最高类型的值决定。而且byte、short、char类型的值不管如何都是直接转换成int参与远算所以说(byte)(byte)(int)其他两种类型也是如此 强制类型转换   由自动类型转换可知int类型旳值在必要的时候会自动转换为double类型但是当我们需要将double转int的时候该怎么办呢这个时候就要通过强制类型转换了。   浮点型强转为整型直接丢掉小数只保留整数部分而并不是简单的将结果四舍五入Math.round()方法可以四舍五入(返回值是long型)。如果在强制转换的过程中数值超过小范围取值的话结果将会被截断成两个完全不同的值eg(byte)1500 -36原理如下  显式转换一般就是强制类型转换隐式转换除了自动类型转换还有一种那就是结合赋值运算符也就是、-等。如果这种运算符得旳结果类型与左侧的的类型不一样且无法进行自动类型转换就会默认使用强制类型转换将右侧的类型强转为左侧的数据类型   整型值和布尔值之间无法进行相互转换无论是自动转换还是强制转换都不可以 最后给两道题自测一下类型转换的掌握程度 关于变量和常量你都知道啥? 变量 首先变量的声明遵循以下规范 只能包含字母、数字、美元符(“$”)、下划线(“”)但是不能以数字开头且$“和”_并不推荐在命名的时候使用不能使用Java中的保留字变量的命名区分大小写虽不强制要求但是尽量使用小驼峰(第一个单词首字母小写其他单词的首字母大写)命名 其次Java支持一次声明多个变量但并不提倡因为这种形式会降低程序的可读性(int i, j;)可以使用逐一声明的形式而且变量的声明要尽可能的靠近变量第一次使用的地方   最后变量声明之后还必须要进行初始化才能使用否则会编译器就会认为这个变量的使用是非法的。变量的初始化有三种机制 构造器Java中如果没有在构造器中显式的为字段设置初始值的话就会将其设置成默认值也就是不同数据类型数据所对应的默认值显式赋值也就是最常见的使用等于号初始化代码块Java中将类中使用花括号包裹起来的几行代码称为是初始化代码块初始化代码块随着类的加载而加载只要构造这个类的对象就会执行这个块里的代码。而且初始化代码块中的代码会在main方法之前执行初始化代码块中定义的变量和构造器一样有默认值机制 类变量(static修饰的变量)、实例变量可以使用构造器、显式赋值和初始化块三种机制进行初始化但是局部变量则只能也必须使用赋值语句进行显式初始化。常量   Java中常量必须在main方法的外部使用final进行定义常量定义的时候必须进行初始化一旦初始化之后就无法改变值的大小且常量的命名必须全部大写。静态变量和静态常量   静态变量也叫类变量是使用static修饰的变量静态变量属于类而不属于任何单个的对象也就是说即使不创建该类的对象这个静态变量依然存在不管创建多少个对象都是共享这一个静态变量。非静态变量也就是实例变量就不一样了实例对象属于对象必须使用对象调用无法直接调每创建一个对象都会得到一个实例变量的副本不同对象之间互不影响  静态常量的使用频率要明显高于静态变量在静态常量定义的时候往往都会加上final修饰这样的话它既可以被其他类直接使用类名调用又不用担心字段值被调用后随意修改产生的安全问题。之前输出语句的时候使用的out就是一个final修饰的PrintStream类型的静态常量PrintStream类里又内置了print、println、printf等方法用于输出 和、|和||有什么区别 (逻辑与)和(逻辑或)是逻辑运算符(短路与)和(短路或)是短路逻辑运算符。这两种运算符对应的的运算结果相同与运算的话是全真为真、有假则假或运算的是全假为假、有真则真。两种类型的运算符区别在于逻辑运算符会执行完左右两边的表达式之后返回一个结果短路逻辑运算符当能够得到结果的时候就会直接返回也就是说短路与的左边结果为false就会直接返回false短路或的左边结果为true就会直接返回true   补充一种进行位移运算的位运算符位移运算就是对一个数值的二进制表示进行左右移动的运算其中表示右移高位符号位补充表示左移低位补0则是右移高位补0注意并没有这个运算符 可以修改一个字符串中的值吗? 不能。因为Java中字符串并不是内置的数据类型而是标准Java类库中提供的一个预定义类。字符串不只一种使用new的实例化方式使用双引号(“”)括起来的字符都是String类的一个实例使用new方式实例的对象对分配在堆内存中使用双引号实例的对象在常量池中字符串变量的值则是字符串对象的地址引用不管使用哪种方式给字符串变量重新赋一个新值都是看上去改变了这个字符串的值实际上只不过是改变了变量值的地址引用原来的字符串对象还在内存中存储且值不变   由上图就得以验证我们之前的结论是正确的使用双引号实例化的字符串对象存储在字符串常量池中所以连续创建两个内容相同的字符串会指向同一个地址。而且使用new实例化的字符串对象存储在堆内存中所以说使用两种方式实例化内容相同的字符串对象地址会不同。接下来的三种方式改变字符串的内容地址都和原地址不同也就是证实了字符串是一个不可变的数据类型 字符串使用和equals判等的区别? 是对两个字符串变量的引用地址值是否相等进行判断就算字符串的内容相等如果存储的位置不同也会返回false。String类中重写了Object的equals方法使之可以对字符串的内容进行判等查看源码不难发现重写之后的equals方法是对两个字符串的地址、长度、每一个字符顺序进行判断在判断的过程中但凡有一个不等就直接返回false。   结合字符串的两种实例化方式和两种判等方式   补充一个知识点如果想忽略字母大小写比较两个字符串的内容是否相等可以使用equalsIgnoreCase方法用法与equals相同 空串和null串有啥区别? 空串是一个长度为0内容为空的字符串属于是一个字符串对象null串表示这个变量并未引用任何的对象或者基本数据类型值属于是一个供引用数据变量引用的值。空串引用String的API 会返回值但是null串调用String的API 的话会报NullPointerException的错误。一般情况下都会使用if(str ! null str.length() ! 0)对字符串进行检查符合条件才会使用这个字符串。 String StringBuilder StringBuffer的异同 这三种都是Java中用来操作字符串的类。区别就是String声明一个不可变的对象每次操作都会生成一个新的对象并将新的对象地址赋给原来的字符串变量而StringBuilder和StringBuffer都是在原来的对象上进行操作并不会产生新的对象所以说在需要经常改变字符串内容的情况下最好使用这两种类。  StringBuilder和StringBuffer也是有区别的StringBuffer线程安全但是性能相对较差StringBuilder线程不安全但是性能较高于是单线程的情况下推荐使用StringBuilder多线程的话就使用安全的StringBuffer 关于输入输出你都知道啥? 输入   若是想要通过控制台进行输入操作的话首先需要创建一个标准输入流对象然后才能使用相应的方法进行键入值的读取根据方法的不同读取的数据类型也不一样。使用new方法实例化scanner对象的时候参数System.in是调用System类的静态常量in这个静态常量的类型就是一个InputStream  使用scanner键入值需要注意next方法获取单个单词的时候如果有不止一个单词的话下一次执行读取方法的时候会继续读取直到单词全都读取完成。读取数值的时候如果键入的类型不对的话会抛出异常如果读取浮点数但是键入一个整数的话会自动转换 输出   文件的输出很简单直接使用System类的静态常量out(PrintStream类型)调用打印方法即可。但是除了普通的输出之外简单的格式化输出也需要掌握比如格式化输出的两种方式out的printf方法和String的format方法 关于控制路程都需要注意啥? 在学习控制流程之前我们需要了解一下块的概念。块就是将若干条Java语句使用一对大括号括起来又叫复合语句。需要注意的是块确定了变量的作用范围块之间可以嵌套而且嵌套的几个块中不能声明同名的变量while循环和do-while循环的区别   while循环执行循环体之前就判断是否应该执行循环体而do-while循环在执行循环体之后才进行判断所以说while的循环体可能一次都不执行但是do-while至少执行一次 for循环   关于for循环相信大家都已将不陌生了接下来说的就是一些大家可能忽略的点。for循环内部定义变量的作用范围只在for循环里当循环结束时变量也就失去了作用。循环条件尽量不要使浮点数否则的话由于舍入误差的存在极有可能永远无法达到精确地最终值也就是说会陷入死循环switch语句   switch语句中有很多需要大家注意的点。首先switch语句中的case标签值不能重复。其次choice选项和case标签的类型只能是char、byte、short、int的常量表达式或者包装类枚举常量(标签中不用指定枚举名可从choice选项推知)String字符串切记choice选项不可以是一个条件判断语句   最后如果没有break;语句的话会触发多个case分支又被称为是switch的穿透性也就是说如果case分支语句没有break语句的话会从与choice相匹配的那个case分支开始执行之后所有的case分支语句直至遇到break语句或者执行完整个switch语句。但是switch的穿透性也并不一定就是坏事我们可以利用其穿透性完成一些代码的简化  流程中断语句break 结束当前所在循环、条件语句或者switch所在分支的执行。continue 结束本次循环继续下一次循环(只能在循环语句中使用) 数组初始化的小细节 一维数组的初始化   数组就是用来存储相同类型的一个序列所以在声明的时候需要使用数据类型对元素类型进行规范而且一旦数组创建之后就固定了元素的类型与个数一旦超出数组的长度就会抛出ArrayIndexOutOfBoundsException异常  以上四种初始化方式第二种不能指定数组长度否则会报错第三种不能直接将一个大括号中的所有元素赋值给一个已经声明过的数组变量需要new之后再赋值第四种声明的的时候必须指定数组长度(可以为0)且声明之后只能按照索引一个一个的进行初始化未初始化之前使用数组类型对应的默认值 多维数组(以二维数组为例)   由此可见二维数组的初始化方式与一维数组基本一致其实就是一维数组存储一维数组一般都是借助行和列的概念理解数组的两个维度。使用第四种方式对二维数组进行初始化的时候必须指定二维数组的行数(列数可以不指定)。还有一点就是[]的位置不固定可以是int[ ] a[ ]、int a[ ][ ]但一般都用int [ ] [ ] a的形式 类?对象?他们的关系又是什么 类是一种抽象概念是构造对象的模块和蓝图类是具有相同特性和行为的对象的抽象化。正如我们之前所知的标准Java库中提供了很多的类供我们使用但要是想要实现自定义的功能我们还是需要创建一些自己的类以便描述自己的应用程序业务。   众所周知Java是一门面向对象程序设计语言(Object Oriented Programming OPP )在Java中万物都是对象对象是一个具体的概念拥有特定的行为和状态且对象的行为和状态之间会产生相互影响。   类是一种抽象化概念对象是一种具体的概念。对象可以通过类的实例化方式构造出来类是对一种具有相同特性和行为对象的抽象化体现 关于构造器你都知道什么? 首先是构造器的简介构造器又被称为构造函数要想使用对象的话首先必须要构造对象在构造对象的同时构造器会运行并初始化类中字段的初始状态。关于构造器我们还需要了解的是 构造器的命名应该与类相同每个类中能够有一个以上的构造器构造器的参数可以是0~无数个构造器没有返回值千万别在构造器中定义与实例字段同名的局部变量构造器总是伴随着new操作串一起使用来实例化对象(实例化出来的Java对象都在堆中存储使用new实例化会返回该对象的地址) 如果在自定义类编写的时候不去定义构造器的话编译器会默认提供一个该类的无参构造器无参构造器没有初始化方法体的话就会初始化所有的变量成该数据对应类型的默认值有初始化方法体就按方法体对字段进行初始化。只要是你定义了一个有参构造器的话就不会提供默认的无参构造器此时使用无参构造器实例化对象的话就会产生异常 关于类的源文件都需要知道什么? 一个.java文件中只能有一个public修饰的公共类但是可以有任意数目的非公共类源文件的文件名就是由这个public修饰的类名加上.java后缀组成  当编译这个拥有两个类的.java文件时编译器会在目录下创建两个类文件一个是Test_construtor.class字节码文件一个是Test.class字节码文件  main方法可以在任意一个类中(公共类或者非公共类都可以)运行的时候需要将包含main方法的类名交给java.exe程序即可执行返回相应的结果  使用通配符*可以完成多个源文件的调用这里假设People类和Fork类被分别放在两个源代码文件中如果编译器发现People源文件使用到了Fork类的时候就会查找名为Fork.class的字节码文件如果找不到就会搜索Fork.java源代码文件进行编译。更重要的是如果Fork.java的字节码文件有更新的话(也就是内容发生改变)java编译器就会自动重编译这个源代码文件。 重载和重写都是什么?区别又是什么 方法重载   方法重载就是一个类中方法名相同但是方法的参数列表不相同的一堆方法在这里参数列表不同说的是参数的类型、参数的个数、甚至于参数的顺序不一样。访问权限和方法的返回值类型不能作为方法是否重载的判断依据也就是说当方法名和参数列表一样的时候访问权限和方法的返回值类型不管是否一样都不算是方法重载。  方法出现重载的时候说明这个类中有两个以上的同名方法当我们调用方法的时候编译器是如何确定到底调用的是哪个方法的呢这就要说到重载解析的概念了重载解析就是使用各个重载方法中的参数类型与特定方法调用所使用的值类型和顺序进行匹配从而挑选出正确的方法当然都找不到的话就会产生编译时异常。   方法签名的概念方法名加参数类型的组合就是方法的签名一个类中无法存在两个签名一样的方法方法重写   方法重写就是当子类继承超类之后就拥有了超类的属性和行为但是超类不想原封不动的使用超类的行为于是就重新定义超类的方法体。方法的重写应该注意的是 重写发生在超类和子类之间重写时的方法名、参数列表、返回值类型相同但是如果重写方法的返回类型是被重写方法返回类型的子类的话也可以重写方法的访问权限要大于被重写方法(publicprotecteddefaultprivate)重写方法不能抛出新的检查异常或者抛出比被重写方法范围更大的异常子类无法重写超类的静态方法和私有方法 重载与重写的区别 1、前者实现的是编译时的多态性而后者实现的是运行时的多态性。 2、重写发生在子类与父类之间重载发生在同一个类里 3、重写同名同参重载同名不同参(同名参数的类型、参数的个数、甚至于参数的顺序) 4、重写的返回值类型要兼容(大于)被重写方法(publicprotecteddefaultprivate)重载对返回值类型没有要求 5、子类无法重写超类的静态方法和私有方法 关键字 this和super关键字 属性和方法的调用   this.属性或方法名调用当前对象中的属性或方法如果本类中没有超类中有的话就调用超类中的都没有的话就报错。super.属性或方法名只访问超类中的属性或方法即使超类没有子类中有的话也会报错构造器的调用   构造器的调用讲究比较大首先构造器的调用使用的是this(参数列表)或者super(参数列表)。当子类继承超类并使用构造器进行初始化的时候子类每一个构造器的第一行会有一行默认的隐式语句super();调用超类的无参构造器且超类的无参构造器会先于子类构造器执行  前面说过如果超类中只定义了一个有参构造器的话就不会提供超类的默认无参构造器如果此时子类再定义构造器的话构造器会默认调用超类的无参构造器这就会产生报错。解决方案就是超类定义无参构造器或者在子类的构造器中显式使用Super(参数列表)调用超类的有参构造器this(参数列表)调用的是本类的构造器super(参数列表)调用的是超类的构造器 instanceof关键字 a instanceof A用来判断某个实例变量a是否属于A这个类的类型如果是的话就返回true否则返回false。instanceof 关键字的使用场景就是在进行向下转型之前判断一下左边的对象是不是右边的子类如果是的话才能进行类型强转否则会出现ClassCastException异常。值得注意的是如果 a instanceof A 返回true的话A的父类放在右边也会返回true但是A的子类放在右边不一定返回true。 static关键字 静态变量   使用static修饰的变量又称为是静态变量或者类变量静态变量的特点是通过该类创建的所有对象共享一个变量一个对象修改静态变量的值会造成其他所有对象对该静态变量的引用值发生改变而不使用static修饰的实例变量的特征是每个对象之间的变量修改互不影响。 静态方法   静态非静态之间的相互调用 使用static修饰的方法又称为是静态方法静态方法和静态变量的使用有很大的相似之处值得注意的是静态方法中无法调用该类的非静态方法和属性而非静态方法可以调用静态和非静态的方法和属性。  静态方法内部无法使用的关键字 值得注意的是静态方法内部无法使用this和super关键字因为this和super都是对对象属性或方法的引用static是随着类的加载而加载也就是说static的加载是在对象之前的static加载的时候还没有对象的存在所以说此时调用会产生逻辑错误导致报错  静态方法无法被重写覆盖 此外比较重要的就是static修饰的方法不能被重写覆盖也就是说子类和超类中满足重写条件的static方法不会导致重写覆盖使用多态调用的还是超类中的方法与此同时private修饰的方法封装在超类中也无法被重写  静态方法调用 还有就是我们都知道static修饰的方法可以使用类名直接调用但是并不是所有的静态属性都可以直接使用类名进行调用因为如果静态属性被private修饰的话就无法使用类名调用所以说“静态属性可以直接用类名调用”这句话是不正确的应该是“非私有的静态属性可以直接用类名调用” final关键字 修饰类使用final修饰的类无法被其他类所继承也就说该类不能拥有子类比如String、System等类   修饰方法使用final修饰的方法无法被重写   修饰变量使用final修饰的“变量”的值无法修改也就是一个常量常量的初始化方式可以是显示初始化代码块初始化构造器初始化   修饰局部变量可以在方法体中使用final修饰一个局部“变量”或者方法的参数使用final修饰无论是哪一种该局部“变量”的值都无法进行修改 权限修饰符的作用范围知多少? Java中一共定义了四种权限修饰符它们按照作用范围从小到大依次是private、缺省(default)、protected、public其中缺省的意思就是在不使用权限修饰符的情况下就默认使用该修饰符。这四种权限修饰符可用来修饰属性、方法、构造器、内部类等结构但是类只能使用缺省和public修饰   这里我以属性的调用为例方法的效果与之相同接下来就使用代码测试上图结论方便大家的理解。首先是属性定义的本类内部属性定义所在类的所在package下的所有类中属性定义所在类的不同package有继承关系的子类属性定义所在类的不同package的普通类 抽象类 一旦一个类使用abstract修饰之后就代表他是一个抽象类抽象类的显著特征就是抽象类无法实例化。有的小伙伴可能就会问了既然抽象类已经无法实例化对象了那么它是不是已经没有构造器了不抽象类依然有构造器构造器的作用就是供子类继承的时候进行调用。 抽象方法 方法声明 使用abstract修饰的方法只能有方法的声明不能有方法体和大括号   抽象的类与方法之间的关系 包含抽象方法的类一定是一个抽象类但是抽象类中不一定包含抽象方法。而且继承了抽象类的子类必须重写该类中所有的抽象方法(如果超类的超类中也有抽象方法此时子类也必须重写超类的超类的抽象方法)否则子类也要使用abstract修饰成抽象类这样的话子类也就无法实例化  abstract关键字不能使用的地方 关键字只能用来修饰类和方法无法修饰属性、构造器、代码块等结构。方法的声明中abstract关键字无法与private修饰符、static关键字、final关键字一起使用因为私有、静态、final方法无法被子类重写但是抽象方法必须被子类重写产生冲突。类的声明中abstract关键字无法与final关键字一同使用final类无法被继承那么它内部的抽象方法也就无法被重写发生冲突 interface接口 接口的存在类似于抽象类将一些类的共同行为特征作为抽象方法抽取出来但是他又和抽象类有着本质的不同接口根本就不是一个类。接下来我们就使用抽象类来类比学习接口的特性   接口源文件 接口虽然不是一个类但是接口在很多方面和类十分相似比如接口的源文件接口的源文件也是一个.java文件可以通过javac命令进行编译生成.class字节码文件。一个.java源文件中只能有一个使用public修饰的类或者接口这个源文件是以public修饰的接口或者类命名的 接口的结构 JDK 7以及之前,接口中只能定义公共静态常量和公共抽象方法JDK 8之后接口中还可以定义公共静态方法和公共默认方法所以说接口中的公共静态常量在声明的时候可以省略public static final接口中的方法声明可以省略public 静态、默认方法 接口中的静态方法无法通过接口实现类的对象调用只能使用接口直接调用但是接口中的默认方法可以通过接口实现类的对象调用而且抽象超类中的静态方法也可以使用子类对象调用。子类无法重写覆盖超类或者接口中的静态方法但是可以重写覆盖接口中的默认方法  接口构造器 接口中绝对不能出现构造器这也就意味着接口无法实例化创建对象虽然抽象类也无法实例化创建对象但是抽象类中有构造器(供子类调用) 接口的实现和方法引用 接口的实现 接口无法实现接口但是可以继承接口或者类类使用implements关键字实现接口一个类可以实现多个接口使用逗号隔开。接口中定义了抽象方法实现接口的类必须重写这个接口中的所有抽象方法(包括通过extends继承来的抽象方法)否则这个类就必须定义成抽象类就近引用、类优先、接口冲突原则   ① 如果一个类的超类和超类的超类中定义了一个同名变量或者方法的话由于就近原则这个类中引用的就是超类的变量或者方法如果子类中也有的话就近调用子类的  ②类和接口是一个同一级的概念如果一个类的超类和它实现的接口中定义一个同名变量这就会导致这个类中变量的引用不明确而报错但是如果是方法的话就默认类优先原则调用超类中的方法  ③还有一种就是一个类实现多个接口中有同名变量或方法由于接口冲突此时引用这个变量或者方法就会报错需要重写该方法或者重定义该变量 总结来说同名情况下多重继承变量方法都就近一接口一超类变量报错、方法类优先多实现接口冲突 为什么使用接口 可能有的小伙伴要问了既然抽象类跟接口都可以定义抽象方法它们的子类或者实现类也都必须实现这个抽象方法那么为什么不直接使用抽象类而是大费周折的再引入接口的概念呢抽象类毕竟是个类类的话就只能继承一个抽象超类但是一个类可以实现无数多个接口也就是说接口弥补了Java语言只能单继承的局限性。   接口的使用很广泛比如想要实现序列化就要实现Serializable 接口想要自定义对象的排序规则就要实现Comparable接口重写CompareTo方法…… 方法的参数传递机制是什么? 方法的参数分为基本数据类型和引用数据类型基本数据类型就是前面说过的四类八种具体都有什么可以参考下面这篇博客的 强制and自动类型转换都是啥?这个问题部分在Java中除了这四类八种的基本数据类型之外都是引用数据类型 在答题之前先介绍一下相关概念 参数传递机制的两个专业术语按值调用 表示方法接收到的是调用者提供的值按引用调用 表示方法接收到的是调用者提供的变量地址   方法参数的两种形式实参 就是方法调用的时候方法名后面的括号里的数据形参 方法声明的时候方法名后面的括号里的数据这就相当于一种局部变量只在它定义的这个方法内部有效 基本数据类型作参数 基本数据类型的参数采用按值调用的传递机制也就是说方法调用的时候传过去的是一个值但是实参并没有作为这个值传递过去而是将实参的副本传递过去方法中对形参的所有操作都是对实参副本的操作并不会更改实参的实际值 引用数据类型作参数 引用数据类型的参数也是采用按值调用的传递机制也就是说方法调用的时候传过去的也是一个值一个实参的副本  但是看上面代码的运行结果许多小伙伴是不是就懵了不是说传过去的是一个实参的副本嘛为什么实参的结果值也互换了呢?别急仔细想一想数组是一个引用数据类型也就是说是一个对象那么实参的arr是个什么当然是这个数组对象的引用地址了那么传递的副本也就是这个引用地址的副本形参按照这个引用地址的副本对数组进行操作当然也就按照地址操作了实参所对应的对象的值了。这么一来是不就是可以解释的通了 综上所述Java中方法的参数传递机制只有按值调用并没有按引用调用 关于包装类你都知道什么? 包装类基本知识 数据类型包装类储存空间(byte)大小byteByte1字节8位shortShort2字节16位intInteger4字节32位longLong8字节64位floatFloat4字节32位doubleDouble8字节64位charCharacter2字节16位booleanBoolean1字节8位 每一个基本数据类型都有一个对应的包装类前六个类派生于公共的超类Number 包装类是不可变的也即是说一旦构造了包装器就不允许更改它的值与此同时包装类还是final修饰的因此不能派生他们的子类 泛型也必须声明成包装类型而不能使用基本数据类型 包装类的值比较需要使用equals方法而不是判断 什么是装箱和拆箱 Java主张一切皆对象的思想但是基本数据类型并不是对象于是就有了包装类的概念。其中装箱就是将一个基本数据类型包装成其对应的包装类拆箱就是反过来将包装类拆成对应的基本数据类型数据。根据转换时是否使用方法装箱又称为自动拆装箱和手动拆装箱 手动装箱有两种方式使用包装类构造器、包装类的valueOf方法手动拆箱有一种包装类的xxxValue方法。但是Java提供了自动拆装箱如果二者相互复赋值的话就会自动转换而无需使用方法进行转换且使用泛型定义的集合添加基本数据类型的时候也会自动装箱为对应的包装类型 装箱和拆箱是编译器的工作 包装类cache缓存机制 从Java5开始包装类新增了自动拆、装箱的功能除此之外还新增了cache缓存机制该机制会将取值在一定范围内的值创建成相应的对象缓存起来这些缓存起来的对象在以后使用到的时候就可以直接用这样就可以避免在这个范围内的值重复创建造成的内存损耗从而降低性能。为了更好的了解这个机制让我们看一下接下来的这段代码写下自己的答案然后结合cache机制的解释再推测一下答案最终结果在后面给出  这里通过源码(下面给出)以Integer为例来了解一下包装类的cache缓存机制Integer类中有一个IntegerCache类这个类的主要作用即就是创建[-128,127]之间的所有对象并添加到cache数组中等到调用valueOf方法的时候就使用if判断是不是在范围内如果在的话就直接在cache数组中直接返回反之就使用构造器创建一个对应的Integer对象返回 private static class IntegerCache {static final int low -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h 127;String integerCacheHighPropValue sun.misc.VM.getSavedProperty(java.lang.Integer.IntegerCache.high);if (integerCacheHighPropValue ! null) {try {int i parseInt(integerCacheHighPropValue);i Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high h;cache new Integer[(high - low) 1];int j low;for(int k 0; k cache.length; k)cache[k] new Integer(j);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high 127;}private IntegerCache() {} }public static Integer valueOf(int i) {if (i IntegerCache.low i IntegerCache.high)return IntegerCache.cache[i (-IntegerCache.low)];return new Integer(i); }但是你可能就会有疑问了如果我就使用一次包装类的话它也会缓存创建范围内的所有对象这样的话怎么实现节省内存提升性能呢当然包装类的cache缓存机制是针对大程序而言的概念小程序并不能很好地体现。于是当确定同值对象使用的次数很少时我们就可以使用构造器来创建包装类对象因为缓存类只能通过valueOf方法才会生效   你可能又有疑问了使用自动装箱直接赋值的形式创建包装类对象会不会使用到缓存呢答案是 会因为自动装箱底层就是调包装类对应的valueOf方法那么你是怎么确定的呢对自动装箱和自动拆箱代码编译生成的字节码文件进行反编译得出下面的内容根据12和19行得知自动装箱调用的是valueOf方法自动拆箱调用的是intValue方法 public class com.example.demo.code.AutoPacking {java.lang.Integer i1;int i2;public com.example.demo.code.AutoPacking();Code:0: aload_01: invokespecial #1 // Method java/lang/Object.init:()V4: aload_05: iconst_16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;9: putfield #3 // Field i1:Ljava/lang/Integer;12: aload_013: new #4 // class java/lang/Integer16: dup17: iconst_218: invokespecial #5 // Method java/lang/Integer.init:(I)V21: invokevirtual #6 // Method java/lang/Integer.intValue:()I24: putfield #7 // Field i2:I27: return }通过上面的分析我们就可以很容易的得出上面那段代码的答案了 包装类的缓存范围 数据类型包装类缓存范围整型Byte、Short、Long、Integer[-128,127]浮点型Float、Double无字符型Character[0,127]布尔型Booleantrue、false 关于迭代器你都知道什么? 什么是迭代器? 所谓迭代的意思就是交换替代迭代器并不是一种数据结构或者集合而是可以过迭代器中的方法逐个访问集合中的每个元素的一种方法。提到迭代器最重要的就是Iterator接口所有想要使用迭代器迭代的结构都需要实现或者继承这个接口 迭代器的4个API Iterator接口包含4个方法分别是next、hasNext、remove、forEachRemaining接下来在学习构造器如何使用之前我们先学习一下它的四种方法 首先一开始构造器中的有一个类似于指针的标志指在集合中第一个元素的前面调用构器中的next方法会使这个指针的位置后移到第一和二个元素之间然后返回它跨过的那个元素给构造器也就是第一个元素如果指针到最后一个元素后面再调用next方法的话就会导致抛出NoSuchElementException remove方法则是删除当前元素可以理解为next方法返回的那个元素如果调用remove方法之前没有调用next方法或者是使用remove方法删除该元素之后都会导致构造器为空此时调用remove方法就会导致程序抛出IllegalStateExceptions异常 hasNext方法就是判断迭代器是否还有可迭代的下一个元素如果有的话就返回true否则返回false为了避免上述两种异常remove方法每次都要配合另外两种方法一起使用每次调用next方法之前都使用hasNext方法判断一下避免没有可迭代元素导致的异常每一次调用remove之前都使用next方法获得元素避免迭代器为空导致的状态异常 forEachRemaining方法则可以直接遍历迭代器中的每一个元素并调用方法参数中的Lambda表达式直到迭代器将集合的元素全部迭代完为止 如何使用迭代器 接下来将结合上面的描述给出一个将集合中的元素全部删除的标准代码然后再给出两个错误的代码大家可以在使用迭代器的时候规避一下 正确写法 // 创建一个ArrayList集合 CollectionString strings new ArrayList(); strings.add(a); strings.add(b); strings.add(c); strings.add(d);// 获得迭代器对象 IteratorString iterator strings.iterator();// 使用while循环迭代集合中元素 使用hasNext方法判断 while (iterator.hasNext()) {// 使用next获取下一个元素iterator.next();// 删除这个元素iterator.remove();// 打印集合中元素System.out.println(strings); }错误示范一 // 创建一个ArrayList集合 CollectionString strings new ArrayList(); strings.add(a); strings.add(b); strings.add(c); strings.add(d);// 获得迭代器 IteratorString iterator strings.iterator();// 使用while循环迭代集合中元素 使用next方法判断下一个元素是否为空 while (iterator.next() ! null) {// 输出这个元素System.out.println(iterator.next()); }第一个的错误原因使用next方法判断下一个元素是否为空这样就会导致指针到最后一个元素后面依旧会执行next方法这样就会导致抛出NoSuchElementException异常而且next方法每调用一次都会将指针向后移动一位哪怕只是用于if判断。所以程序就会每隔一个元素输出一次最后抛出NoSuchElementException异常错误示范二 // 创建一个ArrayList集合 CollectionString strings new ArrayList(); strings.add(a); strings.add(b); strings.add(c); strings.add(d);// 获取迭代器对象 并使用while循环迭代集合中元素 使用hasNext方法判断 while (strings.iterator().hasNext()) {// 获取迭代器对象 并使用next获取下一个元素 然后输出System.out.println(strings.iterator().next()); }第二个的错误原因每使用iterator方法获得一次集合对应的迭代器对象都会默认将指针放到第一个元素的前面于是第二个错误示范中一直使用集合中的第一个元素a进行判断有没有下一个元素所以会导致程序陷入死循环循环体里也会一直创建结合的迭代器对象并将指针放到第一个元素的前面然后调用next方法输出元素a Collection集合接口知多少? 集合大体上可以分为两种一种是单列的Collection集合一种就是双列的Map集合所谓的单双列可以理解为元素中数据的个数单列集合一个数据作为元素存储双列集合两个具有映射关系的数据作为元素存储。这一篇我们先学习Collection集合接口的内容Collection集合按照元素存储是否有序又可分为List集合、Set集合 前面之所以先学习Iterator接口的原因就是Collection接口继承了Iteator接口于是它的子接口set和list都可以使用迭代器对集合中的元素进行迭代 为什么不使用数组而是集合? 在学习集合之前我们将存储多个对象或者元素的任务都交给了数组但是数组存储元素有以下缺点①数组一旦初始化之后长度就确定不可修改元素个数超出数组长度的话会抛异常。②数组中提供的API很少增加元素需要现将索引后的元素后移空出位置将元素添加进去删除元素需要删除之后将后面的元素前移将空出来的位置补足以上操作只能通过代码实现并没有现成的API可以使用。③数组存储数据的特点有序、可重复对于一些无需、不可重复的业务需求就很难满足 集合就可以很好的弥补数组的上述缺点而且集合提供了一组较为完善的数据结构我们可以根据具体的业务需求来选择具体使用的集合类型。比如说存储元素无序不可重复的Set集合有序可重复的List集合具有映射关系的Map集合等 Collection接口的API都有什么? AbstractCollection类知多少? 以上给出的Collection接口中的API都是抽象方法也就意味着每一个此接口的实现类都需要重写这些抽象方法实际上Collection接口的直接或者间接实现类有很多如果每一个都需要重写这些方法的话就会很是麻烦。于是Java类库的设计者提供了AbstractCollection类该类中只将size方法和Iterator方法声明为抽象其他方法都提供了默认实现如果子类不提供这些方法的方法体的话就使用该类中的默认实现   下图可知list、set集合的实现类都直接或者间接的继承了AbstractCollection类为的就是简化重写Collection接口的抽象方法 关于List集合类你都知道什么? 我们将实现了List接口的类称为是List集合类List集合类中元素存储有一个特点有序、可重复List接口常用的有三个实现类ArrayList、LinkedList和Vector List接口三个实现类的异同? 三者相同点ArrayList、LinkedList和Vector都实现了List接口所以它们存储数据的特点都一致那就是有序、可重复 ArrayList和Vector相比相同点就是底层结构上都用到了Object [ ]数组存储元素不同点就是ArrayList是线程不安全但是效率高的而Vector是线程安全但是效率低的造成这个不同的原因就是Vector中的方法都使用了同步锁这样在保证线程安全的同时也会降低它的效率 ArrayList和LinkedList相比最大的不同点就是底层存储结构上面说过ArrayList使用的是Object [ ]数组存储元素而LinkedList则是使用双向链表进行存储双向链表的特点就是将每一个元素都存储在一个单独的链接(link)中这个链接由三部分组成上一个链接的运用、数据、下一个链接的引用双向链表就是通过上下引用将所有的链接链成一张表。  要知道数组最令人诟病的就是对元素的添加和删除每次操作都需要移动它后面的所有元素双向链表每次添加和删除元素只和它前后的两link有关只需要改变上下链接的引用即可这样的话就可以很好的解决这个弊端。于是涉及到频繁的添加删除操作的话可以选择使用LinkedList集合。但是由于数组中可以使用索引快速定位一个元素而链表则是需要从头开始顺着链查找所以涉及到频繁的查询数据可以选择使用ArrayList ArrayList类源码浅析 ArrayList的源码在jdk 7和jdk 8之间还是有些设计上的不同的接下来就通过对两个版本的分析来体会不同点并思考一下jdk 8改变设计的原因 jdk 7  使用无参构造器创建一个ArrayList对象会调用它的有参构造器并传参为10也就是说使用无参构造器默认创建一个长度为10的Object [ ]数组。然后每次调用add方法添加元素之前都会通过ensureCapacityInternal方法判断当前集合再添加新元素也就是集合中元素个数size 1之后会不会大于数组长度如果超过的话就调用grow方法进行扩容。扩容的时候先将当前数组长度扩大1.5倍如果扩大1.5之后还是无法没有size 1大的话直接将扩容后的数组长度设置为size 1如果扩大1.5倍之后大于给定的常量值判断size 1有没有大于这个常量值大于的话数组长度设为整型的最大值否则就设置成给定的常量值至此扩容后的数组长度newCapacity就确定了然后就是调用Arrays工具类的copyOf方法将原来的数组内容拷贝到长度为newCapacity的新数组中 数组扩容完成之后就是添加新元素回到add方法中将参数元素添加到数组中索引为size的位置然后size 1索引向后移(这一步就是size的效果)如果数组无需扩容的话就直接执行添加操作 jdk 8  jdk 8 的时候ArrayList集合调用无参构造器默认创建一个空数组对象而不是一个有长度的数组这样做的好处就是可以节省内存提高效率。jdk 7 就是创建一个长度为10的数组这样的话一旦加载ArrayList类就会给数组定义长度就要按照长度分配内存空间而jdk 8 中则使用空数组解决了这个问题等到ArrayList集合调用add方法添加元素的时候才会动态的创建数组类似于单例设计模式的懒汉模式思想 调用add方法都会发生什么呢根据上图源码浅析一下为了便于区分不同的方法调用使用不同颜色标记。 ①在添加元素之前先将当前集合再添加新元素时的长度也就是集合中元素个数size 1之后的值使用②③方法进行一系列的判断 ②判断当前的数组是否为空如果为空的话返回默认数组长度10与size 1之间的最大值否则直接返回size 1 ③将②中的返回值作为参数执行③方法判断size 1的大小是否大于数组的长度如果是的话就调用grow方法扩容 ④扩容的机制和jdk 7中的一致先将当前数组长度扩大1.5倍如果扩大1.5之后还是无法没有size 1大的话直接将扩容后的数组长度设置为size 1如果扩大1.5倍之后大于给定的常量值判断size 1有没有大于这个常量值大于的话数组长度设为整型的最大值否则就设置成给定的常量值至此扩容后的数组长度newCapacity就确定了然后就是调用Arrays工具类的copyOf方法将原来的数组内容拷贝到长度为newCapacity的新数组中 根据上述分析梳理jdk 8 的时候ArrayList集合第一次添加元素流程首先实例化ArrayList对象的时候会调用无参构造器创建一个空数组对象然后第一次调用add方法添加元素会被拦截到方法②③进行判断执行方法②的时候数组为空对象执行判断体返回10(默认数组长度)和1(size 1)的最大值10然后执行方法③10(方法②返回值作③的参数)减去0(空数组长度)0执行判断体中方法④扩容数组空数组扩容1.5倍还是小于10所以将新数组长度定为10并将原空数组拷贝到新数组中(这一步虽然像废话但是代码中定义有)最后将add的参数放到索引为0的位置然后索引自增1 所以说有了上面分析的前车之鉴大家如果在使用ArrayList集合的时候明显知道元素的个数或者知道一定多于10个的话可以在创建ArrayList对象的时候使用有参构造器指定底层数组的长度这样的话就可以避免向集合对象中添加元素的时候多次扩容提高程序的效率 LinkedList类源码浅析 LinkedList类内部定义了一个内部类Node也就是双向链表的一个节点前面讲过它是由三个部分组成于是内部类Node也包含三个属性与之对应(上一个节点的引用 prev、数据 item、下一个节点的引用 next)然后使用无参构造器创建LinkedList对象底层什么都不创建并不会像ArrayList一样创建一个数组啥的但是它会默认初始化类属性first(头结点)和last(尾结点)为null。 再之后就是调用add方法添加元素了add方法底层使用的是linkLast方法第一次添加元素的时候last的值为默认初始化的null否则就是原链表的尾节点。将last的值赋值给 l 这个 l 就是通过有参构造器创建Node对象时的第一个参数也就是将新链接的前一个节点引用指向原链表的尾节点然后第二个参数是数据e第三个参数是null(因为将数据添加到了最后后面没有节点了所以下一个节点的引用为null)。将创建好的Node对象赋值给last也就是指定新链表的尾节点l 是null的话就将创建好的Node对象也赋值给first也就是指定新链表的头节点表示新链表的头节点和尾节点都是新建节点(因为第一次添加元素双向链表中就只有一个节点)如果不为null的话就将原链表的尾节点的下一个链接的引用指向新节点。 根据上面的分析可以得知所有的新节点都链在了双向链表的尾部所以这种方法就是双向链表的尾插法 vector类源码简析 由于vector类已经很久未更新于是它的底层源码就和ArrayList的jdk 7 版本的几乎一致使用无参构造器创建对象的时候会调用有参构造器创建一个长度为10的object数组只不过是扩容的时候会扩容到原来长度的2倍它和ArrayList的区别前面也说过就是vector的方法上都加了锁因此会牺牲性能来保证线程安全 关于Set集合类你都知道什么? 与list集合相类似的是我们将实现了Set接口的类称为是Set集合类Set集合类中元素存储有一个与List集合类正好相反的特点无序、不可重复Set接口常用的有三个实现类HashSet、LinkedHashSet和TreeSet 如何理解Set集合的无序不可重复? 无序性指的是每次新增的元素都根据元素的哈希值向set中进行存储而非按照元素新增的顺序从左到右向set集合依次存入。 不可重复性指的是Set中新增的元素不会与已有的元素重复判断是否重复的标准是先使用hashCode方法获取元素的哈希值找到新元素的位置如果结该位置已经有元素的话再判断哈希值是否相等如果还相等的话再使用equals()方法判断如果还相等的话就说明说明该元素已经存在不可添加进set 以上述不可重复性的判断标准引用数据类型元素判断是否已重复的依据就是引用地址因为hashCode()和equals()在未重写之前就是根据引用数据类型地址进行哈希值计算和判等就算是属性相等的自定义类对象依旧会被set集合认定为非重复元素。   于是想要属性相等的自定义类对象不再添加到set集合的话就要重写自定义类的hashCode()和equals()方法 Map集合类 首先HashSet的底层实现就是直接使用了HashMap所以可以借助Set的知识来学习Map集合   Map集合相对于单列集合Collection而言是一种双列集合也就是说Map集合中的所有元素都是成对出现的一般存储的都是KV键值对的形式尽管如此实际上Map存储元素使用的是EntryKV是Entry中的两个属性。Map集合主要有以下实现类HashMap、LinkedHashMap、TreeMap、Hashtable、Properties他们的关系如下图所示 Map集合类之间的区别 相同点都直接或间接实现了Map接口所以它们存储数据的特点都一致那就是键值对形式、无序、不可重复。 HashMap与Hashtable HashMap是新类线程不安全的但是效率高Hashtable是老类线程安全的但是效率低。除此之外HashMap的KV都可以存储null但是Hashtable都不能存储null否则就会抛出NullPointerException异常。   HashMap与LinkedHashMap 只有一点区别那就是LinkedHashMap因为使用了双向链表的前后元素指向 所以可以实现按照元素的添加顺序遍历集合元素。   TreeMap 底层使用红黑树可以实现对集合元素进行排序具体的排序规则可以参考TreeSet的自然排序和定制排序的设置方法   Properties 是Hashtable的子类它的KV都是String类型常用于处理配置文件 HashSet类、HashMap类数据结构及源码浅析 由上图源码可知HashSet底层使用的就是HashMapHashSet新增元素就是把元素作为键向底层的HashMap新增一个元素。所以要知道HashSet的底层原理就要知道HashMap。   HashMap的源码在jdk 7和jdk 8之间还是有些设计上的不同的接下来就通过对两个版本的分析来体会不同点并思考一下jdk 8改变设计的原因 jdk 7   使用无参构造实例化HashMap对象的时候底层调用默认初始化容量是16、默认加载因子是0.75有参构造器进行实例化。有参构造器首先判断初始容量是否小于零小于零抛出异常然后判断初始容量是否大于定义的最大容量大于将初始容量赋值为定义的最大容量再判断加载因子是否小于等于0或者为nan满足则抛出异常。然后使用while循环左移运算(左移一次相当于乘2一回)找到数组的长度接着通过min(数组的长度乘以加载因子和定义的最大容量1)得到数组长度临界值threshold(作为后面扩容的依据)最后创建出来Entry数组。 使用put()方法新增元素的时候先判断key是null的话直接新增或者value替换然后调用hash()计算key的哈希值此哈希值经过某种算法indexFor(哈希与length-1进行与运算)计算得到在Entry数组中的存放位置如果此位置上的数据为空直接添加成到此位置。如果此位置上的数据不为空使用for循环判断新元素key与链表上的所有元素key的哈希值和equals是否都相等都相等的话将value进行替换。都不相等的话也添加新元素。 添加新元素的时候先判断是否需要扩容也就是判断当前位置没有元素并且当前数组元素大于等于数组长度临界值threshold如果满足的话就将数组长度扩容为原来的2倍然后重新计算链表在新数组上的存放位置。最后再将新元素作为头元素放到该位置上的链表中也就是createEntry方法中的先取出数组中该位置上链表的头元素然后将新元素的下一个节点指向原链表形成新链表然后将新链表放到数组的该位置上 jdk 8   使用无参构造实例化HashMap对象的时候底层只有一个默认加载因子是0.75的赋值操作也就是说没有涉及到任何的数组创建。 使用put()方法新增元素的时候 先判断tab数组是否为null或者长度为0如果是的话就执行resize()方法进行扩容扩容过程下面讲。然后经过某种算法(哈希与length-1进行与运算)计算得到在Node数组中的存放位置如果此位置上的数据为空直接添加成到此位置否则的话判断新元素key与链表上头元素key的哈希值和equals是否都相等相等value替换不等且链表不为红黑树循环对比链表中剩余元素找到替换找不到新增。每次新增之前都判断是否超过数组长度临界值(是否需要扩容)每次在链表上新增之后都判断链表长度是否超过8且数组长度是否超过64超过就将链表转换为红黑树存储。 使用resize()方法进行扩容的时候分为两种原数组长度为零且原加载因子为0和原数组长度非零且原加载因子非0。第一种原数组长度为零且原加载因子为0即初次执行put操作经过if和else if最终进入else里新数组赋值默认初始化容量是16计算新数组长度临界值即默认初始化容量是16乘以默认加载因子是0.75依次创建新数组。第二种则是通过左移运算将长度扩容为原来的2倍。 当新增一个数组之后大于数组长度时list才会进行扩容原长度加上右移一位也就是原长度的1.5倍而map在超过数组长度临界值时就会扩容(直接2倍)这样做是为了避免数组中的链表过多也就是说同一位置上的元素尽可能多一些形成树形存储而数组长度临界值默认初始化容量是16乘以默认加载因子是0.75于是可以通过减小加载因子从而增大数组的扩容频率提高map中数据的读取效率 LinkedHashSet类、LinkedHashMap类源码浅析 与上面一样LinkedHashSet底层使用的就是LinkedHashMap要知道LinkedHashSet的底层原理就要知道LinkedHashMap   LinkedHashMap的所有方法都是使用super()继承自父类也就是HashMap但是LinkedHashSet却可以按照元素的添加顺序输出原因是LinkedHashMap使用的Entry在继承Node的基础上又添加了before和after属性这样既可以知道同一位置链表上的下一个元素(next)还可以知道元素添加前后元素(before、after)从而可以按照元素的添加顺序输出 TreeSet类、TreeMap类源码浅析 与上面一样TreeSet底层使用的就是TreeMap要知道TreeSet的底层原理就要知道TreeMap TreeMap的key必须是同一类型的数据因为TreeMap会根据key进行排序输出如果是不同类型的元素会报ClassCastException排序方式可以分为自然排序和定制排序 自然排序 自然排序就是自定义类实现Comparable接口然后重写compareTo方法在该方法中定义排序规则。如果compareTo方法的返回值为0的话TreeSet就会认为该元素为重复元素重复元素不可添加进数组里。 Data public class Student implements Comparable{private String name;private int age;Overridepublic int compareTo(Object o) {if (o instanceof Student){Student student (Student) o;int compare this.name.compareTo(student.name);if (compare ! 0) {return compare;} else {return Integer.compare(this.age, student.age);}}else {throw new RuntimeException(输入的格式有误);}} }public class TestFour {public static void main(String[] args) {Set set new TreeSet();Student stu new Student(Tom,20);Student stu1 new Student(Jerry,20);Student stu2 new Student(Mary,23);Student stu3 new Student(June,21);set.add(stu);set.add(stu1);set.add(stu2);set.add(stu3);for (Object o : set) {System.out.println(o);}} }定制排序 定制排序是新建一个Comparator对象作为TreeSet对象实例化的参数重写compare方法在该方法中定义排序规则。如果compareTo方法的返回值为0的话TreeSet就会认为该元素为重复元素重复元素不可添加进数组里。 Data public class Student{private String name;private int age;}public class TestFour {public static void main(String[] args) {Set set new TreeSet(new Comparator(){Overridepublic int compare(Object o1, Object o2) {Student stu1 (Student)o1;Student stu2 (Student)o2;int result stu2.getStuAge() - stu1.getStuAge(); if(result 0){result stu2.getStuName().compareTo(stu1.getStuName());}return result;}});Student stu new Student(Tom,20);Student stu1 new Student(Jerry,20);Student stu2 new Student(Mary,23);Student stu3 new Student(June,21);set.add(stu);set.add(stu1);set.add(stu2);set.add(stu3);for (Object o : set) {System.out.println(o);}} }自然排序是自定义类中实现Comparable接口然后重写compareTo方法定制排序是新建一个Comparator对象作为TreeSet对象实例化的参数重写compare方法。 如果自然排序和定制排序同时存在时定制排序优先级更高
http://www.pierceye.com/news/475040/

相关文章:

  • 免费的html网站东丽手机网站建设
  • 网站建设谈客户说什么广州网站快速制作
  • 寻花问柳专注做男人喜爱的网站做网站教程 第一课
  • 个人做外贸接订单网站简道云crm
  • 小程序免费制作平台教学东莞seo关键词
  • 微网站设计平台网络营销相关的岗位有哪些
  • 手机网站建设软件有哪些内容如何做自己的加盟网站
  • 做购物平台网站 民治农业信息网站建设方案
  • 苏州网站建设找哪家东莞seo关键词排名优化排名
  • 怎么描述网站设计软件排行榜
  • 宁波网站制作优化服务公司推广找客户平台
  • 个人网站 域名选择在线画图网页版
  • 外贸网站建设双语网站建设广州景点
  • 深圳宝安p2p网站系统的建设手机网站开发+手机模拟器
  • 合肥营销网站建设设计网站关键词做标签
  • 网站建设与管理实训心得怎么写wordpress图片太大
  • 用个人免费空间快速建立个人网站后方平台网络营销案例2022
  • 网站搭建h5是什么做图软件ps下载网站有哪些内容
  • 企业网站推广技巧和方法免费个人简历模板官网
  • wordpress 全站备份网站建设的实验心得体会
  • 给网站开发APP网站可信度必须做吗
  • 用地方名字做网站做网站那种语言好
  • 天河网站(建设信科网络)濮阳市城乡一体化示范区主任
  • 扬州网站建设link5深圳建外贸网站
  • 网站开发用什么编程淘宝网站开发方式
  • 网站ui设计包括哪些原则网站flash代码
  • 北京建设执业注册中心网站北京中兴时代网站建设
  • 深圳建站公司设计肥城网站建设推广
  • 对网站建设服务公司的看法wordpress主题自定义打不开
  • 宁夏电力建设工程公司门户网站万能编程软件