java基础之语法篇1

此文已经同步至个人站点博客,点击下方链接可以体验更加阅读模式:
《java题库》


一、排序和对象的语法

1.Comparator 与 Comparable 接口是干什么的?列出它们的区别?

Comparable & Comparator 都是用来实现集合中元素的比较、排序的。
Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable接口的方法。 Comparator位于包java.util下,而Comparable位于包 java.langComparable 是一个对象本身就已经支持自比较所需要实现的接口(如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口) 自定义的类要在加入list容器中后能够排序,可以实现Comparable接口,在用Collections类的sort方法排序时,如果不指定Comparator,那么就以自然顺序排序, 这里的自然顺序就是实现Comparable接口设定的排序方式。
Comparator是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。 可以说一个是自已完成比较,一个是外部程序实现比较的差别而已。 用Comparator是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。 比如:你想对整数采用绝对值大小来排序,Integer 是不符合要求的,你不需要去修改Integer类(实际上你也不能这么做)去改变它的排序行为,只要使用一个实现了 Comparator接口的对象来实现控制它的排序就行了。

2.如何实现对象的拷贝,深拷贝和浅拷贝区别?
  • 实现Cloneable接口并重写Object类中的clone()方法;
  • 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
    在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
    简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制
3.如何实现深度拷贝?

深复制(深克隆)被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量,那些引用其他对象的变量将指向被复制过的新对象,而不再试原有的那些被引用的对象,换言之,深复制把要复制的对象所引用的对象都复制了一遍。
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
在项目中我们需要克隆的对象可能包含多层引用类型,这就要涉及到多层克隆问题,多层克隆不仅要将克隆对象实现序列化接口,引用对象也同样的要实现序列化接口。下面贡献一份深拷贝的Demo,替换需要的属性即可使用了:

public class User implements Serializable{
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    private Person person; //引用类型

    public User myColon(){
        User copy=null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            //将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            copy = (User) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
            return copy;
    }

    //此处省略get-set方法代码
}
//假如有引用类型也需要序列化
public class Person implements Serializable {
    private int id;
    private String userName ;
    private int age ;
    private String mobilePhone ;
    public  Person(){}
    public Person(int id,String userName, int age, String mobilePhone) {
        this.id = id;
        this.userName = userName;
        this.age = age;
        this.mobilePhone = mobilePhone;
    }
    //此处省略get-set方法
}

敲黑板划重点:

1、BeanUtilscopyProperties()方法并不是完全的深度克隆,在包含有引用类型的对象拷贝上就可能会出现引用对象指向同一个的情况,且该方法的性能低下,项目中一定要谨慎使用。
2、要实现高性能且安全的深度克隆方法还是实现Serializable接口,多层克隆时,引用类型均要实现Serializable接口。

4.构造器链是什么

构造一个类的实例时, 将会调用沿着继承链的所有父类的构造方法。 当构造一个子类的对象时, 子类构造方法会在完成自己的任务之前, 首先调用它的父类的构造方法。 如果父类继承自其他类, 那么父类构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法。 这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。

5.创建对象时构造器的调用顺序

父类静态初始化块
子类静态初始化块
父类初始化块
调用了父类无参构造器
子类初始化块
调用子类的构造器
总的一个结论就是:静态对象总是在第一次创建对象前就已经初始化完毕(随着类的加载而加载),初始化的顺序从最顶端的父类到子类,只会初始化一次。然后就是从父类到子类中,依次初始化非静态对象和构造器。

二、不可变对象

1.什么是不可变对象(immutable object)

不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Java中的8个包装类如 IntegerString类都是不可变类,所以不可变类并不是指该类是被final修饰的,而是指该类的属性是被final修饰的
可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
创建不可变对象的好处在于:线程安全。不可变对象是线程安全的,在线程之间可以相互共享,不需要利用特殊机制来保证同步问题,因为对象的值无法改变。可以降低并发错误的可能性,因为不需要用一些锁机制等保证内存一致性问题也减少了同步开销。

《Java不可变对象》

2.为什么 Java 中的 String 是不可变的(Immutable)

String对象在内存创建后就不可改变,不可变对象的创建一般满足以上5个原则,我们看看String代码是如何实现的:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];
    /** The offset is the first index of the storage that is used. */
    private final int offset;
    /** The count is the number of characters in the String. */
    private final int count;
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ....
    public String(char value[]) {
         this.value = Arrays.copyOf(value, value.length); // deep copy操作
     }
    ...
     public char[] toCharArray() {
     // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
    ...
}
  • String类被final修饰,不可继承
  • String内部所有成员都设置为私有变量
  • 不存在valuesetter,并将valueoffset设置为final
  • 当传入可变数组value[]时,进行copy而不是直接将value[]复制给内部变量.
  • 获取value时不是直接返回对象引用,而是返回对象的copy.

这都符合上面总结的不变类型的特性,也保证了 String类型是不可变的类。String的不可变特性主要为了满足常量池、线程安全、类加载的需求。合理使用不可变类可以带来极大的好处。

3.如何构建不可变的类结构?关键点在哪里

1. 类添加final修饰符,保证类不被继承。
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。

2. 保证所有成员变量必须私有,并且加上final修饰
通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。

3. 不提供改变成员变量的方法,包括setter
避免通过其他接口改变成员变量的值,破坏不可变特性。

4.通过构造器初始化所有成员,进行深拷贝(deep copy)
如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。为了保证内部的值不被修改,可以采用深度copy来创建一个新内存保存传入的值。

//不能保证不可变性,浅拷贝可以改引用对象的值
public final class ImmutableDemo {  
    private final int[] myArray;  
    public ImmutableDemo(int[] array) {  
        this.myArray = array; // wrong  
    }  
}
//深拷贝
public final class MyImmutableDemo {  
    private final int[] myArray;  
    public MyImmutableDemo(int[] array) {  
        this.myArray = array.clone();   
    }   
}

5. 在getter方法中,不要直接返回对象本身,而是返回克隆对象
这种做法也是防止对象外泄,防止通过getter获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。

4.能创建一个包含可变对象的不可变对象吗?

上面的第四点方法就是实现Demo,用深度拷贝来实现。

5.如何对一组对象进行排序?

就是本篇第一题所提到的,实现自定义排序接口。

6.String、StringBuffer、与StringBuilder的区别

StringString类是不可变类,如果我们用String来操作字符串的时候,一旦我们字符串的值改变,就会在内存创建多一个空间来保存新的字符串,字符串频繁的操作,是很低效率的事啊!
一般涉及到字符串操作的,我们一般使用StringBuffer或者StringBuilderStringBufferStringBuilder都集成了AbstractStringBuilder,而StringBuffer大部分方法都是synchronized,也就是线程安全的,而StringBuilder就没有,所以,我们查看API可以知道,StringBuilder可以操作StringBuffer,但是StringBuffer不可以操作StringBuilder,这也是线程的原因;

所以,可想而知,StringBuffer的效率肯定没有StringBuilder,因为StringBuffer要维持同步锁,这肯定要消耗部分资源

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

推荐阅读更多精彩内容