• 【java面试题之基础/语法题汇总 ——长篇挖坑ing】


     

    1.java跨平台原理:

    java具有一次编译,到处运行的特点,

    .java文件经过编译后生成.class文件(字节码文件),字节码文件再通过java虚拟机(jvm)解释为机器码运行,

    当然c/c++也可以跨平台运行,但是却需要在平台上用对应编译器重新编译,而java不用;

    java虚拟机的作用:将字节码文件转化为对应平台的机器码并执行;

    java语言的执行需要编译和解释,其中解释是由java虚拟机中的解释器执行的。

    ps:当跨平台运行时:

    由于跨平台原理,java相比c的运行要慢一步,因为java需要先将class文件转化为机器码再执行,而c经过二次编译后可以直接执行

    2.string对象,StringBuffer,StringBuilder区别简述

    string是final的,不可变的;

    StringBuffer是可变类,线程安全但效率低;

    StringBuilder是可变的,线程不安全但是效率高;

    3.继承,封装,多态

    继承:子类可以从父类中继承所有的非私有化属性和方法;

    封装:隐藏实现细节,将属性私有化,提供公有方法(setget)访问私有属性,可以

    提高程序安全性,隐藏代码细节,提高系统可维护性

    多态:多态性是对象多种表现形式的体现,

    比如A extends B
    可以有A a = new A();
    也可以有B b = new A();//父类的引用指向子类的类型
    对象能执行哪些方法和左边类型有关

    4.java的安全性

    和c相比,Java取消了强大但又危险的指针,而代之以引用。由于指针可进行移动运算,指针可随便指向一个内存区域,而不管
    这个区域是否可用,这样做是危险的,因为原来这个内存地址可能存储着重要数据或者是其他程序运行所占用的,并
    且使用指针也容易数组越界。
     
    Java异常机制主要依赖于try、catch、fifinally、throw、throws五个关键字
     

    并且,在强制转换方面,只有当符合转换规则下才能强转成功;

    5.J2EE,J2SE 和J2ME

    J2ME现在已经不怎么使用了,规范名称是java2平台下的微缩版(Micro Edition

    J2SE是大家最先接触的,标准版,

    J2EE(企业版),可以理解为J2SE的进阶: J2SE包含于J2EE中

    6.JVM,JRE和JDK

    JVM:

    Java Virtual Machine即java虚拟机,前面也说过了,是java实现跨平台的重要前提;JVM的主要工作是解释自己的指令集
    (即字节码)到CPU的指令集或对应的系统调用,保护用户免被恶意程序骚扰。 JVM对上层的Java源文件是不关心
    的,它关注的只是由源文件生成的类文件(.class文件)。
     
    JRE:
    java runtime environment,java运行环境:字面意思,想要运行java程序,JRE是必须安装的,而且解析字节码(.class)文件时也需要JRE中的lib; 注意,JRE中包含JVM
     
    JDK:
    java development kit ,java开发工具包,其内部也有运行环境JRE。JRE是JAVA程序运行时需要的运行环境,就是说
    如果你光是运行JAVA程序而不是去搞开发的话,只安装JRE就能运行已经存在的JAVA程序了。JDk、JRE内部都包含
    JAVA虚拟机JVM,JAVA虚拟机内部包含许多应用程序的类的解释器和类加载器等等。
     
    7.打印当前时刻:
    import java.text.ParseException;
    import java.util.Calendar;
    
    public class Test {
        public static void main(String[] args) throws ParseException {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, 0);
        System.out.println(cal.getTime());
        }
    }

    打印昨天的当前时刻:
    import java.text.ParseException;
    import java.util.Calendar;
    
    public class Test {
        public static void main(String[] args) throws ParseException {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);
        System.out.println(cal.getTime());
        }
    }

     8.&和&& , |和|| 的区别和联系

    &和 | 分别称为按位与和按位或,对应还有&&(逻辑与) 和 || (逻辑或) 

    一般来说,逻辑与,逻辑或都是用来判断true 或者 false,而按位操作可以得出具体的值;

    另外,逻辑与操作有个坑:&&    如果符号左边为假则右侧表达式不会继续执行。

    9.排序方法小结

    排序方法 最优时间 平均时间 最坏时间 空间复杂度 稳定性
    直接插入 O(n) O(n²) O(n²) O(1) 稳定
    二分插入 O(n) O(n²) O(n²) O(1) 稳定
    冒泡排序 O(n) O(n²) O(n²) O(1) 稳定
    快速排序 O(nlogn) O(nlogn) O(n²) O(logn) 不稳定
    希尔排序   O(n1.25   O(1) 不稳定
    直接选择 O(n²) O(n²) O(²) O(1) 不稳定
    堆排序 O(nlogn) O(nlogn)

    O(nlogn)

      不稳定
    归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
    基数排序 O(d(rd+n)) O(d(rd+n)) O(d(rd+n)) O(rd+n) 稳定
               

     10.关于冒泡排序

    大体思想就是对于一队数字,相邻数字通过比较两两交换,实现从小到大或者从大到小排列。利用两个for循环实现,执行内层for循环可以理解为进行一趟比较,每次比较都可以从无序序列中将一个max或者min值交换到首或者尾(想从大到小排列,就每次比较的时候将较小的[i]交换至[i+1]处,反之同理),外层for循环就是执行数趟,直到全部比较结束(添加flag做标志的话就可以减少一些比较次数,在数列第一次基本有序时就跳出循环~)

    import java.util.Arrays;
    
    public class MaoPao {
        public static void main(String[] args) {
    
            MaoPao maoPao = new MaoPao();
            int[] a= {23,46,1,89,43,22,23,44,3};
            //maopaosort(a);
            int [] maopaosort = maoPao.maopaosort(a);
            //System.out.println(Arrays.toString(maopaosort));//可以直接输出数组
            //或者用循环
           for (int num: maopaosort) {
                System.out.print(num+"\t");
            }
    
    
        }
        public int [] maopaosort(int [] b) {
    
            int temp = 0;
            for (int i = 0; i < b.length - 1; i++) {
                int flag = 0;//标记无序
                for (int j = 0; j < b.length - 1 - i; j++) {
                    if (b[j] >= b[j + 1]) {//若左大右小,则data交换
                        temp = b[j];
                        b[j] = b[j + 1];
                        b[j + 1] = temp;
                        flag = 1;//标记有序
    
                    }
                }
    
                if (flag == 0) {//完成某趟排序后发现已经基本有序,就跳出,可以减少一些比较次数
                    break;
                }
    
            }
            return b;
    
        }
    
    }

    11.关于简单选择排序

    思想:每一趟都能找出一个当前最小值,比如第一趟找到的最小值放在a[0],第二趟找到的次小值就放在a[1],以此类推,在数列中有相同关键字有序的时候很不稳定,因为

    交换后会破坏原有关键字的前后顺序,比如1,3,3,2, ,3和2比较,如果有多个3就只能把第一个3换过去,排序后:1,2,3,3 但是原来的 3,3变为3,3,相对位置改变了,现在前后颠倒(虽然看起来一样hh)

    public class SelectSort{
    
    
        public int[] sort(int arr[]) {
            int temp = 0;
            for (int i = 0; i < arr.length - 1; i++) {
                // 认为目前的数就是最小的, 记录最小数的下标
                int minIndex = i;
                for (int j = i + 1; j < arr.length; j++) {
                    if (arr[minIndex] > arr[j]) { // 修改最小值的下标
                         minIndex = j;
                    }
                }
                // 当退出for就找到这次的最小值,就需要交换位置了
                if(i != minIndex) {
                        temp = arr[i];
                        arr[i] = arr[minIndex];
                        arr[minIndex] = temp;
                }
                    }
            return arr;
                }
    
        public static void main(String[] args) {
            SelectSort selectSort = new SelectSort();
            int[] array = {2,5,1,6,4,9,8,5,3,11,2,0};
            int[] sort = selectSort.sort(array);
            for (int num:sort){
                System.out.print(num+"\t");
            }
        }
    
    }

    12.关于直接插入排序

    思想:其实并不涉及手动插入,而是像整理扑克牌一样,一般我们都习惯将牌升序或者降序排列来方便出牌(也有人会用别的方法,我就是举个栗子)

    发牌后发现基本都是乱序,假设我们从左开始理牌,想要让牌从小到大排列。依然是比较思想,先用第二张和第一张比较,然后就是第三张和前两张比较,看起来和选择排序有些像,

    用例子说明:

    发牌后手中牌:2,6,3,1,13,7,2

    第一趟:2,6,3,1,13,7,2       无交换

    第二趟:2,3,6,1,13,7,2      3和6交换

    第三趟:1,2,3,6,13,7,2   1先和6交换,然后发现1还可以和3交换,交换后,又双叒发现能和2交换,于是就是第三趟排序后就是前面那样子;

    第四趟:1,2,3,6,13,7,2    无交换

    第五趟:1,2,3,6,7,13,2    7和13交换

    第六趟:1,2,2,3,6,7,13    2和13交换,然后2又和7,交换,....交换............最后和3交换,完成排序 

    所以,和选择排序还是很有区别的,选择排序是只找当前最小值,然后不断放在序列头部,直接插入虽然也是头部有序,但并不一定是每一趟都能固定好关键字的位置,

    简单选择每趟排序都能确定一个关键字的位置,直到最后一趟结合素其位置都不会改变,这点在数据结构中好像有过考察。

    public class InsertSort {
        private int[] sort(int[] arr) {
            //如果传入的数组为空或者只有一个值,就直接返回
            if (arr == null || arr.length < 2) {
                return arr;
            }//不为空则进循环判断
            // 外层循环控制总数量
            for (int i = 1; i < arr.length; i++) {
                //内层循环依次减少并提出结果
                for (int j = i; j > 0; j--) {
                    //如果当前数字小于前一个,则交换,否则不变
                    if (arr[j] < arr[j - 1]) {
                        int temp = arr[j];
                        arr[j] = arr[j - 1];
                        arr[j - 1] = temp;
                    } else {
                        break;
                    }
                }
            }
            return arr;
        }
    
    
        public static void main(String[] args) {
            InsertSort insertSort = new InsertSort();
            int[] array = {2, 5, 1, 6, 4, 9, 8, 5, 3, 1, 2, 0};
            int[] sort = insertSort.sort(array);
            for (int num : sort) {
                System.out.print(num + "\t");
            }
        }
    }

    13.面向对象与面向过程

    这个问题和实际编程其实没什么关系,初学者也许知道含义但是又不太好解释出来。

    C,是最常见的面向过程编程语言,(其实还有Fortran语言),c++,java,.net都是面向对象编程语言

    1.面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,

    也就是说 面向过程是以实现功能的函数开发为主


    2.面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

    面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能。

     

    其他显著区别:

    面向对象具有继承性和多态性,而面向过程没有继承性和多态性。

     个人理解:面向过程就是那种很细腻的实现功能,环环相扣,很多底层细节都处理的很好,而面向对象更适合于处理宏观上的复杂关系。

    14.重载overload与重写(覆盖)override

    重载:同一个类中有一些方法,其方法名相同,但是参数个数,参数类型不同

    重载规则:

    • 被重载的方法必须改变参数列表(参数个数或类型不一样);
    • 被重载的方法可以改变返回类型;
    • 被重载的方法可以改变访问修饰符;
    • 被重载的方法可以声明新的或更广的检查异常;
    • 方法能够在同一个类中或者在一个子类中被重载。
    • 无法以返回值类型作为重载函数的区分标准。

    和重写的主要区别:重载范围是一个类内,重写存在于是父子类关系之中

     

    重写(override):重写需要类与类有继承关系,比如A extends B,而且是针对非静态的方法

    我们都知道,子类可以继承父类的属性,方法(注意私有类型除外),倘若我们想在父类方法基础上做一些修改,添加,那就叫重写,字面意思,子类可以对父类的方法进行修改(重写后如果还想用父类的原方法就需要super关键字,

    举例:(注意都在一个包下)

    父类Animal

    public class Animal {
    
    
        public void run(){
            System.out.println("动物都会跑");
        }
    
    
    }

    子类Cat

    public class Cat extends Animal {
    
        public void run(){
            super.run();//自己重写父类方法后还想再调用父类的那个方法就用super关键字
            System.out.println("猫会跑");
            System.out.println("猫跑起来静悄悄");
        }
    
    }

    测试类

    public class demo4 {
        public static void main(String[] args) {
    //重写后都是输出子类方法
            Cat cat = new Cat();
    
            Animal animal = new Cat();//向上转型
    
            animal.run();
    
    
        }
    }

    输出结果:

    动物都会跑
    猫会跑
    猫跑起来静悄悄

    如果不用super调用 ,那就只会输出后两句:

    猫会跑
    猫跑起来静悄悄

    总结:方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

     

    15.this与super

    this是对象内部指代自身的引用,同时也是解决成员变量和局部变量同名问题;
    this可以调用成员变量,不能调用局部变量
    this也可以调用成员方法,但是在普通方法中可以省略this,在构造方法中不允许省略,必须是构造方法的第
    一条语句。而且在静态方法当中不允许出现this关键字。
     
     
    super代表对当前对象的直接父类对象的引用,super可以调用直接父类的成员变量(注意权限修饰符的影响,比如
    不能访问private成员)
    super可以调用直接父类的成员方法(注意权限修饰符的影响,比如不能访问private成员);super可以调用直接父
    类的构造方法,只限构造方法中使用,且必须是第一条语句。

     

     

    16.static关键字

     

    字面意思,static(静态修饰)

    简单来说:

    static修饰的属性,是可以直接 类名.属性名,来调用的.static范围是类内使用的,或者说static变量是属于整个类的,也称为类变量,所以非静态的方法我们想引用时必须用new对象才行,再用对象来调用。

    17.权限修饰符的区别(public,protected,default,private)
     
    从访问权限上看从大到小顺序如下:
    public :所有类都能访问
    protected:本类可访问,同一个包下的其他类,或者不同包的子类也可以访问
    default:可以被同包下的类访问
    private:范围最小,只能被本类访问
     
    这四个修饰符中类的权限只有public和default两种,而成员变量和成员方法则四种都有
     
     
    18.“==”和equals
     
    “==”是关系运算符,而equals()是方法,不过他们的返回结果都是boolean(布尔类型)
     
    1. “==” 可以比较值(两侧是基本类型),也可以比较地址,也就是比较两个对象在内存中的地址是否相同(两侧是引用类型),
    基本数据类型包括:boolean,char,short,int,long,float,double(注意string类型不是基本数据类型)
    引用类型:Map。。(引用类型就是除了基本数据类型之外的所有类型,也包括class类型)
    另外数据类型比较一般都是用 ==,不过String类型比较一般都是equals
     
    2.“equals”如果没有经过重写,只是引用object的equals方法,那么equals和==就是一样的功能,
    如果重写过,那就根据重写内容来比较
     
    举例来说:
      使用“==”  ,对于基本数据类型,就是比较变量的值是否相等,比如:int a = 6; int b = 3; a==b;返回就是false,这个大家都很熟悉
     如果是引用类型,就是比较地址   String a = new String(“abc”) ; String b = new String(“abc”);  这种情况下创建出来的”abc”对象会被存储到堆,
     a和b指向的“abc”分别存储到堆内存的不同位置。a和b里面保存的是”abc”在堆里面的内存首地址,这两个地址明显是不同的,所以我们在做 a==b操作的时候返回的为false
     
     
      使用“equals” ,如果类中没有重写过方法,那就是比较地址  
     
    public class equals {
    
        public static void main(String[] args) {
            StringBuffer a = new StringBuffer("aa");
            StringBuffer b = new StringBuffer("aa");
            System.out.println(a.equals(b));//false
            System.out.println(a==b);//false
    
            System.out.println("+++++分界线+++++");
    
            String s1,s2,s3 = "abc", s4 ="abc" ;
            s1 = new String("abc");
            s2 = new String("abc");
    
            System.out.println(s1==s2);//false 两个变量的内存地址不一样,指向的对象不一样,
            System.out.println( s1.equals(s2));//true    //两个变量的所包含的内容是abc,故相等。
        }
    }

    代码输出如下:

    false

    false
    +++++分界线+++++
    false
    true

    分析:

      前两个sout打印false是因为StringBuffer类中没有重写过equals方法,所以本质还是比较对象,而对象不同所以分配的内存地址也不一样的,所以是false

      后两个一个false,一个true,是因为String类重写了equals方法(感兴趣的可以去看看,idea中对着String左键单击就好),所以equals比较的是字符串内容,是true
    19.为什么重写equals还要重写hashcode
     
    一些资料上都会提到,“重写equals时也要同时覆盖hashcode”;
    先简述一下HashMap的原理
      hashmap在jdk8后采用数组+链表+红黑树的数据结构。我们通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后在返回值对象。
     
    详细的说,HashMap存储数据的时候,是取的key值的哈希值,然后计算数组下标,哈希函数构造方法有:(直接定址法,数字分析法,平方取中法,折叠法,除留余数法,随机数法等等),其中除留余数法是最常用的,也是最合理的。
    如果通过哈希函数计算得出的存储地址相同,称为哈希冲突,也叫哈希碰撞,
    Hash解决冲突的方法: 开放定址法,再散列函数法,链地址法。其中开放定址法又包括(主要是:线性探测法,二次探测法,伪随机探测法)
    而HashMap即是采用了链地址法,也就是数组+链表的方式,
     
    其采用链地址法解决冲突,然后进行存储;取数据的时候,依然是先要获取到哈希值,找到数组下标,然后for遍历链表集合,进行比较是否有对应的key。比较关心的有两点:
    1. 无论是 put 还是 get 的时候,都需要得到key的哈希值,去定位key的数组下标;
    2. 在 get 的时候,需要调用equals方法比较是否有相等的key存储过

    第二点解释了为什么我们需要重写equals,==只是比较地址,并不能确认是否有相同的key,不重写的话上一个 话题也提到了,会返回false,所以需要重写equals

    那么为什么重写equals后还要重写hashcode?

    由于在hashMap中在put 时,散列函数根据它的哈希值找到对应的位置,如果该位置有元素,首先会使用hashCode方法判断,如果没有重写hashCode方法,那么即使俩个对象属性相同hashCode方法也会认为他们是不同的元素(另外key值相同却被认为是不同,所以会出现相同的key,而map规定不应该出现重复key),又因为Set是不可以有重复的,所以这会产生矛盾,那么就需要重写hashCode方法

    一句话,如果不重写hashcode方法,任何对象的hashcode值都不相等(即使逻辑上key相同,我们希望它显示相等时,结果依然是不等)

    由于没有重写hashCode方法,所以put操作时,key(hashcode1)–>hash–>indexFor–>最终索引位置 ,而通过key取出value的时候 key(hashcode1)–>hash–>indexFor–>最终索引位置,由于hashcode1不等于hashcode2,导致没有定位到一个数组位置而返回逻辑上错误的值null(也有可能碰巧定位到一个数组位置,但是也会判断其entry的hash值是否相等,上面get方法中有提到。)

    此段引用:https://www.cnblogs.com/chengxiao/p/6059914.html

    所以,在重写equals的方法的时候,必须注意重写hashCode方法,同时还要保证通过equals判断相等的两个对象,调用hashCode方法要返回同样的整数值。而如果equals判断不相等的两个对象,其hashCode可以相同(只不过会发生哈希冲突,应尽量避免)

     
     
     

    To be continued......

     

  • 相关阅读:
    正则表达式
    数据结构与算法-串
    数据结构与算法-优先级队列
    数据结构与算法-词典
    数据结构与算法-高级搜索树
    数据结构与算法-二叉搜索树
    数据结构与算法-图
    数据结构与算法-二叉树
    数据结构与算法-栈与队列
    数据结构与算法-列表
  • 原文地址:https://www.cnblogs.com/dabuliu/p/14364184.html
Copyright © 2020-2023  润新知