主文章(我总结的面试题的索引目录—进不去就说明我还没写完) |
---|
https://blog.csdn.net/grd_java/article/details/122357831 |
字节码指令可以到这篇文章查:https://www.cnblogs.com/longjee/p/8675771.html |
---|
文章目录
1. 语法细节
1. switch是否能作用在byte上,long呢,String呢?
- Java 5之前,switch(expr),expr只能是byte、char、int、short
- Java 5之后,java引入enum枚举类型,expr可以支持enum类型
- Java 7开始,expr可以作用于字符串String类型,long类型在目前所有版本中都不支持
2. java注释有几种,注释有什么用
- java注释有3种,//单行注释,/* 多行注释 * /,/**文档注释 */,其中多行和文档注释不能嵌套
- 注释作用:复杂程序中,适当加入注释,可以增加程序可读性,利用程序修改、调试和交流,注释内容在程序编译时会被忽略,不会对程序执行结果产生任何影响
3. & 和 && 的区别,| 和 || 的区别
- &运算符有两种用法:按位与,逻辑与
- &&运算符是短路与运算,逻辑与和短路与,差别巨大。二者都要求运算符左右两端布尔值都是true,运算结果才为true。
- &&称为短路与的原因是,如果左边表达式为false,右边表达式直接短路,不进行运算
- 而&无论前面已经运算的是不是false,都会和后面的进行运算,不会短路
- 逻辑或运算符(|)和短路或运算符(||)的差别也是如此
4. final 的作用
- 修饰符,可以修饰类、属性、方法,表示最终的,被修饰的对象不可改变
- 被final修饰的
类不可被继承
- 被final修饰的
方法不可以被重写
- 被final修饰的变量不可以被改变,
针对的是变量的引用,引用不可变,引用指向的内容可变
4. final int a = 1; a = 10;正确吗?final Test test = new Test(“zhangsan”);test.name = “lisi”;正确吗?
- final int a = 1; a = 10;不正确,因为1是一个常量,在堆栈中存着,
a变量指向了1
.此时执行a=10;会将a的引用指向10
,而final修饰的变量不可以变换引用
,所以不正确
- final Test test = new Test(“zhangsan”);test.name = “lisi”;是
正确
的,test的引用始终没变过,final修饰的变量,只有引用不可变
,内容可变
,test.name = "lisi";改的是内容
5. break,continue,return的区别和作用
- break
跳出当前层
循环,不在执行当前循环体(不影响外层循环),switch中直接跳出switch- continue
跳出本次
循环,继续执行下次循环(结束本次循环,直接判断下一个循环条件)- return 程序返回,不在执行下面代码(结束当前方法,直接返回)
6. java多重嵌套循环中,如何跳出指定层循环?
- java提供循环标签,break和continue可以指定跳出相应标签的循环
7. == 和 equals 区别是什么
- ==:运算对象是基本数据类型时,比较的是值(两个值相不相同)。运算的是引用类型时,比较两个对象内存地址是否相同(看两个对象是不是同一个对象)
- equals():判断两个对象是否相等
- 类没有覆盖equals()方法,则比较两个对象内存地址,和 == 一样
- 类覆盖了equals()方法,看具体覆盖的实现,比如我们判断两个对象内容是否相等,相等返回true
8. String str = "i"与String str=new String(“i”)一样吗?
- 不一样,因为内存分配方式不一样
- String str = "i"的方式,java虚拟机会将其分配到常量池中(常量池:存储常量)
- String str=new String(“i”)的方式,会将其分配到堆内存中(堆:存储new出来的),然后堆指向常量池,并不是直接指向常量池的"i"
- 还不理解,看下面代码
public static void main(String[] args) {
String a = "abc";//直接将"abc"放在常量池,然后栈中a直接指向常量池"abc"
String b = "a"+"bc";//直接将结果算出来"abc",然后栈中b直接指向常量池"abc"
String c = new String("abc");//new String是多余的,先在堆中开辟内存,c指向堆,堆指向常量池"abc"
System.out.println(a.equals(b)+" "+c.equals(a));//true true,表示a、b、c的值相同
System.out.println(a.hashCode()+" "+ b.hashCode()+" "+c.hashCode());//3个hashCode值都相同,表示他们最终指向的都是同一个"abc"
System.out.println(
System.identityHashCode(a)+" "+
System.identityHashCode(b)+" "+
System.identityHashCode(c)
);//356573597 356573597 1735600054,可见a和b的内存地址相同,c的内存地址不同,因为a和b直接指向常量池,而c指向堆
}
- 拓展(下面是代码的字节码文件,也就是运行时,执行的汇编指令,
先看代码再看字节码
)
public static void main(String[] args) {
String a = "abc";//直接将"abc"放在常量池,然后栈中a直接指向常量池"abc"
String b = "a"+"bc";//直接将结果算出来"abc",然后栈中b直接指向常量池"abc"
String d = "abc"+"";//直接将结果算出来"abc",然后栈中d直接指向常量池"abc"
/**
* 变量参与运算,
* 根据a拿到"abc",
* 然后StringBuilder sb = new StringBuilder();
* 然后sb.append("abc");然后sb.addpen("")
* sb.toString(),将StringBuilder搞成String,源码是return new String(value, 0, count);
* 既然new了,那么现在堆中开辟空间
* call指向堆,然后堆指向"abc"
*/
String call = a + "";
System.out.println(a.hashCode()+" "+call.hashCode());//96354 96354,说明call和a的值都是常量池的"abc"
System.out.println(
System.identityHashCode(a)+" "+
System.identityHashCode(b)+" "+
System.identityHashCode(d)+" "+
System.identityHashCode(call)
);//356573597 356573597 356573597 1735600054,a、b、d 都直接指向常量池"abc" . call指向堆,然后堆指向"abc"
}
9. String、StringBuffer、StringBuilder的区别于联系
- String类是不可变类,一个String对象被创建后,字符序列不可变(字符串是常量),直至对象被销毁(当我们变量换引用后,原来的字符串常量,没有引用指向它,就会被当做垃圾,被JVM底层垃圾回收算法回收)
- StringBuffer类代表一个字符序列可变的字符串,通过append、insert、reverse、setCharAt、setLength等方法操作其内容。当需要生成最终字符串时,通过toString方法将其转变为String(并不是StringBuffer变成String,而是根据值,返回一个String对象)
- JDK1.5新增了一个StringBuilder类,与StringBuffer类似,构造方法和方法基本相同。但是StringBuffer是线程安全的,StringBuilder是线程不安全的,性能略高。通常,创建一个可变字符串,优先StringBuilder
- StringBuffer:JDK1.0就有,效率相对较低,线程安全
- StringBuilder:JDK1.5加入,效率高,线程不安全
三个类,底层维护的都是char[],但是String的char[] 是使用final修饰的,所以String类和它的char[]的引用不可改变,但是char[]中数组值可变,但是因为String类的char[]是private我们没法改,所以我们常说String是常量,不可变,想要变,只能new一个新的String或者指向其它字符串常量
- 而
StringBuffer的数组
,有初始长度为16
,当满了
以后,是可以扩容
的(建立一个容量更大的新数组,然后将满的数组值拷贝),所以叫可变长字符串
10. int 和 Integer 的区别
- java很纯洁,但为了编程方便,依然引入了基本数据类型int,char等等,为了将这些基本数据类型当成对象操作,java为每种基本数据类型都引入了对应包装类(wrapper class),int包装类为Integer,Java 5开始引入了自动装箱/拆箱机制,二者可以相互转换。
- Java为每个原始类型都提供了包装类型
boolean—>Boolean、char—>Character、byte—>Byte、short—>Short、Integer—>Integer、long—>Long、float—>Float、double—>Double
11. Java异常关键字
- try - 用于监听。可能抛出异常的代码放在try语句块中,当他们发生异常时,异常就被抛出
- catch - 用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally - finally语句块总是会被执行。主要用于回收try块里打开的资源(数据库连接,网络连接,IO连接等)。
finally执行完成后,才会回来执行try或者catch块中的return或throw语句,如果finally中使用return或throw等终止方法的语句,则不会跳回执行,直接停止
- throw - 用于抛出异常
- throws - 用在方法签名上,声明该方法可能抛出异常。
12. Java为什么只能单继承,但可以多实现接口
- 多实现方法,不会冲突,因为接口中没有方法体,所以实现都是再实现类中编写,,比如实现的两个接口都有a方法,因为两个接口都没方法体,具体实现再实现类中,不会冲突
- 多继承方法内容,容易冲突,继承的两个类都有a方法,两个父类各有各的实现,就会冲突
13. int value = 0;if(value||(value==0)) return true;会返回true吗?
- 不能,&&、||、!这些符号,都只能作用于boolean值,value是int类型数据,无法运算,编译错误
14. 已知String继承Object类,下面两个泛型集合,可以自动向上转型么?
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
list1 = list2;
- 不能,会编译错误;A继承B类,但是C< A >和C< B >不存在继承关系,是并列关系;
15. 已知String继承Object类,下面两个泛型集合,可以自动向上转型么?
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<? extends Object> list3 = null;
list3 = list1;
list3 = list2;
- 完全可以,<? extends Object>是泛型受限(规定上限),只要是Object或继承于Object都可以受限转型
16. 已知A继承B类,下面代码可以正常运行吗?
List<Object> o = new ArrayList<>();
List<A> a = new ArrayList<>();
List<B> b = new ArrayList<>();
List<? extends B> list3 = null;
list3 = a;
list3 = b;
list3 = o;
- list3 = o;会编译错误,因为Object不是B类的子类
17. 已知A继承B类,下面代码可以正常运行吗?
List<Object> o = new ArrayList<>();
List<A> a = new ArrayList<>();
List<B> b = new ArrayList<>();
List<? super B> list3 = null;
list3 = a;
list3 = b;
list3 = o;
- list3 = a;会编译错误,<? super B> 是泛型受限(规定下限),只要是B类或B类的父类都可以受限转型,但是A类是B的子类,所以不可以受限转型,编译错误。
18. 下面代码中,对与方法参数List<?> list的操作,最终输出结果是什么?
public class A{
public void a(List<?> list){
for(Object o:list){
System.out.println(o)
}
list.add("abc");
list.add(null);
Object s = list.get(0);
System.out.println(s)
}
}
class B{
public static void main(String[] args){
A a = new A();
a.a(new ArrayList<String>());
a.a(new ArrayList<Integer>());
}
}
- list.add(“abc”);这行代码会编译错误,其它代码没问题,因为使用泛型通配符<?>,无法确定list中规定什么数据类型,所以无法进行具体类型的赋值操作,list.add(null);是没有问题的
2. 类型转换题
1. short s1=1;s1 = s1+1;有错吗?short s1=1;s1+=1;有错吗?
1
是int
类型,s1
是short
类型,s1+1
自动向上转型,变成int型
结果,需要强转(short)(s1+1)
才可以赋值给s1- short s1=1;s1+=1;可以正常编译,
s1+=1
;相当于s1=(short(s1+1))
;其中有隐含的强制类型转换
3. 概念题
1. java 面向对象的特征主要有几个方面?
- 封装:把对象属性私有化,有选择的提供可以被外界访问的属性和方法,如果外界没有任何访问某个类的方法,那么这个类没有意义
- 继承:继承父类的功能,以父类为基础,子类可以增加新的功能,也可以使用父类的功能,方便代码复用,是多态的前提
- 子类拥有父类非private的属性和方法
- 子类可以拥有自己的属性和方法,子类就是对父类进行扩展
- 子类可以自己实现父类的方法(重写)
- 多态:一个功能的多种访问(实现)形式,例如我们一个接口,可以有多个实现类,例如List接口,我们可以用ArrayList或LinkedList的实现。虽然调的都是List的方法,但是实现不同。同时也有方法重载,同名方法,根据参数列表不同,可以有不同的实现
- 重载:同名方法a,根据参数列表不同,调用不同的a方法
- 重写:将父类a方法推翻,自己定义新的实现
- 多态虽然提高扩展性,但是提高的不彻底,真正提高还得是反射,传个字符串可以反射任意类
2. 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
- 两个都是
实现多态的方式
,区别在于前者实现的是编译时的多态性,后者实现的是运行时的多态性- 重载:发生在
同一个类中
,方法名相同参数列表不同
(参数类型不同,个数不同,顺序不同
),与方法返回值和访问修饰符无关,重载方法不能根据返回类型进行区分- 重写:发生在
父子类中
,方法名、参数列表必须相同
,返回值小于等于父类(自动向上转型),抛出的异常小于等于父类,访问修饰符大于等于父类(里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象);- 如果
父类方法访问修饰符为private则子类不能重写
,子类中就算有同名方法,也不是重写
3. java内存分配
- 常量池:存放常量,例如"abc".处于方法区中,方法区的一部分
- 堆:存放用new产生的数据
- 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象)
- 寄存器:我们在程序中无法控制
4. 根据代码,判断结果
1. 下面代码中,两个数是否交换成功?
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("输出交换前的两个数:"+a+"---"+b);
changeNum(a,b);
System.out.println("输出交换后两个数"+a+"---"+b);
}
public static void changeNum(int num1,int num2){
int t;
t = num1;
num1 = num2;
num2 = t;
}
}
- a和b的值交换没有成功,num1和num2的倒是换成功了
- 我们调用方法changeNum(a,b),此时传递的是值,将10和20传了过去给num1和num2
- changeNum()方法里面做的任何操作,和a、b已经没有任何关系了
- 最终输出的还是10和20
5. 集合
1. Comparable 和 Comparator
- Comparabel接口(内部比较器),java.lang包下,有个一个compareTo(Obj)方法来排序,比较Obj和this(内部的自己)谁大
- Comparator接口(外部比较器),java.util包下,有一个compare(Obj1,Obj2)方法来排序,比较Obj1和Obj2谁大
- 一般对集合自定义排序,需要重写compareTo或compare方法,当我们需要对集合实现两种排序方式,比如一个song对象中歌名和歌手名分别采用一种排序方式的话,我们可以重写compareTo方法和使用自制的Comparator方法,或者两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort()
- 假设num1 = 10;num2=20;如何比较他俩谁大呢?num1-num2>0,表示num1>num2。如果=0,表示他俩一样大,如果<0表示num2比num1大
- 那么上面两个数比较当然好比较,但是换成对象呢?该如何比较两个对象谁大谁小呢?这就需要你用这两个接口为对象定制比较规则(同样,返回值>0,表示num1>num2…)
- 下面分别介绍Comparable和Comparator的用法,以及如何对集合正确排序
- Comparable需要让对象实现Comparable接口,集合排序时Collections.sort(list);会自动调用对象重写的compareTo()
- Comparator直接用匿名内部类实现即可,将两个需要排序的对象给它,集合排序,Collections.sort(list,new Comparator())即可
- Comparabel接口(内部比较器),实现更简单, Comparator接口(外部比较器)更灵活
import java.util.*;
public class Test implements Comparable<Test>{
private Integer age;
public Test(Integer age) {
this.age = age;
}
/**
* 返回一个int值,正数表示自己(this)比o大,0表示this=o2,负数表示this小于o2
*/
@Override
public int compareTo(Test o) {
//根据年龄决定谁大
return this.age-o.age;
}
@Override
public String toString() {
return "Test{" +
"age=" + age +
'}';
}
public static void main(String[] args) {
Test test = new Test(1);
Test test1 = new Test(2);
ArrayList<Test> tests = new ArrayList<>();
tests.add(test1);
tests.add(test);
System.out.println("list集合排序前"+Arrays.toString(tests.toArray())+"\n\n");
System.out.println("==========Comparable排序===========");
int i = test.compareTo(test1);
System.out.println("Comparable:test和test1谁大(正数表示test大,0表示相等,负数表示test1大)"+i);
Collections.sort(tests);
System.out.println("list集合排序后"+Arrays.toString(tests.toArray())+"\n\n");
System.out.println("==========Comparator排序===========");
Comparator<Test> comparator = new Comparator<Test>() {
/**
* 返回一个int值,正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
*/
@Override
public int compare(Test o1, Test o2) {
//根据年龄决定谁大
return o2.age-o1.age;
}
};
int compare = comparator.compare(test, test1);
System.out.println("Comparator:test和test1谁大(正数表示test1大,0表示相等,负数表示test大)"+compare);
Collections.sort(tests, new Comparator<Test>() {
/**
* 返回一个int值,正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
*/
@Override
public int compare(Test o1, Test o2) {
return o2.age-o1.age;
}
});
System.out.println("list集合排序后"+Arrays.toString(tests.toArray()));
}
}
1. ArrayList和LinkedList 的区别是什么?
- 数据结构不同:ArrayList是动态数组的数据结构,LinkedList是双向链表
- 随机访问效率不同:ArrayList随机访问效率更高,拿到下标直接取。LinkedList是线性数据存储方式,需要移动指针依次从前往后查找
- 增加和删除效率不同:非首尾增加和删除,LinkedList要比ArrayList效率高,ArrayList增删操作要影响数组内其它数据下标。往中间插入,需要其它数据向后移动。LinkedList直接改前驱和后缀
- 内存空间占用:LinkedList相较更费内存,除了存储数据,还要存储前驱和后缀的引用
- 线程安全:哥俩都不同步,都线程不安全
- 频繁读取数据,用ArrayList,插入,删除多,用LinkedList