BTA 常问的 Java基础39道常见面试题及详细答案(一)

最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。

为此我业余时间整理了,Java基础常见的40道常见面试题,及详细答案,望各路大牛,发现不对的地方,不吝赐教,留言即可。

  • 八种基本数据类型的大小,以及他们的封装类
  • 引用数据类型
  • Switch能否用string做参数
  • equals与==的区别
  • 自动装箱,常量池
  • Object有哪些公用方法
  • Java的四种引用,强弱软虚,用到的场景
  • Hashcode的作用
  • HashMap的hashcode的作用
  • 为什么重载hashCode方法?
  • ArrayList、LinkedList、Vector的区别
  • String、StringBuffer与StringBuilder的区别
  • Map、Set、List、Queue、Stack的特点与用法
  • HashMap和HashTable的区别
  • JDK7与JDK8中HashMap的实现
  • HashMap和ConcurrentHashMap的区别,HashMap的底层源码
  • ConcurrentHashMap能完全替代HashTable吗
  • 为什么HashMap是线程不安全的
  • 如何线程安全的使用HashMap
  • 多并发情况下HashMap是否还会产生死循环
  • TreeMap、HashMap、LindedHashMap的区别
  • Collection包结构,与Collections的区别
  • try?catch?finally,try里有return,finally还执行么
  • Excption与Error包结构,OOM你遇到过哪些情况,SOF你遇到过哪些情况
  • Java(OOP)面向对象的三个特征与含义
  • Override和Overload的含义去区别
  • Interface与abstract类的区别
  • Static?class?与non?static?class的区别
  • foreach与正常for循环效率对比
  • Java?IO与NIO
  • java反射的作用于原理
  • 泛型常用特点
  • 解析XML的几种方式的原理与特点:DOM、SAX
  • Java1.7与1.8,1.9,10 新特性
  • 设计模式:单例、工厂、适配器、责任链、观察者等等
  • JNI的使用
  • AOP是什么
  • OOP是什么
  • AOP与OOP的区别

八种基本数据类型的大小,以及他们的封装类

  • 八种基本数据类型:int、short、float、double、long、boolean、byte、char。
  • 封装类分别是:Integer、Short、Float、Double、Long、Boolean、Byte、Character。

引用数据类型
引用数据类型是由类的编辑器定义的,他们是用于访问对象的。这些变量被定义为不可更改的特定类型。

例如:Employee, Puppy 等等

类对象和数组变量就是这种引用数据类型。
任何引用数据类型的默认值都为空。
一个引用数据类型可以被用于任何声明类型和兼容类型的对象。

Switch能否用string做参数
jdk7之前
switch 只能支持 byte、short、char、int 这几个基本数据类型和其对应的封装类型。

switch后面的括号里面只能放int类型的值,但由于byte,short,char类型,它们会?自动?转换为int类型(精精度小的向大的转化),所以它们也支持。

jdk1.7后
整形,枚举类型,boolean,字符串都可以。

原理

switch (expression)  // 括号里是一个表达式,结果是个整数{
  case constant1:   // case 后面的标号,也是个整数
     group of statements 1;
     break;
  case constant2:
     group of statements 2;
     break;
  ...
  default:
     default group of statements
}

jdk1.7后,整形,枚举类型,boolean,字符串都可以。

public class TestString {

    static String string = "123";
    public static void main(String[] args) {
        switch (string) {
        case "123":
            System.out.println("123");
            break;
        case "abc":
            System.out.println("abc");
            break;
        default:
            System.out.println("defauls");
            break;
        }
    }
}

为什么jdk1.7后又可以用string类型作为switch参数呢?

其实,jdk1.7并没有新的指令来处理switch string,而是通过调用switch中string.hashCode,将string转换为int从而进行判断。

equals与==的区别
使用==比较原生类型如:boolean、int、char等等,使用equals()比较对象。

1、==是判断两个变量或实例是不是指向同一个内存空间。
equals是判断两个变量或实例所指向的内存空间的值是不是相同。

