Java SE 面试高频问题:篇二(11-20)

11、Java中的深克隆和浅克隆

✅ 我们先来思考一下为什么需要克隆?

答案是:

克隆的对象可能包含一些已经修改过的属性,保留着你想克隆对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值。即当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。

接下来所叙述的深克隆和浅克隆是建立在对象中是否存在嵌套其他引用对象而言的,一定要注意我说的这句话。
✅ 相同点

无论是深克隆还是浅克隆它们都有一个共同的特点,它们所复制后的对象的变量值都具有与原对象的变量值相同。


✅ 不同点

不同点我们先从浅克隆导致的问题说起吧。

在Java中,我们的一个普通类可以通过实现 Cloneable 接口来克隆对象。具体步骤如下:

  • 普通类实现 Cloneable 接口
  • 覆盖 Object 类中的 Clone 方法,访问修饰符设为public,默认是protected。
  • 在覆盖的 Clone 方法中调用 super.clone() 方法。(后边会谈及深克隆时此处略微有变化)

我们先看看下面一段代码:

package cn.wb.day2;
 
public class Student implements Cloneable {
    private int age;
    private String name;
 
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
 
    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
 
    /**
     * @param args
     * @throws CloneNotSupportedException
     */
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student(20, "张三");
        Student student2 = (Student) student1.clone();
        // 在这里我们修改student2的age值 但是没有影响 student1的值
        student2.setAge(22);
        System.out.println("student1:" + student1.getName() + "-->"+ student1.getAge());
        System.out.println("student2:" + student2.getName() + "-->"+ student2.getAge());
 
    }
}

运行结果:
student1:张三-->20
student2:张三-->22

✅ 由于Student对象中没有嵌套深层对象,所以克隆时复制对象引用中所指向的对象,所以克隆对象变化时原对象没有发生变化。因为它们的内存地址映射是不一样的。

我们此时注释掉 tostring 方法,打印两个对象看看他们的内存地址映射是否一致?

student1:com.cxsw.clone.Student@28d93b30
student2:com.cxsw.clone.Student@1b6d3586

事实证明当没有嵌套深层对象的时候,克隆后的对象和原对象他们的内存地址映射是不一样的,所以当没有嵌套深层次对象的时候,调用 clone 方法得到的对象也是一种深克隆。


当我们在一个对象中的属性中嵌套另一个对象的时候会有怎么样的变化,我们再来看看另一段代码:

package cn.wb.day2;
 
class Teacher implements Cloneable {
    private String name;
    private Student student;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", student=" + student + "]";
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student();
        s1.setAge(20);
        s1.setName("张三");
        Teacher teacher1 = new Teacher();
        teacher1.setName("小赵老师");
        teacher1.setStudent(s1);
        //为什么会出现以下结果, Teacher中的clone方法
        Teacher teacher2 = (Teacher)teacher1.clone();
        Student s2 = teacher2.getStudent();
        s2.setName("李四");
        s2.setAge(30);
        System.out.println("teacher1:"+teacher1);
        System.out.println("teacher2:"+teacher2);
        
    }
    
}

运行结果如下:

teacher1:Teacher [name=小赵老师, student=Student [age=30, name=李四]]

teacher2:Teacher [name=小赵老师, student=Student [age=30, name=李四]

✅ 由于我们克隆是没有考虑到嵌套的深层对象student,所以导致出现了这种结果。

此时我们还是将Student对象中的 tostring 方法注释掉,看一下嵌套对象的内存地址映射:

**teacher1:Teacher [name=小赵老师, student=com.cxsw.clone.Student@28d93b30]
teacher2:Teacher [name=小赵老师, student=com.cxsw.clone.Student@28d93b30]**

我们清楚的看到嵌套的深层对象的内存地址映射是一样的,所以此时无论你改变 teacher1 还是teacher2 他都会引起两个teacher对象的统一变化,因为它们的引用(内存地址映射)是相同的。这就是浅克隆。

接下来,我们就讲一下如何解决这个问题,那当然需要深克隆了。


🛠 深克隆

老规矩,先看代码:

package cn.wb.day2;
 
class Teacher implements Cloneable {
    private String name;
    private Student student;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", student=" + student + "]";
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        //注意以下代码 实现深克隆的关键
        Teacher teacher = (Teacher)super.clone();
        teacher.setStudent((Student)teacher.getStudent().clone());
        return teacher;
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student();
        s1.setAge(20);
        s1.setName("张三");
        Teacher teacher1 = new Teacher();
        teacher1.setName("小杨老师");
        teacher1.setStudent(s1);
        Teacher teacher2 = (Teacher)teacher1.clone();
        teacher2.setName("小魏老师");
        Student s2 = teacher2.getStudent();
        s2.setName("李四");
        s2.setAge(30);
        System.out.println("teacher1:"+teacher1);
        System.out.println("teacher2:"+teacher2);
        
    }
    
}

上边的代码的关键是在clone方法中对student对象调用了一次 clone 方法,使要克隆的类和类中所有非基本数据类型的属性对应起来。

运行结果如下

teacher1:Teacher [name=小杨老师, student=Student [age=20, name=张三]]
teacher2:Teacher [name=小魏老师, student=Student [age=30, name=李四]]

此时我们将Student对象中的 tostring 方法注释掉,看一下我们改变了clone方法的实现后嵌套对象的内存地址映射:

