java基础面试题二

1.日期转换的问题

下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");for (int i = 0; i < 10; i++) {new Thread(() -> {try {log.debug("{}", sdf.parse("1951-04-21"));} catch (Exception e) {log.error("{}", e);}}).start(); }

有很大几率出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果,例如:

19:10:40.859 [Thread-2] c.TestDateParse - {}

java.lang.NumberFormatException: For input string: ""

at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

at java.lang.Long.parseLong(Long.java:601)

at java.lang.Long.parseLong(Long.java:631)

at java.text.DigitList.getLong(DigitList.java:195)

at java.text.DecimalFormat.parse(DecimalFormat.java:2084)

at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)

at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

at java.text.DateFormat.parse(DateFormat.java:364)

at cn.itcast.n7.TestDateParse.lambda$test1$0(TestDateParse.java:18)

at java.lang.Thread.run(Thread.java:748)

19:10:40.859 [Thread-1] c.TestDateParse - {}

java.lang.NumberFormatException: empty String

at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)

at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

at java.lang.Double.parseDouble(Double.java:538)

at java.text.DigitList.getDouble(DigitList.java:169)

at java.text.DecimalFormat.parse(DecimalFormat.java:2089)

at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)

at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

at java.text.DateFormat.parse(DateFormat.java:364)

at cn.itcast.n7.TestDateParse.lambda$test1$0(TestDateParse.java:18)

at java.lang.Thread.run(Thread.java:748)

19:10:40.857 [Thread-8] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

19:10:40.857 [Thread-9] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

19:10:40.857 [Thread-6] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

19:10:40.857 [Thread-4] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

19:10:40.857 [Thread-5] c.TestDateParse - Mon Apr 21 00:00:00 CST 178960645

19:10:40.857 [Thread-0] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

19:10:40.857 [Thread-7] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

19:10:40.857 [Thread-3] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改啊!这样的对象在Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

for (int i = 0; i < 10; i++) {

new Thread(() -> {

LocalDate date = dtf.parse("2018-10-01", LocalDate::from);

log.debug("{}", date);

}).start();

}

不可变对象,实际是另一种避免竞争的方式。

2.不可变设计

1、final的作用

final 关键字一共有三种用法,它可以用来修饰变量、方法或者类。

1.1 final 修饰变量

作用

关键字 final 修饰变量的作用,就是意味着这个变量一旦被赋值就不能被修改了。如果尝试给其赋值,会报编译错误。

目的

(1)第一个目的是出于设计角度去考虑的,比如希望创建一个一旦被赋值就不能改变的量,就可以使用 final 关键字。比如声明常量的时候。

(2)第二个目的是从线程安全的角度去考虑的。不可变的对象天生就是线程安全的,不需要额外进行同步等处理。如果 final 修饰的是基本数据类型,那么它自然就具备了不可变这个性质,所以自动保证了线程安全,去使用它也就非常放心。

赋值时机

被 final 修饰的变量的赋值时机,变量可以分为以下三种:

成员变量,类中的非 static 修饰的属性;

静态变量,类中的被 static 修饰的属性;

局部变量,方法中的变量。

空白 final

如果声明了 final 变量之后,并没有立刻在等号右侧对它赋值,这种情况就被称为“空白 final”。这样做的好处在于增加了 final 变量的灵活性,比如可以在构造函数中根据不同的情况,对 final 变量进行不同的赋值,这样的话,被 final 修饰的变量就不会变得死板,同时又能保证在赋值后保持不变。用下面这个代码来说明:

/**

* 描述:    空白final提供了灵活性

*/

public class BlankFinal {

    //空白final

    private final int a;

    //不传参则把a赋值为默认值0

    public BlankFinal() {

        this.a = 0;

    }

    //传参则把a赋值为传入的参数

    public BlankFinal(int a) {

        this.a = a;

    }

}

(1)成员变量

成员变量指的是一个类中的非 static 属性,对于这种成员变量而言,被 final 修饰后,它有三种赋值时机(或者叫作赋值途径)。

对于 final 修饰的成员变量而言,必须从中挑一种来完成对 final 变量的赋值,而不能一种都不挑,这是 final 语法所规定的。

(2)静态变量

静态变量是类中的 static 属性,被 final 修饰后,只有两种赋值时机。

需要注意的是,不能用普通的非静态初始代码块来给静态的 final 变量赋值。同样有一点比较特殊的是,static 的 final 变量不能在构造函数中进行赋值。

(3)局部变量

局部变量指的是方法中的变量,如果把它修饰为了 final,它的含义依然是一旦赋值就不能改变。对于 final 的局部变量而言,它是不限定具体赋值时机的,只要求在使用之前必须对它进行赋值即可。

这个要求和方法中的非 final 变量的要求也是一样的,对于方法中的一个非 final 修饰的普通变量而言,它其实也是要求在使用这个变量之前对它赋值。

(4)特殊用法:final 修饰参数

关键字 final 还可以用于修饰方法中的参数。在方法的参数列表中是可以把参数声明为 final 的,这意味着没有办法在方法内部对这个参数进行修改。例如:

/**

* 描述:    final参数

*/

public class FinalPara {