2、==是指对内存地址进行比较。
equals()是对字符串的内容进行比较。

3、==指引用是否相同。
equals()指的是值是否相同。

public static void main(String[] args) {

        String a = new String("ab"); // a 为一个引用
        String b = new String("ab"); // b为另一个引用,对象的内容一样
        String aa = "ab"; // 放在常量池中
        String bb = "ab"; // 从常量池中查找

        System.out.println(aa == bb); // true
        System.out.println(a == b); // false,非同一对象
        System.out.println(a.equals(b)); // true
        System.out.println(42 == 42.0);  // true
    }
public static void main(String[] args) {
    Object obj1 = new Object();
    Object obj2 = new Object();
    System.out.println(obj1.equals(obj2));//false
    System.out.println(obj1==obj2);//false
    obj1=obj2;
    System.out.println(obj1==obj2);//true
    System.out.println(obj2==obj1);//true
}

自动装箱,常量池
自动装箱 在jdk?1.5之前,如果你想要定义一个value为100的Integer对象,则需要如下定义:

Integer i = new Integer(100);

int intNum1 = 100; //普通变量
Integer intNum2 = intNum1; //自动装箱
int intNum3 = intNum2; //自动拆箱
Integer intNum4 = 100; //自动装箱

上面的代码中,intNum2为一个Integer类型的实例,intNum1为Java中的基础数据类型,将intNum1赋值给intNum2便是自动装箱;而将intNum2赋值给intNum3则是自动拆箱。

八种基本数据类型: boolean byte char shrot int long float double ,所生成的变量相当于常量。

基本类型包装类:Boolean Byte Character Short Integer Long Float Double。

自动拆箱和自动装箱定义:

自动装箱是将一个java定义的基本数据类型赋值给相应封装类的变量。
拆箱与装箱是相反的操作,自动拆箱则是将一个封装类的变量赋值给相应基本数据类型的变量。

Object有哪些公用方法
Object是所有类的父类,任何类都默认继承Object

clone
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

equals
在Object中与==是一样的,子类一般需要重写该方法。

hashCode
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

getClass
final方法,获得运行时类型

wait
使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。
wait() 方法一直等待,直到获得锁或者被中断。
wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生

1、其他线程调用了该对象的notify方法。
2、其他线程调用了该对象的notifyAll方法。
3、其他线程调用了interrupt中断该线程。
4、时间间隔到了。
5、此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

notify
唤醒在该对象上等待的某个线程。

notifyAll
唤醒在该对象上等待的所有线程。

toString
转换成字符串,一般子类都有重写,否则打印句柄。

Java的四种引用,强弱软虚,用到的场景
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

1、强引用

最普遍的一种引用方式,如String s = “abc”,变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。

2、软引用(SoftReference)

用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。

3、弱引用(WeakReference)

弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

4、虚引用(PhantomReference)

就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用与软引用和弱引用的一个区别在于:

虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

Hashcode的作用
1、HashCode的特性

