栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

Java基础汇总(五)——Class类、Object类、异常

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java基础汇总(五)——Class类、Object类、异常

一、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;
Class obj2=int.class;
obj1=double.class;
//obj2=double.class; 这一行代码是非法的,obj2不能改指向别的类

然而,有个灵活的用法,使得你可以用Class的对象指向基类的任何子类。
Class 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 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

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1040991.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号