Java 基础面试题
1.1 String s = new String(“xyz”);创建了几个字符串对象?
两个对象,一个是静态区的“xyz”,一个是用new创建的对象
1.2 String是最基本的数据类型吗?
- 不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;
- 除了基本类型(primitive type)和枚举类型(enumeration type),剩下的都是引用类型(reference type)。
1.3 两个对象值相同(x.equals(y)==true),但却可有不同的hashcode,这句话对不对?
不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:
- 如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
1.4 java中如何实现序列化,有什么意义?
- 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
- 序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。 要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆。
1.5 构造方法有哪些特性?
- 特征
- 没有返回类型
- 方法名与类名一致
- 作用
- 创建对象
- 实例化一个类,首先调用类的对应的构造方法。当类中没有任意构造方法,JVM会自动创建空的构造方法(空参和空实现)。一但类的有了任意的构造方法,则JVM不会再自动的创建任意形式的构造方法。
- 初始成员变量
- 创建对象
- 其它
- 构造方法调用时也要保证参数的类型、个数和顺序要一致
- 构行方法也可以有方法重载
1.6 Anonymous Inner Class(匿名内部类)是否可以继承其他类?是否可以实现接口?
可以继承其他类或实现其他接口,在Swing编程和Android开发中常用此方式来实现事件监听和回调。
1.7 迭代器iterator是什么?
- 为了方便的处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator).
- 对 Collection 进行迭代的类,称其为迭代器。还是面向对象的思想,专业对象做专业的事情,迭代器就是专门取出集合元素的对象。但是该对象比较特殊,不能直接创建对象(通过new),该对象是以内部类的形式存在于每个集合类的内部。
- 如何获取迭代器?Collection接口中定义了获取集合类迭代器的方法(iterator()),所以所有的Collection体系集合都可以获取自身的迭代器。
- Iterable
- 正是由于每一个容器都有取出元素的功能。这些功能定义都一样,只不过实现的具体方式不同(因为每一个容器的数据结构不一样)所以对共性的取出功能进行了抽取,从而出现了Iterator接口。而每一个容器都在其内部对该接口进行了内部类的实现。也就是将取出方式的细节进行封装。
- 使用Iterator的简单例子
1.8 什么是方法的返回值?返回值在类的方法里的作用是什么?
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出接货,使得它可以作用于其他的操作!
1.9 面向对象三大特征
- 封装 : 安全性
- 体现:把属性私有化,通过公共的get/set或is/set进行公开出去,以后通过方法对属性进行操作。这样做的好处,是在方法中可以对属性的安全性和有效性作出验证。
- 继承 : 重用性、扩展性
- 满足is a的关系
- 用到extends关键字
- 子类能继承父类中非私有的属性和方法
- 方法重写
- @Override
- 使用super关键字
- this: 代表当前类的对象
- super: 代表当前类的父类对象
- 在java中的类是单继承
- 任何类的祖类都是Object,可以省略 extends Object
- 多态性:维护性、扩展性
- 口诀:
- 父类引用指向父类实例,只能调用父类的属性和方法
- 子类引用指向子类实例,可以调用父类中非私有的属性和方法
- 父类引用指向子类实例,只能调用父类的属性和方法,但是方法被子类重写,以子类的实现为准。
- 开闭原则
- 对扩展开放
- 对修改关闭
- 口诀:
1.10 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
帮助子类做初始化工作
1.11 switch是否能作用在byte上,是否能作用在long上,是否能作用在String上
- 在Java 5以前,switch(expr)中,expr只能是byte、short、char、int。
- 从Java 5开始,Java中引入了枚举类型,expr也可以是enum类型
- 从Java 7开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
1.12 用最有效率的方法算出2乘以8等于几?
2 << 3
- 因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算cpu直接支持的,效率最高,所以,2乘以8等於几的最效率的方法是 2 << 3。
1.13 访问修改符的理解
- java中共有四个修饰符
- private 同一个类
- default 同一个包
- protected 同一个包+不同包子类
- public 所有包
- 注:访问修饰符最低原则,为了安全性
- 类有两个
- public 所有类
- default 同一个包
- 方法和属性有四个
- private 同一个类
- default 同一个包
- protected 同一个包+不同包子类
- public 所有包
1.14 Object类中常用的方法
- Object clone() : 克隆。protected它r指的是在同一个包,或不同包子类中能访问
- boolean equals(Object) : 地址相等
- void finalize() : 垃圾回收器调用此方法来回收对象所占空间。protected
- Class getClass() :得到Class对象
- int hasCode() :对象的哈希码。同个对象的哈唏码一致。
- String toString() :把对象用字符串输。一般我们自定义的类都会重写此方法。
- void wait() :线程所用方法,后面讲
- void notify() :线程所用方法,后面讲
- void notifyAll() :线程所用方法,后面讲
1.15 方法重写的特性
- 父子类
- 方法名相同,参数列表相同
- 子类的访问修饰符>=父类
- 子类返回类型<=父类(<=指是父子类)
- 子类抛的异常<=父类(<=指是父子类)
1.16 方法重载的特性
- 在同一个类下
- 方法名一样
- 参数的类型、个数和‘顺序’至少有一个不一样
1.17 重载和重写的区别
- 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
- 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
1.18 值传递与引用区别
- 值传递: 参数的类型为值类型时,则为值传递。值传递,传递的是值复本。
- 引用传递: 参数的类型为引用类型时,则为引用传递。引用传递,传递的是引用的复本。
- 两者区别:
- 值传递,在方法里改变值,之前的值不会改变
- 引用传递,在方法里改变值,之前的值会发生改变。但String、StringBufferer、StringBuilder和枚举除外。
1.19 成员变量与局部变量的区别有哪些?
- 从语法形式上看:
成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;
成员变量可以被 public,protected,default,private, static和final 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。 - 从变量在内存中的存储方式来看:如果成员变量是使用static修饰的,那么这个成员变量是属于类的,如果没有使用使用static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
- 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
- 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值),而局部变量则不会自动赋值。
1.20 抽象类与接口有什么区别
- 相同点:
- 都可以有静态常量
- 都可以有抽象方法
- 都不能被实例化
- 区别点:
- 抽象类中有属性、实现的方法、抽象方法等,但接口中只公共静态常量、公共抽象方法
- 抽象类中的访问修饰符有四种,但接口中只有public访问修饰符
- 子类单继承抽象类,子类多实现接口,接口多继承接口
- 抽象类中包含构造方法,接口没有构造方法
- 使用时机
- 当所有方法都不知道如何实现时,用接口。一般用标准的制订(方法功能描述)
- 当有些方法知道如何实现,有些不知道时,使用抽象。
- 示例
- 数据访问实现
- 使用接口制订数据访问方法的标签
- 使用抽象类 实现接口,实现其中公共代码。(已知如何实现),未知实现(不清楚具体数据)写成抽象方法。
- 使用MySQL数据类 继承 抽象类,实现抽象方法。(已知用MySQL数据库实例 )
- 使用Oralce数据类 继承 抽象类,实现抽象方法。(已知用Oralce数据库实例 )
- 数据访问实现
1.21 队列和栈是什么?有什么区别?
- 队列(Queue):是限定只能在表的一端进行插入和另一端删除操作的线性表
- 栈(Stack):是限定之能在表的一端进行插入和删除操作的线性表
- 队列和栈的规则 :
- 队列:先进先出
- 栈:先进后出
- 队列和栈的遍历数据速度 :
- 队列:基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间,因为在遍历的过程中不影响数据结构,所以遍历速度要快
- 栈:只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,遍历数据时需要微数据开辟临时空间,保持数据在遍历前的一致性
1.22 Java有没有goto?
goto 是Java中的保留字,在目前版本的Java中没有使用。(根据James Gosling(Java之父)编写的《The Java Programming Language》一书的附录中给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字)
1.23 是否可以从一个static方法内部发出对非static方法的调用 ?
不能直接调用 ,但可以通类的对象进行调用
类中的静态方法和静态变量,也称类方法和类变量
静态方法中只能直接调用静态方法和静态变量,如果要调用非静态的内容,必须通过实例.方法()或实例.变量的方式 去进行调用
1.24 java中的final关键字有哪些用法?
修饰类:表示该类不能被继承;
修饰方法:表示方法不能被重写;
修饰变量:表示变量只能一次赋值以后值不能被修改(常量)
1.25 final,finally和finalize的区别?
- final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。
- finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
- finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
1.26 使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:
final StringBuffer a=new StringBuffer('immutable');
执行如下语句将报告编译期错误:
a=new StringBuffer('');
但是,执行如下语句则可以通过编译:
a.append(' broken!');
1.27 抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?
都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。
1.28 异常该如何处理
- try: 存放可能出现异常的代码块,一但在此代码块中有了异常,则立即到跳转到catch中,并把对应异常对象传递给Catch中的形参,如果赋值不对应,则仍然处理不了
- 不能单独使用
- catch:捕获处理
- 不能单独使用
- 与try匹配catch可以有多个,分别捕获处理不同的异常,范围大异常一定要后面
- 当所有catch中的类型都不能匹配时,则相当于catch捕获处理不,仍然终止程序
- finally :最终地无论如何也要执行的代码,一般用于资源的释放。比如,文件的关闭,数据库连接关闭
- 不能单独使用
- try-catch
- try-catch-catch
- try-finally
- 这种情况要注意,如果没catch,则在try中有了异常,相当于没有处理,程序程序会终止,只不过会执行finally中的语句,一般不省略catch
- throw : 抛的是一个对象 ,放置在方法里边
- 一般有了throw,要加上throws
- throw是抛给上一级处理,上一级指的是谁调用谁处理
- 上一级可以选择try-catch掉,也可以选择继续抛,如果到了main仍然继续抛出,则相当于抛了JVM,且等于没有处理异常,程序终止,所以一般到main必须处理掉。
- throws : 抛的是异常类型,放置方法后面,可以有多个
1.29 异常分类和常见异常
- 运行时异常: 都是RuntimeException的子类
- ArithmeticException
- ArrayIndexOutofBoundsException
- NullPointerException
- 非运行时异常(编译时异常): 都是Exception的子类
- ParseException
- IOException
- ClassNotFoundException
1.30 运行异常与非运行时异常有何异同?
- 运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。
- 受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。
1.31 错误与异常的区别
- Object
- Throwable
- Error
- Exception
- Throwable
- Error类: 一般指的是硬件问题,跟程序员无关,比如说硬盘空间不足,内容不够
- Exception类: 一般指提程序员不小心造成的,有可能在某些时候出现(偶现),或都每次都出现(必须),这些异常本可以避免的。例如:算术异常、空指针异常和下标越界异常等
- 注:
- 我们只关心异常,所以以后我提到异常也好,错误也罢,全指异常。
- Exception是所有异常类的祖类
1.32 一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么?
创建类的对象+初始化成员变量
可以执行,因为一个类即使没有声明构造方法 JVM会自动创建的空参参数的构造方法。
1.33 静态方法和实例方法有何不同
- 在外部调用静态方法时,可以使用’类名.方法名’的方式,也可以使用’对象名.方法名’的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问直接实例成员变量和和实例方法;实例方法可以直接文章实例方法和实例变量,也可以访问静态方法
1.34 请手写一下冒泡排序【插入】,接着再用递归的方式写一遍
public static void main(String[] args) {
// 12,45,9,67,455,用冒泡排序实现升序排列。
int[] arr = { 12, 45, 9, 67, 455 };
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("排序后:");
for (int i = 0; i < arr.length; i++)
System.out.print(arr[i] + "\t");
//创建一个数组用于排序
int[]arr2={1,4,3,2,6,12,10};
//调用递归的冒泡
arr2=recursiveBubble(arr2,arr2.length);
for(int i=0;i<arr2.length;i++){
System.out.print(arr2[i]);
}
}
//冒泡排序之递归方法
public static int[] recursiveBubble(int[] arr2,int e){
if(e==0){
return arr2;
}
else{
for(int i=0;i<e-1;i++){
int temp=0;
if(arr2[i]>arr2[i+1]){
temp=arr2[i];
arr2[i]=arr2[i+1];
arr2[i+1]=temp;
}
}
e--;
//System.out.println(e);
recursiveBubble(arr2,e);
}
return arr2;
}
1.35 静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?
Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。
1.36 Math.round(11.5)等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整。
1.37 阐述静态变量和实例变量的区别。
- 静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
- 实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
1.38 请讲讲冒泡排序、快速排序、插入排序 这两种排序的原理
冒泡排序原理:
1,比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2,对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3,针对所有的元素重复以上的步骤,除了最后一个。
4,持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
快速排序原理:
1,先定义两个指针,一个在最前面,一个在最后面,第一次要对数组进行划分,先找出一个中间数,
2,一般默认把第一个数作为中间数,然后从后往前找比中间数小的,把他放在前一个指针的位置,
3,在从前往后找比中间数大的,找到后把它放在第二个指针的位置,直到前面和后面的指针指向同一个位置,
4,我们就把中间值放在这里即可,这样根据中间数,把整个需要排序的数组分为两部分,
5,前一部分都小于中间数,后一部分都大于中间数,此时这两部分的数还是无序的,最后递归调用排序方法,对两部分分别进行排序即可。
插入排序原理:
1,从第一个元素开始,该元素可以认为已经被排序。
2,取出下一个元素,在已经排序的元素序列中从后向前扫描。
3,如果被扫描的元素(已排序)大于新元素,将该元素后移一位。
4,重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
5,将新元素插入到该位置后。
6,重复步骤2~5。
1.39 说说你对多态的理解
- 现象:
调用同一个类型对象的同一个方法,会出现不同的结果 - 实现多态的三个条件:
1)要有继承;
2)要有方法重写;
3)要有父类指针(引用)指向子类对象。 - 多态的原理:
动态连编,根据实际对象的类型来判断重写方法的调用。多态的原理:动态连编,根据实际对象的类型来判断重写方法的调用
1.40 写clone方法时,通常都有一行代码,是什么?
clone 有缺省行为,super.clone();因为首先要把父类中的成员复制到位,然后才是复制自己的成员。
1.41 Java中是如何支持正则表达式操作的?
Java中的String类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。
此外,Java中可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作。
1.42 Java序列化中如果有写字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用transient关键字修饰。
transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
1.43 构造器(constructor)是否可被重写(override)
构造器不能被继承,因此不能被重写,但可以被重载。
1.44 int和Integer的区别?
- java是一个的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
- Java 为每个原始类型提供了包装类型:
- 原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
1.45 自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
1.46 ==与equals的区别?
- ==
- 值类型:判断是值是否相等
- 引用类型:判断是引用地址是否相等
- equals()
- 是Object中的方法,表示地址是否相等
- 但String 中的 equals 方法是被重写过的,它表示的是值相等
1.47 构造方法调用顺序
实例化一个子类首先会调用父类中的空的构造方法,再调用子类中对应的构造方法
除非在子类构造方法的第一行有super(xxx),则调用父类对应构造方法。
一般在子类构造方法的第一行即为super(),默认是可以省略的,所以推荐在父类一般保留一个空的构造方法
1.48 java和 javaX有什么区别
刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
所以,实际上java和javax没有区别。这都是一个名字
1.49 &和&&的区别
- &按位与;&&逻辑与。
- &非短信与; &&运算符是短路与运算
- 逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。
- &&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,
- 例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(‘’),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。
- 注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
1.50 说一下类加载的执行过程
java程序在执行过程中,类,对象以及它们成员加载、初始化的顺序如下:
- 首先加载父类。
- 对静态成员进行加载,主要包括静态成员变量的初始化,静态语句块的执行,在加载时按代码的先后顺序进行。
- 加载非静态的成员,主要包括非静态成员变量的初始化,非静态语句块的执行,在加载时按代码的先后顺序进行。
- 最后执行构造器,构造器执行完毕,对象生成。
1.51 java中实现多态的机制是什么
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
1.52 是否可以继承string类
String 类是final类,不可以被继承。
1.53 数据类型之间的转换:如何将字符串转换为基本数据类型?如何将基本数据类型转换为字符串?
- 调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;
- 一种方法是将基本数据类型与空字符串(‘’)连接(+)即可获得其所对应的字符串;
- 另一种方法是调用String 类中的valueOf()方法返回相应字符串
1.54 接口是否可以继承(extends)接口,抽象类是否可以实现(implements)接口,抽象类是否可以继承具体类(concrete class)?
- 接口可以继承接口,而且支持多重继承。
- 抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。
1.55 内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员
1.56 简述一下你了解的设计模式?
- 所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
- 设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。
- 在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任和算法的抽象化])共23种设计模式,
- 包括:
Abstract Factory(抽象工厂模式),
Builder(建造者模式),
Factory Method(工厂方法模式),
Prototype(原始模型模式),
Singleton(单例模式);
Facade(门面模式),
Adapter(适配器模式),
Bridge(桥梁模式),
Composite(合成模式),
Decorator(装饰模式),
Flyweight(享元模式),
Proxy(代理模式);
Command(命令模式),
Interpreter(解释器模式),
Visitor(访问者模式),
Iterator(迭代子模式),
Mediator(调停者模式),
Memento(备忘录模式),
Observer(观察者模式),
State(状态模式),
Strategy(策略模式),
Template Method(模板方法模式),
Chain Of Responsibility(责任链模式)。
例如: - 工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
- 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。
- 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
- 模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。
除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections工具类和I/O系统中都使用装潢模式)等
1.57 什么是单例模式?实现步骤?
单例模式分为:
- 懒汉式:用的时候,才去创建对象(不会出现线程问题的单例模式)
- 饿汉式:类一加载就创建对象(可能会线程问题的单例模式)
- 饿汉代码
- 外部使用者如果需要使用SingletonClass的实例,只能通过getInstance()方法,并且它的构造方法是private的,这样就保证了只能有一个对象存在。
public class SingletonClass {
private static SingletonClass instance = new SingletonClass();
public static SingletonClass getInstance() {
return instance;
}
private SingletonClass() {
}
}
- 懒汉代码
- 上面的代码虽然简单,但无论这个类是否被使用,都会创建一个instance对象
public class SingletonClass {
private static SingletonClass instance = null;
public static SingletonClass getInstance() {
if(instance == null) {
instance = new SingletonClass();
}
return instance;
}
private SingletonClass() {
}
}
- 上述代码会有线程安全的问题
public class SingletonClass {
private static SingletonClass instance = null;
public synchronized static SingletonClass getInstance() {
if(instance == null) {
instance = new SingletonClass();
}
return instance;
}
private SingletonClass() {
}
}
- 性能调优一下,double-check
public class SingletonClass {
private static SingletonClass instance = null;
public static SingletonClass getInstance() {
if (instance == null) {
synchronized (SingletonClass.class) {
if (instance == null) {
instance = new SingletonClass();
}
}
}
return instance;
}
private SingletonClass() {
}
}
- 创建一个变量需要哪些步骤呢?一个是申请一块内存,调用构造方法进行初始化操作,另一个是分配一个指针指向这块内存。这两个操作谁在前谁在后呢?JMM规范并没有规定。
线程1开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,1已经把instance指向了那块内存,只是还没有调用构造方法,因此2检测到instance不为null,于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还是毛坯房
只要我们简单的把instance加上volatile关键字就可以了。volatile在Java并发编程中常用于保持内存可见性和防止指令重排序
public class SingletonClass {
private volatile static SingletonClass instance = null;
public static SingletonClass getInstance() {
if (instance == null) {
synchronized (SingletonClass.class) {
if(instance == null) {
instance = new SingletonClass();
}
}
}
return instance;
}
private SingletonClass() {
}
}