teacher1:Teacher [name=小杨老师, student=com.cxsw.clone.Student@28d93b30]
teacher2:Teacher [name=小魏老师, student=com.cxsw.clone.Student@1b6d3586]

我们清楚的看到嵌套的深层对象的内存地址映射是不一样的,所以此时无论你改变 teacher1 还是teacher2 他不会引起两个teacher对象的统一变化,因为它们的引用(内存地址映射)是不相同的。这就是深克隆。

✅ 总的来说,它们的不同点可以用以下两句话概括:

  1. 浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
  2. 深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象


    看了这么久,欣赏下美丽的风景吧

12、Java中的final有哪些用法

如果问到这个问题,我们通常回答出以下五种就可以了:

  • 被final修饰的类不能被其他类继承。
  • 被final修饰的方法不能被重写。
  • 被final修饰的变量不可改变,如果修饰的是引用,表示引用不可变,引用指向的内容可变。
  • 被final修饰的变量,JVM会尝试将其内联,以提高运行效率。
  • 被 final修饰的常量,在编译阶段会存入常量池中。

❓ 我这里再补充一个问题,被final修饰的变量一定是常量吗?

答案是否定的。这个后边我会陆续讲到静态常量池的一些相关问题。

13、static 都有哪些用法?

  1. static 修饰的方法被称为静态方法,可以不再初始化该类的情况下直接调用该方法
  2. static 修饰的变量被称为静态变量,又称类变量,他是存储在静态区中的,而非静态变量是存储在堆内存中。此外,静态变量实在类加载时加载的,因此他可以直接通过类名调用。
  3. static可以修饰内部类,被称为静态内部类
  4. static 也可以用作静态代码块,用于初始化操作,
  5. jdk 1.5 之后,static 可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名,比如:
import static java.lang.Math.*;
public class Test{
        public static void main(String[] args){
             //System.out.println(Math.sin(20));传统做法 
                System.out.println(sin(20));

        }
}

14、有没有可能两个不相等的对象有相同的hashcode?

答案是有可能的,两个不相等的对象就会有相同的 hashcode 值,当hash冲突产生时,一般
有以下几种方式来处理:

  1. 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链 表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.
  2. 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总 能找到,并将记录存入
  3. 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个....等哈希函数计算地址,直到无冲突.

15、Java创建对象的方式有哪几种?

  • new创建新对象
  • 通过反射机制
  • 采用clone机制
  • 通过序列化机制

📖 这里我在补充一下通过反射机制如何创建一个对象:方法有很多种,这里只举例其中一种:

package com.cxsw.clone;

import java.util.Objects;

public class Test {
    public static void main(String[] args) {
        // 获取类名
        try {
            Class<?> className = Class.forName("com.cxsw.clone.Person");
            Person person = (Person)className.newInstance();
            person.setAge(18);
            person.setName("test");
            System.out.println(person);;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
class Person{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

✅ 打印结果

Person{name='test', age=18}

16、泛型常用特点

以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整 型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型 数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向 上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型

17、Java的四种引用,强弱软虚

✅ 强引用:强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:

String str = new String("str");
System.out.println(str);

✅ 软引用:软引用在程序内存不足时,会被回收,使用方式:

// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T 
SoftReference<String> wrf = new SoftReference<String>(new String("str"));

可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。

✅ 弱引用:弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:

WeakReference<String> wrf = new WeakReference<String>(str);

可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。

✅ 虚引用:回收机制跟弱引用差不多,但是它被回收之前,会被放入ReferenceQueue 中。

其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue ,使用例子:

PhantomReference<String> prf = new PhantomReference<String>(new String("str"),new ReferenceQueue<>());

可用场景: 对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这 类动作,但是这个方式即不安全又低效。

上诉所说的几类引用,都是指对象本身的引用,而不是指Reference的四个子类的引用(SoftReference等)。

18、Collection包结构,与Collections的区别

Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、 Set;

Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的 Collection框架。

19、HashMap 和 HashTable 有什么不同?

  1. 两者父类不同

HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。

  1. 对外提供的接口不同

Hashtable 比 HashMap 多提供了 elments() 和 contains() 两个方法。 elments() 方法继承自
Hashtable 的父类 Dictionnary。elements() 方法用于返回此 Hashtable 中的value的枚举。contains()方法判断该 Hashtable 是否包含传入的 value 。它的作用与 containsValue() 一致。事实上,contansValue() 就只是调用了一下 contains() 方法。

  1. 对null的支持不同

Hashtable:key和value都不能为null。HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个
key值对应的value为null。

  1. 安全性不同

HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

  1. 初始容量大小和每次扩充容量大小不同
  2. 计算hash值的方法不同

20、ArrayList 和 LinkedList 的区别?

List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
List有两个重要的实现类:ArrayList和LinkedList。ArrayList: 可以看作是能够自动增长容量的数组。

ArrayList的toArray方法返回一个数组
ArrayList的asList方法返回一个列表

ArrayList:查找元素的效率比LinkedList高,因为它的底层实现是通过数组来实现的,它使用索引在数组中搜索和读取数据是很快的。

LinkedList:它的底层实现逻辑是链表,因此它插入或删除元素时效率特别高,由于ArrayList。当然,它们的效率高低是针对插入大量元素时而言的。

😁 今天的分享就到此结束了 ✌️

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容