Java面试宝典
JavaSE基础篇
面向对象都有哪些特性以及你对这些特性的理解。
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同事继承也是封装程序中可变因素的重要手段。
- 封装:封装是把数据和操作数据的方法绑定起来,数据只能通过已定义的接口访问。面对对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。编写一个类就是对数据和数据操作的封装;在类中编写的方法就是对实现细节的一种封装。简言之,封装就是隐藏一切可以隐藏的东西,只向外界提供最简单的编程接口。
-
多态:多态是允许不同子类型的对象对同一消息作出不同的响应。简言之,用同样的对象引用调用同样的方法但是做了不同的事情。
多态分为编译时多态和运行时多态:
1.方法的重载(Overload)实现的就是编译时的多态也称为前绑定。
2.方法的重写(Override)实现的是运行时的多态也称为后绑定。(如果将对象的方法视为对象向外界提供的服务,那么运行时的多态可以解释为:当A系统访问B系统提供服务时,B系统有多重提供服务的方式,但一切对A系统来说都是透明的。)
运行时的多态是面对对象最精髓的东西,要实现多态需要做两件事:
1、方法的重写(子类继承父类并重写父类中已有的或抽象的方法);
2、对象造型(用父类引用子类对象,这样引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
注意:默认情况下面向对象有3大特性,封装、继承、多态,4大特性,把抽象加上去。
访问权限修饰符public、private、protected, 以及不写(默认)时的区别。
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
如何理解clone对象
-
为什么要用clone:
在编程过程中,遇到这种情况:有一个对象A,A中包含了一些有效值,此刻需要一个和A对象完全相同的新对象B,而对B对象的任何改动不会影响到A对象中的值,即A与B是两个独立的对象,但B对象的初始值由A对象确定。(实现clone()方法是其中最简单,也是最高效的手段。) -
new一个对象的过程和clone一个对象的过程区别
1、new操作符的本意是分配内存。程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
2、clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和原对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
.java源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个public类,且public类名和文件名相同。
Java 有没有 goto 语句?
goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。
& 和 && 异同。
&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与。
同:二者都要求运算符左右两端的布尔值都是TRUE整个表达式的值才是 TRUE。
异:如果&&左边的表达式的值是 FALSE,右边的表达式会被直接短路掉,不会进行运算。
1.用&&多于&,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为
username != null&&!username.equals("")
,二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals 比较,否则会产生 NullPointerException异常。
2.3!=3&&++y>0
y不会自增长;3!=3&++y>0
y会自增长;3&2
(位运算)结果为5。
注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
在 Java 中,如何跳出当前的多重嵌套循环?
- 方法一:在最外层循环前加一个标记如 A,然后用 break A,可以跳出多重循环。
ok:
for(inti=0;i<10;i++){
for(intj=0;j<10;j++){
System.out.println(“i=”+ i + “,j=” + j);
if(j == 5)
break ok;
}
}
- 方法二:通过标识作为循环条件,用break跳出。
int arr[][] = {{1,2,3},{4,5,6,7},{9}};
boolean found = false;
for(int i=0;i<arr.length &&!found;i++){
for(intj=0;j<arr[i].length;j++){
System.out.println(“i=”+ i + “,j=” + j);
if(arr[i][j]== 5){
found= true;
break;
}
}
}
两个对象值相同 (x.equals(y) == true) ,但却可有不同的hashCode这句话对不对?
不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hashCode)应当相同。
Java 对于 eqauls 方法和 hashCode 方法是这样规定的:
(1)如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定相同
(2)如果两个对象的 hashCode 相同,它们并不一定相同。
equals 方法必须满足:
(1)自反性(x.equals(x)必须返回 true)、
(2)对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true)
(3)传递性(x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true)
(4)一致性(当x 和 y 引用的对象信息没有被修改时,多次调用 x.equals(y)应该得到同样的返回值),而且对于任何非 null 值的引用 x,x.equals(null)必须返回 false。
实现高质量的 equals 方法的诀窍包括:
(1)使用==操作符检查”参数是否为这个对象的引用”;
(2)使用 instanceof 操作符检查”参数是否为正确的类型”;
(3)对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
(4)编写完 equals 方法后,问自己它是否满足对称性、传递性、一致性;
(5)重写 equals 时总是要重写 hashCode;
(6)不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉
@Override 注解。
是否可以继承 String
String 类是 final 类,不可以被继承。继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
值传递。
Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
C++和 C#中可以通过传引用或传输出参数来改变传入的参数的值。说明:Java 中没有传引用实在是非常的不方便,这一点在 Java 8 中仍然没有得到改进,正是如此在 Java 编写的代码中才会出现大量的 Wrapper 类(将需要通过方法调用修改的引用置于一个 Wrapper 类中,再将 Wrapper 对象传入方法),这样的做法只会让代码变得臃肿,尤其是让从 C 和 C++转型为 Java 程序员的开发者无法容忍。
重载(overload)和重写(override)的区别?重载的方法能否根据返回类型进行区分?
- 定义:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
- 重载:重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
-
方法重载的规则:
- 方法名一致,参数列表中参数的顺序,类型,个数不同。
- 重载与方法的返回值无关,存在于父类和子类,同类中。
- 可以抛出不同的异常,可以有不同修饰符。
-
方法重写的规则:
- 参数列表必须完全一致,返回类型必须完全一致。
- 构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次声明。
- 访问权限不能比父类中被重写的方法的访问权限更低。
- 重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常。但是重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
为什么函数不能根据返回类型来区分重载?
因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。
//例一:
/**
*当调用 max(1, 2);时无法确定调用的是哪个,单从这一点上来说,仅返回值类型不同的重载是不应该允许的。
*/
float max(int a, int b);
int max(int a, int b);
//例二:
/**
* 若编译器可根据上下文(语境)明确判断出含义,比如在 int x=f()中,那么这样做完全没有问题。
* 然而,我们也可能调用一个方法,同时忽略返回值;我们通常把这称为“为它的副作用去调用一个方法”,因为我们关心的不是返回值,而是方法调用的其他效果。
* 所以假如我们像下面这样调用方法: f(); Java 怎样判断 f()的具体调用方式呢?而且别人如何识别并理解代码呢?由于存在这一类的问题,所以不能。
* 函数的返回值只是作为函数运行之后的一个“状态”,他是保持方法的调用者与被调用者进行通信的关键。 并不能作为某个方法的“标识”。
*/
void f(){}
int f(){}
char 型变量中能不能存储一个中文汉字,为什么?
char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。
补充:使用 Unicode 意味着字符在 JVM 内部和外部有不同的表现形式,在 JVM 内部都是 Unicode,当这个字符被从 JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以 Java 中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader 和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于 C程序员来说,要完成这样的编码转换恐怕要依赖于 union(联合体/共用体)共享内存的特征来实现了。
内存溢出和内存泄漏有什么区别
内存溢出:指在给程序分配内存时没有足够的内存供其所用,出现out of memory。
内存泄漏:指分配出去的内存,不再使用但无法收回。
IO和NIO的区别
IO:是面向流的,各种流是阻塞的
NIO:是面向缓存区的,NIO是非阻塞的
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
什么是线程安全,如何保证线程安全
线程安全:多线程访问同一代码,不会产生不确定的结果
保证线程安全:
- 对非安全的代码进行加锁控制;
- 使用线程安全的类;
- 多线程并发情况下,线程共享的变量改为方法级的局部变量
String 不可变相当于常量,线程安全,不可继承
StringBuffer 线程安全
StringBuild 非线程安全
HashMap 非线程安全
HashTable 线程安全
多线程的实现方式
继承Thread类,实现Runnable接口
线程的状态转换
初始状态
就绪状态
运行状态
阻塞状态
终止状态
Java中的集合类及关系图
List和Set继承自Collection接口。
Set无序不允许元素重复。HashSet和TreeSet是两个主要的实现类。
List有序且允许元素重复。ArrayList、LinkedList和Vector是三个主要的实现类。
Map也属于集合系统,但和Collection接口没关系。Map是key对value的映射集合,其中key列就是一个集合。key不能重复,但是value可以重复。HashMap、TreeMap和Hashtable是三个主要的实现类。
SortedSet和SortedMap接口对元素按指定规则排序,SortedMap是对key列进行排序。
ArrayList、LinkedList
同:都继承了List接口
异:ArrayList:底层是数组,查询修改速度快。LinkedList:底层是链表,增删插入速度快,更占内存(为每个节点存储两个引用一个指向前一个元素,一个指向下一个元素)
当操作是在一列数据后面添加数据而不是在前面或中间,并且是随机访问其中元素时,使用arrayList会提供比较好的性能;当你操作是在一列数据的前面或中间添加或删除数据,并不是按照顺序访问其中的元素,就应该使用LinkedList了
类的实例化顺序
父类静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类非静态变量->父类构造器->子类非静态变量->子类构造器
自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率,由编译器来完成,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。
hashCode和equals方法的关系
equals相等,hashcode必相等;hashcode相等,equals可能不相等
数据类型
基本数据类型
数值型:整数类型(byte、short、int、long)、浮点类型(double、float)
字符型:char
布尔型:boolean
引用数据类型
类:class
接口:interface
数组:array
项目中事务处理方案。
4种事务特性:
- 原子性(atomicity):强调事务的不可分割.
- 一致性(consistency):事务的执行的前后数据的完整性保持一致.
- 隔离性(isolation):一个事务执行的过程中,不应该受到其他事务的干扰
- 持久性(durability) :事务一旦结束,数据就持久到数据库
5种隔离级别:
- DEFAULT 默认的隔离级别,使用数据库默认的事务隔离级别
- 未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
- 已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生
- 可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生
- 串行化的 (serializable) :避免以上所有读问题
7种传播行为:
保证同一个事务中
- PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认)
- PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
- PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
保证没有在同一个事务中
- PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
- PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
- PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
- PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
分布式事务处理方案(XA,TCC等,也可以了解一下阿里最近推出的 GTS)
拦截器和过滤器的区别。
拦截器和过滤器都可以用来实现横切关注功能,其区别主要在于:
拦截器是基于Java反射机制的,而过滤器是基于接口回调的。
过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容器。
拦截器只能对Action请求起作用,而过滤器可以对所有请求起作用。
拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
数据库优化策略:
- 选择存储范围较小的字段,尽可能的设为notnull.
- 使用关联查询(如:left join on)代替子查询
- 使用union联合查询时,手动创建临时表
- 开启事物,当数据库执行多条语句出现错误时,事物会回滚,可以维护数据库的完整性
- 使用外键保证数据的关联性
- 使用索引索引是提高数据库性能的常用方法。对于max,min,order by查询时,效果更明显
- sql优化,简练