JAVA面试题2021最新整理——JAVA基础篇

点赞、收藏不迷路,转载请注明出处,感谢各位!

目录

8种基本数据类型

自动拆装箱

String

static关键字

final关键字

continue、break、return区别

面向对象的三大特征

接口与抽象类的区别

continue、break、和 return 的区别

==与equals区别

为什么重写equals()需要重写hashcode()

类的构造方法

序列化

Error与Exception

String、StringBuilder、StringBuff比较

List、Set、Map比较

17、ArrayList、LinkedList区别

List如何去重

未完待续...


 

 

 

8种基本数据类型

  • 字符型:char
  • 布尔型:boolean
  • 数值型:整型 byte、short、int、long;  浮点型 float、double

注意:

  • string不是基本数据类型,是引用类型
  • Java 里使用 long 类型的数据一定要在数值后面加上 L

自动拆装箱

  • 自动装箱: 就是将基本数据类型自动转换成对应的包装类。
  • 自动拆箱:就是将包装类自动转换成对应的基本数据类型。
    Integer i = 10;  //自动装箱
    int b = i;     //自动拆箱

自动装箱都是通过包装类的 valueOf() 方法来实现的.自动拆箱都是通过包装类对象的 xxxValue() 来实现的。

哪些场景会自动拆装箱?

  • 将基本数据类型数据放入集合中,此时会自动装箱
  • 包装类型与基本类型数据的大小比较,此时会把包装类型数据拆箱再进行比较
  • 包装类型数据运算,此时会被自动拆箱成基本类型进行

包装对象的数值比较,不能简单的使用 ==,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用 equals 比较。

String

一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。

如果需要对字符串做很多修改,那么应该选择使用StringBuffer & StringBuilder 类

static关键字

  • 被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
  • 一个类中的静态变量,不属于类的对象或者实例。因为静态变量与所有的对象实例共享,因此他们不具线程安全性。
  • 静态方法中不能访问非静态成员方法和非静态成员变量,反之则可以。
  • 静态方法不能被覆盖(重写),因为方法的覆盖是基于运动时动态绑定,而静态方法是在编译器静态绑定。

final关键字

  • 被final修饰的类无法被继承。
  • 被final修饰的方法不能被重写。
  • 对于final修饰的变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

continue、break、return区别

  • continue :指跳出当前的这一次循环,继续下一次循环。
  • break :指跳出整个循环体,继续执行循环下面的语句。
  • return :用于跳出所在方法,结束该方法的运行。

面向对象的三大特征

封装:通过公有的方法访问私有的属性

继承:继承就是子类继承父类的特征和行为,或子类从父类继承方法,使得子类具有父类相同的行为,但是子类不能选择性地去继承父类,它可以增加新的属性和行为

     关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。

多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果

   多态的前提: 

  • 类与类之间必须有关系,要么继承(extends),要么实现(implement)。 
  • 子类要重写父类的方法
  • 父类的引用指向子类对象

7、重载(Overloading)和重写(Overriding)

重载就是方法有同样的名称,但是参数列表不相同;重写指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况,子类的新方法覆盖父类的方法。

重载的条件:

  • 被重载的方法必须改变参数列表;
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。

重写的条件:

  • 参数列表必须完全与被重写方法的相同;
  • 返回值类型也必须相同;
  • 访问级别的限制性一定不能比被重写方法的强;
  • 重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常。

接口与抽象类的区别

使用abstract来修饰一个类或方法,则称为抽象类或抽象方法;从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。

抽象类

  • 含有抽象方法的类必须被声明为抽象类,有抽象方法的类一定是抽象类,抽象类不一定有抽象方法
  • 抽象类必须被继承,(抽象类本身无法实例化,不被继承也就没有存在的意义)
  • 抽象方法必须被重写  (抽象方法不全重写,子类也应该是抽象类)
  • 抽象类不能被实例化  (可以有自己的构造方法,但是不能直接通过new进行实例化)
  • 抽象方法只需声明,而不需实现

接口

  • 接口不能用于实例化对象,接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口除了 static 和 final 变量不能包含其他动态变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

continue、break、和 return 的区别

  • continue是指跳出当前循环,继续下一次循环
  • break是指跳出循环体,继续执行后面的代码
  • return是指跳出所在方法,结束该方法的运行

==与equals区别

== 对于基本数据类型,它比较的是值;对于引用类型,它比较的是内存地址。

equals它的作用也是判断两个对象是否相等,它不能用于比较基本数据类型的变量。

equals方法存在Object类中而Object类是所有类的间接或者直接父类所以在使用equals方法时存在两种情况:

  • 类没有重写equals方法,则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象
  • 类重写equals方法,则比较两个对象的value值是否相等(String中就重写了)

注意:

  • 《阿里巴巴Java开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。 
  • equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。

