标签(空格分隔): Java面试题 相关概念
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。
类的实例化可生成对象,一个对象的生命周期包括三个阶段:生成、使用、消除。
当不存在对一个对象的引用时,该对象成为一个无用对象。Java的垃圾收集器自动扫描对象的动态内存区,把没有引用的对象作为垃圾收集起来并释放。当系统内存用尽或调用System.gc( )要求垃圾回收时,垃圾回收线程与系统同步运行。
类是具有相同属性和方法的一组对象的集合,它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和方法两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性和方法两个主要部分。
Java中的类实现包括两个部分:类声明和类体。
封装,继承,多态,有时候也加上抽象.
面向对象最基础的一个特性,封装性,是指隐藏对象的属性和现实细节,仅对外提供公共访问方式。
封装的原则:将不需要对外提供的内容都隐藏(设置访问修饰符为“private”)起来。把属性都隐藏,仅提供公共方法对其访问,可以在访问方式中加入逻辑判断等语句。
继承是指一个对象从另一个对象中获得属性和方法的过程。
例如:有一个Animal类,有一个属性重量(weight),有一个Dog类继承自Animal类,那么Dog也会有weight这个属性,方法同理,但是值得一说的是:当子类调用一个方法时,它会先从本类里去寻找这个方法,如果本类里有这个方法,就会直接调用这个方法,否则再去调用父类的方法,这就叫方法的重写(override),需要特别说明的是两个方法的头部必须是完全一样的,包括返回值、方法名和参数,还有一种是方法的复载(overload),这是指在同一个类中有两个同名的方法,但是它们有不同的参数(可以是参数的个数不同,也可以是参数的类型不同)。
多态是在继承的基础上,实现的。 多态实现的三个必要条件:
- 继承
- 方法的重写
- 父类引用指向子类对象; 例如:Animal an = new Dog();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。 多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
将类的变量声明为 private ,提供公共 setter 和 getter 方法以修改和查看变量值。
Plain Old Java Object 的缩写,可以理解为简单的 JavaBean 。 POJO 实质上可以理解为简单的实体类,顾名思义 POJO 类的作用是方便程序员使用数据库中的数据表,对于广大的程序员,可以很方便的将 POJO 类当做对象来进行使用,当然也是可以方便的调用其 getter , setter 方法。
就是属性类,通常定义在 model 层里面。一般的实体类对应一个数据表,其中的属性对应数据表中的字段。实体类就是一个拥有 getter 和 setter 方法的类。 entity (实体类)的作用一般是和数据表做映射。实体类通常总是和数据库之类的(所谓持久层数据)联系在一起。介于 dao 和 service 之间。
是遵循 Sun 定义的 JavaBeans 约定的类。 JavaBeans是 Java 中一种特殊的类,可以将多个对象封装到一个对象(bean)中。特点是可序列化,提供无参构造器,提供 getter 方法和 setter 方法访问对象的属性。
如果一个Java类的每个实例变量都被 private 修饰,并为每个实例变量提供了 public 修饰的 setter 和 getter 方法,那么这个类是符合 JavaBean 规范的类。因此, JavaBean 总是一个封装良好的类。
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
主要有以下优点:
- 可替换性:多态对已存在代码具有可替换性.
- 可扩充性:增加新的子类不影响已经存在的类结构.
- 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的.
- 灵活性:它在应用中体现了灵活多样的操作,提高了使用效率
- 简化性:多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要
实现多态主要有以下三种方式:
- 接口实现
- 继承父类重写方法
- 同一类中进行方法重载
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.
接口的意义用三个词就可以概括:规范,扩展,回调.
####规范性
所谓的规范性,当然规范java类的,为了避免类在设计上的不一致,很常见的,假如我们定义了一个类要实现【Comparator】接口,这个大家应该比较熟悉的吧,一个作用于比较的接口,实现这个接口,就必须去覆写它的compare(比较用来排序的两个参数)和equals(指示某个其他对象是否“等于”此 Comparator)方法,所以这样就形成了一种规范,要求以实现某个接口的时候,必须具备接口原来所具有的功能,这在大型开发中显的尤为重要
比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类,可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦,如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
一个接口它只返回你想要的结果,而不提供程序实现的细节,对外皆是透明不可见,这样就比较的安全一些,严密些
抽象类的意义可以用三句话来概括:
- 为其他子类提供一个公共的类型
- 封装子类中重复定义的内容
- 定义抽象方法,子类虽然有不同的实现,但是定义是一致的
比较 | 抽象类 | 接口 |
---|---|---|
默认方法 | 抽象类可以有默认的方法实现 | java 8之前,接口中不存在方法的实现. |
实现方式 | 子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现. | 子类使用implements来实现接口,需要提供接口中所有声明的实现. |
构造器 | 抽象类中可以有构造器, 接口中不能和正常类区别 | 抽象类不能被实例化 接口则是完全不同的类型 |
访问修饰符 | 抽象方法可以有public,protected和default等修饰 | 接口默认是public,不能使用其他修饰符 |
多继承 | 一个子类只能存在一个父类 | 一个子类可以存在多个接口 |
添加新方法 | 想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 | 如果往接口中添加新方法,则子类中需要实现该方法. |
不能.重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏,调用的方法为定义的类所有的静态方法。
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。
静态变量存储在方法区,属于类所有.
实例变量存储在堆当中,其引用存在当前线程栈.
当然可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。
最常见的例子就是对象中包含一个日期对象的引用.
- 采用new
- 通过反射
- 采用clone
- 通过序列化机制
前2者都需要显式地调用构造方法. 造成耦合性最高的恰好是第一种,因此你发现无论什么框架,只要涉及到解耦必先减少new的使用.
在idk 1.7之前,switch只能支持byte,short,char,int或者其对应的封装类以及Enum类型。从idk 1.7之后switch开始支持String.
intern()方法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回. 比如 String s1=”aa”; String s2=s1.intern(); System.out.print(s1==s2);//返回true
equals() clone() getClass() notify(),notifyAll(),wait() toString
强引用,软引用,弱引用,虚引用.不同的引用类型主要体现在GC上:
如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象
在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象
顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
不像C语言,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协.
有以下几个使用场景可以充分的说明:
利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能.
==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等.默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样.
换句话说:基本类型比较用==,比较的是他们的值.默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法
hashCode()是Object类的一个方法,返回一个哈希值.如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值.
如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的.)
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,使用 equal() 方法来判断两个相等的对象,必须具有相同的 hashcode。
将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合.如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入.
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。如果两个对象相等,必须有相同的hashcode 值,反之不成立.
不行,因为同一对象的 hashcode 值必须是相同的
如果a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。
例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。
false,因为有些浮点数不能完全精确的表示出来。
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换.如: byte a = 127; byte b = 127; b = a + b; // error : cannot convert from int to byte b += a; // ok (译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
有错误,short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型.
+=操作符会自动对右边的表达式结果强转匹配左边的数据类型,所以没错.
首先记住&是位操作,而&&是逻辑运算符.另外需要记住逻辑运算符具有短路特性,而&不具备短路特性.
public class Test{
static String name;
public static void main(String[] args){
if(name!=null&userName.equals("")){
System.out.println("ok");
}else{
System.out.println("erro");
}
}
}
以上代码将会抛出空指针异常.
只能有一个public公共类,但是可以有多个default修饰的类.
使用标号和break; 通过在外层循环中添加标识符
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.
创建内部类对象的时刻不依赖于外部类对象的创建.内部类并没有令人疑惑的”is-a”关系,它就像是一个独立的实体.
内部类提供了更好的封装,除了该外围类,其他类都不能访问
java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
几乎所有的人都知道static关键字这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享.
除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作:
public calss PreCache{
static{
//执行相关操作
}
}
此外static也多用于修饰内部类,此时称之为静态内部类.
最后一种用法就是静态导包,即import static.import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名.资源名,可以直接使用资源名,比如:
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));
}
}
final也是很多面试喜欢问的地方,能回答下以下三点就不错了:
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
- 被final修饰的方法,JVM会尝试将其内联,以提高运行效率
- 被final修饰的常量,在编译阶段会存入常量池中.
- 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序.
- 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序.
类型 | 位数 | 字节数 |
---|---|---|
short | 2 | 16 |
int | 4 | 32 |
long | 8 | 64 |
float | 4 | 32 |
double | 8 | 64 |
char | 2 | 16 |
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。
意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。
Integer是int的包装类型,在拆箱和装箱中,二者自动转换.int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象.
Integer 对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是int是一个原始类型的数据,所以占用的空间更少。
String是字符串常量,final修饰;StringBuffer字符串变量(线程安全); StringBuilder 字符串变量(线程不安全).
String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象.所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能.
StringBuffer是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,我们建议使用StringBuffer.
但是需要注意现在JVM会对String拼接做一定的优化: String s=“This is only ”+”simple”+”test”会被虚拟机直接优化成String s=“This is only simple test”,此时就不存在拼接过程.
StringBuffer是线程安全的可变字符串,其内部实现是可变数组.StringBuilder是jdk 1.5新增的,其功能和StringBuffer类似,但是非线程安全.因此,在没有多线程问题的前提下,使用StringBuilder会取得更好的性能.
公共静态不可变(public static final)变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。
我们可以做强制转换,但是Java中int是32位的而byte是8 位的,所以,如果强制转化int类型的高24位将会被丢弃,byte 类型的范围是从-128到128