面向对象的特征
抽象、封装、继承、多态。
多态、重写和重载
多态性是指允许不同子类型的对象对同一消息做出不同的响应。
多态的实现是靠重载和重写。
重载是编译时多态,重写是运行时多态。
重写规则:
- 参数列表必须不变;
- 返回值类型可以不同,但是必须是原来的子类;
- 访问权限不能比重写前的更低;
- 重写的方法能够抛出任何非强制性异常,无论被重写的方法是否抛出异常;但重写的方法不能抛出比被重写方法更广泛的强制性异常;
- 父类成员方法只能被子类重写;
- final的方法不能重写;static的方法不能重写,但能被再次申明;构造方法不能被重写;
重载规则:
- 参数列表必须改变(参数类型或个数不一样);
- 可以改变返回类型,但无法以返回值类型作为重载函数的区分标准。
- 可以改变访问修饰符;
- 可以申明新的或更广的检查异常;
- 方法可以在同一个类或在一个子类中重载;
不能以返回值类型作为方法重载的区分标准
在调用时,无法指定类型信息,编译器不能直到程序调用的是哪个方法。
在JVM中,要重载一个方法,除了要与原方法有相同的简单名称外,还必须有一个与原方法不同的特征签名。特征签名就是方法中的参数在常量池中的引用的集合,不包括返回值类型。Class文件的特征签名的范围更大,包括返回类型,应为JVM规范和Java语言规范不同。
基本类型
byte
, 1short
, 2int
, 4long
, 8float
, 4double
, 8char
, 2boolean
, 4
boolean
可以用1bit
存储,JVM在编译期将boolean转换为int
存储。
包装类型
基本类型都有对应的包装类型,基本类型与包装类型之间的赋值使用自动装箱和拆箱完成。
String
String
被声明为final
,不可修改。
Java8中String
使用char
数组存储,Java9中是用byte
数组存储,同时用coder
标识编码方式。
String
是线程安全的;可以作为map的key
,缓存hash值;String
创建过的对象会放在String Pool中。
switch … case
支持char
, byte
, short
, int
, Character
, Byte
, Short
, Integer
, String
, enum
。
String, StringBuilder, StringBuffer
String
不可变,StringBuilder
和StringBuffer
可变;
StringBuilder
线程不安全,String
和StringBuffer
线程安全。
抽象类和接口
含有抽象方法的类一定是抽象类,抽象类不能被实例化。
接口在Java8之前不能有任何方法,是完全抽象的类。在Java8之后,可以定义default
方法。
区别:
- 接口的成员方法是
public
的,字段是public
,static
,final
的;抽象类没有这种限制; - 接口可以实现多个;抽象类只能继承一个;
- 抽象类要满足里氏替换原则,子类能够替换所有父类对象;接口不需要;
static
- 修饰变量,即静态变量。静态变量属于类,所有类的实例共享一份静态变量,可以通过类名访问。而实例变量依赖对象实例。
- 修饰方法,即静态方法。在类加载的时候就存在了,不依赖于任何实例。静态方法必须有实现,只能调用静态方法和静态变量,不能有
super
和this
关键字。 - 修饰语句,即静态语句块。只在类初始化时运行一次。
- 修饰类。只有内部类可以被
static
修饰,静态内部类可以通过外部类的类名直接访问内部类。 - 静态导包。调用方法时,不用再写类名。
调用顺序:
- 父类静态变量,静态语句块;
- 子类静态变量,静态语句块;
- 父类实例变量,普通语句块;
- 父类构造函数;
- 子类实例变量 ,普通语句块;
- 子类构造函数;
反射
什么是反射?
在运行状态中,对于任意一个类,都知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
哪里用到了反射?
- JDBC中,利用反射动态地加载了数据库驱动程序;
- Web服务器中利用反射调用了Servlet的服务方法;
- 开发工具利用反射动态剖析对象的类型与结构,动态提示对象的属性和方法;
- 很多框架都用到反射机制,注入属性,调用方法,如Spring。
反射的优缺点
优点:
- 可扩展性:应用程序可利用全限定名创建可扩展对象实例,来使用来自外部的用户自定义类;
- 类浏览器和可视化开发环境:一个类浏览器需要可以枚举类的成员;可视化开发环境(如IDE)可以利用反射中可用的类型信息,帮助程序员正确编写代码;
- 调试器和测试工具:调试器需要能够检查一个类里的私有成员;测试工具可以利用反射来自动调用类里定义的可被发现的API定义,以确保一组测试中有较高的代码覆盖率。
缺点:
- 性能开销:反射涉及了动态类型的解析,JVM无法尽心优化,操作效率低;
- 安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中进行;
- 内部暴露:反射代码允许执行一些正常情况下不允许的操作,破坏了抽象性,当平台发生改变,代码的行为可能会发生变化。
如何使用反射?
获取一个Class对象
Class.forName("全限定类名")
;类名.class
;对象.getClass()
;
创建对象实例
clz.getConstructor([String.class]).newInstance([参数])
;clz.newInstance()
;相当于new 类名()
;
通过Class对象获得一个属性对象
Filed c = clz.getFields
;获得所有public
字段,包括父类中的字段;Filed c = clz.getDeclaredFields()
;获得某个类的所有申明字段,不包括父类字段;
通过Class对象获得一个方法对象
clz.getMethod("方法名", class ... paramType)
,只能获取公共的;clz.getDeclareMethod("方法名")
,获取任意修饰的方法,不能执行私有;M.setAccessiable(true)
,让私有方法可以执行;
异常
Java异常有哪几种,特点是什么?
异常是发生在程序执行过程中阻碍程序正常执行的错误操作,只要在Java语句执行中产生异常则一个异常对象就会被创建。Throwable
是所有异常的父类,它有两个直接子类Error
和Exception
,其中Exception
又被继续分为Checked Exception
(受检异常)和Runtime Exception
(运行时异常)。Error
是JVM不能处理的错误;受检异常在程序中能预期且要尝试修复(使用try-catch
捕获异常,并进行处理;或用thows
关键字抛出);运行时异常又称为不受检异常,是程序运行时出现的异常,可能会导致程序崩溃,不要求被处理或用throws
抛出。
Java中throw
和throws
的区别是什么?
throw
使用的位置在方法中,后面跟的是异常对象实例,表示抛出异常,由方法体内语句处理,如果方法中有throw
抛出RuntimeException
及其子类则申明上可以没有throws
。
throws
的使用位置在方法参数括号后面,后面跟的是一个或者多个异常类名且用逗号隔开,表示抛出异常并交给调用者去处理,如果后面跟的是RuntimeException
及其子类则方法可以不用处理,如果后面跟的是Exception
及其子类则必须要编写代码进行处理或者调用的时候抛出。
Java中受检异常和运行时异常有什么区别?
- 受检异常应该用
try-catch
代码块处理或用throws
关键字抛出,运行时异常在程序中不要求被处理或用throws
抛出; Exception
是所有受检异常的基类,而RuntimeException
是所有运行时异常的基类;- 受检异常适用于那些不是程序引起的错误情况(如
FileNotFoundException
),而运行时异常通常是由于代码错误引起(如NullPointerException
);
Java中Error
和Exception
有什么区别?
Error
表示系统级错误,是Java运行环境内部错误或者硬件问题不能指望程序来处理这样的问题,除了退出运行外别无选择,它是Java虚拟机抛出的。
Exception
表示程序需要捕捉、需要处理的异常,是由于程序设计的不完善而出现的问题,是程序可以处理的问题。
Java中什么是异常链?
异常链是指在进行一个异常处理时,抛出了另外一个异常,由此产生了一个异常链条大多用于将受检异常封装成为非受检异常。特别注意如果你因为一个异常而决定抛出另一个新的异常时,一定要包含原有的异常这样处理程序才可以通过getCause()
和initCause()
方法来访问异常最终的根源。
Java中如何编写自定义异常
可以通过继承Exception
类或其任何子类来实现自定义异常类,自定义异常类可以有自己的变量和方法来传递错误代码或其他异常相关信息来处理异常。
Java异常处理经验
- 方法返回值尽量不要用
null
,这样可以避免很多NullPointerException
异常; catch
到了异常,至少打印一下,以便排查问题;- 接口方法抛出的异常尽量是运行时异常;
- 避免在
finally
中使用return
语句或者抛出异常,如果调用的其它代码可能抛出异常则应该捕获异常并进行处理,因为finally
中return
不仅会覆盖try
和catch
内的返回值,而且还会掩盖try
和catch
内的异常; - 方法定义中
throws
后面尽量定义具体的异常列表,不要直接throws Exception
; - 尽量不要在
catch
中压制异常(即什么也不处理直接return
); - 捕获异常时尽量捕获具体的异常类型而不要直接捕获其父类,这样容易造成混乱。
- 避免在
finally
块中抛出异常,不然第一个异常的调用栈会丢失。 - 不要使用异常控制程序的流程,譬如本应该使用
if
语句进行条件判断的情况下却使用异常处理是非常不好的习惯,会严重影响性能。 - 不要直接捕获
Throwable
类,因为Error
是Throwable
类的子类,当应用抛出Errors
的时候一般都是不可恢复的情况。
泛型
泛型是什么?使用泛型的好处?
在集合中存储对象并在使用前进行类型转换很不方便,泛型就是为了防止这种情况的发生。
它提供了编译器的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException
。
泛型是如何工作的?什么是类型擦除?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关信息。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。
什么是泛型中的限定通配符和非限定通配符?
限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>
,它确保类型必须是T的子类来设定类型的上界;另一种是<? super T>
,它确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。
另一方面,<?>
表示了非限定通配符,因为<?>
可以用任意类型来代替。
如何编写一个泛型方法,让它能够接受泛型参数并返回泛型类型?
用类型占位符来代替原始类型。
public V put(K key, V value){
return cache.put(key, value);
}
编写一段泛型程序来实现LRU缓存?
- Map中带入参数
<K, V>
- 重写
LinkedHashMap
的removeEldestEntry()
的方法即可
public class UseLinkedHashMapCache<K, V> extends LinkedHashMap<K, V> {
private int cacheSize;
public UseLinkedHashMapCache(int cacheSize) {
super(16, 0.75f, true);
// true表示让LinkedHashMap按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
this.cacheSize = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest){
// 当map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
return size() > cacheSize;
}
}
可以把List<String>
传递给一个接受List<Object>
参数的方法吗?
这样会导致编译错误。因为List<Object>
可以存储任何类型的对象包括String
, Integer
等,而List<String>
却只能来存储String
。可以加上<? extends Object>
,来解决。
Array
中可以用泛型吗?
不行,用List
代替Array
,因为List
可以提供编译时期的类型安全保证,而Array
不行。