一、Java 基础知识
(引自知乎:https://zhuanlan.zhihu.com/p/215878135 稍加修改)
1、Object 类相关方法
- getClass 获取当前运行时对象的 Class 对象。
- hashCode 返回对象的 hash 码。
- clone 拷贝当前对象, 必须实现 Cloneable 接口。
- 浅拷贝对基本类型进行值拷贝,对引用类型拷贝引用;
- 深拷贝对基本类型进行值拷贝,对引用类型对象不但拷贝对象的引用还拷贝对象的相关属性和方法。两者不同在于深拷贝创建了一个新的对象。
- equals 通过内存地址比较两个对象是否相等,String 类重写了这个方法使用值来比较是否相等。(==与equals的区别 ==判断基本数据类型是直接判断值相等?,判断引用类型是判断指向地址的值;equals判断指向地址的值,但是一般重写,String类中重写为判断字符串内容)
- toString 返回类名@哈希码的 16 进制。(在Java中,如果一个对象未重写toString()方法,那么它将会调用父类的toString(),如果父类也没有重写这个方法,那么就迭代往上调用,直到Object的toString()方法。String类重写了toString方法,展示的是我们想要的String内容)
- notify 唤醒当前对象监视器的任一个线程。
- notifyAll 唤醒当前对象监视器上的所有线程。
- 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁
- wait ()与sleep()
- 暂停线程的执行;
- 三个不同参数方法(等待多少毫秒;额外等待多少毫秒;一直等待)
- 与
Thread.sleep(long time)
相比,sleep 使当前线程休眠一段时间,并没有释放该对象的锁,wait 释放了锁。(sleep是Thread的方法,但是wait是Object的方法。sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要notify notifyall唤醒(不指定时间需要被别人中断)。)
- finalize 对象被垃圾回收器回收时执行的方法。
2、基本数据类型
- 整型:byte(8)、short(16)、int(32)、long(64)
- 浮点型:float(32)、double(64)
- 布尔型:boolean(8)
- 字符型:char(16)
3、序列化
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型这些信息构成的字节序列可以用来在内存中新建对象。(还原出原来那个对象的意思,但是地址不一定相同)
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
Java 对象实现序列化要实现 Serializable 接口。
- 反序列化并不会调用构造方法。反序列的对象是由 JVM 自己生成的对象,不通过构造方法生成。
- 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
- 如果想让某个变量不被序列化,使用 transient 修饰。
- 单例类序列化,需要重写 readResolve() 方法。
- 如果你想知道一个 Java 标准类是否是可序列化的, 只需要查看该类是否实现java.io.Serializable接口。
4、String、StringBuffer、StringBuilder
- String 由 char[] 数组构成,使用了 final 修饰,是不可变对象,可以理解为常量,线程安全;对 String 进行改变时每次都会新生成一个 String 对象,然后把指针指向新的引用对象。
- StringBuffer 线程安全,效率低;StringBuiler 线程不安全,效率高。
- 操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer。
- String的一些常见操作:
获取字符:charAt(int n)
字符串比较:compareTo(String anotherString)长度相同返回不同字符的相差的int值,长度不同返回字符串长度相差值
字符串翻转:String reverse = new StringBuffer(string).reverse().toString();返回翻转字符串
字符串转变为字符数组:char[] s=str.toCharArray();返回字符数组
字符串截取:subString(int m,int n)从m开始截取,到n为止,不包括n(从0开始计算),当只有一个参数时,从该数直到字符串结尾
- StringBuffer的一些常见操作:
public StringBuffer append(String s):将指定的字符串追加到此字符序列。
public StringBuffer reverse():将此字符序列用其反转形式取代。
public delete(int start, int end):移除此序列的子字符串中的字符。
int capacity(): 返回当前容量。注意,不是长度。SB的动态扩容办法是将新容量扩为大小变成 2 倍 + 2,直接扩充到需要的容量大小 。
public insert(int offset, String s):将String s字符串插入从0开始计算的offset位置。
5、重载与重写
- 重载 发生在同一个类中,方法名相同,参数的类型、个数、顺序不同,方法的返回值和修饰符可以不同。
- 重写 发生在父子类中,方法名和参数相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 或者 final 则子类就不能重写该方法。
6、final
- 修饰基本类型变量,一经出初始化后就不能够对其进行修改。
- 修饰引用类型变量,不能够指向另一个引用。
- 修饰类或方法,不能被继承或重写。
7、反射
反射是java提供的一个重要功能,可以在运行时检查类、接口、方法和变量等信息,无需知道类的名字,方法名等。还可以在运行时实例化新对象,调用方法以及设置和获取变量值。检查一个类之前,必须获取到java.lang.Class对象,java中的所有类型,包括long,int,数组等基本数据类型,都和Class对象有关系。Class类对象就相当于B超的探头,将一个类的方法、变量、接口、类名、类修饰符等信息告诉运行的程序。Java提供了两种方式获取Class对象,一种是使用.class,另外一种是使用Class.forName()。.class方式适用于在编译时已经知道具体的类。
- 在运行时动态的获取类的完整信息
- 增加程序的灵活性
- JDK 动态代理使用了反射
8、JDK 动态代理(AOP)
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
- 使用步骤
- 创建接口及实现类
- 实现代理处理器:实现 InvokationHandler ,实现 invoke(Proxy proxy,Method method,Object[] args) 方法
- 通过 Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h) 获得代理类
- 通过代理类调用方法。
9、Java IO
- 普通 IO ,面向流,同步阻塞线程。
- NIO,面向缓冲区,同步非阻塞。
10.JVM
- 程序计数器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。
- java虚拟机栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储
局部变量表
、操作数栈
、动态链接
、方法出口
等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)。
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
- 本地方法栈
区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。
- java堆
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
- 方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
11.异常体系
throws和throw的区别:
throw作用于方法内,主动抛出一个Throwable类型的异常。一旦遇到到throw语句,后面的代码将不被执行。然后,便是进行异常处理——包含该异常的try-catch最终处理,也可以向上层抛出。
如果一个方法可能抛出异常,但是没有能力处理该异常或者需要通过该异常向上层汇报处理结果,可以在方法声明时使用throws来抛出异常。方法本身处理不了这个异常,向上层提交。