基础语法
- 计算机语言的分类:
1、机器语言(二进制语言),由0和1组成的序列。
2、汇编语言:用助记符描述的指令系统。
3、面向过程(如c,vb等)。
4、面向对象(如c++,c#,java等)。 - 标识符
标识符的含义:
是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。 - 命名规则:(硬性要求)
标识符可以包含英文字母,0-9的数字,$以及_
标识符不能以数字开头
标识符不是关键字 - 命名规范:(非硬性要求)
类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。
变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。
方法名规范:同变量名 - java变量命名的要求:
1、合法的标识符,由字母,数字,_和$符组成,但不能为数字开头;
2、严格区分大小写;
3、变量不能使用java关键字;
4、可以以汉字命名,但不建议使用,命名应该见名知意。 - java变量使用的要求:
1、必须与数据类型匹配;
2、对变量的使用就是对它存储的数据的使用;
3、变量的使用必须声明并初始化。 - 相对路径和绝对路径:
1、路径的作用在于指明一个文件或目录在文件系统中的位置;
2、相对路径:文件或目录相对于当前工作目录的位置,如“soft01/workspace”表示当前目录下的soft01目录下的workspace,有两个特殊的相对路径
“.”表示当前目录,“…”表示上级目录;
3、绝对路径:文件或目录相对于根目录的位置,绝对路径都以“/”开始。 - JDK目录结构:
1、bin目录:用于存放JDK的工具命令;
2、jre目录:用于存放Jdk所包含的JRE,其中包含有JVM和核心类库;
3、lib目录:用于存放JDK工具命令所对应的工具包
4、demo目录:用于存放一些示例程序
5、src-zip文件:用于存放核心类库的java源代码 - instanceof
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过
Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true
//在JavaSE规范中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回false
System.out.println(null instanceof Object);
基本数据类型
- 变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间,内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。
- Java的两大数据类型:
- 内置数据类型(基本数据类型):4种整数类型(byte、short、int、long),2种浮点数类型(float、double),1种字符类型(char),1种布尔类型(boolean)
- 引用数据类型。
- 基本数据类型(8种):
- byte
byte 数据类型是8位、有符号的以二进制补码表示的整数;
最小值是 -128(-2^7);
最大值是 127(2^7-1);
默认值是 0;
byte 类型用在大型数组中节约空间,主要代替整数,因为byte变量占用的空间只有int类型的四分之一;
例子:byte a = 100,byte b = -50。 - short
short 数据类型是16位、有符号的以二进制补码表示的整数;
最小值是 -32768(-2^15);
最大值是 32767(2^15 - 1);
short 数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一;
默认值是 0;
例子:short s = 1000,short r = -20000。 - int
int数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);
最大值是 2,147,483,647(2^31 - 1);
一般地整型变量默认为int类型;
默认值是 0 ;
例子:int a = 100000, int b = -200000。 - long
long数据类型是64 位、有符号的以二进制补码表示的整数;
最小值是 -9,223,372,036,854,775,808(-2^63);
最大值是 9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是 0L;
例子: long a = 100000L,Long b = -200000L;
"L"不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩,所以最好大写。 - float
float数据类型是单精度32位、符合IEEE754标准的浮点数, 1 位符号位,8 位指数,23 位有效尾数;
float 在储存大型浮点数组的时候可节省内存空间;
默认值是 0.0f;
浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f;
"F"不分大小写。 - double
double数据类型是双精度64位、符合IEEE754标准的浮点数, 1 位符号位,11 位指数,52 位有效尾数;
浮点数的默认类型为double类型;
double类型同样不能表示精确的值,如货币;
默认值是 0.0d;
例子:double d1 = 123.4;
"D"不分大小写。
double运算时会出现舍入误差 - char
char类型是一个单一的16位Unicode字符;
最小值是\u0000(即为0);
最大值是\uffff(即为65,535);
char 数据类型可以储存任何字符;
默认值是空;
例子:char letter = ‘A’;。
char类型事实上是一个16为的无符号整数,这个值是对应字符的编码。java字符类型采用Unicode字符集编码。
Unicode是世界通用的定长字符集,所有的字符都是16位,Unicode又称为通用码,统一码或万国码。
字符a实际值为97,字符A实际值为65,字符0实际值为48。
对char型变量赋值有以下几种方式:
1、字符直接量,如: char c = ‘A’;
2、整型直接量,范围0-65535之间,变量中实际存储的即该整型值,但表示的是该整型值所对应的Unicode字符,如 char c = 65;
3、Unicode形式,形如’\u0041’,Unicode字符的16进制形式,如 char c = ‘\u0041’; - boolean
boolean数据类型表示一位的信息;
只有两个取值:true 和 false;
这种类型只作为一种标志来记录true/false情况;
默认值是 false;
例子:boolean one = true
package com.ziania.util;
public class NumberTest {
private byte byteVal;
private short shortVal;
private int intVal;
private long longVal;
private char charVal;
private boolean booleanVal;
private float floatVal;
private double doubleVal;
public static void main(String[] args) {
NumberTest number = new NumberTest();
System.out.println("byte的默认值:" + number.getByteVal());//
System.out.println("short的默认值:" + number.getShortVal());
System.out.println("int的默认值:" + number.getIntVal());
System.out.println("long的默认值:" + number.getLongVal());
System.out.println("float的默认值:" + number.getFloatVal());
System.out.println("double的默认值:" + number.getDoubleVal());
System.out.println("char的默认值:" + number.getCharVal());
System.out.println("char默认值转int:" + (int)number.getCharVal());
System.out.println("boolean的默认值:" + number.isBooleanVal());
}
public byte getByteVal() {
return byteVal;
}
public void setByteVal(byte byteVal) {
this.byteVal = byteVal;
}
public short getShortVal() {
return shortVal;
}
public void setShortVal(short shortVal) {
this.shortVal = shortVal;
}
public int getIntVal() {
return intVal;
}
public void setIntVal(int intVal) {
this.intVal = intVal;
}
public long getLongVal() {
return longVal;
}
public void setLongVal(long longVal) {
this.longVal = longVal;
}
public char getCharVal() {
return charVal;
}
public void setCharVal(char charVal) {
this.charVal = charVal;
}
public boolean isBooleanVal() {
return booleanVal;
}
public void setBooleanVal(boolean booleanVal) {
this.booleanVal = booleanVal;
}
public float getFloatVal() {
return floatVal;
}
public void setFloatVal(float floatVal) {
this.floatVal = floatVal;
}
public double getDoubleVal() {
return doubleVal;
}
public void setDoubleVal(double doubleVal) {
this.doubleVal = doubleVal;
}
}
- 引用数据类型:
在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
对象、数组都是引用数据类型。
所有引用类型的默认值都是null。
一个引用变量可以用来引用任何与之兼容的类型。
例子:Site site = new Site(“Runoob”)。 - 1、整数的直接量默认为int类型,如果直接写出的整数超过int的表示范围,将会出现编译错误;
- 2、整型数据除法运算中取整,小数部分无条件舍弃;
- 3、两个整数运算时,其结果可能会超出整数的范围而发生溢出;
- 4、double运算时会出现舍入误差。
// 类型间的转换
// 1、自动类型转换(隐式类型装换):从小类型到大类型可以自动完成;
// 2、强制类型转换:从大类型到小类型需要强制转换符,强制类型转换有可能造成精度损失或者溢出;
// 3、数值运算时的自动类型转换:多种基本类型参与的表达式运算中,运算结果会自动的向较大的类型转换;
// 4、byte、char、short转换为int,这三种类型实际存储的数据都是整数,在实际使用中遵循如下规则:
// 4.1、int直接量可以直接赋值给byte、char、short,只要不超过其表示的范围,若超过其范围则会编译错误;
// 4.2、byte、char、short三种类型参与运算时,先转换成int类型再进行运算。
//编译失败
short s1 = 1;
s1 = s1 + 1;
//正常编译
short s1 = 1;
s1 += 1;
//编译失败
short s1 = 1;
short s2 = 3;
short sum = s1 + s2;
//正常编译
short s1 = 1;
short s2 = 3;
short sum = (short)(s1 + s2);
// 对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
// += 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。
- 虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
Class.forName 与 ClassLoader.loadClass
- 类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
- 装载:查找和导入类或接口的二进制数据;
- 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
2.1.校验:检查导入类或接口的二进制数据的正确性;
2.2.准备:给类的静态变量分配并初始化存储空间;
2.3.解析:将符号引用转成直接引用; - 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
//Class.forName(String className)其实调用的方法是Class.forName(className,true,classloader)
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
//ClassLoader.loadClass(String className)其实他调用的方法是ClassLoader.loadClass(className,false)
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
- 因此Class.forName(String className),Class被loading后经过了初始化;ClassLoader.loadClass(String className),Class没有被链接。
newInstance: 弱类型,低效率,只能调用无参构造。–动态加载
new: 强类型,相对高效,能调用任何public构造。–静态加载
final修饰符:
1、final修饰变量:变量不能被改变,如果修饰引用,那么表示引用不可变,引用指向的内容可变.;
2、final修饰方法:方法不能被重写;
3、final修饰类:类不能被继承;
4、被final修饰的方法,JVM会尝试将其内联,以提高运行效率;
5、被final修饰的常量,在编译阶段会存入常量池中。
- 内联函数就是在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来直接进行替换。
ArrayList和LinkedList的区别
- ArrayList和LinkedList的区别
1、ArrayList是基于索引的数据接口,它的底层是数组,物理地址是连续的。它可以以O(1)时间复杂度对元素进行随机访问;
2、LinkedList是以元素列表的形式存储它的数据,物理地址不必是连续的,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
3、相对于ArrayList,LinkedList的添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
4、LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素; - ArrayList和Vector的区别
1、线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
2、性能:ArrayList 在性能方面要优于 Vector。
3、扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。 - ArrayList和Array的区别
1、Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
2、Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
3、Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。
4.ArrayList可以存储多种类型的数据,Array只能存储一种类型的数据。
Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据,(因为删除数据以后, 需要把后面所有的数据前移)
缺点: 数组初始化必须指定初始化的长度, 否则报错。
例如:
int[] a = new int[4];//推介使用int[] 这种方式初始化
int c[] = {23,43,56,78};//长度:4,索引范围:[0,3]
- List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
List有两个重要的实现类:ArrayList和LinkedList;
ArrayList: 可以看作是能够自动增长容量的数组;
ArrayList的toArray方法返回一个数组;
Arrays的asList方法返回一个列表;
ArrayList底层的实现是Array, 数组扩容实现。 Arrays.copyOf(); - LinkedList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁。
抽象类和接口的区别:
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象;
- 抽象类要被子类继承,接口要被类实现;
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现;jdk1.8接口中也可做方法实现default
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量;
- 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类;
- 抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){};
- 抽象类里可以没有抽象方法;
- 如果一个类里有抽象方法,那么这个类只能是抽象类;
- 抽象方法要被实现,所以不能是静态的,也不能是私有的;
- 接口可继承接口,并可多继承接口,但类只能单根继承;
- 抽象类可以有构造器,接口不能有构造器;
- 抽象方法可以有public、protected或default修饰,但接口方法默认修饰符是public;
- 抽象类:
- 抽象类中可以定义构造器
- 可以有抽象方法和具体方法
- 抽象类中的成员可以是 private、默认、protected、public
- 抽象类中可以定义成员变量
- 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
- 抽象类中可以包含静态方法
- 一个类只能继承一个抽象类
- 接口:
- 接口中不能定义构造器
- 方法全部都是抽象方法
- 接口中的成员全都是 public 的
- 接口中定义的成员变量实际上都是常量
- 接口中不能有静态方法,jdk1.8及以后可以有static和default方法
- 一个类可以实现多个接口
- 相同:
- 不能够实例化
- 可以将抽象类和接口类型作为引用类型
- 一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类访问修饰符 public、private、protected、default
访问修饰符 public、private、protected、default
- public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
- private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
- protect: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
- default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。
深拷贝与浅拷贝
- 引用拷贝与对象拷贝:
- 引用拷贝:正如它的名称所表述的意思, 就是创建一个指向对象的引用变量的拷贝。如果我们有一个 Car 对象,而且让 myCar 变量指向这个变量,这时候当我们做引用拷贝,那么现在就会有两个 myCar 变量,但是对象仍然只存在一个。
- 对象拷贝:会创建对象本身的一个副本。因此如果我们再一次服务我们 car 对象,就会创建这个对象本身的一个副本, 同时还会有第二个引用变量指向这个被复制出来的对象。
- 深拷贝与浅拷贝:
1、深拷贝和浅拷贝都是对象拷贝;
2、对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。"里面的对象“会在原来的对象和它的副本之间共享;
3、不同于浅拷贝,深拷贝是一个整个独立的对象拷贝。如果我们对整个 Person对象进行深拷贝,我们会对整个对象的结构都进行拷贝。
4、Object的clone()方法是浅拷贝。 - 如果想要深拷贝一个对象,这个对象必须要实现 Cloneable 接口,实现 clone方法,并且在 clone 方法内部,把该对象引用的其他对象也要 clone 一份,这就要求这个被引用的对象必须也要实现Cloneable 接口并且实现 clone 方法。
- 如何实现对象克隆
有两种方式。
1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法;
2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用 Object 类的 clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。
数组与链表
- 数组:是将元素在内存中连续存放,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组;
- 链表:链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表;
- 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;
- 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项);
- 存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取;
- 存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定;
- 存储空间上,链表由于带有指针域,存储密度不如数组大;
- 按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);
- 按值查找时,若数组无序,数组和链表时间复杂度均为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
- 插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可;
- 空间分配方面:数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;
Java异常
- 一、基本概念
Java异常结构
Throwable是所有异常的根,java.lang.Throwable
Error是错误,java.lang.Error
Exception是异常,java.lang.Exception - 二、Exception
一般分为Checked异常(也叫编译时异常或强制性异常)和Runtime异常(也叫运行时异常或非强制性异常),所有RuntimeException类及其子类的实例被称为Runtime异常,不属于该范畴的异常则被称为CheckedException。
- Checked异常
只有java语言提供了Checked异常,Java认为Checked异常都是可以被处理的异常,所以Java程序必须显示处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种
1 当前方法知道如何处理该异常,则用try…catch块来处理该异常。
2 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。
我们比较熟悉的Checked异常有
Java.lang.ClassNotFoundException
Java.lang.NoSuchMetodException
java.io.IOException - RuntimeException
特点:Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fast机制产生的ConcurrentModificationException异常(java.util包下面的所有的集合类都是快速失败的,“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制,这个错叫并发修改异常。Fail-safe,java.util.concurrent包下面的所有的类都是安全失败的,在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap迭代器弱一致的表现。ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap一样了。)等,都属于运行时异常。
我们比较熟悉的RumtimeException类的子类有
Java.lang.ArithmeticException
Java.lang.ArrayStoreExcetpion
Java.lang.ClassCastException
Java.lang.IndexOutOfBoundsException
Java.lang.NullPointerException
java.lang.NumberFormatException
java.lang.IllegalArgumentException
java.lang.ClassNotFoundException
- 三、Error
当程序发生不可控的错误时,通常做法是通知用户并中止程序的执行。与异常不同的是Error及其子类的对象不应被抛出。
Error是throwable的子类,代表编译时间和系统错误,用于指示合理的应用程序不应该试图捕获的严重问题。
Error由Java虚拟机生成并抛出,包括动态链接失败,虚拟机错误等。程序对其不做处理。 - 异常处理:try catch finally
不管有没有出现异常,finally块中代码都会执行;
当try和catch中有return时,finally仍然会执行;
finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
public class MyTest {
private volatile String string;
public static void main(String[] args) {
System.out.println(call());//4
}
public static int call() {
try {
return 2;
} catch (Exception exp) {
return 3;
} finally {
return 4;
}
}
}
public class MyTest {
private volatile String string;
public static void main(String[] args) {
System.out.println(call());//4
}
public static int call() {
try {
return 2/0;
} catch (Exception exp) {
return 3;
} finally {
return 4;
}
}
}
public class MyTest {
private volatile String string;
public static void main(String[] args) {
System.out.println(call());//3
}
public static int call() {
try {
return 2/0;
} catch (Exception exp) {
return 3;
} finally {
// return 4;
}
}
}
- error 和 exception 的区别:
Error 类和 Exception 类的父类都是 Throwable 类,他们的区别如下。
Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception 类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,
出现这类异常,程序会终止。而受检查的异常,要么用 try。。。catch 捕获,要么用 throws 字句声明抛出,交给它
的父类处理,否则编译不会通过。
switch…case注意事项:
1、case后面的值各不相同;
2、default语句可以放到任何位置,放到最后时break可以省略,放到其他位置放与不放break的结果不同;
3、case后的数据可以为:byte、short、int、char和字符串,注意没有long型整数;
4、switch…case通常与break搭配使用,以保证一个case对应一个功能;
5、switch…case较if…else if结构清晰,效率高。
6、Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始Java 中引入了枚举类型,expr 也可以是 enum 类型。从 Java 7 开始expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
类和对象:
1、真实存在的个体称为对象;
2、类是对象的模板,对象是类的实例化;
3、一个类可以创建多个对象,同一个类的对象,结构相同,数据不同;
4、类包括两部分:
4.1、描述该类共有的特性和属性,类的成员变量;
4.2、描述该类共有的行为,类的方法。
构造方法:
1、构造方法的作用是在创建对象时用来对成员变量赋初值;
2、构造方法名与类名相同,构造方法无返回值,也不能有void;
3、构造方法不会手动调用,jvm在创建对象时自动调用;
4、如果一个类没有显示写上构造方法,那么编译器会自动为该类添加一个无参的构造方法;
5、如果一个类已经显示的写上构造方法,那么编译器不会再为该类添加无参构造方法,此时如果调用无参的构造方法会编译错误;
6、任何类都有构造方法,且必须有构造方法;
7、构造方法可以以重载的形式存在多个;
8、接口中没有构造方法,抽象类中有构造方法。
成员变量与局部变量的区别:
1、定义位置不同:成员变量定义在类中,方法外;局部变量定义在方法中;
2、默认值不同:成员变量有默认的初始值;局部变量没有默认的初始值;
3、生命周期不同:成员变量随对象的创建而创建,随对象的销毁而销毁;局部变量随方法的调用而存在,方法调用结束时消失;
4、作用域不同。
正则表达式:
1、正则表达式就是使用一系列预定义的特殊字符来描述一个字符串的格式规则,然后使用该规则匹配某个字符串是否符合格式的要求。
2、[abc]a,b,c中的任意一个;
3、[^abc]除了a,b,c的任意字符;
4、[a-z]a到z中的任意一个字符;
5、[a-zA-Z0-9]a到z,A到Z,0到9的任意一个字符;
6、[a-z&&[^bc]]a到z不包含b,c中的任意一个字符;
7、.表示任意字符
8、\d表示任意一个数字字符,相当于[0-9];
9、\w单词字符,相当于[a-zA-Z0-9_];
10、\s空白字符,相当于[\t\n\f\r];
11、\D非数字字符,相当于[^0-9];
12、\W非单词字符,相当于[^a-zA-Z0-9_];
13、\S非空白字符;
14、x? 表示0个或1个x;
15、x* 表示0个或多个x;
16、x+ 表示1个或多个x;
17、x{n} 表示n个x;
18、x{n,}表示至少n个x;
19、x{n,m}表示n个到m个;
包装类:
1、包装类都是final修饰的不能被继承;
2、8中基本类型分别有对应的包装类,其中6(byte,short,int,long,float,double)种继承Number类,另外2(char和boolean)种继承Object;
3、Integer有一个字节的缓存,因此
int i = 127;
Integer i1 = new Integer(i);
Integer i2 = new Integer(i);
System.out.println(i1 == i2); //false
Integer i3 = Integer.valueOf(i);
Integer i4 = Integer.valueOf(i);
System.out.println(i3 == i4); //true
对象流要求被序列化的对象所属的类实现Serializable接口,java编译器在编译实现该接口的类时会自动插入3个方法这些方法将被对象流调用,用于对象序列化,对象序列化可以实现对象属性的持久化。
XML文件的读与写
常见实体引用:
1、< <
2、> >
3、& &
4、&apos ’
5、" "
6、<![CDATA[]]>
- XML的解析方式
- SAX:(Simple API For XML)是一种XML解析的替代方式,相比于DOM,SAX是一种速度更快,更有效的方法,它逐行扫描文档,一边扫描,一遍解析,而且相比于DOM,SAX可以在解析文档的任意时刻停止解析。
优点:解析可以立即开始,速度快,没有内存压力;
缺点:不能对节点做修改。 - DOM:(Document Object Model)即文档对象模型是W3C组织推荐的处理XML的一种方式。DOM解析器在解析XML文档时,会把文档中的所有元素按照其出现的层次关系解析成一个个Node对象(节点)。
优点:把XML文件在内存中的构建树形结构,可以遍历和修改节点;
缺点:如果文件比较大,内存有压力,解析的时间会比较长。
- 读XML的大致流程:
- 创建SAXReader;
SAXReader xmlReader = new SAXReader(); - 使用SAXReader读取数据源,并生产一个Document对象;
Document doc = xmlReader.read(new File(String xmlFileName); - 通过Document对象获取根节点;
Element root = doc.getRootElement(); - 根据XML文档结构从根元素开始逐层获取子元素最终达到遍历XML文档内容的目的。
- 写XML的大致流程:
- 创建一个Document对象表示一个空文档;
Document doc = DocumentHelper.createDocument(); - 向Documet中添加根元素;
Element root = doc.addElement(String rootName); - 按照文档应有的结构从根元素开始顺序添加子元素形成该文档结构;
- 创建XMLWriter对象;
XMLWriter writer = new XMLWriter(); - 将Document对象写出。
FileOutputStrem fos = new FileOutputStrem(new File(String fileName));
writer.setOutputStrem(fos);
writer.writer(doc);
writer.close();
- Xpath是一门在XML文档中查找信息的语言,XPath可以用来在XML文档中对元素和属性进行遍历。DOM需要一层一层定位,而XPath定位轻松,可以根据路径,属性,甚至是条件进行节点的检索。
字符串常量池
- 字符串常量池的设计思想
- 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
- JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
为字符串开辟一个字符串常量池,类似于缓存区
创建字符串常量时,首先检查字符串常量池是否存在该字符串
存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中 - 实现的基础
实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收
- 堆
存储的是对象,每个对象都包含一个与之对应的class
JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定 - 栈
每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)
每个栈中的数据(原始类型和对象引用)都是私有的
栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)
数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失 - 方法区
方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量
字符串常量池则存在于方法区 - 字符串对象的创建
面试题:String str4 = new String(“abc”) 创建多少个对象?
- 在常量池中查找是否有“abc”对象
有则返回对应的引用实例
没有则创建对应的实例对象 - 在堆中 new 一个 String(“abc”) 对象
- 将对象地址赋值给str4,创建一个引用
- 所以,常量池中没有“abc”字面量则创建两个对象,否则创建一个对象,以及创建一个引用
根据字面量,往往会提出这样的变式题:
String str1 = new String(“A”+“B”) ; 会创建多少个对象?
String str2 = new String(“ABC”) + “ABC” ; 会创建多少个对象?
str1:
字符串常量池:“A”,“B”,“AB” : 3个
堆:new String(“AB”) :1个
引用:str1 :1个
总共 :5个
str2 :
字符串常量池:“ABC” : 1个
堆:new String(“ABC”) :1个
引用:str2 :1个
总共 :3个
转发(forward)和重定向(redirect)的区别
- forward 是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。
- redirect 就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显 redirect 无法访问到服务器保护起来资源,但是可以从一个网站 redirect 到其他网站。
java.util.Map
- Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap,类继承关系如下图所示:
- (1) HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻如果有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。 HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题,Java中HashMap采用了链地址法。链地址法,简单来说,就是数组加链表的结合。在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。
HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的;
HashMap最多只允许一条记录的键为null,允许多条记录的值为null;
HashMap非线程安全;
ConcurrentHashMap线程安全;
解决碰撞:当出现冲突时,运用拉链法,将关键词为同义词的结点链接在一个单链表中,散列表长m,则定义一个由m个头指针组成的指针数组T,地址为i的结点插入以T(i)为头指针的单链表中;
Java8中,冲突的元素超过限制(8),用红黑树替换链表。 - (2) Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
- (3) LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
- (4) TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
对于上述四种Map类型的类,要求映射中的key是不可变对象。不可变对象是该对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map对象很可能就定位不到映射的位置了。
Java加载机制
- vm和类的关系
当调用java命令运行一个java程序时,必会启动一个jvm,即java虚拟机。
该java程序的所有线程,变量都处于jvm中,都使用该jvm的内存区。 - jvm终止的情况:
- 程序自然运行结束
- 遇到System.exit();
- Runtime.getRuntime.exit();
- 遇到未捕获异常或错误时
- 程序所在的平台强制结束了JVM进程
jvm终止,jvm内存中的数据全部丢失。
- 类的加载
类的加载 又称为 类的初始化,实际上可细分为 类的 加载、连接、初始化。下面将讲述着三个阶段的过程!
类的加载 指.class文件读入内存,并为之创建一个 java.lang.Class对象
类加载,是通过类加载器来完成的,类加载器通常由JVM提供,通常称为系统类加载器(也可以是自己写的加载器,只要继承ClassLoader基类)。
类加载无须等到“首次使用该类”时加载,jvm允许预加载某些类。。。。
加载来源:
- 本地.class文件
- jar包的.class文件
- 网络.class文件
- 把一个java源文件动态编译,加载。
- 类的连接
负责把类的二进制数据合并到JRE(java运行环境)中
- 验证 检测被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的类变量(非对象变量)分配内存,并设置默认初始值
- 解析 将类的二进制数据中的符号引用替换成直接引用。。(static final 好像跟这个有点关系)
- 类初始化
主要对类变量(而非对象变量)的初始化
声明类变量的初始值 = 静态初始化块 他们是相同的,等效的。都会被当成类的初始化语句,JVM会按照这些语句在程序中的顺序依次执行它们
JVM初始化一个类包含如下几个步骤:
- 假设类还没有被加载和连接,那么先加载和连接该类;
- 假设该类的父类还没被初始化,那么先初始化父类; ----jvm总是最先初始化java.lang.Object类
- 假设类中有初始化语句,则依次执行这些初始化语句。
- 类初始化的时机
1.创建类的实例(new,反射,反序列化);
2.使用某类的类方法–静态方法;
3.访问某类的类变量,或赋值类变量;
4.反射创建某类或接口的Class对象Class.forName(“Hello”);—注意:loadClass调用ClassLoader.loadClass(name,false)方法,没有link,自然没有initialize;
5.初始化某类的子类;
6.直接使用java.exe来运行某个主类。即cmd java 程序会先初始化该类。
6.类加载器
类加载器负责加载所有的类,为被加载入内存中的类生成一个java.lang.Class实例。一旦类被载入内存,同一个类就不会再加载第二次。
- 如何判断是同一个类:
java中 一个类用其 全限定类名标示–包名+类名
jvm中 一个类用其 全限定类名+加载器标示—包名+类名+加载器名 - 加载器层次结构:
JVM启动时,姓曾的三个类加载器组成的机构
- Bootstrap ClassLoader 根类 ------引导类加载器,加载java核心类。非java.lang.ClassLoader子类,而是JVM自身实现;
- Extension ClassLoader 扩展类-----加载JRE的扩展目录中的JAR包的类(%JAVA_HOME%/jre/lib/ext或java.ext.dirs系统属性指定的目录);
- System ClassLoader 系统类-----加载cmd java -cp **,环境变量指定的jar包和类路径。ClassLoader.getSystemClassLoader获得 系统类加载器;
- 用户类加载器。
- 类的加载机制
- 全盘负责。某类以及其所依赖的所有类,都由一个加载器负责加载。除非显示使用另外一个加载器。
- 父类委托。先父类加载器加载该Class,不行后,才尝试从自己的类路径中加载该类。
- 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存。当程序需要Class时,先从缓存区中寻找Class对象,没有的话,才加载该类的.class对象。
- 类加载器加载Class大致要经过9个步骤
- 检测此Class 是否被载入过(即在缓存区中是否由此 Class),有,则进入第8步,否则执行第2步。
- 如果父类加载器不存在(要么parent 一定是根类加载器,要么本身就是根类加载器),则跳到第4步;如果父类加载器存在,则执行第3步。
- 请求使用父类加载器去载入目标类,如果成功则跳到第8步,否则执行第5步
- 请求使用 根类加载器 载入目标类,成功则跳到第8步,否则跳到第7步
- 当前类加载器 尝试寻找 Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,否则跳到第7步。
- 从文件中载入Class,成功后跳到第8步。
- 抛出ClassNotFoundException异常。
- 返回对应的 java.lang.Class对象。
其中 第5,6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程。
Jdk1.8新特性
- Lambda表达式
Lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。
//匿名内部类
Comparator<Integer> cpt = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> set = new TreeSet<>(cpt);
System.out.println("=========================");
//使用lambda表达式
Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);
TreeSet<Integer> set2 = new TreeSet<>(cpt2);
Lmabda表达式的语法总结: () -> ();
前置 | 语法 |
无参数无返回值 | () -> System.out.println(“Hello WOrld”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
- 函数式接口
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。 - 什么是函数式接口?
简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface
常见的四大函数式接口
Consumer :消费型接口,有参无返回值
Supplier 《T》:供给型接口,无参有返回值
Function 《T,R》::函数式接口,有参有返回值
Predicate《T》: 断言型接口,有参有返回值,返回值是boolean类型 - 方法引用
若lambda体中的内容有方法已经实现了,那么可以使用“方法引用”,
也可以理解为方法引用是lambda表达式的另外一种表现形式并且其语法比lambda表达式更加简单。
(a) 方法引用
三种表现形式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名 (lambda参数列表中第一个参数是实例方法的调用 者,第二个参数是实例方法的参数时可用)
(b)构造器引用
格式:ClassName::new
©数组引用格式:
Type[]::new
- Stream API
Stream操作的三个步骤
- 创建stream
- 中间操作(过滤、map)
- 终止操作
- stream的创建:
// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
List<String> list = new ArrayList<>();
Strean<String> stream1 = list.stream();
// 2.通过Arrays的静态方法stream()获取数组流
String[] str = new String[10];
Stream<String> stream2 = Arrays.stream(str);
// 3.通过Stream类中的静态方法of
Stream<String> stream3 = Stream.of("aa","bb","cc");
// 4.创建无限流
// 迭代
Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);
//生成
Stream.generate(() ->Math.random());
- Stream的中间操作:
/**
* 筛选 过滤 去重
*/
list.stream().filter(e -> "CC".equals(e))
.limit(4)
.skip(4)
.distinct()
.forEach(System.out::println);
/**
* 生成新的流 通过map映射
*/
list.stream()
.map((e) -> e.getBytes())
.forEach(System.out::println);
/**
* 自然排序 定制排序
*/
emps.stream()
.sorted((e1 ,e2) -> {
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else{
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
- Stream的终止操作:
- LocalDate 与 LocalTime API,最终,有一个稳定、简单的日期和时间库可供你使用扩展方法,
- 接口中可以有静态(static)、默认(default)方法;
- 重复注解,现在你可以将相同的注解在同一类型上使用多次。
Java反射
- Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 获取Class对象的三种方式:
Object ——> getClass();
任何数据类型(包括基本数据类型)都有一个“静态”的class属性;
通过Class类的静态方法:forName(String className)(常用)。
jdbc就是典型的反射, hibernate,struts等框架使用反射实现的。
Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类
- 反射机制的优缺点:
- 优点
能够运行时动态获取类的实例,提高灵活性。 - 缺点
使用反射性能较低,需要解析字节码,将内存中的对象进行解析。 解决方案:
1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度;
2、多次创建一个类的实例时,有缓存会快很多。
3、ReflectASM工具类,通过字节码生成的方式加快反射速度;
相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
JVM
- JVM加载类的过程,双亲委派模型中有哪些方法
- 类加载过程:加载、连接(连接又分为3个阶段:验证(验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害)、准备(准备阶段为变量分配内存并设置类变量的初始化)和解析(解析过程是将常量池内的符号引用替换成直接引用))和初始化。
- 双亲委派模型中方法:双亲委派是指如果一个类收到了类加载的请求,不会自己先尝试加载,先找父类加载器去完成。当顶层启动类加载器表示无法加载这个类的时候,子类才会尝试自己去加载。当回到发起者加载器还无法加载时,并不会向下找,而是抛出ClassNotFound异常。
- 方法:启动(Bootstrap)类加载器,标准扩展(Extension)类加载器,应用程序类加载器(Application ),上下文(Custom)类加载器。意义是防止内存中出现多份同样的字节码 。
- GC算法(什么样的对象算是可回收对象,可达性分析),CMS收集器
jvm是如何判断一个对象已经变成了可回收的“垃圾”,一般是两个方法:引用记数法和根搜索算法。
引用记数法没办法解决循环引用的问题,所以用根搜索。从一系列的”GC Roots“对象开始向下搜索,搜索走过的路径称为引用链。当一个对象到”GC Roots“之间没有引用链时,被称为引用不可达。引用不可到的对象被认为是可回收的对象。 - 几种垃圾收集器:
1、Serial New/Serial Old(串行);
2、Parrallel New (并行);
3、Parrallel Scavenge;
4、Parrallel Old;
5、CMS(CMS收集器是一个以获得最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-sweep算法);
6、G1(是一款并行与并发收集器,并且可建立可预测的停顿时间模型,整体上是基于标记清理,局部采用复制)。 - JVM分为哪些区,每一个区干吗的
方法区(method):被所有的线程共享。方法区包含所有的类信息和静态变量;
堆(heap):被所有的线程共享,存放对象实例以及数组,Java堆是GC的主要区域;
栈(stack):每个线程包含一个栈区,栈中保存一些局部变量等;
程序计数器:是当前线程执行的字节码的行指示器。 - JVM新生代,老年代,持久代,都存储哪些东西
持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。所有新生成的对象首先都是放在年轻代的,年老代中存放的都是一些生命周期较长的对象。 - 内存溢出和内存泄漏
内存溢出:程序申请内存时,没有足够的内存,out of memory;
内存泄漏:指垃圾对象无法回收,可以使用memory analyzer工具查看泄漏。 - 进程与线程
进程指运行中的程序(独立性,动态性,并发性),线程指进程中的顺序执行流。区别是:1.进程间不共享内存; 2.创建进程进行资源分配的代价要大得多,所以多线程在高并发环境中效率高。 - 序列化与反序列化
序列化指将java对象转化为字节序列,反序列化相反。主要是为了java线程间通讯,实现对象传递。只有实现了Serializable或Externalizable接口类对象才可被序列化。 - Java 中堆和栈有什么区别
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。 - 进程间通信有哪几种方式
1)管道(Pipe);
2)命名管道(named pipe);
3)信号(Signal);
4)消息(Message)队列;
5)共享内存;
6)内存映射(mapped memory);
7)信号量(semaphore);
8)套接口(Socket) - 操作系统什么情况下会死锁
所谓死锁:是指多个进程在运行过程中因争夺资源而造成的一种僵局。
产生的原因:
竞争资源:当系统中多个进程使用共享资源,并且资源不足以满足需要,会引起进程对资源的竞争而产生死锁;
进程间推进的顺序非法:请求和释放资源的顺序不当,也同样会导致产生进程死锁。 - 产生死锁的四个条件
1.互斥条件(进程独占资源);
2.请求与保持(进程因请求资源而阻塞时,对已获得的资源保持不放);
3.不剥夺条件(进程已获得的资源,在末使用完之前,不能强行剥夺);
4.循环等待(若干进程之间形成一种头尾相接的循环等待资源关系)。 - 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
线程同步与否跟阻塞非阻塞没关系,同步是个过程,阻塞是线程的一种状态。多个线程操作共享变量时可能会出现竞争。这时需要同步来防止两个以上的线程同时进入临界区内,在这个过程中后进入临界区的线程将阻塞,等待先进入的线程走出临界区。 - 同步和异步有什么区别?
同步和异步最大的区别就在于:同步需要等待,异步不需要等待。同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。 - 实现线程的几种方法
1)继承Thread类,重写run方法;
2)实现Runnable接口,重写run方法;
3)实现Callable接口,重写call方法。 - TCP( Transmission Control Protocol)如何保证可靠传输?三次握手过程?
在TCP的连接中,数据流必须以正确的顺序送达对方。
TCP的可靠性是通过顺序编号和确认(ACK)来实现的。
TCP连接是通过三次握手进行初始化的。
三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口大小信息。
第一次是客户端发起连接;
第二次表示服务器收到了客户端的请求;
第三次表示客户端收到了服务器的反馈。 - 常用的hash算法有哪些?
1)加法hash:所谓的加法Hash就是把输入元素一个一个的加起来构成最后的结果;
2)位运算hash:这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素;
3)乘法hash:33*hash + key.charAt(i)。 - TCP协议与UDP协议有什么区别
TCP(Transmission Control Protocol)的缩写,是一种面向连接的保证传输的协议,在传输数据流前,双方会先建立一条虚拟的通信道。很少差错传输数据。
UDP(User Datagram Protocol)的缩写,是一种无连接的协议,使用UDP传输数据时,每个数据段都是一个独立的信息,包括完整的源地址和目的地,在网络上以任何可能的路径传到目的地,因此能否到达目的地,以及到达目的地的时间和内容的完整性都不能保证。
所以TCP比UDP多了建立连接的时间。相对UDP而言,TCP具有更高的安全性和可靠性。
TCP协议传输的大小无限制,一旦连接被建立,双方可以按照一定的格式传输大量的数据,而UDP是一个不可靠的协议,大小有限制,每次不能超过64K。
Java集合
- HashMap 与 HashTable 的区别:
- 两者父类不同
- HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
- 对外提供的接口不同
Hashtable比HashMap多提供了elments() 和contains() 两个方法。
elments() 方法继承自Hashtable的父类Dictionnary,elements() 方法用于返回此Hashtable中的value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。 - 对null的支持不同
HashMap允许空的键值对, 但最多只有一个空对象,而HashTable不允许。Hashtable:key和value都不能为null。HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null。 - 安全性不同
HashTable同步,而HashMap非同步,效率上比HashTable要高,HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。 - 初始容量大小和每次扩充容量大小不同
HashMap初始容量16,扩充容量扩大一倍;
HashTable初始容量11,扩充容量扩大一倍再加1。 - 计算hash值的方法不同
//HashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//HashTable
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
- TreeMap
TreeMap用红黑树实现。 - ConncurrentHashMap和HashTable
ConncurrentHashMap和HashTable比较(两个线程并发访问map中同一条链,一个线程在尾部删除,一个线程在前面遍历查找,问为什么前面的线程还能正确的查找到后面被另一个线程删除的节点)
ConcurrentHashMap融合了HashTable和HashMap二者的优势。HashTable是做了同步的,即线程安全,HashMap未考虑同步。所以HashMap在单线程情况下效率较高。HashTable在的多线程情况下,同步操作能保证程序执行的正确性。但是HashTable是阻塞的,每次同步执行的时候都要锁住整个结构,ConcurrentHashMap正是为了解决这个问题而诞生的;
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术(一个Array保存多个Object,使用这些对象的锁作为分离锁,get/put时随机使用任意一个)。它使用了多个锁来控制对hash表的不同部分进行的修改。在JDK 1.6中,有HashEntry结构存在,每次插入将新添加节点作为链的头节点(同HashMap实现),而且每次删除一个节点时,会将删除节点之前的所有节点拷贝一份组成一个新的链,而将当前节点的上一个节点的next指向当前节点的下一个节点,从而在删除以后有两条链存在,因而可以保证即使在同一条链中,有一个线程在删除,而另一个线程在遍历,它们都能工作良好,因为遍历的线程能继续使用原有的链。
Java8中,采用volatile HashEntry保存数据,table元素作为锁;从table数组+单向链表+红黑树。红黑树是一种特别的二叉查找树,特性为:1.节点为红或者黑 2.根节点为黑 3.叶节点为黑 4.一节点为红,则叶节点为黑 5.一节点到其子孙节点所有路径上的黑节点数目相同。 - Java 中,Comparator 与Comparable 有什么不同
Comparable 接口用于定义对象的自然顺序,是排序接口,而 comparator 通常用于定义用户定制的顺序,是比较接口。我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。 - Object中定义了哪些方法
clone(), equals(), hashCode(), toString(), notify(), notifyAll(), wait(), finalize(), getClass()
其中finalize和clone为protected;equals、hashCode、toString、notify、notifyAll、wait和getClass为public。
String、StringBuilder和StringBuffer
- String
String不可变,每一次执行“+”都会新生成一个新对象,所以频繁改变字符串的情况中不用String,以节省内存。 - StringBuilder
StringBuilder可变,并没有对方法进行加同步锁,所以是非线程安全的。 - StrngBuffer
StringBuffer可变,并没有对方法进行加同步锁,所以是非线程安全的。 - String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。Java 9改成byte数组存储。
private final char[] value;//jdk 1.8
private final byte[] value;//jdk 1.9
每次+操作:隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法拼接后面的字符。
StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到
char[] value;
他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作 ,抽象类AbstractStringBuilder内部都提供了一个自动扩容机制,当发现长度不够的时候(初始默认长度是16),会自动进行扩容工作,扩展为原数组长度的2倍加2,创建一个新的数组,并将数组的数据复制到新数组,所以对于拼接字符串效率要比String要高。另外StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
- Effective Java 中对 不可变类 的解释:
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并且在对象的整个生命周期内固定不变。为了使类不可变,要遵循下面五条规则:
- 不要提供任何会修改对象状态的方法。
- 保证类不会被扩展。 一般的做法是让这个类称为 final 的,防止子类化,破坏该类的不可变行为。
- 使所有的域都是 final 的。
- 使所有的域都成为私有的。 防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象。
- 确保对于任何可变性组件的互斥访问。 如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。
- Effective Java 中总结了不可变类的特点:
1.不可变类比较简单。
2.不可变对象本质上是线程安全的,它们不要求同步。不可变对象可以被自由地共享。
3.不仅可以共享不可变对象,甚至可以共享它们的内部信息。
4.不可变对象为其他对象提供了大量的构建。
5.不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。 - 综上所述,String 的确是个不可变类,但是真的没有办法改变 String 对象的值吗?答案肯定是否定的,反射机制可以做到很多平常做不到的事情。
Java自动装箱与拆箱
装箱就是自动将基本数据类型转换为包装器类型(int–>Integer);调用方法:Integer的valueOf(int) 方法;
拆箱就是自动将包装器类型转换为基本数据类型(Integer–>int)。调用方法:Integer的intValue方法;
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:
Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了
Integer i = 10;
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//false
}
}
//Integer有1个字节的缓存([-128,127])导致。
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false
System.out.println(i3==i4);//false
}
}
重写与重载的区别:
- 重写(Override):
就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。 - 重写的特点:
1.发生在父类与子类之间;
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同;
3.访问修饰符的限制一定要不小于被重写方法的访问修饰符(public>protected>default>private);
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常(里氏代换原则)。
5.运行时绑定。 - 重载(Overload):
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。 - 重载的特点:
1.重载Overload是发生在一个类中;
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序);
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回值类型作为重载函数的区分标准;
4.编译时绑定。
equal与==的区别
- 最大的区别是一个是方法一个是运算符。
对于基本类型和引用类型 == 的作用效果是不同的:
基本类型:比较的是值是否相同;
引用类型:比较的是引用是否相同; - == :
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
1、比较的是操作符两端的操作数是否是同一个对象。
2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。
3、比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true,如:int a=10 与 long b=10L 与 double c=10.0都是相同的(为true),因为他们都指向地址为10的堆。
public class MyTest {
public static void main(String[] args) {
int a = 10;
long b = 10L;
double c = 10D;
System.out.println(a == b);//true
System.out.println(a == c);//true
System.out.println(b == c);//ture
}
}
public class MyTest {
public static void main(String[] args) {
System.out.println(new A() == new B());//编译错误
}
}
class A {
}
class B {
}
- equals:
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。 - 总结:
所有比较是否相等时,都是用equals 并且在对常量相比较时,把常量写在前面,因为使用object的equals object可能为null则空指针
在阿里的代码规范中只使用equals ,阿里插件默认会识别,并可以快速修改,推荐安装阿里插件来排查老代码使用“==”,替换成equals。 - == 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
Java 中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用
- 强引用
强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:
String str = new String("str");
- 软引用
它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2 之后提供了SoftReference类来实现软引用。软引用在程序内存不足时,会被回收,使用方式:
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));
可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。
- 弱引用
它也是用来描述非必须对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在 JDK1.2 之后,提供了WeakReference类来实现弱引用。弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:
WeakReference<String> wrf = new WeakReference<String>(str);
可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。
- 虚引用
也称为幻引用,最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2 之后提供了PhantomReference类来实现虚引用。虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue ,使用例子:
PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());
可用场景: 对象销毁前的一些操作,比如说资源释放等。** Object.finalize() 虽然也可以做这类动作,但是这个方式即不安全又低效。
泛型
- 泛型是Java SE 1.5之后的特性,《Java 核心技术》中对泛型的定义是:
“泛型”意味着编写的代码可以被不同类型的对象所重用。
“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如:
List<Integer> iniData = new ArrayList<>();
- 使用泛型的好处
以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。
- Java1.5 引入了泛型,所有的集合接口和实现都大量地使用它。
- 泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。
- 这避免了在运行时出现 ClassCastException,因为你将会在编译时得到报错信息。
- 泛型也使得代码整洁,我们不需要使用显式转换和 instanceOf 操作符。
- 它也给运行时带来好处,因为不会产生类型检查的字节码指令。
Java创建对象有几种方式:
- new创建新对象;
- 通过反射机制;
- 采用clone机制;
- 通过序列化机制
static都有哪些用法:
- 修饰变量,静态变量;
- 修饰方法,静态方法;
- 静态块,多用于初始化操作;
public calss PreCache{
static{
//执行相关操作
}
}
- 修饰内部类,静态内部类;
- 静态导入,import static xxxx,static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名,比如:
import static java.lang.Math.*;
public class Test{
public static void main(String[] args){
//System.out.println(Math.sin(20));传统做法
System.out.println(sin(20));
}
}
OOM与SOF
- OutOfMemoryError异常:除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。
- Java Heap 溢出
一般的异常信息:java.lang.OutOfMemoryError:Java heap spacess
java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。 - 虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常,这里需要注意当栈的大小越大可分配的线程数就越少。 - 运行时常量池溢出
异常信息:java.lang.OutOfMemoryError:PermGenspace
如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。 - 方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
异常信息:java.lang.OutOfMemoryError:PermGenspace
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。 - SOF(栈溢出StackOverflow):
当应用程序递归太深而发生栈溢出时,抛出该错误。因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大等。
Java 中 IO 流
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按角色划分,可以划分为节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)与处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。);
- Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系,Java I0 流的 40 多个类都是从如下4个抽象类基类中派生出来的:
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流;
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
BIO、NIO、AIO 有什么区别
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
Iterater 和 ListIterator 之间有什么区别
- 可以使用 Iterator 来遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只可以向前遍历,而 ListIterator 可以双向遍历。
- ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
面向对象的特征有哪些方面
主要有以下四方面:
- 抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。 - 继承:
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。 - 封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的接口。面向对象始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 - 多态:
多态性是指允许不同类的对象对同一消息作出不同的响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。多态性分为编译时的多态性和运行时的多态性。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)
TCP 建立连接的三次握手过程,以及关闭连接的四次握手过程
贴一个 telnet 建立连接,断开连接的使用 wireshark 捕获的 packet 截图。
- 建立连接协议(三次握手)
(1)客户端发送一个带 SYN 标志的 TCP 报文到服务器,这是三次握手过程中的报文 1。
(2)服务器端回应客户端的,这是三次握手中的第 2 个报文,这个报文同时带 ACK 标志和 SYN 标志,因此它表示对刚才客户端 SYN 报文的回应,同时又标志 SYN 给客户端,询问客户端是否准备好进行数据通讯。
(3)客户必须再次回应服务段一个 ACK 报文,这是报文段 3。 - 连接终止协议(四次握手)
由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。 这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。 收到一个 FIN 只意味着这一方向上没有数据流动, 一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1)TCP 客户端发送一个 FIN,用来关闭客户到服务器的数据传送(报文段 4)。
(2)服务器收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加 1(报文段 5)。和 SYN 一样,一个 FIN 将占用一个序号。
(3)服务器关闭客户端的连接,发送一个 FIN 给客户端(报文段 6)。
(4)客户段发回 ACK 报文确认,并将确认序号设置为收到序号加 1(报文段 7)。 - 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的 LISTEN 状态下的 SOCKET 当收到 SYN 报文的建连请求后,它可以把ACK 和 SYN(ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的 FIN 报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你未必会马上会关闭 SOCKET,也即你可能还需要发送一些数据给对方之后,再发送 FIN 报文给对方来表示你同意现在可以关闭连接了,所以它这里的 ACK 报文和 FIN 报文多数情况下都是分开发送的。 - tcp 和 udp的区别:
tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输,而 udp 则常被用于让广播和细节控制交给应用的通信传输。
两者的区别大致如下:
tcp 面向连接,udp 面向非连接即发送数据前不需要建立链接;
tcp 提供可靠的服务(数据传输),udp 无法保证;
tcp 面向字节流,udp 面向报文;
tcp 数据传输慢,udp 数据传输快;
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
- Iterator 的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util 包下面的所有的集合类都是快速失败的,而 java.util.concurrent 包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException 异常,而安全失败的迭代器永远不会抛出这样的异常。
JDK、JRE和JVM
- JDK(Java Development Kit)即java开发工具包,包含编写Java程序所必须的编译,运行等开发工具以及JRE。开发工具如:用于编译java程序的javac命令,用于启动JVM运行java程序的java命令,用于生成文档的javadoc命令以及用于打包的jar命令等;
- JRE(Java Runtime Environment)即为java运行环境,提供了运行java应用程序所必须的软件环境,包含有JVM和丰富的系统类库;
- JVM(Java Virtual Machines)即为java虚拟机,提供字节码文件的运行环境支持。
普通类和抽象类有哪些区别
- 普通类不能包含抽象方法,抽象类可以包含抽象方法。
- 抽象类是不能被实例化的(抽象类也有构造函数),就是不能用new调出构造方法创建对象,普通类可以直接实例化。
- 如果一个类继承于抽象类,则该子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为abstract类。
equals()和hashCode()
- 如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hashCode)应当相同。
Java 对于 eqauls 方法和 hashCode 方法是这样规定的:
- 如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同;
- 如果两个对象的 hashCode 相同,它们并不一定相同。
- 当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
- equals 方法必须满足:
- 自反性(x.equals(x)必须返回 true);
- 对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true);
- 传递性(x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true);
- 一致性(当x 和 y 引用的对象信息没有被修改时,多次调用 x.equals(y)应该得到同样的返回值),而且对于任何非 null 值的引用 x,x.equals(null)必须返回 false。
- 实现高质量的 equals 方法的诀窍包括:
- 使用==操作符检查"参数是否为这个对象的引用";
- 使用 instanceof 操作符检查"参数是否为正确的类型";
- 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
- 编写完 equals 方法后,问自己它是否满足自反性、对称性、传递性和一致性;
- 重写 equals 时总是要重写 hashCode;
- 不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉@Override 注解。
final、finally、finalize 的区别:
- final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
- finally:异常处理语句结构的一部分,表示总是执行。
- finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。
Java 8 日期与时间特性
- Java 8 日期/时间 API 是 JSR-310 的实现,它的实现目标是克服旧的日期时间实现中所有的缺陷,新的日期/时间API 的一些设计原则是:
- 不变性:新的日期/时间 API 中,所有的类都是不可变的,这对多线程环境有好处。
- 关注点分离:新的 API 将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
- 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用 now()方法,在所有的类中都定义了 format()和 parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
- 实用操作:所有新的日期/时间 API 类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。
- 可扩展性:新的日期/时间 API 是工作在 ISO-8601 日历系统上的,但我们也可以将其应用在非 ISO 的日历上。
- Java 8 日期/时间 API 包解释
- java.time 包:这是新的 Java 日期/时间 API 的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate,LocalTime, LocalDateTime, Instant, Period, Duration 等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
- java.time.chrono 包:这个包为非 ISO 的日历系统定义了一些泛化的 API,我们可以扩展 AbstractChronology类来创建自己的日历系统。
- java.time.format 包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为 java.time 包中相应的类已经提供了格式化和解析的方法。
- java.time.temporal 包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。
- java.time.zone 包:这个包包含支持不同时区以及相关规则的类。
- Java 8 日期/时间常用 API
- java.time.LocalDate
LocalDate 是一个不可变的类,它表示默认格式(yyyy-MM-dd)的日期,我们可以使用 now()方法得到当前时间,也可以提供输入年份、月份和日期的输入参数来创建一个 LocalDate 实例。该类为 now()方法提供了重载方法,我们可以传入 ZoneId 来获得指定时区的日期。该类提供与 java.sql.Date 相同的功能。 - java.time.LocalTime
LocalTime 是一个不可变的类,它的实例代表一个符合人类可读格式的时间,默认格式是 hh:mm:ss.zzz。像
LocalDate 一样,该类也提供了时区支持,同时也可以传入小时、分钟和秒等输入参数创建实例。 - java.time.LocalDateTime
LocalDateTime 是一个不可变的日期-时间对象,它表示一组日期-时间,默认格式是 yyyy-MM-dd-HH-mm
ss.zzz。它提供了一个工厂方法,接收 LocalDate 和 LocalTime 输入参数,创建 LocalDateTime 实例。 - java.time.Instant
Instant 类是用在机器可读的时间格式上的,它以 Unix 时间戳的形式存储日期时间。
字节流和字符流的区别:
- 字节流读取的时候,读到一个字节就返回一个字节; 字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。
- 字节流可以处理所有类型数据,如:图片,MP3,AVI 视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
- 字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是 OutputStream、InputStream字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。
- 在程序中一个字符等于两个字节,java 提供了 Reader、Writer 两个专门操作字符流的类。
List和Map、Set的区别
- 结构特点
List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的); - 实现类
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。
Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null键;HashTable:线程安全,低效,不支持 null 值和 null 键;LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序;SortMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。
Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)。 - 区别
- List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素;
- Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;
- Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现 Java.util.Comparator接口来自定义排序方式。
静态代理与动态代理
- 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
- 代理三要素
- 共同接口
- 真实对象
- 代理对象
- 静态代理
- 具体用户管理实现类
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String userId, String userName) {
System.out.println("UserManagerImpl.addUser");
}
@Override
public void delUser(String userId) {
System.out.println("UserManagerImpl.delUser");
}
@Override
public String findUser(String userId) {
System.out.println("UserManagerImpl.findUser");
return "张三";
}
@Override
public void modifyUser(String userId, String userName) {
System.out.println("UserManagerImpl.modifyUser");
}
}
- 代理类–代理用户管理实现类
public class UserManagerImplProxy implements UserManager {
// 目标对象
private UserManager userManager;
// 通过构造方法传入目标对象
public UserManagerImplProxy(UserManager userManager){
this.userManager=userManager;
}
@Override
public void addUser(String userId, String userName) {
try{
//添加打印日志的功能
//开始添加用户
System.out.println("start-->addUser()");
userManager.addUser(userId, userName);
//添加用户成功
System.out.println("success-->addUser()");
}catch(Exception e){
//添加用户失败
System.out.println("error-->addUser()");
}
}
@Override
public void delUser(String userId) {
userManager.delUser(userId);
}
@Override
public String findUser(String userId) {
userManager.findUser(userId);
return "张三";
}
@Override
public void modifyUser(String userId, String userName) {
userManager.modifyUser(userId,userName);
}
}
- 客户端调用
public static void main(String[] args){
//UserManager userManager=new UserManagerImpl();
UserManager userManager=new UserManagerImplProxy(new UserManagerImpl());
userManager.addUser("1111", "张三");
}
}
- 静态代理类优缺点
- 优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。 - 缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。 - 动态代理
态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象,在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持。
java.lang.reflect.InvocationHandler接口的定义如下:
//Object proxy:被代理的对象 `在这里插入代码片`
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
java.lang.reflect.Proxy类的定义如下:
//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
- 具体实现类
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String userId, String userName) {
System.out.println("UserManagerImpl.addUser");
}
@Override
public void delUser(String userId) {
System.out.println("UserManagerImpl.delUser");
}
@Override
public String findUser(String userId) {
System.out.println("UserManagerImpl.findUser");
return "张三";
}
@Override
public void modifyUser(String userId, String userName) {
System.out.println("UserManagerImpl.modifyUser");
}
}
- 动态创建代理对象的类
/动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
public class LogHandler implements InvocationHandler {
// 目标对象
private Object targetObject;
//绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
public Object newProxyInstance(Object targetObject){
this.targetObject=targetObject;
//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
//第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
//第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
//第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
//根据传入的目标返回一个代理对象
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),this);
}
@Override
//关联的这个实现类的方法被调用时将被执行
/*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("start-->>");
for(int i=0;i<args.length;i++){
System.out.println(args[i]);
}
Object ret=null;
try{
/*原对象方法调用前处理日志信息*/
System.out.println("satrt-->>");
//调用目标方法
ret=method.invoke(targetObject, args);
/*原对象方法调用后处理日志信息*/
System.out.println("success-->>");
}catch(Exception e){
e.printStackTrace();
System.out.println("error-->>");
throw e;
}
return ret;
}
}
被代理对象targetObject通过参数传递进来,我们通过targetObject.getClass().getClassLoader()获取ClassLoader对象,然后通过targetObject.getClass().getInterfaces()获取它实现的所有接口,然后将targetObject包装到实现了InvocationHandler接口的LogHandler对象中。通过newProxyInstance函数我们就获得了一个动态代理对象。
- 客户端代码
public class Client {
public static void main(String[] args){
LogHandler logHandler=new LogHandler();
UserManager userManager=(UserManager)logHandler.newProxyInstance(new UserManagerImpl());
//UserManager userManager=new UserManagerImpl();
userManager.addUser("1111", "张三");
}
- 静态代理与动态代理的区别
- 静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
- 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
- 动态代理是实现 JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象。
- 还有一种动态代理 CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子。
Queue add与offer、poll与remove、element与peek的区别
- add与offer
- poll与remove
- element与peek
- add和offer的区别在于,add方法在队列满的情况下将选择抛异常的方式来表示队列已经满了,而offer方法通过返回false表示队列已经满了;在有限队列的情况,使用offer方法优于add方法;
- remove方法和poll方法都是删除队列的头元素,remove方法在队列为空的情况下将抛异常,而poll方法将返回null;
- element和peek方法都是返回队列的头元素,但是不删除头元素,区别在与element方法在队列为空的情况下,将抛异常,而peek方法将返回null。
Math.round(11.5)等于多少?Math.round(- 11.5) 又等于多少
System.out.println(Math.round(-1.6)); // -2
System.out.println(Math.round(-1.5)); // -1
System.out.println(Math.round(-1.4)); // -1
System.out.println(Math.round(1.6)); // 2
System.out.println(Math.round(1.5)); // 2
System.out.println(Math.round(1.4)); // 1
如何取得年月日、小时分钟秒
publiclasDateTimeTes{
publistativoimain(String[args {
CalendacaCalendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH))
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));
//Jav8
LocalDateTimdLocalDateTime.now();
System.out.println(dt.getYear())
System.out.println(dt.getMonthValue())
System.out.println(dt.getDayOfMonth());
System.out.println(dt.getHour());
System.out.println(dt.getMinute());
System.out.println(dt.getSecond());
}
}
如何取得从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数
Calendar.getInstance().getTimeInMillis(); // 第一种方式
System.currentTimeMillis(); // 第二种方式
// Java 8
Clock.systemDefaultZone().millis(); // 第三种方式
如何取得某月的最后一天
//获取当前月第一天:
Calendar c = Calendar.getInstance();
c.add(Calendar.MONTH, 0);
c.set(Calendar.DAY_OF_MONTH,1);//设置为 1 号,当前日期既为本月第一天
String first = format.format(c.getTime());
System.out.println("===============first:"+first);
//获取当前月最后一天
Calendar ca = Calendar.getInstance();
ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
String last = format.format(ca.getTime());
System.out.println("===============last:"+last);
//Java 8
LocalDate today = LocalDate.now();
//本月的第一天
LocalDate firstday = LocalDate.of(today.getYear(),today.getMonth(),1);
//本月的最后一天
LocalDate lastDay =today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("本月的第一天"+firstday);
System.out.println("本月的最后一天"+lastDay);
打印昨天的当前时刻
import java.util.Calendar;
class YesterdayCurrent {
public static void main(String[] args){
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}
//java-8
import java.time.LocalDateTime;
class YesterdayCurrent {
public static void main(String[] args) {
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);
System.out.println(yesterday);
}
}