(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址。

(2)如果两个对象相同,?equals方法一定返回true,并且这两个对象的HashCode一定相同。

(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。

(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写。

2、HashCode作用

Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。

equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。?于是,Java采用了哈希表的原理。

哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。

这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。

(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了。

(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了。

(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起(很少出现)。

这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

如何理解HashCode的作用:

从Object角度看,JVM每new一个Object,它都会将这个Object丢到一个Hash表中去,这样的话,下次做Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。若HashCode相同再去调用equal。

3、HashCode实践(如何用来查找)

HashCode是用于查找使用的,而equals是用于比较两个对象是否相等的。

(1)例如内存中有这样的位置

0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。

但以上问题如果用HashCode就会使效率提高很多
定义我们的HashCode为ID%8,比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。依此类推。

(2)但是如果两个类有相同的HashCode,例如9除以8和17除以8的余数都是1,也就是说,我们先通过?HashCode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过equals在这个桶里找到我们要的类。

请看下面这个例子

public class HashTest {
    private int i;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public int hashCode() {
        return i % 10;
    }

    public final static void main(String[] args) {
        HashTest a = new HashTest();
        HashTest b = new HashTest();
        a.setI(1);
        b.setI(1);
        Set<HashTest> set = new HashSet<HashTest>();
        set.add(a);
        set.add(b);
        System.out.println(a.hashCode() == b.hashCode());
        System.out.println(a.equals(b));
        System.out.println(set);
    }
}

输出结果为:

true
False
[HashTest@1, HashTest@1]

以上这个示例,我们只是重写了HashCode方法,从上面的结果可以看出,虽然两个对象的HashCode相等,但是实际上两个对象并不是相等,因为我们没有重写equals方法,那么就会调用Object默认的equals方法,显示这是两个不同的对象。

这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象ab都被放到了HashSet中,这样HashSet就失去了他本身的意义了。

下面我们继续重写equals方法:

public class HashTest {
    private int i;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (object == this) {
            return true;
        }
        if (!(object instanceof HashTest)) {
            return false;
        }
        HashTest other = (HashTest) object;
        if (other.getI() == this.getI()) {
            return true;
        }
        return false;
    }

    public int hashCode() {
        return i % 10;
    }

    public final static void main(String[] args) {
        HashTest a = new HashTest();
        HashTest b = new HashTest();
        a.setI(1);
        b.setI(1);
        Set<HashTest> set = new HashSet<HashTest>();
        set.add(a);
        set.add(b);
        System.out.println(a.hashCode() == b.hashCode());
        System.out.println(a.equals(b));
        System.out.println(set);
    }
}

输出结果如下所示。

true
true
[HashTest@1]

从结果我们可以看出,现在两个对象就完全相等了,HashSet中也只存放了一份对象。

注意:

hashCode()只是简单示例写的,真正的生产换将不是这样的

HashMap的hashcode的作用
hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的。

如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同。

如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点。

两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

什么时候需要重写?

一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为什么要重载hashCode呢?

要比较两个类的内容属性值,是否相同时候,根据hashCode 重写规则,重写类的 指定字段的hashCode(),equals()方法。

例如

public class EmpWorkCondition{

    /**
     * 员工ID
     */
    private Integer empId;

    /**
     * 员工服务总单数
     */
    private Integer orderSum;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        EmpWorkCondition that = (EmpWorkCondition) o;
        return Objects.equals(empId, that.empId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(empId);
    }

    // 省略 getter setter
}
public static void main(String[] args) {

    List<EmpWorkCondition> list1 = new ArrayList<EmpWorkCondition>();

    EmpWorkCondition emp1 = new EmpWorkCondition();
    emp1.setEmpId(100);
    emp1.setOrderSum(90000);
    list1.add(emp1);

    List<EmpWorkCondition> list2 = new ArrayList<EmpWorkCondition>();

    EmpWorkCondition emp2 = new EmpWorkCondition();
    emp2.setEmpId(100);
    list2.add(emp2);

    System.out.println(list1.contains(emp2));

}

输出结果:

true

上面的方法,做的事情就是,比较两个集合中的,实体类对象属性值,是否一致

OrderSum 不在比较范围内,因为没有重写它的,equals()和hashCode()方法

为什么要重载equal方法?

因为Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,否则不相等;如果你现在需要利用对象里面的值来判断是否相等,则重载equal方法。

为什么重载hashCode方法?
一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为什么要重载hashCode呢?

如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。

这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。

为什么equals()相等,hashCode就一定要相等,而hashCode相等,却不要求equals相等?

1、因为是按照hashCode来访问小内存块,所以hashCode必须相等。
2、HashMap获取一个对象是比较key的hashCode相等和equal为true。

之所以hashCode相等,却可以equal不等,就比如ObjectA和ObjectB他们都有属性name,那么hashCode都以name计算,所以hashCode一样,但是两个对象属于不同类型,所以equal为false。

为什么需要hashCode?

1、通过hashCode可以很快的查到小内存块。
2、通过hashCode比较比equal方法快,当get时先比较hashCode,如果hashCode不同,直接返回false。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jysf98746

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值