1.面向对象的三大特征:封装、继承、多态。有时候面试官也会说成是类的三大特性,其实是一回事。
面向对象的三个基本特征是:封装、继承、多态。
封装 封装是对象和类概念的主要特性。封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 继承 继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。继承概念的实现方式有三类:实现继承、接口继承和可视继承。实现继承是指使用基类的属性和方法而无需额外编码的能力; 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力; 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。 在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。 抽象类仅定义将由子类创建的一般属性和方法,创建抽象类时,请使用关键字 Interface 而不是 Class。 OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。属性:父类中定义的属性子类可以直接访问到。
| 父类 | 子类 |
| public | 可以 |
| protected | 可以 |
| 缺省 | 同包可以,不同包不可以 |
| private | 不可以 |
多态 多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单来说就是允许将子类类型的指针赋值给父类类型的指针。 实现多态,有二种方式,覆盖,重载。 覆盖,是指子类重新定义父类的虚函数的做法。 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。 其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚邦定,它就不是多态。” 多态的作用:封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用,多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
多态的优点:允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用).主要有以下优点:
可替换性:多态对已存在代码具有可替换性.
可扩充性:增加新的子类不影响已经存在的类结构.
接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的.
灵活性:
简化性:
虚拟机是如何实现多态的
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.如果你知道Hotspot中oop-klass模型的实现,对这个问题就了解比较深了.
1.下面java concurrent包下的4个类中差别最大的一个是? 正确答案:B.Future,因为他是接口,其他都是类。表示异步计算的结果。
A CountDownLatch B Future C Semaphore D ReentrantLock
继承和实现的区别继承:如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们相同的部分都放到父类中,让他们都继承这个类。
实现:如果多个类处理的目标都是一样的,但是处理的方法不同,那么就可以定义一个接口,让他们实现这个接口,各自通过自己的处理方法来处理那个目标。
也可以理解为继承是通过部分相同的功能,产生不同的结果。而实现则是通过不同的方法来实现同一个结果。
继承和实现的区别:
1、数量不同:java只支持接口的多继承,不支持“继承”的多继承,继承在java中具有单根性,子类只能继承一个父类。总结就是:单继承,多实现。
2、修饰不同:继承:extends;实现:iimplements
3、属性不同:在接口中只能定义全局变量和无实现的方法。而在继承中可以定义属性方法,变量,常量等。
4、调用不同:当接口被类实现时,在类中一定要实现接口中的抽象方法;而继承想调用哪个方法就调用哪个方法。
原子操作是在多线程环境下的一个概念,它是针对访问共享变量的操作而言的,指该操作是不可再分的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。
原子操作的"不可分割"包括以下两层含义:
1.访问(读、写)某个共享变量的操作从执行线程以外的任何线程来看,该操作要么已经执行结束要么尚未发生,其他线程不会"看到"该操作执行了部分的中间效果。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。比如 a = 1;
2.访问同一组共享变量的原子操作是不能够被交错的。
final 在 java 中有什么作用?final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
普通类(class)、抽象类(abstract class)、接口(interface)见博客:
java编译后生成 class文件,那.class文件包含的信息包括三个部分:
| 包含的信息 | 具体信息 | 备注 |
| 类信息 | 包括类的名字、修饰符(public或private)、父类、接口以及注解(annotation)。 | 每一个编译后的class文件只包括一个类的信息,如果在java源文件中定义了多个类,在编译后会分成多个独立的class文件。 |
| 类变量信息 | 包括每个变量的名字、修饰符、类型和注解(annotation) | |
| 类方法信息 | 包括每个方法的名字、修饰符、参数的类型和返回值的类型、方法的注解(annotation),还包括编译后的、字节码形式的方法代码。 | |
| 每个编译后的class文件: 1.不包括包名(package)和引用(import),所有的非原始类型引用都以全包名形式出现,这称作内部名称或本质名称; 2.包括一个常量池,这个常量池是一个数组,包括数字、字符和类型的常量; 3.不包括代码注释。 | ||
这些信息通过java提供的Class类能够获取,并通过反射类使用。也是动态编译和java逆向工程的基础。
java 中 IO 流分类按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
BIO、NIO、AIO 的区别BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞IO,异步 IO 的操作基于事件和回调机制。
Files的常用方法都有哪些?Files.exists():检测文件路径是否存在。
Files.createFile():创建文件。
Files.createDirectory():创建文件夹。
Files.delete():删除一个文件或目录。
Files.copy():复制文件。
Files.move():移动文件。
Files.size():查看文件个数。
Files.read():读取文件。
Files.write():写入文件。
HashMap 和Hashtable 的区别| HashMap | Hashtable | |
| 允许 key 和 value 为 null | 不允许 | |
| 默认初始容量 | 16 | 11 |
| 扩容 | 扩容为原来的 2 倍 | 为原来的 2 倍加 1 |
| 线程安全 | 否 | 是 |
| hash 值 | 重新计算过 | 直接使用 hashCode |
| 去掉了 Hashtable 中的 contains 方法 | ||
| 父类 | 继承自 AbstractMap 类 | 继承自 Dictionary 类 |
为什么重写equals时必须重写hashCode方法
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码(可以快速找到所需要的对象)
当把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这就是HashSet检查重复的方法,这种方法大大减少了 equals 的次数,相应就大大提高了执行速度。hashCode() 的默认行为是对堆上的对象产生独特值。因此如果两个对象相等,则hashcode一定相同;那么,如果hashcode相同,两个对象就一定相等吗,答案是否定的,两个对象有相同的hashcode值,它们也不一定相等,有一个概念叫hash冲突,在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值,当hash冲突产生时,一般有以下几种方式来处理:
1.拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储。
2.开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
3.再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突。
可以在hashcode中使用随机数字吗?
不行,因为同一对象的 hashcode 值必须是相同的.
两个对象相等,对两个对象分别调用equals方法都返回true。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。
以下说法正确的是
ArrayList的写性能要优于linkedList,在写多于读的场景,建议使用ArrayList;
For循环以及迭代循环遍历一个linkedList的效果都是一样的;
HashMap是由数组、链表、红黑树组成,链表长度超过阈值时,会再转红黑树;
JDK1.8版本中,HashMap使用了红黑树优化哈希碰撞所带来的链表过长的问题。
重载(Overload)和重写(Override)的区别| 重写(Override) | 重载(Overload) | |
| 实现多态的方式 | 运行时的多态性 | 编译时的多态性 |
| 发生位置 | 发生在子类与父类之间 | 发生在一个类中 |
| 修饰符 | 大于等于父类(里氏代换原则),比父类被重写方法更好访问 | 同名的方法如果有不同的参数列表(参数类型或个数不同)则视为重载 |
| 返回值 | 必须有相同的返回值类型,返回值小于等于父类 | 没有特殊的要求(参数列表不同(参数类型不同、个数不同、顺序不同)),与方法返回值和访问修饰符无关,因此重载的方法不能根据返回类型进行区分 |
| 方法名 | 必须相同 | 必须相同 |
| 抛出的异常 | 不能比父类被重写方法声明更多的异常(里氏代换原则),小于等于父类 | |
| 备注 | 访问修饰符;如果父类方法访问修饰符为private则子类中就不是重写。 |
构造器(constructor)是否可被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。
22、char 型变量中能不能存贮一个中文汉字,为什么?
答:char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。
final是一个修饰符,用于修饰变量,方法和类.如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。
finalize()方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会.但是该方法由Finalizer线程调用,但调用时机无法保证。
finally是一个关键字,与 try和catch一起用于异常的处理,finally{}一定会被执行,在此处我们通常用于资源关闭操作。
简述++i和i++的区别 int i=0;
System.out.print("++i先自加1再赋值得到结果是=");
System.out.println(++i);
//控制台打印:++i先自加1再赋值得到结果是=1
int i=0;
System.out.print("i++先赋值,然后再自加1得到结果=");
System.out.println(i++);
//控制台打印:i++先赋值,然后再自加1得到结果=0
深拷贝和浅拷贝的区别
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍。
如下图,对对象C进行拷贝(个人理解,如有错误请指正):
equals方法:equals 重写要彻底
只要有属性、方法,就用equals重写,equals是用来比较2个对象的,判断两个对象是否相同,相同就返回ture,不相同就返回false。
finalize方法(JDK已经弃用) hashcode()方法返回的是哈希码
哈希吗(hashcode)
clone 深克隆和浅克隆
匿名内部类:匿名内部类:在类的内部重新定义了一个新的类,被称为内部类。
内部类的分类:静态内部类:类似于静态变量(前面有static)
实例内部类:类似于实例变量(没有static)
局部内部类:类似于局部变量(局部变量)匿名内部类是局部内部类的一种,因为没有名字。
doSome()方法中的局部内部类Inner,在doOther()中不能用
匿名内部类:接口下面的实现类不用写了,new一个接口就可以了,加一个大括号(对接口的实现)
不建议使用匿名内部类,因为一个类没有名字,无法重复使用。代码太乱,可读性太差。
IDEA快捷键 ALT+回车 纠错
以下代码输出什么:
public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
如果不明就里很容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,如果看看valueOf的源代码就知道发生了什么。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是Integer的内部类,其代码如下所示:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false。
Math.round(11.5) 等于12,Math.round(-11.5)等于-11。四舍五入的原理是在参数上加0.5然后进行下取整。
用最有效率的方法计算2乘以8?
适应位运算, 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。
class StringEqualTest {
public static void main(String[] args) {
String s1 = "Programming";
String s2 = new String("Programming");
String s3 = "Program" + "ming";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1 == s1.intern());
}
}
补充:String对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。
数据类型的转换:
- 如何将字符串转换为基本数据类型?反之呢?
- 调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;
- 一种方法是将基本数据类型与空字符串("")连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串
54、Collection和Collections的区别?
答:Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。
55、List、Map、Set三个接口存取元素时,各有什么特点?
答:List以特定索引来存取元素,可以有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。
57、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?
答:sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
spring原理:
hashmap:
SQL调优:
serializable接口的作用和原理:一般情况下,我们在定义实体类时会继承Serializable接口。并且会定义serialversionUID(如果我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID)
package com.gosuncn.entity;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialversionUID = 1L;
private Integer id;
private String name;
private Integer age;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
'}';
}
}
Serializable接口是一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。
作用:Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。接口里面什么内容都没有,我们可以将它理解成一个标识接口。通知jvm,我不对这个类做序列化了,jvm会自动对其序列化(序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。)把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化
serialversionUID作用
serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且报出错误
下列哪些设计模式能够提高系统扩展性?
1)singleton
2)abstract factory
3)adapter
4)decorator
答案:2、3、4.工厂方法实现松耦合,可以提高扩展性。
适配器模式可以将一个接口转换成另一个接口,方便引入外部接口
装饰者模式可以扩展接口
下面代码输出的结果是
public class Test01 {
public static void print(){
System.out.println("ABCD");
}
public static void main(String[] args){
try{
((Test01)null).print();
}catch (NullPointerException e){
System.out.println("NullPointerException");
}
}
}
ABCD
无法正常编译
都不输出
NullPointerException
11.类a继承类b并重写b类的protected方法func时,a中func方法的访问修饰符可以是
12.关于以下程序,说法正确的是(会输出,equals)
public class Equals {
public static void main(String[] args) {
String s1 = "abc" +"def";
String s2 = new String(s1);
if (s1==s2)
System.out.println("hello==");
if (s1.equals(s2))
System.out.println("hello.equals");
}
}
13.列算法的时间复杂度是O(n)
public int fun(int n){
if(n<2){
return1;
}return n*fun(n-2);
}
14.下面代码输出的结果是
public class Judge {
public static void main(String[] args) {
Boolean result = false? false : true==false?true:false;
System.out.println(""+result+"");
}
}
15.下面java concurent包下的4个类中差别最大的一个是
ReentrantLock、Future、Semphore、CountDownLatch
17.java中关于finally块中的代码描述正确的是
如果try块后没有catch块时,finally块中的代码才会执行
异常发生时才会执行
finally也可以在return后执行
异常没有发生时可以执行
19.java类中,下面对method1方法的覆写写法中,语法正确且使用形如method(1,2)调用子类的method1方法时,能返回1的是哪些?



