1、final 修饰一个对象,能否调用对象修改属性的方法?
2、fina l和 static关键字的区别?
3、static 修饰的方法可以被子类重写吗?为什么?
4、final、finally、finalize 分别表示什么含义?
5、抽象类能否实例化,理论依据是什么?
6、抽象类跟接口的区别,接口的默认修饰符?
7、面向对象编程的特点,多态性如何体现,Java虚拟机中的多态执行机制?
8、面向对象编程的四大特性及其含义?
9、重写和重载的区别?
10、谈谈对面向过程编程、面向对象编程还有面向切面编程的理解?
11、编译期注解跟运行时注解
12、注解如何获取,反射为何耗性能?
13、如何使用反射访问 private 数据?
14、什么是反射,有什么作用和应用?
15、int、long 的取值范围以及 BigDecimal,数值越界了如何处理?
16、Integer 类对 int 的优化?
17、装箱、拆箱什么含义?
18、int 和 Integer的区别?
19、简单介绍一下 Java 中的泛型,泛型擦除以及相关的概念?
20、String、StringBuffer 和 StringBuilder的区别?
21、String a="" 和 String a=new String("") 的关系和异同?
22、Java 是值传递还是引用传递?
23、Object 有哪些公用方法?
24、Object 的 equals 和 == 的区别?
25、为什么复写 equals 方法的同时需要复写 hashcode 方法,前者相同后者是否相同,反过来呢?为什么?
26、深拷贝和浅拷贝的区别
27、clone 的默认实现是深拷贝还是浅拷贝?如何让clone()实现深拷贝?
28、对 Java.nio 了解多少?
29、遇见过哪些运行时异常?异常处理机制知道哪些?
30、什么是内部类?有什么作用?静态内部类和非静态内部类的区别?
31、为什么匿名内部类中使用局部变量要用 final 修饰?
可以,final 修饰的是对应的引用,这意味着引用不可改变,即不可重新赋值,但对象内部的成员是可变的
final 修饰的对象不可重新赋值,static 修饰的对象是属于类本身的,即生命周期和类相同
不行,子类如果实现了和父类名称和参数相同的 staitc 方法,只会隐藏父类的实现(隐藏的意思是:当这个 static 方法可见性为 protected、public 时,在子类访问这个方法,只会调用到子类自己的)。因为 static 修饰的方法是属于类本身的,而多态这一特性是针对单个对象而言的。
final:修饰对象时,引用不可变;修饰基本数据类型时,值不可变;修饰类时,不可被继承;修饰方法时,不能被复写
finally:和 try-catch 配合使用,除非 JVM 终止,否则一定会被调用,常用于释放资源。但如果 try-catch 中执行了 return,则 finally 无法修改返回值
finalize:对象在回收时会被调用,只会被调用一次
不能,抽象类是不完整的,某些方法可能只有声明,而没有定义(实现),调用这些方法会出现未知的结果
个人理解,抽象类就像是一个模板,实现了子类共有的内容,剩下的部分交给子类自定义自己的实现;接口就像是一套规范,任何类都可以去按要求去实现这套规范。
即抽象类对类抽象,接口对行为抽象。
具体的区别如下:
面向对象的思想把一个问题分解成多个对象,然后使用这些对象互相配合以解决问题。可以提高程序的重用性、灵活性和可扩展性。
三个基本特征是封装、继承和多态。
实现多态的关键有 3 个:继承、重写、父类引用指向子类对象,它可以使程序具有良好的可扩展性,可以对类的所有对象进行通用处理,当改变对象的实际类型时,程序不需要做出其它改变。但不可以使用子类特有的成员变量和方法。
在 Java 虚拟机中,一切方法调用在 Class 文件里面存储的都只是符号引用,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。多态之所以能够被实现,是由字节码指令 invokevirtual 的特性决定的,invokevirtual 指令会在运行时找到对象的实际类型,并调用对应的方法。而出于性能的考虑,大部分虚拟机会为类在方法区中建立一个虚方法表(invokeinterface 对应接口方法表),使用虚方法表来代替元数据查找以提高性能。
继承:子类继承父类的特性和行为,是 is-a 关系,可以使得父类更通用,子类更具体
封装:将实现细节包装、隐藏起来的一种方法,比如 setter 和 getter,能够防止类的代码和数据被外部访问,从而减少耦合性,使得类更安全
多态:实现多态的关键有 3 个:继承、重写、父类引用指向子类对象,它可以使程序具有良好的可扩展性,可以对类的所有对象进行通用处理,当改变对象的实际类型时,程序不需要做出其它改变。但不可以使用子类特有的成员变量和方法。
重写:子类重写父类的方法,是实现多态的关键
重载:方法名相同,参数个数或类型不同
// TODO 如果有更好的理解,欢迎指出
面向过程编程:将问题划分为多个步骤,一步一步实现以解决问题
面向对象编程: 把一个问题分解成多个对象,然后使用这些对象互相配合以解决问题。
面向切换编程:通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,可以在运行时(或者编译期、类加载时)动态地将代码切入到类的指定方法的指定位置上。
编译期注解的信息会被编译器丢弃,可用于帮助编译器检查一些代码是否正确,比如 @IntDef
运行时注解的信息可以在运行时通过反射机制获取,比如 Dagger2、ButterKnife 等依赖注入框架就是通过运行时注解 + 反射机制实现的
运行时注解的信息可以在运行时通过反射机制获取
由于反射涉及动态地解析类型,无法执行 Java 虚拟机的某些优化措施(比如 JIT?公共子表达式消除?数组范围检查消除?方法内联?逃逸分析?),因此性能低于非反射操作。如果是依赖注入,生成新的类时,还需要执行一遍类的加载过程(加载、验证、准备、解析、初始化)。
public class ReflectPrivate {
private int a = 0;
public void test() {
System.out.println("ReflectPrivate: " + a);
}
}
public class Test {
public static void main(String[] args) {
ReflectPrivate reflectPrivate = new ReflectPrivate();
try {
Field field = reflectPrivate.getClass().getDeclaredField("a");
field.setAccessible(true);
field.setInt(reflectPrivate, 10);
reflectPrivate.test();
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
输出:
ReflectPrivate: 10
反射可以在运行时打开和检查 class 文件,从而获取类的某些信息
应用:Dagger2 等依赖注入框架
int:2^32
long:2^64
// TODO BigDenimal 未研究
BigDenimal:不限
基本数据类型溢出:按二进制处理
数组越界:抛出 ArrayIndexOutOfBoundsException
// TODO 优化指什么?
在常量池中缓存了 -128 ~ 127 的值
装箱:基本类型数据存储到集合或 Map 中的时候会自动转化为对应的装箱类型
拆箱:装箱类型赋值给基本数据类型时,类型会自动转换
int:基本数据类型,直接存数值,默认值为 0,使用 == 判断是否相等
Integer 是 int 的封装类,默认值为 null,使用 equals 判断是否相等
泛型可以把运行时的类型检查转换到编译时,消除了许多类型的强制转换,提高程序的安全性,同时增强了代码的可复用性,使得容器的使用十分方便
extends:上界匹配符,代表 T 及 T 的子类,适用于返回类型限定
super:下界匹配符,代表 T 及 T 的父类,适用于参数类型限定
泛型擦除:List 和 List 在运行时实际上是相同的类型——List,参数类型信息会被擦除,如果需要获取确切的参数类型,可以使用 Object 转型或反射创建对应的类型
String:不可变类,对 String 的任何改变都会生成新的对象,内部使用 char[] 实现,存储格式是 UTF-16
StringBuilder:内容可变,继承自 AbstractStringBuilder,线程不安全
StringBuffer:内容可变,继承自 AbstractStringBuilder,几乎所有成员方法都使用 synchronized 修饰,线程安全
a 指向常量池引用,假如 “” 在常量池中已存在,那么 a 不会创建新的对象
new String 必定会创建一个新的对象,因此 a != b,但 a.equals(b) 为 true
都是值传递,引用存储的是对象的地址,重新赋值会修改引用指向的地址,但原来的对象不会被改变
参考:Java 到底是值传递还是引用传递?
equals:用于自定义比较对象是否相等的逻辑
hashCode:返回一个哈希值,该类对象作为 Map 容器的 Key 时,会通过这个方法确定存储该对象的数组的下标
toString:用于自定义内容输出逻辑
wait:用于等待某个条件满足时再向下执行,会释放对象的内置锁,唤醒后重新获取该对象锁
notify:通知某个条件被满足了,正在等待同一对象锁的线程会被唤醒并向下执行
关于 Object 的通用方法的各种注意事项可以参考:Object | Android Developers
== 用于比较引用地址是否相等
equals 用于自定义比较的逻辑,通常会通过比较 Object 的内部数据来判断是否相等
equals 常用于比较内容是否相等
hashCode 用于返回一个哈希值,当该类的对象作为 Map 容器的 Key 时,这个哈希值代表的是存储该对象的数组的下标
equals 和 hashCode 需要同时复写
考虑到该类的对象在 Map 中作为 Key 的情况,如果只复写 equals 方法而没有复写 hashCode 方法,可能会导致程序运行结果不符合预期,例如:
因为 HashMap 的运行机制是:先调用 hashCode 找到存储该对象的数组下标,接着遍历链表并逐个比较 hash 值和 equals 方法,都为 true 时即表明找到对应的元素了。因此,如果没有同时复写 equals 和 hashCode,可能无法在 HashMap 中查找到想要的对象,即使这个对象已经存储在 HashMap 里面。
深拷贝:拷贝出另一个一模一样的对象
浅拷贝:创建一个指向原对象的引用
默认浅拷贝
注意:
NIO(Non-blocking I/O,在 Java 领域,也称为 New I/O),是一种同步非阻塞的 I/O 模型,也是 I/O 多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
旧的 I/O 包已经用 nio 重新实现过,因此即使不显式地用 nio 编写代码,也能从中受益
NIO 速度地提高来自于所使用的结构更接近与操作系统控制 I/O 的方式:通道存储数据,缓冲器与通道进行通信
编写代码时不直接与通道交互,只和缓冲器交互
通道对应的接口是 Channel,有 FileChannel、SocketChannel 等实现类,唯一与通道交互的缓冲器是 ByteBuffer
参考:Java NIO浅析
常见异常 | 使用场合 |
---|---|
IllegalArgumentException | 参数(非 null)使用不正确 |
IllegalStateException | 对象的状态不正确 |
NullPointerException | 禁止使用 null 的情况下对象值为 null |
IndexOutOfBoundsException | 下标参数值越界 |
ConcurrentModificationException | 禁止并发修改的情况下,检测到对象的并发修改 |
UnsupportedOperationException | 对象不支持用户请求的方法 |
异常处理机制:
可抛出类型 Throwable有 两个子类——Error 和 Exception
a) 其中 Error 代表编译时错误和系统错误,例如内存溢出、线程死锁等,程序本身无法克服和恢复
b) Exception 是可以被抛出的基本类型,程序本身能够克服和恢复,是开发者需要关心的
Exception 同样可以分为两类——不受检查的异常(运行时异常)和受检查的异常
a) 运行时异常代表的是编程错误,自动被 Java 虚拟机抛出,所以不必在异常说明中列出来
b) 其它异常称为受检查的异常,在编译器就变强制检查
异常链、异常查找顺序、子类父类、吞噬异常等可以自行网上查阅相关资料
内部类分为静态内部类和非静态内部类,非静态内部类又可以分为局部类、匿名类、普通(成员)内部类
非静态内部类的创建依赖于外围类,拥有一个隐式地指向外部类的引用,因此可以访问外围对象的所有成员,除了基本数据类型和 String 类型的 static final 变量外,不能存在 static 成员(包括变量、方法、内部类)
静态内部类的创建不依赖于外围类,不能访问外围对象的非静态成员,可以拥有 static 成员
作用:
这是由 Java 的闭包实现机制决定的,闭包可以简单地认为是:
Java 到处都是闭包,比如类的成员方法、内部类等,都是闭包。但 Java 编译器对闭包的支持不完整,它会偷偷地把外部局部变量复制一个副本到闭包里面,即 Java 编译器实现的只是 capture-by-value,并没有实现 capture-by-reference,而只有后者才能保持匿名内部类和外部环境局部变量保持同步。既然内外不能同步,Java 就干脆一刀切,不允许改变外部的局部变量。
参考:java为什么匿名内部类的参数引用时final?