一、Class类定义
Java在运行时,系统一直对所有的对象进行所谓的运行时类型信息,即所谓的RTTI
- 运行时类型信息记录了每个对象所属的类
- 虚拟机运行时使用类型信息选准正确方法去执行
- Class类保存这些类型信息,即Class类的对象内容是你创建的类的类型信息(如创建一个class类shapes,那么Java会生成一个内容是shapes的Class类的对象。此Class为类对象(也为类的一种)而非类class)
- Class类的对象不能像普通类一样,以 new shapes() 的方式创建,因为这个类没有public构造函数(构造函数为private)
- Class类的对象只能在装载class 类时,由JVM自动创建或通过类装载器中的 defineClass 方法生成
- Class类封装一个对象和接口运行时的状态
Class类的作用是运行时提供或获得某个对象的类型信息,并用于反射
二、Class类对象的创建
getclass()函数:
getclass()是用来取得该类实例化的对象的引用,这个引用指向的是Class类的对象
例1:
public class A{} public class B{} A a1 = new A(); A a2 = new A(); B b1 = new B(); Class obj1 = a1.getClass(); Class obj2 = a2.getClass(); Class obj3 = b1.getClass();
该类实例化的对象都会有个字段记录该对象所属类在CLass类的对象的所在位置,如图所示:
Class类的forName函数:
例2:
public class shapes{} Class obj= Class.forName("shapes");
类字面常量:
例3:
Class obj=String.class; Class obj1=int.class;
使用类字面常量生成Class类对象时,不会使JVM自动加载该类,而getclass()函数和forname函数会使得JVM初始化该类
三、newInstance() 函数:使用Class类的对象来生成目标类的实例
例1:
Class c = Class.forName("Super"); //通过Class类的newInstance()方法创建对象 Super sup = (Super)c.newInstance(); System.out.println(sup.supPublic());
- 该函数并不能直接生成目标类的实例,只能生成object类的实例
- 因为有类型限制(如Super),所以使用泛化Class语法的对象引用不能指向别的类
例2:
Class obj1=int.class; Classobj2=int.class; obj1=double.class; //obj2=double.class; 这一行代码是非法的,obj2不能改指向别的类 然而,有个灵活的用法,使得你可以用Class的对象指向基类的任何子类。 Class extends Number> obj=int.class; obj=Number.class; obj=double.class; 因此,以下语法生成的Class对象可以指向任何类。 Class> obj=int.class; obj=double.class; obj=shapes.class; 最后一个奇怪的用法是,当你使用这种泛型语法来构建你手头有的一个Class类的对象的基类对象时,必须采用以下的特殊语法 public class shapes{} class round extends shapes{} Class rclass=round.class; Class super round> sclass= rclass.getSuperClass(); //Class sclass=rclass.getSuperClass(); 我们明知道,round的基类就是shapes,但是却不能直接声明 Class < shapes >,必须使用特殊语法
四、Object类
- Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起
- Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入
- Object类没有定义属性,一共有13个方法,13个方法之中并不是所有方法都是子类可访问的,一共有9个方法是所有子类都继承了的
类构造器public Object()
大部分情况下,Java中通过形如 new A(args..)形式创建一个属于该类型的对象,其中A即是类名,A(args..)即此类定义中相对应的构造函数。通过此种形式创建的对象都是通过类中的构造函数完成。为体现此特性,Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一个无参数的构造函数,作为所有类的基类,Object类自然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。当然,并不是所有的类都是通过此种方式去构建,也自然的,并不是所有的类构造函数都是public
registerNatives()方法(private static native void registerNatives();)
- registerNatives函数前面有native关键字修饰。Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用
- native修饰表示操作系统需要提供此方法,Java本身需要使用
- registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦
例1:registerNatives()源码
private static native void registerNatives(); static { registerNatives(); }
静态代码块保证了修饰符为private的registerNatives(),且没有执行的代码,发挥作用
Clone()方法(protected native Object clone() throwsCloneNotSupportedException)
- clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的
- clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间
- clone()方法被protected修饰,在同一个包内或者不同包的子类可以访问
- 不同包的子类是指当两个类不在同一个包中时,继承自父类的子类内部且调用者为子类的引用时才能访问父类用protected修饰的成员(属性/方法)。 在子类内部,调用者为父类的引用时并不能访问此protected修饰的成员!(super关键字除外))
例1:调用者为父类的引用(报错:Object对于person类来说为父类)
package com.corn.objectsummary; import com.corn.Person; public class ObjectTest { public static void main(String[] args) { Object o1 = new Object(); // The method clone() from the type Object is not visible Object clone = o1.clone(); } }
例2:调用者为子类的引用(正常编译)
public class clone方法 { public static void main(String[] args) { } public void test1() { User user = new User(); // User copy = user.clone(); } public void test2() { User user = new User(); // User copy = (User)user.clone(); } }
例2中代码会抛出"java.lang.CloneNotSupportedException",因为Java中的语法规定clone()的正确调用是需要实现Cloneable接口,如果没有实现Cloneable接口,并且子类直接调用Object类的clone()方法,则会抛出CloneNotSupportedException异常
深拷贝和浅拷贝的区别
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址
- 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。 深拷贝需要用Clonable接口覆盖并实现clone深拷贝方法
例1:
static class Body implements Cloneable{ public Head head; public Body() {} public Body(Head head) {this.head = head;} @Override protected Object clone() throws CloneNotSupportedException { Body newBody = (Body) super.clone(); newBody.head = (Head) head.clone(); return newBody; } } static class Head implements Cloneable{ public Face face; public Head() {} public Head(Face face){this.face = face;} @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } static class Face{} public static void main(String[] args) throws CloneNotSupportedException { Body body = new Body(new Head(new Face())); Body body1 = (Body) body.clone(); System.out.println("body == body1 : " + (body == body1) ); System.out.println("body.head == body1.head : " + (body.head == body1.head)); System.out.println("body.head.face == body1.head.face : " + (body.head.face == body1.head.face)); /*body == body1 : false body.head == body1.head : false body.head.face == body1.head.face : true /* }
上述代码内存代码如图所示:
上述代码对Body对象来说算是深拷贝,因为对Body对象内所引用的其他对象(目前只有Head)都进行了拷贝,也就是说两个独立的Body对象内的head引用已经指向了独立的两个Head对象。但是,这对于两个Head对象来说,他们指向了同一个Face对象,这就说明,两个Body对象还是有一定的联系,并没有完全的独立。这应该说是一种不彻底的深拷贝。
equals()方法(public boolean equals(Object obj))
Object类中原生的equals()方法内部与“==”具有相同的含义
例1:
public boolean equals(Object obj) { return (this == obj); }
但是在实际应用中,equals()方法基本都是重写的。其和“==”的主要区别为equals判断是对象的内容是否完全相同,==表示的是变量值完成相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址)
- 重写equals()方法后,必须重写hasCode()方法
hashCode()方法(public native int hashCode())
- hashCode()方法返回一个整形数值,表示该对象的哈希码值
hashCode()具有如下约定:
- 在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致
- 如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等
- 反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等
hashCode()的作用:
- 增强哈希表的性能(以集合类Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现))
wait() notify() notifAll()
主要用于java多线程之间的协作(wait(...) / notify() | notifyAll()一般情况下都是配套使用)
-
wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法
-
wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量
-
wait(...)方法调用后当前线程将立即阻塞,且适当其所持有的同步代码块中的锁,直到被唤醒或超时或打断后且重新获取到锁后才能继续执行
notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程 - notify()/notifyAll()方法调用后,其所在线程不会立即释放所持有的锁,直到其所在同步代码块中的代码执行完毕,此时释放锁,因此,如果其同步代码块后还有代码,其执行则依赖于JVM的线程调度
例1:生产者消费者模型
public class wait和notify { //volatile保证线程可见性 volatile static int flag = 1; //object作为锁对象,用于线程使用wait和notify方法 volatile static Object o = new Object(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { //wait和notify只能在同步代码块内使用 synchronized (o) { while (true) { if (flag == 0) { try { Thread.sleep(2000); System.out.println("thread1 wait"); //释放锁,线程挂起进入object的等待队列,后续代码运行 o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread1 run"); System.out.println("notify t2"); flag = 0; //通知等待队列的一个线程获取锁 o.notify(); } } } }).start(); //解释同上 new Thread(new Runnable() { @Override public void run() { while (true) { synchronized (o) { if (flag == 1) { try { Thread.sleep(2000); System.out.println("thread2 wait"); o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread2 run"); System.out.println("notify t1"); flag = 1; o.notify(); } } } }).start(); } //输出结果是 // thread1 run // notify t2 // thread1 wait // thread2 run // notify t1 // thread2 wait // thread1 run // notify t2 //不断循环 }
finalize()方法(protected void finalize())
- 与Java垃圾回收机制有关
- Java中每一个对象都将具有finalize这种行为
- finalize()将在JVM准备对 对象所占用的内存空间进行垃圾回收前被调用
- finalize()不是由我们主动去调用的
Object类9个方法总结
1.clone方法
- 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
2.getclass方法
final方法
3.toString方法
- 该方法用得比较多,一般子类都有覆盖
4.finalize方法
- 该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用
5.equals方法
- 该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法
6.hashCode方法
- 该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到
- 一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价
7.wait方法
- wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回
- 调用该方法后当前线程进入睡眠状态,直到以下事件发生
- 其他线程调用了该对象的notify方法
- 其他线程调用了该对象的notifyAll方法
- 其他线程调用了interrupt中断该线程
- 时间间隔到了
- 此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常
8.notify方法
- 该方法唤醒在该对象上等待的某个线程
9.notifyAll方法
- 该方法唤醒在该对象上等待的所有线程
Class类和Object类的关系
- Object类和Class类没有直接的关系
- Object类是一切java类的父类,对于普通的java类,即便不声明,也是默认继承了Object类
- Class类是用于java反射机制的,一切java类,都有一个对应的Class对象,他是一个final类
- Class 类的实例表示正在运行的 Java 应用程序中的类和接口
转一个知乎很有趣的问题 先有Class还是先有Object? - 知乎
Java的对象模型中:
1 所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例。2 所有的类都最终继承自Object类,Class是类,那么Class也继承自Object。
3 这就像是先有鸡还是先有蛋的问题,请问实际中JVM是怎么处理的?
这个问题中,第1个假设是错的:java.lang.Object是一个Java类,但并不是java.lang.Class的一个实例。后者只是一个用于描述Java类与接口的、用于支持反射操作的类型。这点上Java跟其它一些更纯粹的面向对象语言(例如Python和Ruby)不同。
而第2个假设是对的:java.lang.Class是java.lang.Object的派生类,前者继承自后者。虽然第1个假设不对,但“鸡蛋问题”仍然存在:在一个已经启动完毕、可以使用的Java对象系统里,必须要有一个java.lang.Class实例对应java.lang.Object这个类;而java.lang.Class是java.lang.Object的派生类,按“一般思维”前者应该要在后者完成初始化之后才可以初始化…
事实是:这些相互依赖的核心类型完全可以在“混沌”中一口气都初始化好,然后对象系统的状态才叫做完成了“bootstrap”,后面就可以按照Java对象系统的一般规则去运行。JVM、JavaScript、Python、Ruby等的运行时都有这样的bootstrap过程。
在“混沌”(boostrap过程)里,JVM可以为对象系统中最重要的一些核心类型先分配好内存空间,让它们进入[已分配空间]但[尚未完全初始化]状态。此时这些对象虽然已经分配了空间,但因为状态还不完整所以尚不可使用。
然后,通过这些分配好的空间把这些核心类型之间的引用关系串好。到此为止所有动作都由JVM完成,尚未执行任何Java字节码。然后这些核心类型就进入了[完全初始化]状态,对象系统就可以开始自我运行下去,也就是可以开始执行Java字节码来进一步完成Java系统的初始化了。
五、异常
具体见文章:
https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/10%E3%80%81Java%E5%BC%82%E5%B8%B8.md
六、 参考文章:
https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/9%E3%80%81Java%E4%B8%AD%E7%9A%84Class%E7%B1%BB%E5%92%8CObject%E7%B1%BB.md
https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/10%E3%80%81Java%E5%BC%82%E5%B8%B8.md