【JAVA语言】
Java程序运行机制
- 程序员编写*.java源文件
- 使用javac编译成*.class字节码文件
- JVM解释字节码文件为机器码,并执行
【JAVA语言基础】
String 是最基本的数据类型吗?
答:不是。Java中的基本数据类型只有8个:byte、short、int、long,float、double,char,boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5以后引入的枚举类型也算是一种比较特殊的引用类型。
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
用最有效率的方法计算2乘以8?
答: 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。
switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?
答:在Java 5以前,switch(expr)中,expr只能是byte、short、char、int。从Java 5开始,Java中引入了枚举类型,expr也可以是enum类型,从Java 7开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
在Java中,如何跳出当前的多重嵌套循环?
答:在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法其实不知道更好)
break&return&continue区别
- break用于完全结束一个循环,跳出循环体。break不仅可以结束其所在的循环,还可结束其外层循环。此时需要在break后紧跟一个标签,这个标签用于标识一个外层循环。Java中的标签就是一个紧跟着英文冒号(:)的标识符。且它必须放在循环语句之前才有作用。
- return并不是专门用于跳出循环的,return的功能是结束一个方法。一旦在循环体内执行到一个return语句,return语句将会结束该方法,循环自然也随之结束。与continue和break不同的是,return直接结束整个方法,不管这个return处于多少层循环之内。
- continue的功能和break有点类似,区别是continue只是中止本次循环,接着开始下一次循环。
【面向对象】
面向对象三大特征
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
- 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。
访问修饰符public,private,protected,以及不写(默认)时的区别?
修饰符 |
当前类 |
同 包 |
子 类 |
其他包 |
public |
√ |
√ |
√ |
√ |
protected |
√ |
√ |
√ |
× |
default |
√ |
√ |
× |
× |
private |
√ |
× |
× |
× |
类中成员有哪些
- 成员变量:定义该类或该类的实例所包含的状态数据
- 成员方法:定义该类或该类的实例的行为特征或者功能实现
- 构造方法:创建对象的根本途径
- 初始化块:初始化块是java类中出现的第四类成员(成员变量、成员方法、构造方法) ,其功能与构造方法非常类似,可以对java进行初始化操作,每次类的创建会隐式的调用它。初始化块分为静态初始化块和普通初始化块。其好处是减少多个构造器内重用的代码
- 内部类:将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。
成员变量和局部变量的区别
- 作用域。成员变量:针对整个类有效。局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
- 存储位置。成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
- 生命周期。成员变量:随着对象的创建而存在,随着对象的消失而消失。局部变量:当方法调用完,或者语句结束后,就自动释放。
- 初始值。成员变量:有默认初始值。局部变量:没有默认初始值,使用前必须赋值。
- 使用原则。在使用变量时需要遵循的原则为:就近原则。首先在局部范围找,有就使用;接着在成员位置找。
方法值传递和引用传递
- 按值传递指的是在方法调用时,传递的参数是实参值的副本。
- 按引用传递指的是在方法调用时,传递的参数是实参的引用。
方法可变长参数特点
- 只能是最后一个参数
- 只能位于变量的类型和变量名之间
- 编译器为可变参数隐含创建一个数组
重写和重载区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
- 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
为什么重载的方法不能根据返回类型进行区分?
- 因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。
- 若编译器可根据上下文(语境)明确判断出含义,这样做完全没有问题。然而, 我们可能调用一个方法,同时忽略返回值;我们通常把这称为“为它的副作用去调用一个方法”,因为我们关心的不是返回值,而是方法调用的其他效果。
- 函数的返回值只是作为函数运行之后的一个“状态”,他是保持方法的调用者与被调用者进行通信的关键。并不能作为某个方法的“标识”
Java 可以根据函数名,参数个数,参数类型判断重载方法,但是不能根据返回值判断重载方法
如下两个方法:
void f(){}
int f(){ return 1;}
只要编译器可以根据语境明确判断出语义,比如在int x = f();中,那么的确可以据此区分重载方法。不过,有时你并不关心方法的返回值,你想要的是方法调用的其他效果(这常被称为“为了副作用而调用”),这时你可能会调用方法而忽略其返回值,所以如果像下面的调用:
fun();
此时Java如何才能判断调用的是哪一个 f() 呢?别人如何理解这种代码呢?所以,根据方法返回值来区分重载方法是行不通的。
以上参考《Java 编程思想》
构造器(constructor)是否可被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。
创建对象的方法
- 通过new关键字来调用构造方法
- 调用对象的clone方法
- 通过反射机制来创建对象
- 通过反序列化的方式创建对象
new关键字作用
- 将类加载到方法区
- 在堆中开辟一块内存用于存储新创建的对象
- 在栈中创建指向对象的引用
this和super
- this:this关键字总是指向调用该方法的对象。最大的作用就是让类中一个方法,访问该类里另一个方法或实例变量。
- super:如果需要在子类方法中调用父类被覆盖的实例方法,则可使用super限定来调用父类被覆盖的实例方法。如果构造器中使用super,则super用于限定改构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量。
从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
Static关键字
- static是java语言中的关键字,表示“静态的”,它可以用来修饰变量、方法、代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。
- static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。
- 而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。
- JVM会将静态变量存储在方法区。
final、finally和finalize区别
- final用于声明属性、方法和类,分别表示属性不可变,方法不可被覆盖,类不可被继承。
- finally作为异常处理的一部分,只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定被执行,经常被用于需要释放资源的情况下。
- finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收。
抽象类和普通类的主要区别
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
- 抽象类不能用来创建对象;
- 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
抽象类和接口异同
定义
- 抽象类:包含抽象方法的类,抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。
- 接口,是抽象方法的集合,英文称作interface,在软件工程中,接口泛指供别人调用的方法。
相同点
- 都不能被实例化
- 抽象类的子类或者接口的实现类都是由继承或实现,抽象类或接口的方法后才能被实例化
不同点
音记法:盛世买房至多愁
- 声明。抽象类声明关键字为abstract,接口声明关键字为interface。
- 实现。子类使用extends继承抽象类,子类使用implements关键字来实现接口。
- 构造器。抽象类可以有构造器,接口不能有构造器。
- 访问控制符。抽象类中的方法可以是任意访问控制符,接口方法修饰符是public或default。
- 字段声明。抽象类的字段声明可以是任意的,接口的字段声明是static和final。
- 多继承。类只能单继承,接口可以多重实现。
- 抽象类充当公共类,而接口用于实现常用功能,是调用规则。
Java程序初始化原则
- 静态优先于非静态
- 父类优先于子类
- 按照成员变量定义顺序进行初始化
父类静态变量 - 父类静态代码块 - 子类静态变量 - 子类静态代码块 - 父类非静态变量 - 父类非静态代码块 - 父类构造方法 - 子类非静态变量 - 子类非静态代码块 - 子类构造方法
== 和 equals 的区别是什么?
- 当比较基本数据类型时,只能采用==,比较的是数值
-
当比较引用类型时,==关键字用于判断两个对象的地址是不是相等,即判断两个对象是不是同一个对象。equals方法是判断两个变量或实例所指向的内存空间的值是不是相同
【API】
JDK 中常用的包有哪些
-
java.lang:这个是系统的基础类;
-
java.io:这里面是所有输入输出有关的类,比如文件操作等;
-
java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
-
java.net:这里面是与网络有关的类;
-
java.util:这个是系统辅助类,特别是集合类;
-
java.sql:这个是数据库操作的类。
Arrays类的常用方法
- binSearch():使用二分法查询元素值在数组中出现的索引
- copyOf():把原始数组复制成一个新数组
- equals():比较数组长度和对应元素是否相同
- fill():把a数组中的元素赋值给b数组
- sort():数组元素排序
- toString():数组转换出字符串
String类的常用方法
-
length():返回字符串长度。
-
getBytes():返回字符串的 byte 类型数组。
-
equals():字符串比较。
-
trim():去除字符串两端空白。
-
substring():截取字符串。
-
toLowerCase():将字符串转成小写字母。
-
toUpperCase():将字符串转成大写字符。
-
replace():字符串替换。
-
indexOf():返回指定字符的索引。
-
charAt():返回指定索引处的字符。
-
split():分割字符串,返回一个分割后的字符串数组。
org.apache.commons.lang.StringUtils常用方法
-
isBlank():严谨判空,包含的有空串("")、null值、空白符(空格""," ",制表符"\t",回车符"\r","\n"等)。
-
isEmpty():判空,包含是空串("")和null值。
-
remove():移除字符。
-
deleteWhitespace():去除空白。
-
substring():截取。
-
capitalize():首字母大写。
-
uncapitalize():首字母小写。
-
upperCase():全部大写。
-
lowerCase():全部小写。
-
reverse():反转。
String类可以被继承吗
不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
关于final修饰符,介绍如下:
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
String str="i"与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String s = new String(“xyz”);创建了几个字符串对象
两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
String&StringBuffer&StringBuilder区别
- String对象一旦被创建,其值将不能被改变。而StringBuffer是可变类,当对象被创建后,仍然可以对其值进行改变。
- String是线程安全的,StringBuffer是线程安全的,StringBuilder也是线程不安全的。
- String << StringBuffer < StringBuilder。
在使用 HashMap 的时候,用 String 做 key 有什么好处?
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
Object类的方法
- clone():创建并返回此对象的一个副本
- equals():判断obj对象是否与此对象相等
- getClass():返回此Object的运行时类
- hashCode():返回该对象的散列值
- toString():返回该对象的字符串表示
- notify():唤醒此对象监视器上单个等待的线程
- wait():在其他线程调用此对象的notify()方法前,使当前线程等待
- finalize():当垃圾回收器确定不存在该对象的更多引用时,由对象的垃圾回收器调用此方法
int和Integer区别
- int是Java8种基本原始数据类型之一,Integer是Java为int提供的封装类。
- int是基本数据类型,在使用的时候是值传递,Integer是引用传递。
- int只能用来运算,Integer提供了很多有用的方法。
- 当往容器里存放整数时,无法直接存放int,这种情况只能使用Integer。
Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整。
【集合】
数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
下面列出了Array和ArrayList的不同点:
- Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
- Array大小是固定的,ArrayList的大小是动态变化的。
- ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
List、Set、Map是否继承自Collection接口?
List、Set 是,Map 不是。
List、Map、Set三个接口存取元素时,各有什么特点?
List以特定索引来存取元素,可以有重复元素。
Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。
Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。
Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。
阐述ArrayList、Vector、LinkedList的存储性能和特性。
ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。
LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
Java集合类浅谈
【异常】
Java中的两种异常类型是什么?他们有什么区别?
Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。
Error和Exception有什么区别?
- Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;
- Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。
运行时异常与检查时异常的区别
定义:
- 运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常),IndexOutOfBoundsException(数组下标越界)等等,这些异常都属于不检查异常,程序可以捕获处理,也可以不处理。这些异常通常时程序的逻辑出现了错误,当出现此类异常时,应该分析程序的逻辑是否正确。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中出现此类错误时,即使没有对异常进行显式处理,也能够编译通过。
- 检查时异常(非运行时异常):是非RuntimeException及其子类的异常,都属于Exception及其子类。这类异常,是程序必须进行处理的异常,否则不能被编译,即需要显示处理。
总结:
- 运行时异常:间接或直接继承RuntimeException,且不需要显式处理
- 检查时异常:未间接或直接继承RuntimeException,且需要显式处理
处理异常的方法:
- 使用try-catch-finally处理
- 使用throw,throws抛出处理
throw和throws有什么区别
【相同点】
【不同点】
- throw语句用来抛出一个异常,throws是方法可能抛出异常的声明;
- throw通常出现在方法体,throws通常出现方法头;
- throw是抛出了异常,throws则表示出现的异常是一种可能性;
【泛型】
【多线程】
Java语言中,多线程的实现方法
(1)继承Thread,重写run()方法,但是只能单继承
(2)实现Runnable接口,并实现该接口的run()方法
- 自定义类并实现Runnable接口,实现run()方法
- 创建Thread对象,用实现Runnable接口对象作为参数实例化该Thread对象
- 调用Thread的start()方法
(3)实现Callable接口,重写call()方法
- Callable接口可以在任务结束后提供一个返回值,Runnable无法提供这个功能
- Callable接口中的call()方法可以抛出异常,而Runnable()的run()方法不能抛出异常
- 运行Callable可以拿到一个Future对象,用以表示异步计算结果。
(4)线程池
线程的生命周期
-
新建:就是刚使用new方法,new出来的线程;
-
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
-
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
-
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
-
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
对线程池的理解
(1)线程池产生原因
在Java语言中,可以通过new Thread的方式来创建一个新的线程执行任务,但是线程的创建是非常耗时的,而且创建出来的新线程都是各自运行,缺乏管理。
(2)线程池运行过程
- 当使用线程池控制线程数量时,其他线程排队等候。
- 当一个任务执行完毕后,再从队列中取出最前面的任务开始执行。
- 如果没有等待线程,那么线程池这一资源会处于等待任务状态。
(3)线程池好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
(4)线程池创建
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
方式一:通过构造方法实现
方式二:通过Executor 框架的工具类Executors来实现
可以创建三种类型的ThreadPoolExecutor:
- FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
- SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
- CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
(5)线程池原理
- 先判断线程池中线程数量是否达到核心线程池corePoolSize大小,如果没有达到,则当提交新的任务时,一律创建新的任务来执行此任务,即使线程池中有空的线程。(刚开始的目标就是尽快达到corePoolSize)
- 当线程池中线程数量已经达到了corePoolSize大小,如果继续提交新的任务,则判断任务队列是否已满,如果没有满,则新提交的任务进入到任务队列中,如果已满,执行如下操作。(任务队列的作用就是缓冲存储新的任务,而不是再去一味地创建爱新的线程)
- 如果任务队列已满,再判断线程池中线程数量是否达到maxmumPoolSize,如果没有,则创建新的线程(新任务太多,队列缓冲不完,只能继续创建一些线程来缓解压力)。如果已经达到了maxmumPoolSize则开始执行饱和策略。
(6)线程池生命周期
线程池有运行、关闭、停止、结束四种状态,结束后就会释放所有资源。
- shutdown 与 shutdowNow 分别对应:平缓关闭:已经启动的任务全部执行完毕,同时不再接受新的任务。立即关闭:取消所有正在执行和未执行的任务。
- 检测线程池是否正处于关闭中,使用isShutdown()描述的是非RUNNING状态,也就是SHUTDOWN/STOP/TERMINATED三种状态。
- 检测线程池是否已经关闭使用isTerminated()。描述的是关闭状态,也就是TERMINATED三种状态
- 定时或者永久等待线程池关闭结束使用awaitTermination()操作。shutdown 与 shutdowNow不是阻塞操作,只是发起关闭任务,awaitTermination则是等待到线程isTerminated()
【IO】
Files的常用方法都有哪些?
-
Files. exists():检测文件路径是否存在。
-
Files. createFile():创建文件。
-
Files. createDirectory():创建文件夹。
-
Files. delete():删除一个文件或目录。
-
Files. copy():复制文件。
-
Files. move():移动文件。
-
Files. size():查看文件个数。
-
Files. read():读取文件。
-
Files. write():写入文件。
BIO,NIO,AIO 有什么区别?
-
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
-
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
-
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
【反射】
什么是反射?
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
Java获取反射的三种方法
-
通过new对象实现反射机制
-
通过路径实现反射机制
-
通过类名实现反射机制
如何使用Java的反射?
(1)通过一个全限类名创建一个对象
- Class.forName(“全限类名”);
- 类名.class;
- 对象.getClass();
(2)获取构造器对象,通过构造器new出一个对象
- Clazz.getConstructor([String.class]);
- Con.newInstance([参数]);
- Cls.newInstance();
(3)通过class对象获得一个属性对象
- Field c=cls.getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
- Field c=cls.getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是 不包括父类的声明字段
(4)通过class对象获得一个方法对象
- Cls.getMethod(“方法名”,class……parameaType);(只能获取公共的)
- Cls.getDeclareMethod(“方法名”);(获取任意修饰的方法,不能执行私有)
- M.setAccessible(true);(让私有的方法可以执行)
【JDBC】
JDBC访问数据库一般步骤
- 加载JDBC驱动器。将数据库的JDBC驱动加载到classpath中,在基于JavaEE的Web应用开发过程中,通常要把目标数据库产品的JDBC驱动复制到WEB/lib下。
- 加载JDBC驱动。并将其注册到DriverManager中。一般使用反射Class.forName。
- 建立数据库连接,取得Connection对象。一般通过DriverManager.getConnection(url, username, passwd)方式实现。
- 建立Statement对象或PreparedStatement对象。
- 通过Statement对象或PreparedStatement对象执行SQL语句。
- 访问结果集ResaultSet对象。
- 依次将ResaultSet、Statement对象或PreparedStatement对象、Connection等对象关闭,释放所占用资源。
【网络】
【JVM】
GC是什么?为什么要有GC?
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。
JVM常见面试题
【J2EE】
Servlet处理客户端请求步骤
- 用户单击一个链接来向Servlet发起请求;
- Web服务器接收到该请求后,会把该请求提交给相应的容器处理,当容器发现这是对servlet发起请求后,会创建HttpServletRequest和HttpServletResponse两个对象;
- 容器可以根据请求消息中的URL消息找到对应的Servlet,然后针对请求创建一个单独的线程,同时把HttpServletRequest和HttpServletResponse两个对象以参数形式传递给新创建的线程中;
- 容器调用Servlet的service()方法来完成对用户请求的响应,service()方法会调用doPost()方法或doGet()方法来完成具体的响应任务,同时把生成的动态页面返回给容器。
- 容器把响应消息组装成Http格式返回给客户端。此时这个线程运行结束,同时删除HttpServletRequest和HttpServletResponse两个对象。
Servlet的生命周期
-
加载
-
创建
-
初始化
-
处理客户请求
-
卸载
【设计模式】
单例模式中懒汉模式和饿汉模式的区别
- 懒汉模式:在类加载的时候不被初始化。饿汉模式:在类加载时就完成了初始化,但是加载比较慢,获取对象比较快。
- 饿汉模式是线程安全的,在类创建好一个静态对象提供给系统使用,懒汉模式在创建对象时不加上synchronized,会导致对象的访问不是线程安全的