相关推荐recommended
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
作者:mmseoamin日期:2024-02-02

大家好,我是码农阿豪,一位热爱 Java 编程的程序员。今天我想和大家分享一些常见的 Java 面试题,通过收集解析这些问题,希望能够帮助大家更好地准备面试,突破技术瓶颈,把面试官按在地上摩擦

经典面试题收集一

    • 第一章
      • 1. 运算符
      • 2. 数据类型
      • 3. try-catch-finally
      • 4. try-with-resource
      • 5. 文件API和递归
      • 第二章
        • 1. 字符串(重点)
        • 2. 面向对象
        • 3. 接口
        • 第三章(集合框架List)
          • 1. 说下Vector和ArrayList、LinkedList联系和区别?分别的使用场景
          • 2. 如果要保证线程安全,ArrayList应该怎么做,有几种方式?
          • 3.了解CopyOnWriteArrayList吗?它和Collections.synchronizedList实现线程安全有什么区别,使用场景是怎样的?
          • 4.CopyOnWriteArrayList的设计思想是怎样的,有什么缺点?
          • 5.说一下ArrayList的扩容机制是怎样的?
          • 6.设计一个简单的ArrayList(需要包含构造函数、add(e)、扩容机制)
          • 期望与后续更新
          • 感谢阅读

            第一章

            1. 运算符

            • 运算符&和&&、|和||的区别?
              //& 按位与操作,只有对应的两个二进制数为1时,结果位才为1
              1&1=1
              1&0=0
              0&1=0
              0&0=0
              //| 按位或操作,有一个为1的时候,结果位就为1
              1|1=1
              1|0=1
              0|1=1
              0|0=0
              //& 和 && 都能实现 和 这个功能
              //区别:& 两边都运算,而&&先算左侧,若左侧为false,那么右边就不运算,判断语句中推荐&&,效率高
              //| 和 || 和上面的类型
              //区别:|| 只要满足第一个条件,后面的条件就不再判断,而|要对所有条件进行判断
              //把&&和||称为短路运算符
              
              • 用最有效率的方法计算2乘以2的3次方
                //原理:将一个数左移n位,就是将这个数乘以2的n次方
                2 << 3 = 16
                //扩展:常见的JDK源码里面HashMap的默认容量是16
                int DEFAULT_INITAL_CAPACITY = 1 << 4; //16
                //直接是二进制操作了,表示将1左移4位,变成10000,变成十进制就是16
                
                • 写个方法,传递两个非0的int数值进去,实现变量交换,有几种方式?
                  //方式一
                  public static void swap(int a, int b) {
                      System.out.printf("a=%d,b=%d", a, b);
                      a = a + b;
                      b = a - b; //b = a + b - b = a
                      a = a - b; //a = a + b - a = b
                      System.out.printf("a=%d,b=%d", a, b);
                  }
                  //方式二 异或运算(一个数与另一个数异或两次是其本身,一个数和自身异或结果是0)
                  public static void swap2(int a, int b) {
                      System.out.printf("a=%d,b=%d", a, b);
                      a = a ^ b; //a1 = a^b
                      b = b ^ a; //b = b^a^b = a
                      a = a ^ b; //a = a1^b = a^b^a = b
                      System.out.printf("a=%d,b=%d", a, b);
                  

                  2. 数据类型

                  • 说下Java数据类型分类
                    //基础数据类型:byte、short、int、long、float、double、char、boolean
                    //引用类型:其它都是引用类型
                    //String和Enum也是引用类型
                    
                    • 运算:定义变量int i = 5,那么return i++;和return ++i; 返回的结果是什么?
                      //i++ 返回 5,先返回后增加
                      //++i 返回 6,先增加后返回
                      
                      • ==和equals的区别
                        //基本数据类型的比较,要用==判断是否相等
                        //引用数据类型:==比较的是内存地址是否一样,不同对象的内存地址不一样,equals比较的是具体内容,也可以自定义什么条件去判断两个对象是否一样
                        

                        3. try-catch-finally

                        • 1.下面代码的 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
                          //在执行catch中的return之前一定会先执行finally中的代码(如果有finally),如果finally中有return语句就直接执行return方法
                          

                          4. try-with-resource

                          • 1.使用新版的JDK处理IO流,编写一下基础代码,将一个txt文本里面的内容拷贝到另一个txt文本中
                            //需要关闭的资源只要实现了java.lang.AutoCloseable,就可以⾃动被关闭
                            //try()⾥⾯可以定义多个资源,它们的关闭顺序是最后在try()定义的资源先关闭
                            try (
                                FileInputStream fis = new FileInputStream("/Users/lcz/Desktop/test.txt");
                                BufferedInputStream bis = new BufferedInputStream(fis);
                                FileOutputStream fos = new FileOutputStream("/Users/lcz/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();
                            }
                            

                            5. 文件API和递归

                            • 找出某目录下的所有子目录和子文件,并打印到控制台上
                              public static void main(String[] args) {
                                  List paths = new ArrayList<>();
                                  getAllFilePaths(new File("/Users/lcz/Desktop/demo"), paths);
                                  
                                  for (String path: paths) {
                                      System.out.println(path);
                                  }
                              }
                              private static void getAllFilePaths(File filePath, List paths) {
                                  File[] files = filePath.listFiles();
                                  if (files == null) {
                                      return;
                                  }
                                  
                                  for (File f: files) {
                                      if (f.isDirectory()) {
                                          paths.add(f.getPath());
                                          getAllFilePaths(f, paths);
                                      } else {
                                          paths.add(f.getPath());
                                      }
                                  }
                              }
                              

                              第二章

                              1. 字符串(重点)

                              • 1.String str = new String(“xxx”); 创建了几个对象
                                //创建一个对象:常量池存在,则直接new一个对象
                                //创建两个对象:常量池不存在,则在常量池创建一个对象,在堆中也创建一个对象
                                
                                • 2.下面是比较什么?输出结果是什么?为什么是这样的结果?
                                  String str1 = new String("s1");
                                  String str2 = "s2";
                                  String str3 = "s2";
                                  System.out.println(str1 == str2); //false
                                  System.out.println(str2 == str3); //true
                                  //比较引用的内存地址是否一样
                                  //第一个是false:new 创建新的对象会开辟新的内存空间,所以地址不一样
                                  //第二个是true:都是从常量池里面获取,"s2"存在于常量池中
                                  
                                  • 写出下面代码的各个结果,如果需要两个都为true,应该怎么修改?
                                    String s1 = "s1";
                                    String s2 = s1 + "s2"; //变量+常量 来自堆
                                    String s3 = "s1" + "s2"; //常量+常量 来自常量池
                                    System.out.println(s2 == "s1s2"); //false
                                    System.out.println(s3 == "s1s2"); //true
                                    //第⼀条语句打印的结果为false, s2 = s1 + ".net",变量+常量=堆,构建了⼀个新的string对象,并将对象引⽤赋予s2变量,常量池中的地址不⼀样,但是值⼀样。
                                    //第⼆条语句打印的结果为true,javac编译可以对【字符串常量】直接相加的表达式进⾏优化,不⽤等到 运⾏期再去进⾏加法运算处理,⽽是直接将其编译成⼀个这些常量相连的结果
                                    //如果需要第⼀个输出为true,只需要把变量改为常量即可 fianl String s1 = "s1"; 不管是new String("XXX")和直接常量赋值, 都会在字符串常量池创建.只是new String("XXX")⽅式会在堆中创建⼀个对象去指向常量池的对象, 普通的常量赋值是直接赋值给变量
                                    
                                    • String、StringBuffer和StringBuilder的区别?分别在哪些场景下使用?
                                      //三者都是final, 不允许被继承, 本质都是char[]字符数组实现
                                      //String、StringBuffer与StringBuilder中,String是不可变对象,另外两个是可变的
                                      //StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全
                                      //StringBuffer ⾥⾯操作⽅法⽤synchronized ,效率相对更低,是线程安全的
                                      //使⽤场景: 
                                      //操作少量的数据⽤String,但是常改变内容且操作数据多情况下最好不要⽤String,因为每次⽣成中间对象性能会降低
                                      //单线程下操作⼤量的字符串⽤StringBuilder,虽然线程不安全但是不影响
                                      //多线程下操作⼤量的字符串,且需要保证线程安全 则⽤StringBuffer
                                      

                                      2. 面向对象

                                      • 面向对象的四大特性是?分别解释下
                                        //抽象、封装、继承、多态
                                        //抽象:
                                        //关键词abstract声明的类叫作抽象类,abstract声明的⽅法叫抽象⽅法;
                                        //⼀个类⾥包含了⼀个或多个抽象⽅法,类就必须指定成抽象类;
                                        //抽象⽅法属于⼀种特殊⽅法,只含有⼀个声明,没有⽅法体;
                                        //封装:
                                        //封装是把过程和数据包围起来,对数据的访问只能通过已定义的接⼝即⽅法;
                                        //在java中通过关键字private,protected和public实现封装;
                                        //封装把对象的所有组成部分组合在⼀起,封装定义程序如何引⽤对象的数据,封装实际上使⽤⽅法将类的数据隐藏起来,控制⽤户对类的修改和访问数据的程度;
                                        //继承:
                                        //⼦类继承⽗类的特征和⾏为,使得⼦类对象具有⽗类的⽅法和属性,⽗类也叫 基类,具有公共的⽅法和属性;
                                        //多态:
                                        //同⼀个⾏为具有多个不同表现形式的能⼒;
                                        //优点:减少耦合、灵活可拓展;
                                        //⼀般是继承类或者重写⽅法实现;
                                        

                                        3. 接口

                                        • Overload和Override的区别?
                                          //重载Overload:表示同⼀个类中可以有多个名称相同的⽅法,但这些⽅法的参数列表各不相同,参数个数或类型不同;
                                          //重写Override:表示⼦类中的⽅法可以与⽗类中的某个⽅法的名称和参数完全相同;
                                          
                                          • 接口是否可以继承接口?接口是否支持多继承?类是否支持多继承?接口里面是否可以有方法实现?
                                            //接口里可以有静态方法和方法体
                                            //接口中所有的方法必须是抽象方法(JDK8之后就不是了)
                                            //接口不是要被类继承了,而是实现
                                            //接口支持多继承,类不支持多个类继承
                                            //一个类只能继承一个类,但是能实现多个接口,接口能继承另一个接口,接口的继承使用exstends关键字,和类继承一样
                                            
                                            • 是否了解JDK8里面接口新特性?
                                              //interface中可以有static方法,但必须有方法体,该方法只属于接口,接口名直接调用该方法
                                              //接口中新增default关键字修饰的⽅法,default⽅法只能定义在接⼝中,可以在⼦类或⼦接⼝中被重写,default定义的⽅法必须有⽅法体
                                              //⽗接⼝的default⽅法如果在⼦接⼝或⼦类被重写,那么⼦接⼝实现对象、⼦类对象,调⽤该⽅法,以重写为准
                                              //本类、接⼝如果没有重写⽗类(即接⼝)的default⽅法,则在调⽤default⽅法时,使⽤⽗类 (接⼝) 定义的default⽅法逻辑
                                              

                                              第三章(集合框架List)

                                              1. 说下Vector和ArrayList、LinkedList联系和区别?分别的使用场景

                                              //线程安全:
                                              //ArrayList:底层是数组实现,线程不安全,查询和修改非常快,但是新增和删除慢(特殊场景除外:尾端新增和删除)
                                              //LinkedList:底层是双向链表实现,线程不安全,查询和修改速度慢(特殊场景除外:首端的查询和修改),新增和删除速度快
                                              //Vector:底层是数组实现,线程安全的,操作的时候用synchronized进行加锁
                                              //使用场景:
                                              //Vector已经很少用了
                                              //增加和删除的场景多,用LinkedList
                                              //查询和修改的场景多,用ArrayList
                                              

                                              2. 如果要保证线程安全,ArrayList应该怎么做,有几种方式?

                                              //自己写一个包装类,根据业务对List操作进行加锁
                                              //Collections.synchronizedList(new ArrayList<>());使用synchronized加锁
                                              //CopyOnWriteArrayList<>() 使⽤ReentrantLock加锁
                                              

                                              3.了解CopyOnWriteArrayList吗?它和Collections.synchronizedList实现线程安全有什么区别,使用场景是怎样的?

                                              //CopyOnWriteArrayList:执行修改操作时,会拷贝一份新的数组进行操作,代价十分昂贵,在执行完成后将原来的集合指向新的集合来完成修改操作,源码里面使用ReentrantLock可重入锁来保证不会有多个线程同时拷贝一份数组
                                              //场景:读高性能,适用读操作远远大于写操作的场景中使用(读的时候是不需要加锁的,直接获取,删除和新增是需要加锁的,读多写少)
                                              //Collections.synchronizedList:几乎在每个方法上就加了synchronized同步锁
                                              //场景:写操作性能比CopyOnWriteArrayList好,读操作不如
                                              

                                              4.CopyOnWriteArrayList的设计思想是怎样的,有什么缺点?

                                              //设计思想:读写分离+最终一致
                                              //缺点:内存占用问题,写时的复制机制,内存会同时驻扎两个对象的内存,旧对象和新的写入对象,如果对象大,则容易发生Yong GC和Full GC
                                              

                                              5.说一下ArrayList的扩容机制是怎样的?

                                              //未指定集合容量,默认是0,若已经指定⼤⼩则集合⼤⼩为指定的
                                              //当集合第⼀次添加元素的时候,集合⼤⼩扩容为10
                                              //当ArrayList的元素个数⼤于其容量,扩容的⼤⼩=原始⼤⼩+原始⼤⼩/2
                                              

                                              6.设计一个简单的ArrayList(需要包含构造函数、add(e)、扩容机制)

                                              //计算容量+确保容量
                                              private void ensureCapacityInternal(int minCapacity){  
                                                  //如果是初次扩容,则使⽤默认的容量  
                                                  if(elementData == EMPTY_ELEMENT_DATA){
                                                      minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 
                                                  }
                                                  //是否需要扩容,需要的最少容量⼤于现在数组的⻓度则要扩容    
                                                  if(minCapacity - elementData.length > 0){
                                                      int oldCapacity = elementData.length;
                                                      int newCapacity = oldCapacity + (oldCapacity >> 1);
                                                      //如果新容量 < 最⼩容量, 则将最新的容量赋值给新的容量
                                                      if(newCapacity - minCapacity < 0){
                                                          newCapacity = minCapacity;
                                                      }
                                                      //创建新数组
                                                      Object[] objects = new Object[newCapacity];
                                                      //将旧的数组复制到新的数组⾥⾯
                                                      System.arraycopy(elementData,0,objects,0,elementData.length);
                                                      //修改引⽤
                                                      elementData = objects;
                                                  }  
                                              }
                                              
                                              • 总结重点,鼓励读者在日常工作中不断深化对 Java 的理解,持续学习和实践。

                                                期望与后续更新

                                                我希望这些 Java 面试题的分享能够对你有所帮助,为你的面试之路提供有力支持。未来,我将持续更新类似的内容,涵盖更多深入的主题,如Map,并发编程基础与进阶,中间件,数据库,通信协议,框架等,帮助大家更全面地了解 Java 生态系统。

                                                如果你有任何关于 Java 面试题、技术深度解析或其他方面的建议和期望,欢迎在评论区分享。我将根据大家的反馈,优先处理感兴趣的话题,并确保内容的实用性和深度。

                                                感谢阅读

                                                最后,感谢大家花时间阅读我的博客。如果你喜欢这类内容,记得关注我的博客,我们一起在技术的海洋中不断前行,共同成长。