@wxf
2018-05-14T10:28:44.000000Z
字数 8152
阅读 722
面试系列
Java是如何实现跨平台的
Java的跨平台是通过Java虚拟机JVM来实现的。不同的平台有不同版本JVM。
JDK、JRE、JVM和配置环境变量
JDK:Java开发环境和Java运行环境
JRE:Java运行环境,包括Java运行需要的类库+JVM
配置环境变量:
- 环境变量的配置:
1):永久配置方式:JAVA_HOME={安装路径}\Java\jdk
path=%JAVA_HOME%\bin
2):临时配置方式:set path=%path%;{安装路径}\Java\jdk\bin- classpath的配置:
classpath的作用是指定类搜索路径,要使用已经编写好的类,前提当然是能够找到它们了,JVM就是通过CLASSPATH来寻找类的。我们需要把jdk安装目录下的lib子目录中的dt.jar和tools.jar设置到CLASSPATH中,当然,当前目录“.”也必须加入到该变量中。这里CLASSPATH为:
.;{安装路径}/Java/jdk1.6.0_21/lib/dt.jar;{安装路径}/Java/jdk1.6.0_21/lib/tools.jar
重载(Overload)是指一个类中允许存在多个同名函数,参数不同;
重写(Override)是指子类重写定义父类方法。
注意:构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
private:同类可见
default:同包可见
protected:同包可见、子类可见
public:全局可见
相同点:不能实例化,都包含抽象方法。
不同点:抽象类中允许有非抽象方法,接口只包含抽象方法(JDK1.8之前);类只能继承一个父类,可以实现多个接口。
接口:1.8新特性 默认方法(default修饰);1.9新特性 私有方法。
hashCode()方法根据对象的地址或字符串的值来计算一个整型的哈希码;equals用于对比两个对象或字符串是否相同。
如果两个对象根据equals()方法比较是相等的,那么两个对象调用hashCode()方法返回的结构必须相等;
如果两个对象根据equals()方法比较是不相等的,那么两个对象调用hashCode()方法返回的结果不一定不相等。
- 泛型的本质是参数化类型。
- 使用泛型可以避免不同数据类型存储到同一对象中,使用时导致数据强转异常。
- 泛型擦除是指在编译期间,Java会将所有的泛型信息擦除掉,编译后再变成原始类型。
片段一
public void compare(){
String s1 = "Java";
String s2 = "Java";
System.out.println(s1 == s2);
}
# 输出:true。说明s1与s2引用同一个String对象 -- "Java"
片段二
public void compare(){
String s1 = "Java";
String s2 = new String("Java");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
# 输出:false和true。s1与s2分别引用了两个String对象 -- "Java"
字符串缓冲池
程序在运行的时候会创建一个字符串缓冲池,当使用 s2 = "Java" 表达创建字符串的时候,程序首先会在String缓冲池中寻找相同值的对象。
在片段一中,s1先被放到了字符串缓冲池中,所以在s2被创建的时候,程序找到了具有相同值的s1,s2将引用s1所引用的字符串对象"Java"。
在片段二中,使用了new操作符,它的告诉程序:"我要一个新的!不要旧的!"。于是一个新的"Java"字符串对象被创建在内存中。它们的值相同,但是位置不同。
片段三
public void compare(){
String s1 = "Java";
String s2 = new String("Java");
s2 = s2.intern();
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
# 输出:true和true。s1与s2分别引用了两个String对象 -- "Java"
String的intern()方法
"Java".intern()方法的返回值还是字符串"Java",表面上看起来好像这个方法没什么用处。但实际上,它做了个小动作:检查字符串池里是否存在"Java"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把"Java"添加到字符串池中,然后再返回它的引用。
Integer缓存是在java5中引入的一个有助于节省内存、提高性能的特性。下面我们用一段代码来展示一下Integer的缓存行为。
public void testInteger(){
Integer integer1 = 10;
Integer integer2 = 10;
if(integer1 == integer2)
System.out.println("Equals!");
else
System.out.println("Not Equals!");
//=======分割线=======
Integer integer3 = 256;
Integer integer4 = 256;
if(integer3 == integer4)
System.out.println("Equals!");
else
System.out.println("Not Equals!");
}
在Java中,==比较的是对象的引用,而equals比较的是值。所以你有可能认为上面的两个判断结果都是false。但是奇怪的是,上面的两个判断结果却返回不同的布尔值。其结果如下:
Equals!
Not Equals!
Java中Integer缓存实现
在Java5中,整数对象在内部实现中通过使用相同的对象引用实现了缓存和重用。其缓存的整数区间为-128到127。
这种Integer缓存策略仅在自动封箱的时候有用,使用构造函数创建的Integer对象不能被缓存。
自动装箱和拆箱的原理
Java编译器把原始类型自动转换为包装类的过程称为自动装箱。自动装箱时编译器调用valueOf方法将原始类型值转换成对象,同样自动拆箱时,编译器通过调用类似于intValue(),doubleValue()这类的方法将对象转成原始类型值。明白了自动装箱和拆箱的原理后,我们来分析一下Integer的自动装箱的代码实现:
public static Integer valueOf(int i) {
//判断i是否在-128和127之间,存在则从IntegerCache中获取包装类的实例,否则new一个新的实例。
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//使用享元模式,来减少对象的创建
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[]; //cache属于静态常量,存放在jvm的方法区中
//使用静态代码块为常量赋值(类加载的时候对cache[]进行初始化),静态常量cache[]存放在常量池中。
static {
int 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);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
8中基本数据类型的自动装箱代码实现
//byte原生类型自动装箱成Byte
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
//short原生类型自动装箱成Short
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
//int原生类型自动装箱成Integer
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//long原生类型自动装箱成Long
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
//boolean原生类型自动装箱成Boolean
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
//char原生类型自动装箱成Character
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
//float原生类型自动装箱成Float
public static Float valueOf(float f){
return new Float(f);
}
//double原生类型自动装箱成Double
public static Double valueOf(double d){
return new Double(d);
}
通过上面的源码分析发现,只有double和float的自动自动装箱没有使用缓存,每次都是new新的对象。其他的6种基本类型都使用了缓存策略。
在Stirng中equals()方法比较的是字符串的内容是否相同,其实现原理是根据char字符数组元素逐个进行比较的。
# 源码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
# 通过上面的源代码可以看出:equals()首先做的是比较引用,如果引用是同一个对象,结束比较并返回true。如果引用不是同一个地址,判断是不是String的一个实例。同样,不是的话直接结束比较并返回false。如果是,则拿字符串的长度做循环的控制变量,拿字符串的字符数组做比较内容,进行循环比较。
可变和不可变
private final char value[];
# 因为使用final修饰,所以String对象是不可修改的。
char value[];
# 可知StringBuffer和StringBuilder是可变长度的。
是否多线程安全
StringBuffer和StringBuilder的共同点
throw是用来抛出异常的,包括自定义异常。如:throw new CustomException();
throws是用来声明异常的。
片段一
try{
} catch(Exception e){
} finally{
}
return;
# 按正常顺序执行。
片段二 *
try{
return;
} catch(Exception e){
} finally{
}
return;
# 程序先执行try语句块中return之前(包括return语句中的表达式运算)的代码,再执行finally语句块,最后执行try中return;,finally语句块后面的return语句不再执行。
片段三
try{
} catch(Exception e){
return;
} finally{
}
return;
# 有异常:则先执行catch中return之前(包括return语句中的表达式运算)的代码,再执行finally语句中全部代码,最后执行catch块中的return;,finally块后面的return语句不再执行。
# 无异常:执行完try再finally再执行最后的return语句。
片段四
try{
return;
} catch(Exception e){
} finally{
return;
}
# 程序先执行try语句块中return之前(包括return语句中的表达式运算)的代码,再执行finally块,因为finally块中有return所以提前退出。
代码事例
public int test(){
int x = 1;
try {
x++;
return x;
} finally {
++x;
}
}
# 执行结果:2,即使finally中对变量x进行了改变,也不会影响返回结果。
BIO:即IO,同步阻塞IO
NIO:同步非阻塞IO
AIO:异步非阻塞IO
标准IO是基于字节流和字符流进行操作;而NIO是基于通道和缓冲区进行操作,数据从通道读取到缓冲区中或者从缓冲区写入到通道。
NIO引入了选择器概念,选择器用于监听多个通道的事件(比如:链接打开、可读。可写),因此NIO可以通过一个线程监听多个数据通道。相比标准IO为每个连接创建一个线程,NIO大大降低了线程创建资源的开销。
序列化是指将对象的状态信息转换为可以存储或传输形式的过程。通过序列化可以将对象的状态保存为字节数组,需要的时候再将字节数组反序列化为对象。