基础面试题总结
基本功
面向对象的特征
-
继承
子类继承父类,子类可以获取父类的属性和方法,这里需要注意权限修饰符的使用,父类使用private修饰的属性和方法不能被继承,子类可以在父类的基础上进行方法的重写,
-
封装
是指的是类的内部信息进行隐藏,一般是指对类内部的属性进行私有化,外部无法直接访问或影响内部的属性,只能通过特定的方法对封装的内容进行访问,提高了代码的安全性。
-
多态
一个接口可被多个类实现,对接口方法进行不同的实现,父类可以接受所有子类对象。
静态方法的作用
1、初始化类
2、访问静态变量
3、所有对象都可直接使用该类的方法,无需创建对象
try-with-resource用法(java7特性)
被try后面小括号包裹的资源会自动释放,与添加finally代码块的作用一样,但是省略了对close()方法繁琐的try-catch(),释放的资源对象需要实现AutoCloseable接口,否者无法自动找到关闭方法
try(FileInputStream fix = new FileInputStream("1.text")){
int data = fis.read();
}catch(Exception e){
e.printStackTrace();
}
optional的作用(java8特性)
Optional没有给出公共的构造方法,需要调用三个静态方法来构建Optional对象,
1、Optional.of()直接构建Optional对象
2、Optional.ofNullable()判断是否为空对象,如果给的入参为null则返回空对象Optional.empty
3、Optional.empty()直接返回空对象Optional.empty
判断是否为空对象,需要调用Optional的isPresent()和isEmpty()方法,但是isEmpty方法在Java11才出现,后续操作可以直接集成在isPresent(Consumer<? super T> action)和isPresentOrElse(Consumer<? super T> action,Runnable emptyAction)方法中,直接设置对象属性的默认值,或是抛出异常
final、finally、finalize的区别
final修饰符可作用于类和类属性上,作用在类上时,表示该类为最终的,不可被继承,例如String类。作用于属性上时,表示这是个常量,仅可被赋值一次后续不可被更改。
finally是try-catch的一个部分,常用于IO流和链接的释放,避免出现非必要的内存和通道的占用。
finalize是手动清理对象的方法,通过调用finalize()方法完成垃圾回收器将对象从内存中清除前做必要的清理工作。
int和Integer的区别
int是Java的基本数据类型,而Integer是int的包装类,int的默认值是0,而Integer的默认值是null,使用Integer作为接口接收参数类型时,可以自动转换String类型的数字串为数字,作为返回值时,属性值可为空。
Java如何禁止变量被序列化
想要类中的某些变量不被序列化,使用transient修饰即可,表示这是个临时变量,不能被序列化
重载和重写的区别
重载指的是类中多个同名方法,参数类型和个数都不同,而返回值相同,常用于类的构造器。
重写指的是子类继承父类,将父类方法进行重定义,写自己需要的业务代码,方法的参数类型和个数都不可更改。
抽象类和接口的区别
一个类只能继承一个抽象类,而一个类可以实现多个接口
抽象类可以有方法实现,而接口不能(jdk8里可以有默认实现代码)
抽象类中的变量为普通变量,而接口中的是公共的静态常量
接口里的方法全是public的方法,抽象类可以有非抽象的方法
接口是对方法的抽象,是行为的规范,而抽象方法是对类的抽象,是模版设计
需要子类继承成员变量,或需要控制子类实现时需要继承抽象类(表现在抽象类的构造去上,抽象类构造器添加参数,来达到规范子类的目的),其他的则使用接口
反射的用途及实现
核心是JVM在运行时才动态加载类或调用方法/访问属性,不需要事先知道运行对象是谁,主要用于开发各种通用框架
获取类
直接获取某个类的class
调用类实例的getClass()方法
使用Class类的forName()方法
创建实例
使用Class类的newInstance()方法
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例
获取方法
getDeclaredMethods()方法
getMetheds()方法
getMethed(String methedName, …Object args)方法
自定义注解作用及实现
请求拦截,校验是否满足要求,全局方法拦截,进行日志打印
注解分为元注解和自定义注解
常用元注解:
Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType
定义,常用的包括:
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述方法变量
- TYPE:用于描述类、接口或enum类型
Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy
中,取值为:
- SOURCE:在源文件中有效,编译过程中会被忽略
- CLASS:随源文件一起编译在class文件中,运行时忽略
- RUNTIME:在运行时有效
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {
String description();
int length();
}
HTTP请求的GET和POST的区别
get请求通过url直接请求数据,数据信息在url中能直接看见,post请求的数据被隐藏,不能直观的看见请求数据,且请求url长度有限制,因此get请求携带的数据量是受到限制的,post请求理论上没有限制
session和cookie的区别
session存放于服务端,cookie存放于客户端
cookie存储的数据有限制,而session没有
cookie生命周期是累积的,而session的生命周期是间隔的
session的分布式处理
使用token替代session,改用jwt验证
服务器间session的同步
使用redis存放session信息,以达成服务间的数据同步
JDBC流程
加载驱动类
获取链接对象
获取语句对象
执行sql,如果是查询语句,需要处理结果集
关闭语句对象、数据库链接
MVC流程
equals和==的区别
equals比较的是对象的值,而==比较的是对象的地址
可重写hashCode和equals方法,用于比较两个类是否相等
lambda表达式
使用场景:简化函数式接口匿名内部类的写法
使用前提:必须是函数式接口
函数式接口:只有一个抽象方法的接口,通常会用@FunctionalInterface注解标识
lambda只能简化函数式接口的写法,表达形式为(匿名内部类被重写的形参列表) ->{
被重写的方法代码
};
示例:
public class LambdaDemo {
public static void main(String[] args) {
String str = "小狗快跑"
//实现方式1
Animal animal = (str) ->{
System.out.println(str);
};
animal.run();
//实现方式2
run((str) -> {
System.out.println(str);
});
}
static void run(Animal animal){
animal.run();
}
}
@FunctionalInterface
public interface Animal {
void run(String str);
}
可省略部分:
Arrays.sort(args, (Integer arg1, Integer arg2) ->{
return arg1 - arg2;
})
1、参数类型可省略不写
Arrays.sort(args, (arg1, arg2) ->{
return arg1 - arg2;
})
2、如果只有一个参数,括号也可以省略不写
3、如果方法体只有一行代码,大括号可以不写,删除分号
Arrays.sort(args, (arg1, arg2) -> return arg1 - arg2)
4、如果方法只有一行代码,且是return语句,省略return,删除分号和大括号
Arrays.sort(args, (arg1, arg2) -> arg - arg2)
集合
List和Set的区别
List与Set都继承自Collection接口,List保持插入时的顺序,而Set不会(linkedHashSet除外,linkedHashSet可保留插入顺序,但不能拥有重复值),Set存入元素值时根据计算出的HashCode值进行排列存放,因此Set中不允许存在重复的值,且只能有一个空值,反之List可以有多个空值。List底层以数组或双向链表的形式存储值,而Set以哈希表或树形存储数据值。
List和Map的区别
Map以k-v键值对的形式存储值,底层以数组加链表的形式存储数据,在1.8之后,当数据量超过8时会转化为红黑树,HashMap和TreeMap都是不安全的集合,需要在存取数据时加锁以达成数据一致性,ConcurrentHashMap和HashTable是线程安全的Map集合,但不推荐使用HashTable,其底层时添加synchronized关键字来达成线程安全,在大量线程访问时可能会进入阻塞或轮询状态,而ConcurrentHashMap底层采用分段锁,在Map的node上添加16个Segment,这也意味着最多只支持16个线程同时操作数据,Segment继承了ReentrantLock,具备锁和释放锁的能力,因此在Segment下的操作上安全的。
线程
线程的生命周期
它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5中状态。
启动线程使用start(),而不是run()。永远不要调用线程对象的run()方法。如果调用直接调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其他线程无法并发执行——也就是说,系统把线程对象当成一个普通对象,而run()也是一个普通方法,而不是线程执行体。
Vector是一个线程安全类吗?
Vector是一个线程安全的类,其在add()等操作上添加了synchronized关键字实现同步,但是并非是绝对的线程安全类.
当进行迭代遍历时,如果在另一个线程执行add(),remove()操作,仍然会有机率抛出异常ConcurrentModificationException.
创建线程的方式
直接初始化Thread类,实现Runnable接口的run方法
public static void main(String args[]){
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("test create thread by Runable");
}
}).start();
}
实现Runnable接口
class Demo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String args[]) {
Demo d = new Demo();
Thread thread1 = new Thread(d,"first thread");
Thread thread2 = new Thread(d,"second thread");
thread1.start();
thread2.start();
}
实现callable接口配合FutureTask类实现
class Demo implements Callable{
@Override
public Object call() throws Exception {
int sum=0;
for(int i=0;i<10;i++){
sum=sum+i;
Thread.sleep(300);
}
System.out.println("sum:"+sum);
return sum;
}