为什么重写equals()需要重写hashcode()

从第10题引伸而来,如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。

来看一下示例:

@SpringBootTest
class DemoApplicationTests {

    public static class Key{
        private Integer id;

        public Key(Integer id){
            this.id = id;
        }
        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public boolean equals(Object o){
            if (!(o instanceof Key)){
                return false;
            }else{
                Key key = (Key) o;
                return Objects.equals(id,key.id);
            }

        }

//        public int hashCode(){
//            return id.hashCode();
//        }
    }
    @Test
    void testOne(){
        Key k1 = new Key(1);
        Key k2 = new Key(1);
        System.out.println(k1.equals(k2));
        System.out.println(k1.hashCode());
        System.out.println(k2.hashCode());
        HashMap<Key, Object> hashMap = new HashMap<>();
        hashMap.put(k1,"开门");
        System.out.println(hashMap.get(k2));
    }
}

此时重写了equals(),把重写的hashCode()注释掉,这里虽然k1和k2是相等的。但是HashMap中的get源码:get的时候会先比较hashCode然后再去比较equals, 返回结果为null其实都是hashCode惹的祸

把重写的hashCode()放开之后

类的构造方法

它主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了,所以我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。

序列化

序列化就是将一对象(标志对象的类型)及其状态转换为字节码,保存起来(可以保存在数据库,内存,文件等),然后可以在适当的时候再将其状态恢复(也就是反序列化)。

疑问:为什么类没有实现序列化也可以持久化到数据库中?

答:实际上我们并不是将整个对象持久化到数据库中, 而是将对象中的属性持久化到数据库中, 而这些属性都是实现了Serializable接口的基本属性.

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

Error与Exception

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 检查异常(必须处理) 和 非检查异常(可以不处理)。
  • Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获 。例如,虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

这里面试时可能会让你说几个常见的非检查异常(也就是运行时异常)或者检查异常:

unchecked exception:

  1. NullPropagation:空指针异常;
  2. ClassCastException:类型强制转换异常
  3. IllegalArgumentException:传递非法参数异常
  4. IndexOutOfBoundsException:下标越界异常
  5. NumberFormatException:数字格式异常

checked exception

  1. ClassNotFoundException:找不到指定class的异常
  2. IOException:IO操作异常
  3. SqlException

String、StringBuilder、StringBuff比较

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

List、Set、Map比较

  • List: 存储的元素是有序的、可重复的。
  • Set: 存储的元素是无序的、不可重复的。
  • Map: 使用键值对(key-value)存储,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。

ArrayList、LinkedList区别

ArrayList底层是动态数组,当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量,新的容量=旧的容量 * 1.5 ,看一下jdk1.8中主要的源码

 

int newCapacity = oldCapacity + (oldCapacity >> 1);  

新数组的长度为旧数组长度+旧数组长度对应的二进制码右移一位。二进制码右移一位 

比如11100(对应十进制是28)右移一位是1110(对应十进制是14)就减少一半

所以应该新的容量是原容量*1.5 

LinkedList是双向链表结构,双向链表的每一个结点都有一条指向其后继结点的next链和一条指向其前结点的pre链。双向链表既可以从第一项开始遍历也可以从最后一项开始往前遍历。 

对比:

ArrayList   删除 ,插入数据慢
LinkedList, 插入,删除数据快
ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢

List如何去重

1、如果集合中的数据类型是基本数据类型(如果是自建的对象类型,则需要重写equals和hashcode方法),可以直接将list集合转换成set,就会自动去除重复的元素,但是这种无法保证原有顺序。

 	/**
     * 去除重复数据
     * 由于Set的无序性,不会保持原来顺序
     * @param list
     */
    public static List<String> list distinct(List<String> list) {
        final boolean sta = null != list && list.size() > 0;
        List doubleList= new ArrayList();
        if (sta) {
            Set set = new HashSet();
            set.addAll(list);
            doubleList.addAll(set);
        }
        return doubleList;
    }

2、利用Set集合保持顺序一致去重

// Set去重并保持原先顺序的两种方法
   public static void delRepeat(List<String> list) {
   	   //方法一
       List<String> listNew = new ArrayList<String>(new TreeSet<String>(list));
       //方法二
       List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(list));
   }

3、遍历List,把元素添加到另一个新的List中

// 遍历后判断赋给另一个list集合,保持原来顺序
public static List<String> delRepeat(List<String> list) {
	  List<String> listNew = new ArrayList<String>();
	  for (String str : list) {
	       if (!listNew.contains(str)) {
	           listNew.add(str);
	       }
	   }
	  return listNew ;
}

4、使用java8 stream去重

public static List<String> delRepeat(List<String> list) {
     List<String> myList = list.stream().distinct().collect(Collectors.toList());
	 return myList ;
}

 


未完待续...

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LoneWalker、

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值