总结梳理java八股文及其延展相关知识点(内容来源于各讲解文档的梳理总结)
(一)Object相关
1、getClass
获取当前运行时对象的 Class 对象。
Java的反射机制:
Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
1.1获取属性
1.1.1获得某个对象的属性
public Object getProperty(Object owner, String fieldName) throws Exception {
Class ownerClass = owner.getClass();//得到该对象的Class。
Field field = ownerClass.getField(fieldName);//通过Class得到类声明的属性。
Object property = field.get(owner);//通过对象得到该属性的实例,如果这个属性是非公有的,这里会报IllegalAccessException。
return property;
}
1.1.2获取某个类的静态属性
public Object getStaticProperty(String className, String fieldName)
throws Exception {
Class ownerClass = Class.forName(className);//className=参数是包名+类名
Field field = ownerClass.getField(fieldName);
Object property = field.get(ownerClass);//这里和上面有些不同,因为该属性是静态的,所以直接从类的Class里取。
return property;
}
1.2获取方法
1.2.1执行某对象的方法
public Object invokeMethod(Object owner, String methodName,
Object[] args) throws Exception {
Class ownerClass = owner.getClass();
//配置参数的Class数组,作为寻找Method的条件。
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName,argsClass);//通过methodName和参数的argsClass(方法中的参数类型集合)数组得到要执行的Method。
return method.invoke(owner, args);
}
1.2.2执行某个类的静态方法
public Object invokeStaticMethod(String className, String methodName,
Object[] args) throws Exception {
Class ownerClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName,argsClass);
return method.invoke(null, args);//第一个参数是null,因为这是静态方法,不需要借助实例运行
}
1.3新建实例
public Object newInstance(String className, Object[] args) throws Exception {
Class newoneClass = Class.forName(className);
//1.无参构造方法newoneClass.newInstance()
//2.有参构造方法
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Constructor cons = newoneClass.getConstructor(argsClass);
return cons.newInstance(args);
}
1.4判断是否为某个类的实例
public boolean isInstance(Object obj, Class cls) {
return cls.isInstance(obj);
}
1.5得到数组中的某个元素
public Object getByArray(Object array, int index) {
return Array.get(array,index);
}
2、hashcode
返回对象的 hash 码。
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
3、equals
通过内存地址比较两个对象是否相等,String 类重写了这个方法使用值来比较是否相等。
equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
(equals和hashCode的关联和方法重写:
a、不会创建“类的散列表”
我们不会在HashSet, HashTable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。
此时hashCode() 和 equals() 没有关系。
b、创建“类的散列表”
在这种情况下:
如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等,此时就出现所谓的哈希冲突场景。
在这种情况下,如果类使用再散列表的集合对象中,要判断两个对象是否相同,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
原因:
hashSet使用的是hashMap的put方法,而hashMap的put方法,使用hashCode()用key作为参数计算出hash值,然后进行比较,如果相同,再通过equals()比较key值是否相同,如果相同,返回同一个对象。
)
4、clone
拷贝当前对象, 必须实现 Cloneable 接口。浅拷贝对基本类型进行值拷贝,对引用类型拷贝引用;深拷贝对基本类型进行值拷贝,对引用类型对象不但拷贝对象的引用还拷贝对象的相关属性和方法。两者不同在于深拷贝创建了一个新的对象。
(必备知识:Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。
String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!)
4.1浅拷贝
①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。②对于数据类型是引用数据类型的成员变量,浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。实际上指向同一个实例。在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
4.1.1 通过拷贝构造方法实现浅拷贝
类的构造方法参数为该类的对象
class Person{
//两个属性值:分别代表值传递和引用传递
private Age age;
private String name;
public Person(Age age,String name) {
this.age=age;
this.name=name;
}
//拷贝构造方法
public Person(Person p) {
this.name=p.name;
this.age=p.age;
}
//拷贝构造方法
public Person(Person p) {
this.name=p.name;
this.age=p.age;
}
//如果在拷贝构造方法中,对引用数据类型变量逐一开辟新的内存空间,创建新的对象,也可以实现深拷贝。而对于一般的拷贝构造,则一定是浅拷贝。
}
4.1.2通过重写clone()方法进行浅拷贝
在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。
//重写Object类的clone方法
public Object clone() {
Object obj=null;
//调用Object类的clone方法,返回一个Object实例
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
使用方法:
Age a=new Age(20);
Student stu1=new Student("摇头耶稣",a,175);
//通过调用重写后的clone方法进行浅拷贝
Student stu2=(Student)stu1.clone();
4.2深拷贝
4.2.1通过重写clone方法来实现深拷贝
假设一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。那么每一层的每个对象都进行浅拷贝=深拷贝
/*
* 创建年龄类
*/
class Age implements Cloneable{
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age=age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return this.age+"";
}
//重写Object的clone方法
public Object clone() {
Object obj=null;
try {
obj=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
/*
* 创建学生类
*/
class Student implements Cloneable{
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name,Age a,int length) {
this.name=name;
this.aage=a;
this.length=length;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage=age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length=length;
}
public String toString() {
return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();
}
//重写Object类的clone方法
public Object clone() {
Object obj=null;
//调用Object类的clone方法——浅拷贝
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//调用Age类的clone方法进行深拷贝
//先将obj转化为学生类实例
Student stu=(Student)obj;
//学生类实例的Age对象属性,调用其clone方法进行拷贝
stu.aage=(Age)stu.getaAge().clone();
return obj;
}
}
4.2.2通过对象序列化实现深拷贝
将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝
Age a=new Age(20);
Student stu1=new Student("摇头耶稣",a,175);
//通过序列化方法实现深拷贝
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(stu1);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Student stu2=(Student)ois.readObject();
如果某个属性被transient修饰,那么该属性就无法被拷贝了
(
1、transient关键字只能修饰变量,而不能修饰方法和类。本地变量是不能被transient关键字修饰的。
(1、成员变量(实例变量,属性)
2、本地变量(局部变量)
3、类变量(静态属性))
2、被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
3、一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化。
总结:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。
但是:Java序列化提供两种方式。
一种是实现Serializable接口
另一种是实现Exteranlizable接口。 需要重写writeExternal和readExternal方法,它的效率比Serializable高一些,并且可以决定哪些属性需要序列化(即使是transient修饰的,即这种方式下该关键字是失效的),但是对大量对象,或者重复对象,则效率低。
)
5、toString 返回类名@哈希码的 16 进制
6、notify 唤醒当前对象监视器的任一个线程。
7、notifyAll 唤醒当前对象监视器上的所有线程。
7.1对象监视器——管程锁
任意线程对Object的访问,首先要先获得Object的监视器。如果获取失败了,线程进入同步队列,线程状态变为BLOCKED。当访问Object的线程(获得了所的线程)释放了锁,则该释放操作唤醒在同步队列中的线程,使其重新尝试对监视器的获取。即同一时刻是能有一个线程获得到由synchronized所保护对象的监视器
三个监视器方法:
/**
* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,能够使得当前线程进入等待队列,导致当前线程等待WAITING状态。该wait方法应由当前的持有对象监视器来调用。
* 想要唤醒该等待线程,也应该由其他持有相同的对象监视器的线程调用notify方法。即要求其他线程和需要被唤醒的等待线程持有相同的对象监视器。或者其他线程调用该线程的interrupt()方法, 该线程抛出InterruptedExce ption 异常返回。
* Wait的两个重载方法:
*/
public final void wait()
/**
* 限时等待timeout milliseconds(毫秒)
* @param timeout
*/
public final native void wait(long timeout)
/**
* 如果一个线程调用共享对象的该方法挂起后, 没有在指定的timeout ms 时间内被其他线程调用该共享变量的notify() 或者notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。
* 如果将timeout 设置为0 则和wait 方法效果一样,因为在wait 方法内部就是调用了wait(O)。需要注意的是,如果在调用该函数时,传递了一个负的timeout 则会抛出IllegalArgumentException 异常。
*
* @param timeout
* @param nanos
*/
public final void wait(long timeout, int nanos)
/**
* 线程调用对象监视器的notify()方法,唤醒一条在该对象监视器上调用wait系列方法后被挂起的线程。如果有多条线程在同一等待队列中等待,只会选择唤醒其中一个线程,并且该选择是任意性的。
*/
public final void notify();
/**
* 调用notifyAll方法就可以唤醒对应对象监视器的等待队列当中所有的等待的线程。只对在该方法被调用之前等待的线程有效。
*/
public final void notifyAll();
7.2condition监视器
Object的监视器方法与Condition接口的对比
JDK1.5出现的Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。
Lock 替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。并用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。Condition的强大之处在于它可以为多个线程间建立不同的Condition,使用synchronized/wait()只有一个阻塞队列,notifyAll会唤起所有阻塞队列下的线程,而使用lock/condition,可以实现多个阻塞队列,signalAll只会唤起某个阻塞队列下的阻塞线程。
调用Lock.lock()获取锁,调用lock.newCondition()获取condition对象。调用condition.await()挂起线程。
8、wait
1、暂停线程的执行;2、三个不同参数方法(等待多少毫秒;额外等待多少毫秒;一直等待)3、与 Thread.sleep(long time) 相比,sleep 使当前线程休眠一段时间,并没有释放该对象的锁,wait 释放了锁。
8.1wait方法和sleep方法的区别:
8.1.1
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
8.1.2
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法或者notifyAll()后本线程才进入对象锁定池准备,获取对象锁进入运行状态。(也可以给他指定一个时间,使该线程自动醒来)
8.1.3
wait()方法必须放在同步控制方法和同步代码块中使用,sleep()方法则可以放在任何地方使用。sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用他的interrupt(),产生InterruptedException。由于sleep不会释放锁标志,容易导致死锁问题的发生,因此一般情况下,推荐使用wait()方法。
9、finalize–垃圾回收机器GC
(Garbage Collection)
对象被垃圾回收器回收时执行的方法,当一个堆空间中的对象没有被栈空间变量指向的时候,这个对象会等待被java回收。
9.1主要有一下特点:
9.1.1当对象不再被程序所使用的时候,垃圾回收器将会将其回收
9.1.2垃圾回收是在后台运行的,我们无法命令垃圾回收器马上回收资源,但是我们可以告诉他可以尽快回收资源(System.gc()和Runtime.getRuntime().gc())
9.1.3垃圾回收器在回收某个对象的时候,首先会调用该对象的finalize()方法
9.1.4GC主要针对堆内存
9.1.5单例模式的缺点