Java概述
体系结构
- JRE:Java核心类库
- JVM:Java虚拟机
- JDK:包含JRE和JVM,以及其他Java命令与工具(如java、javadoc、javac等)
Java最大特点
跨平台:Java文件编译后是class文件,class文件符合Java虚拟机规范,每个平台都有一套JVM,class依赖与JVM而不是操作系统
Java数据类型
- 8种基本数据类型:byte、short、int、long、float、double、char、boolean
- 引用数据类型:数组、类对象、枚举、注解
区别:基本数据类型存储在栈中;引用类型数据存储在堆中,对象的引用存储在栈中
String,StringBuffer,StringBuilder的区别
- String是不可变字符串(String不可变的原因是因为String内部的存储字符串字面量的成员变量是final修饰的),StringBuffer、StringBuilder是可变的字符串对象
- String、StringBuffer是线程安全的,StringBuilder是线程不安全的
Java传参问题
Java方法的传参——传值(不论是基本类型还是引用类型);传入的都是引用的副本(这里引用并不是一个地址,而是存储在栈中的值),如果方法中将参数指向另一个对象不会影响实参,但对于引用类型,对参数指向的对象进行修改会影响实参
写一个方法将两个Integer变量交换笔试题
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Integer a = new Integer(10);
Integer b = new Integer(20);
swap(a,b);
System.out.println(a);
System.out.println(b);
}
public static void swap(Integer a,Integer b) throws NoSuchFieldException, IllegalAccessException {
Class c = Integer.class;
Field field = c.getDeclaredField("value");
//设置可访问性,成败就在这里,由于value字段是private final,只有这一步设置才可以在外部改变他的值
field.setAccessible(true);
int numA = (int) field.get(a);
field.set(a,field.get(b));
field.set(b,numA);
}
包装类
每一个基本数据类型都对应有一个包装类:Byte、Short、Integer、Long、Float、Double、Character、Boolean
自动装箱与拆箱
int i = new Integer(100)
:拆箱
Integer i = 100
:装箱
体现:方法传参、泛型集合
Integer的比较
Integer内部有缓冲区默认范围是-128~127,在范围内的数据都存储在常量池中,创建相同数据时会将同一个值返回;超过这个范围会在堆中创建一个Integer对象,两个相同大小的值对应着两个相同的对象,但在堆中的地址不同;而==对于引用类型比较的是地址
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1==i2);//true
Integer i1 = 128;
Integer i2 = 128;
System.out.println(i1==i2);//false
变量
- 方法中的变量无初始值,使用前要先赋值
- 类中的变量为全局变量,引用类型默认为null;基本数据类型默认值int为0,boolean为false;
- 代码块中的局部变量默认没有初始值
运算符
- 整数的除法运算出现小数会出现精度缺失,如5/2=2
- 取余运算结果的符号取决于被除数(前面的数),如5%-2=1;-5%2=-1
- 当参与运算中出现精度更高的类型,则结果为该类型,如
7/2?1:0.9
结果为1.0而不是1 - 逻辑运算符的短路,&&当第一个为false,则第二个不会执行;||当第一个为true,则第二个不会执行
- 位运算符&,|即两边转换为二进制数对应位进行逻辑运算,如4(100)&6(110)结果为4(100)
&和&&的区别
&运算的是boolean和数字,&&运算的是boolean
&没有短路,&&会短路
==与equals的区别
equals如果没有Override,默认实现就是==
==可以操作基本类型和引用类型;equals只有引用类型有
OOP
- 类与对象:实例化对象的方式——new、反射、克隆、序列化
- 构造方法:名字与所在类一致,没有返回值类型
- 方法重载overload:同一类中,方法名相同,参数不同(类型、数量、顺序)
- 方法重写override:子类继承父类,重写父类相同名称、相同参数方法,子类的返回值可以和父类相同或者返回值是父类的子类,子类抛出的异常不能比父类的多,子类的访问控制符必须比父类的大
关键字
static
- static修饰类时只能修饰内部类,内部类的实例化方式为通过外部类打点找内部类
- static修饰方法,该方法属于类,可以通过类名打点直接调用
- static修饰类成员变量,只加载一次,在类实例化之后加载,所有对象共享一个成员变量
- static修饰代码块,代码块内部只能捕获并处理异常不能抛出异常
- 静态导入,通过
import static.package.class.method
可以将方法进行静态导入,在使用时可以不用写类名直接使用方法,method可以使用通配符*
this
表示当前实例,不能用于静态代码块和静态方法
this()访问本类的其他重载形式的构造方法
final
- final修饰的类不能被继承(构造方法私有也不能被继承)
- final修饰的方法不能重写
- final修饰的变量不可重新赋值,如果修饰的是引用类型的变量内部变量可以修改;如果修饰的是类成员,静态:直接赋值,静态代码块中赋值,非静态:直接赋值,代码块或构造方法赋值
super
表示父类的实例,使用super.来访问父类的属性或方法;super()在子类的构造方法中访问父类的构造方法
extends、implements
Java中不能多继承但能多实现(即一个类不能同时继承多个类,但是能同时实现多个接口),因为如果多个类有相同的方法,在子类调用父类方法时会有歧义,而接口对方法没有实现,需要子类进行实现所以没有歧义,在多实现时对于接口中重名的默认方法,子类必须重写该方法
类的加载
类加载时机
- 创建类的实例,即new一个对象
- 访问类的静态变量;访问类的静态方法
- 访问类的静态方法
- 反射,Class.forName
- 加载该类的子类(会首先加载子类的父类)
- 虚拟机启动时,定义了main()方法的类
类初始化顺序
父类静态代码块——》子类静态代码块——》父类变量——》父类代码块——》父类构造方法——》子类变量——》子类代码块——》子类构造方法
单例模式
保证所有用到该类的实例的地方用到的都是同一个实例
- 饿汉式
public class Singleton{
//防止外部调用构造方法来构造实例
private Singleton(){}
//在类加载时进行实例化
private static Singleton sinleton = new Sinleton();
//获取实例化的对象
public static Singleton getInstance(){
return sinleton;
}
}
该方式时线程安全的,但是如果有其他静态成员,可能不需要实例化时却实例化了,浪费资源
2. 懒汉式(线程不安全)
public class Sinleton{
private Sinleton(){}
private static Sinleton sinleton = null;
public static Sinleton getInstance(){
if(sinleton == null){
sinleton = new Sinleton();
}
return sinleton;
}
}
该方法只有在需要使用这个实例时实例化,但是存在线程安全问题
3. 懒汉式(线程安全,使用synchronized关键字)
public class Sinleton{
private Sinleton(){}
private static Sinleton sinleton = null;
public static synchronized Sinleton getInstance(){
if(sinleton == null){
sinleton = new Sinleton();
}
return sinleton;
}
}
- 静态内部类
public class Singleton {
private Singleton () {}
//利用静态内部类创建实例来保证实例只创建一次
private static class SingletonHolder{
private static final Singleton singleton= new Singleton();
}
public static final Singleton getInstance () {
return SingletonHolder.singleton;
}
}
面向对象的特征(经典题目)
面向对象三大特性:封装,继承,多态
- 封装:仅暴露可供调用的接口,将内部实现细节隐藏起来,Java通过访问控制修饰符来进行封装
- 继承:子类可以保有且复用父类的属性或方法 ,除了私有成员、构造方法,Java通过extends关键字来实现继承
- 多态:一种写法可以在不同的场景下有不同的意义,Java通过使用接口或者父类作为参数或者返回值,在不同场景下有不同的子类实现
异常
类层次结构
异常常见面试题
- 受检异常和运行时异常的区别:受检异常必须显示处理,运行时异常不处理也能运行
- try-catch捕获异常时,可以捕获父类异常
- 只要执行了try-catch,不论什么情况都会执行finally; 如果try、catch、finally中都有return,finally中的代码就会在try和catch之前执行,会执行finally中的return而不会执行try、catch中的,如代码所示:
public static void main(String[] args) throws ClassNotFoundException {
System.out.println(test());
}
public static int test(){
try{
int i = 1/1;
return 1;
}catch (Exception e){
return 0;
}finally {
return -1;
}
}
输出结果为-1而不是1
public static void main(String[] args) throws ClassNotFoundException {
System.out.println(test());
}
public static int test(){
try{
int i = 1/0;
return 1;
}catch (Exception e){
return 0;
}finally {
return -1;
}
}
输出结果时0而不是1
- 常见的异常有:NullPointException、IOException、ArrayIndexOutOfBoundsException、PersistenceException、ClassNotFoundException
- 自定义异常继承自运行时异常,实现四个构造方法,分别时无参、message、throwable、message+throwable;作用:前端可以针对不同的异常做出不同的响应、事务可以根据异常进行回滚
集合
层次结构
List和Set的区别
- List:保证有序(增加进去的顺序),可重复,在查找操作频繁时效率更高
- Set:不保证顺序,不可重复(HashSet通过hashCode和equals来保证不重复,TreeSet通过compareTo方法来保证不重复),在集中删除或修改时效率更高
ArrayList、LinkedList、Vector的区别
- ArrayList和Vector内部实现相同,使用数组存储数据,检索更快,ArrayList线程不安全,Vector线程安全(通过synchronized关键字)
- LinkedList:存储方式是双向链表,增加删除更快,LinkedList线程不安全
集合线程安全问题
- 在多线程环境下,哪些集合是可用的
Vector,Collection,JUC - 线程安全是什么意思
多个线程同时操作集合的实例时,会导致数据出现不一致的问题
Map
- HashMap和HashTable两个实现的功能基本相同,区别在于HashMap是线程不安全的,HashTable是线程安全的
- 排序的Map:TreeMap
集合的遍历
- 对于List和Set集合,可用通过forEach、for-i、Iterator来遍历(Collection、数组、任何继承了Iterable接口的集合都能够使用迭代器Iterator)
- 对于Map集合,可通过keySet()方法来获取键集合,然后通过遍历键来遍历值;通过values()方法来获取值集合进行遍历;通过Map.Entry来进行forEach遍历(
Map.Entry entry:map.entrySet()
)
将List中的元素去重
- 将元素存入HashSet中,利用Set不重复特性
List list = new ArrayList();
list.add("a");
list.add("a");
list.add("b");
HashSet set = new HashSet(list);
//清空list
list.clear();
list.addAll(set);
- 利用contains方法和一个新的list进行判断
List list = new ArrayList();
list.add("a");
list.add("a");
list.add("b");
List newList = new ArrayList();
list.forEach(s->{
//当新list集合中没有该元素时,将该元素存入
if (!newList.contains(s)){
newList.add(s);
}
});
- 双重循环遍历list,通过equals方法比较去重
IO流
字节流与字符流
输入流
- 字节输入流:InputStream、FileInputStream等
- 字符输入流:Reader、InputStreamReader等
输出流
- 字节输出流:OutputStream、FileOutputStream等
- 字符输出流:Writer、OutputStreamWriter等
进行文件拷贝
public static void main(String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream("D:/1.png");
OutputStream outputStream = new FileOutputStream("D:/2.png");
byte []bytes = new byte[50];
while(inputStream.read(bytes)!=-1){
outputStream.write(bytes);
}
}
网络编程
- 服务器端:ServerSocket;客户端:Socket
BIO、NIO、AIO
-
BIO
同步阻塞I/O,一个服务器连接对应一个线程,及客户端有连接请求时就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可用通过线程池机制来改善,BIO方式使用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。 -
NIO
同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求时才会启动一个线程进行处理,NIO方式适用于连接数目较多且连接比较多的架构(redis单线程却高性能也是因为底层使用到多路复用),比如聊天服务器,并发局限于应用中,编程复杂。典型的使用NIO的网络编程框架Netty
多路复用:
-
AIO
异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理,AIO方式适用于连接数目较多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程复杂
克隆
clone()方法是Object的方法,使用protect修饰,如果子类需要使用到该方法需要重写该方法,修改访问控制符为public,且实现Cloneable接口(类似序列化);克隆的对象与原对像是两个个体,互不相干
浅克隆与深克隆
class Klass{
private String className;
public Klass(String className){
this.className = className;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public String toString() {
return "Klass{" +
"className='" + className + '\'' +
'}';
}
}
class Student implements Cloneable{
private String name;
private Klass klass;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Klass getKlass(){
return klass;
}
public void setKlass(Klass klass){
this.klass = klass;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", klass=" + klass +
'}';
}
}
class Scratch{
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student();
student1.setName("tom");
student1.setKlass(new Klass("一班"));
Student student2 = (Student) student1.clone();
System.out.println(student1);
System.out.println(student2);
student2.setName("jack");
student2.getKlass().setClassName("二班");
System.out.println(student1);
System.out.println(student2);
}
}
结果:
Student{name='tom', klass=Klass{className='一班'}}
Student{name='tom', klass=Klass{className='一班'}}
Student{name='tom', klass=Klass{className='二班'}}
Student{name='jack', klass=Klass{className='二班'}}
这里属于浅克隆,Student实现了Cloneable接口,所以Student的字段能够被克隆出一份新的、与原来对象不相干的数据,而Klass的字段还是共用的同一个数据,所以需要将Klass的字段也实现Cloneable接口,重写Object克隆方法,并在克隆时单独对Klass进行克隆,如下修改Student的clone方法
class Klass implements Cloneable{
private String className;
public Klass(String className){
this.className = className;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Klass{" +
"className='" + className + '\'' +
'}';
}
}
class Student implements Cloneable{
private String name;
private Klass klass;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Klass getKlass(){
return klass;
}
public void setKlass(Klass klass){
this.klass = klass;
}
@Override
public Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.setKlass((Klass) student.getKlass().clone());
return student;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", klass=" + klass +
'}';
}
}
class Scratch{
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student();
student1.setName("tom");
student1.setKlass(new Klass("一班"));
Student student2 = (Student) student1.clone();
System.out.println(student1);
System.out.println(student2);
student2.setName("jack");
student2.getKlass().setClassName("二班");
System.out.println(student1);
System.out.println(student2);
}
}
枚举
- 用于有限个实例的类的编写
- 构造方法默认私有且必须是私有的
- 条件结构switch-case中经常使用
- enum关键字是语法层面的,它声明的每一个枚举类型都是Enum的子类