由于本人要准备面试,所以更新一些常见的,自己收集的面试题
javase基础
1.短路运算符和位运算
短路运算
& 和 && 都可以实现 "和" 这个功能 区别:& 两边都运算,而 && 先算 && 左侧,若左侧为false 那么右侧就不运算,判断语句中推荐使用 &&,效率更高
| 和 || 和上面类似,都可以实现 "或" 这个功能 区别:||只要满足第一个条件,后面的条件就不再判断,而|要对所有的条件进行判断 把&&和||称之为短路运算符
&,| 称为按位与(只有对应的两个二进制数为1时,结果位才为1),按位或(有一个为1的时候,结果位就为1)
例如:3 &5 即 00000011 & 00000101 = 00000001 ,所以 3 & 5的值为1。
位运算
例如 2 << 3 原理:将一个数左移n位,相当于乘以2的n次方,位运算是CPU直接支持的,所以效率高,2 << 3是等于 16
为啥要使用位运算
-
基本的整型都是0和1表示的,那么位运算的使用在一些场合下就是很自然的事情。使用位运算肯定比做乘法和除法效率要高的多。但是现在很多编译器应该也是会优化的。(一些源码都是采用的位运算例如Map int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16) -
设置一些权限的时候可以用位运算来操作。(目前大部分公司的权限设都是控制N多个字段来操作的)
java中如何表现各种进制
二进制:由0,1组成。以0b开头。 八进制:由0,1,...7组成。以0开头。 十进制:由0,1,...9组成。默认整数是十进制。 十六进制:由0,1,...9,a,b,c,d,e,f(大小写均可)组成。以0x开头。
异或运算 ^
一个数与另一个数异或两次是其本身, 一个数和自身异或结果是0 例子:实现两个变量交换
System.out.printf("a=%d, b=%d",a,b);
b = b^a; // b = b^a^b
a = a^b; // a = a1^b = a^b^a
System.out.printf("\na=%d, b=%d",a,b);
上述的方式变量交换就不需要多建一个中间变量,减少内存开支,提高运行效率
数据类型
java类型分类: 基础数据类型:byte、short、int、long、float、double、char、boolean 引用数据类型:其他都是引用类型(所有引用类型的父类是Object类) String和Enum分别是什么类型:引用类型
i++ 与 ++i
i++ 是先把i拿出来使用,然后再+1;++i 是先把i+1,然后再拿出来使用。两者都线程不安全的。
== 和 equals的区别
-
基本数据类型比较 要用==判断是否相等 -
引用数据类型: ==比较的是内存地址是否一样,不同对象的内存地址不一样,equals比较的是具体的内容, 也可以让开发者去定义什么条件去判断两个对象是否一样(通过重写对象的equals的方法)
try-catch-finally
下面代码 的try-catch-finally语句,try里面有个return, finally里面也有个return,结果会返回什么?为什么
public static int test1() {
int a = 1;
try {
System.out.println(a / 0);
a = 2;
} catch (ArithmeticException e) {
a = 3;
return a;
} finally {
a = 4;
}
return a;
}
public static int test2() {
int a = 1;
try {
System.out.println(a / 0);
a = 2;
} catch (ArithmeticException e) {
a = 3;
return a;
}finally{
a = 4;
return a;
}
test1返回3 test2 返回4
-
在执行try、catch中的return之前一定会执行finally中的代码(如果finally存在),如果finally中有return语句,就会直接执行finally中的return方法,所以finally中的return语句一定会被执行的 -
执行流程:finally执行前的代码里面有包含return,则会先确定return返回值,然后再执行finally的代码,最后再执行return
try-with-resource
jdk1.7后增加了try-with-resources,他是一个声明一个或多个资源的try语句。一个资源作为一个对象,必须在程序结束之后关闭。try-with-resources语句确保在语句的最后每个资源都被关闭,任何实现了java.lang.AutoCloseable和java.io.Closeable的对象都可以使用try-with-resource来实现异常处理和关闭资源。
try (
FileInputStream fis = new FileInputStream("/Users/xdclass/Desktop/test.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("/Users/xdclass/Desktop/copy.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
) {
int size;
byte[] buf = new byte[1024];
while ((size = bis.read(buf)) != -1) {
bos.write(buf, 0, size);
}
} catch (Exception e) {
e.printStackTrace();
}
-
需要关闭的资源只要实现了java.lang.AutoCloseable,就可以⾃动被关闭,关闭的时候会调用文件流的onClose方法。 -
try()⾥⾯可以定义多个资源,它们的关闭顺序是最后在try()定义的资源先关闭
字符串考察
String str = new String("abc")创建了几个对象
如果常量池存在,则直接new一个对象; 如果常量池不存在,则在常量池创建一个对象,也在堆里面创建一个对象
字符串比较
String str1= new String("hello word");
String str2= "hello word";
String str3= "hello word";
System.out.println(str1 == str2) //false
System.out.println(str2 == str3) //true
原因:new创建新的对象会开辟新的空间,地址不同。str2与str3都是从常量池中获取,"hellow world"存在于常量池中。
常量池知识补充
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
-
类和接口的全限定名 -
字段名称和描述符 -
方法名称和描述符
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。 String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。
网上的常量池例子:
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
-
s1 == s2这个非常好理解,s1、s2在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。 -
s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = "Hel" + "lo";在class文件中被优化成String s3 = "Hello",所以s1 == s3成立。只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。 -
s1 == s4当然不相等,s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。 如图 -
s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定,所以不做优化,只能等到运行时,在堆中创建s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。 如图 -
s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。 -
s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。
还有一个经典的例子
public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
String s = A + B; // 将两个常量用+连接对s进行初始化
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
s等于t,它们是同一个对象
A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s="ab"+"cd";
public static final String A; // 常量A
public static final String B; // 常量B
static {
A = "ab";
B = "cd";
}
public static void main(String[] args) {
// 将两个常量用+连接对s进行初始化
String s = A + B;
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
s不等于t,它们不是同一个对象
A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。
三个结论:
-
必须要关注编译期的行为,才能更好的理解常量池。 -
运行时常量池中的常量,基本来源于各个class文件中的常量池。 -
程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。
除了最常用的字符串常量池,还有整型常量池、浮点型常量池(java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;两种浮点数类型的包装类Float,Double并没有实现常量池技术) 等等。
但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,(Byte,Short,Integer,Long,Character,Boolean)这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
例如在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆 中的Integer对象,(我们可以这样理解,系统已经把-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。
String、StringBuffer与StringBuilder的区别?分别在哪些场景下使用
三者都是final, 不允许被继承 在本质都是char[]字符数组实现 String、StringBuffer与StringBuilder中,String是不可变对象,另外两个是可变的
StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全 StringBuffer里面操作方法用synchronized ,效率相对更低,是线程安全的;
使用场景:
-
操作少量的数据用String,但是常改变内容且操作数据多情况下最好不要用 String ,因为每次生成中间对象性能会降低 -
单线程下操作大量的字符串用StringBuilder,虽然线程不安全但是不影响 -
多线程下操作大量的字符串,且需要保证线程安全 则用StringBuffer
javase类
面向对象的四大特性是?分别解释下
封装,继承,多态,抽象
overload与override的区别
overload重载:表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,参数个数或类型不同 override重写:表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,也就是重写了父类的方法。
接口是否可以继承接口?接口是否支持多继承?类是否支持多继承?接口里面是否可以有方法实现
接口可以继承接口使用extends关键字。
接口与类都不支持多继承,但一个类可以实现多个接口
接口里面可以有方法实现(java8之后可以的)