    public void withFinal(final int a) {

        System.out.println(a);//可以读取final参数的值

//        a = 9; //编译错误,不允许修改final参数的值

    }

1.2 final 修饰方法

目前使用 final 去修饰方法的唯一原因,就是锁定这个方法,就是说,被 final 修饰的方法不可以被重写,不能被 override。举一个代码的例子:

/**

* 描述:    final的方法不允许被重写

*/

public class FinalMethod {

    public void drink() {

    }

    public final void eat() {

    }

}

class SubClass extends FinalMethod {

    @Override

    public void drink() {

        //非final方法允许被重写

    }

//    public void eat() {}//编译错误,不允许重写final方法

//    public final SubClass() {} //编译错误,构造方法不允许被final修饰

}

同时这里还有一个注意点,在下方写了一个 public final SubClass () {},这是一个构造函数,也是编译不通过的,因为构造方法不允许被 final 修饰。

特例:final 的 private方法

这里有一个特例,那就是用 final 去修饰 private 方法。先来看看下面这个看起来可能不太符合规律的代码例子:

/**

* 描述:    private方法隐式指定为final

*/

public class PrivateFinalMethod {

    private final void privateEat() {

    }

}

class SubClass2 extends PrivateFinalMethod {

    private final void privateEat() {//编译通过,但这并不是真正的重写

    }

}

类中的所有 private 方法都是隐式的指定为自动被 final 修饰的,额外的给它加上 final 关键字并不能起到任何效果。由于这个方法是 private 类型的,所以对于子类而言,根本就获取不到父类的这个方法,就更别说重写了。在上面这个代码例子中,其实子类并没有真正意义上的去重写父类的 privateEat 方法,子类和父类的这两个 privateEat 方法彼此之间是独立的,只是方法名碰巧一样。

1.3 final 修饰类

final 修饰类的含义很明确,就是这个类不可被继承。举个代码例子:

/**

* 描述:    测试final class的效果

*/

public final class FinalClassDemo {

    //code

}

//class A extends FinalClassDemo {}//编译错误,无法继承final的类

这样设计,就代表不但我们自己不会继承这个类,也不允许其他人来继承,它就不可能有子类的出现,这在一定程度上可以保证线程安全。

3、为什么 String 被设计为是不可变的?

在 Java 中,字符串是一个常量,一旦创建了一个 String 对象,就无法改变它的值,它的内容也就不可能发生变化(不考虑反射这种特殊行为)。

调用 String 的 subString() 或 replace() 等方法,同时把 s 的引用指向这个新创建出来的字符串,这样都没有改变原有字符串对象的内容,因为这些方法只不过是建了一个新的字符串而已。

3.1 String 具备不变性背后的原因是什么呢?

来看下 String 类的部分重要源码:

public final class String

    implements Java.io.Serializable, Comparable<String>, CharSequence {

    /** The value is used for character storage. */

    private final char value[];

    //...

}

3.2 String 不可变的好处

如果把 String 设计为不可变的,会带来以下这四个好处:

(1)字符串常量池

String 不可变的第一个好处是可以使用字符串常量池。在 Java 中有字符串常量池的概念,比如两个字符串变量的内容一样,那么就会指向同一个对象,而不需创建第二个同样内容的新对象。正是因为这样的机制,再加上 String 在程序中的应用是如此广泛,就可以节省大量的内存空间。

(2)用作 HashMap 的 key

String 不可变的第二个好处就是它可以很方便地用作 HashMap (或者 HashSet) 的 key。通常建议把不可变对象作为 HashMap的 key,比如 String 就很合适作为 HashMap 的 key。

(3)缓存 HashCode

String 不可变的第三个好处就是缓存 HashCode。在 Java 中经常会用到字符串的 HashCode,在 String 类中有一个 hash 属性,代码如下:

/** Cache the hash code for the String */

private int hash;

(4)线程安全

String 不可变的第四个好处就是线程安全,因为具备不变性的对象一定是线程安全的,不需要对其采取任何额外的措施,就可以天然保证线程安全。

由于 String 是不可变的,所以它就可以非常安全地被多个线程所共享,这对于多线程编程而言非常重要,避免了很多不必要的同步操作。

4.请说明重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

考察点:java重载

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

5.请你讲讲abstract class和interface有什么区别?

考察点:抽象类

参考回答:

声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。

接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。

6.接口和抽象类的区别是什么?

考察点:抽象类

参考回答:

Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:

接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。

类可以实现很多个接口,但是只能继承一个抽象类

类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。

抽象类可以在不提供接口方法实现的情况下实现接口。

Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。

Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。

接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。

也可以参考JDK8中抽象类和接口的区别

7.请你谈谈StringBuffer和StringBuilder有什么区别,底层实现上呢?

考察点:类

参考回答:

StringBuffer线程安全,StringBuilder线程不安全,底层实现上的话,StringBuffer其实就是比StringBuilder多了Synchronized修饰符。

8.请你讲讲wait方法的底层原理

考察点:基础

参考回答:

ObjectSynchronizer::wait方法通过object的对象中找到ObjectMonitor对象调用方法 void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS)

通过ObjectMonitor::AddWaiter调用把新建立的ObjectWaiter对象放入到 _WaitSet 的队列的末尾中然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park 也就是wait。

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

推荐阅读更多精彩内容