精华内容
下载资源
问答
  • java打印一个对象的实际类型
    千次阅读
    2021-03-15 16:19:43

    JAVA对象

    java对象是都是Object类的实例,都可直接调用该类中定义的方法,这些方法提供了处理java对象的通用方法。

    > > 6.2.1打印对象和toString方法

    先看下面程序:

    程序清单:codes/06/6-2/PrintObjectjava

    public classPrintObject{public static voidmain(String args[]){

    Person p= new Person("帅气de猪八戒");

    System.out.println(p.toString());//跟p.toString()效果一样

    }

    }classPerson {privateString name;publicPerson(String name){this.name =name;

    }public voidinfo(){

    System.out.println("此人名为:" +name);

    }

    }

    输出:

    String.Person@c17164

    当读者运行上面程序时,可能看到不同的输出结果:@符号后的6位16进制数字可能发生改变

    但这个输出结果是怎么来的呢?System.out.println方法只能在控制台输出字符串.当使用该方法输出

    Person对象时,实际上输出的是Person对象的toString()方法的返回值,也就是说,下面代码结果完全一样:

    System.out.println(p);

    System.out.println(p.toString());

    toString方法是Object类里的一个实例方法,所有Java类都是Object类的子类,因此所有Java对

    象都具有toString方法。

    不仅如此,所有Java对象都可以和字符串进行连接运算,当Java对象和字符串进行连接运算时,

    系统自动调用Java对象toString方法的返回值和字符串进行连接运算,即下面两行代码的结果也完全

    相同:

    String pStr = p + "";

    String pstr = p.toString() + "";

    toString方法是一个非常特殊的方法,它是一个“自我描述”方法,该方法通常用于实现这样一个

    功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象

    具有的状态信息。

    更多相关内容
  • java中直接打印对象

    千次阅读 2021-02-28 15:02:28
    java中直接打印对象,会调用对象.toString()方法。如果没有重写toString()方法会输出"类名+@+hasCode"值,hasCode是一个十六进制数//没有重写toString()方法的类class Person{private String name;//构造方法public ...

    java中直接打印对象,会调用对象.toString()方法。如果没有重写toString()方法会输出"类名+@+hasCode"值,hasCode是一个十六进制数

    //没有重写toString()方法的类

    class Person{

    private String name;

    //构造方法

    public Person(String name){

    this.name = name;

    }

    }

    //重写了toString()方法的类

    class Animal{

    private String kind = "cat";

    //重写的toString()方法 返回值为String

    public String toString(){

    return kind;

    }

    }

    //测试类

    public class PrintObject {

    public static void main(String[] args){

    //测试重写了toString()方法的类

    Person person = new Person("gxf");

    System.out.println(person);

    //测试没有重写toString()方法的类

    Animal animal = new Animal();

    System.out.println(animal);

    }

    }

    上面代码打印的结果是

    Person@5a20d10a

    cat

    浅谈Java中的对象和引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

    【译】Java中的对象序列化

    前言 好久没翻译simple java了,睡前来一篇. 译文链接: http://www.programcreek.com/2014/01/java-serialization/ 什么是对象序列化 在 ...

    如何使用java中的对象

    使用java中的对象,分2步: 1.创建一个对象: 2.使用对象的属性和方法. 见下面的示例代码: package com.imooc; //1.定义一个类 public class Telphone ...

    Java中String对象的不可变性

    首先看一个程序 package reverse; public class Reverse { public static void main(String[] args) { String c1=n ...

    JAVA中JavaBean对象之间拷贝的方法

    JAVA中JavaBean对象之间的拷贝通常是用get/set方法,但如果你有两个属性相同的JavaBean或有大部分属性相同的JavaBean,有个更简便的方法,他们之间的拷贝可以通过copyPro ...

    Java中的对象池技术

    java中的对象池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间.对象池其实也就是一个内存 ...

    Java中计算对象的大小

    一.计算对象大小的方法 Java中如何计算对象的大小呢,找到了4种方法: 1.java.lang.instrument.Instrumentation的getObjectSize方法: 2.BTrac ...

    【学习笔记】Java中生成对象的5中方法

    概述:本文介绍以下java五种创建对象的方式: 1.用new语句创建对象,这是最常用的创建对象的方式. 2.使用Class类的newInstance方法 3.运用反射手段,调用java.lang.re ...

    浅谈Java中的对象和对象引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

    随机推荐

    【Android】友盟的自动更新组件

    前言 又好又专业的服务能帮开发者省很多时间.一开始做项目也准备自己来统计数据.自己做自动更新,随着使用友盟服务的时间增加,渐渐放弃了这种想法,转而研究如何更充分的使用,这里分享一下使用自动更新组件的心 ...

    oracle系列--第五篇 PLSQL连接本地的Oracle数据库

    这篇blog主要是针对新手,我也是个新手:) 我们把oracle成功的安装在了我们的计算机上面,那我们如何才能将PLSQL developer连 接到本地的oracle呢? 首先,我们必须有下面步准备 ...

    Entity Framework学习 - 4.Code First升级数据库

    1.在nuget控制台中执行:Enable-Migrations 2.将出现的configuation.cs文件中的AutomaticMigrationsEnabled属性改为true 3.在nuge ...

    GIS初学者

    学习编程一直以来没有什么好的思路,感觉就是学了忘,忘了再重复,效率特别低下.大概是从大三第一学期才有意识的转向c#的学习,来熟悉VS2010平台,在这之前我都不知道自己是怎么学习的. 大一第二学期开的 ...

    HOG特征

    HOG(Histogram of gradient)统计图像局部区域的梯度方向信息来作为该局部图像区域的表征.HOG特征具有以下几个特点: (1)不具有旋转不变性(较大的方向变化),实际应用中不变性是 ...

    实现一个JavaScript模块化加载器

    对任何程序,都存在一个规模的问题,起初我们使用函数来组织不同的模块,但是随着应用规模的不断变大,简单的重构函数并不能顺利的解决问题.尤其对JavaScript程序而言,模块化有助于解决我们在前端开发中 ...

    SQL servere 范式、事务

    一.数据库范式: 1.构造数据库必须遵循一定的规则.在关系数据库中,这种规则就是范式. 范式是符合某一种级别的关系模式的集合.数据库中的关系必须满足一定的要求,即满足不同的范式. 满足最低要求的范式是 ...

    iOS打上线包或者测试包详细流程

    首先登陆苹果官方开发者账号:http://developer.apple.com 进入到如下界面 之后进入如下界面:点击第二项创建证书 整个流程如下图4步 然后按照如下图片进行配置即可 接下来去创建C ...

    Linux学习之CentOS(二)--初识linux的一些常用命令(基础命令)

    初次学习linux系统,首先也得会一些linux的基本命令.至少要先学会开启和关闭系统吧!我称为 基础命令! linux命令是对Linux系统进行管理的命令.对于Linux系统来说,无论是中央处理器. ...

    windows端运行.sh脚本

    在复现lightheadrcnn时,碰到这么一句    bash make.sh 下载cygwin安装 在cygwin安装过程中,在选择安装包的时候找到Devel 再在Devel里面找到make,勾选 ...

    展开全文
  • java如何获取一个对象的大小

    千次阅读 2019-09-25 13:21:51
    但当一个系统的内存有限,或者某块程序代码允许使用的内存大小有限制,又或者设计一个缓存机制,当存储对象内存超过固定值之后写入磁盘做持久化等等,总之我们希望像写C一样,java也能有方法实现获取对象占用内存的...

    When---什么时候需要知道对象的内存大小

    在内存足够用的情况下我们是不需要考虑java中一个对象所占内存大小的。但当一个系统的内存有限,或者某块程序代码允许使用的内存大小有限制,又或者设计一个缓存机制,当存储对象内存超过固定值之后写入磁盘做持久化等等,总之我们希望像写C一样,java也能有方法实现获取对象占用内存的大小。

     

    How---java怎样获取对象所占内存大小

    在回答这个问题之前,我们需要先了解java的基础数据类型所占内存大小。

    数据类型所占空间(byte)
    byte    1
    short2
    int4
    long8
    float4
    double8
    char  2
    boolean1

    当然,java作为一种面向对象的语言,更多的情况需要考虑对象的内存布局,java对于对象所占内存大小需要分两种情况考虑:

    对象类型内存布局构成
    一般非数组对象8个字节对象头(mark) + 4/8字节对象指针 + 数据区 + padding内存对齐(按照8的倍数对齐)
    数组对象                                 8个字节对象头(mark) + 4/8字节对象指针 + 4字节数组长度 + 数据区 + padding内存对齐(按照8的倍数对齐)

    可以看到数组类型对象和普通对象的区别仅在于4字节数组长度的存储区间。而对象指针究竟是4字节还是8字节要看是否开启指针压缩。Oracle JDK从6 update 23开始在64位系统上会默认开启压缩指针
    http://rednaxelafx.iteye.com/blog/1010079。如果要强行关闭指针压缩使用-XX:-UseCompressedOops,强行启用指针压缩使用: -XX:+UseCompressedOops。 

    接下来我们来举例来看实现java获取对象所占内存大小的方法:

    假设我们有一个类的定义如下:

     1     private static class ObjectA {  
     2         String str;   // 4  
     3         int i1;       // 4  
     4         byte b1;      // 1  
     5         byte b2;      // 1  
     6         int i2;       // 4   
     7         ObjectB obj;  //4  
     8         byte b3;      // 1  
     9     }  
    10 
    11     private static class ObjectB {  
    12         
    13     }

    如果我们直接按照上面掌握的java对象内存布局进行计算,则有:

    Size(ObjectA) = Size(对象头(_mark)) + size(oop指针) + size(数据区)
    Size(ObjectA) = 8 + 4 + 4(String) + 4(int) + 1(byte) + 1(byte) + 2(padding) + 4(int) + 4(ObjectB指针) + 1(byte) + 7(padding)
    Size(ObjectA) = 40

    我们直接通过两种获取java对象内存占用大小的方式来验证我们的计算是否正确。

     

    方式1---通过Instrumentation来获取

    这种方法得到的是Shallow Size,即遇到引用时,只计算引用的长度,不计算所引用的对象的实际大小。如果要计算所引用对象的实际大小,必须通过递归的方式去计算。
    查看jdk的代码发现,Instrumentation是一个接口,本来我想的是可以直接定义一个类实现该接口。但是看了下该接口里面的方法瞬间傻眼。根本没法去重写。
    calm down,原来Instrumentation接口的实例需要使用代理的方式来获得。具体步骤如下:

    1. 编写 premain 函数

    编写一个 Java 类,包含如下两个方法当中的任何一个
    public static void premain(String agentArgs, Instrumentation inst); [1]
    public static void premain(String agentArgs); [2]
    其中,[1] 的优先级比 [2] 高,将会被优先执行([1] 和 [2] 同时存在时,[2] 被忽略)。
    在这个 premain 函数中,开发者可以进行对类的各种操作。
    agentArgs 是 premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
    Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等。

     1 package instrumentation.test;
     2 
     3 import java.lang.instrument.Instrumentation;
     4 
     5 public class ObjectShallowSize {
     6     private static Instrumentation inst;  
     7 
     8     public static void premain(String agentArgs, Instrumentation instP){  
     9         inst = instP;  
    10     }  
    11 
    12     public static long sizeOf(Object obj){  
    13         return inst.getObjectSize(obj);  
    14     }  
    15 }

    2. 在META-INF下面新建MANIFEST.MF文件,并且指定

    Manifest-Version: 1.0
    Premain-Class: instrumentation.test.ObjectShallowSize

    3. 通过eclipse->export->jar->next->next,然后选中定制的 MANIFEST.MF 文件,进行jar打包。

    4. 给需要使用ObjectShallowSize的工程引入该jar包,并通过代码测试对象所占内存大小:

     1 System.out.println(ObjectShallowSize.sizeOf(new ObjectA())); // 32 
    5. 在运行调用ObjectShallowSize.sizeof的类的工程中加上刚打的jar包依赖,同时eclipse里面run configuration,在VM arguments中添加(标红部分为jar包的绝对路径):

    -javaagent:E:/software/instrumentation-sizeof.jar

     

    方式2---使用Unsafe来获取

    关于Unsafe的使用,后面我会专门开一个专题来详细讲述,这里暂时让我们来见识下Unsafe的神奇之处。

     1     private final static Unsafe UNSAFE;
     2     // 只能通过反射获取Unsafe对象的实例
     3     static {
     4         try {
     5             UNSAFE = (Unsafe) Unsafe.class.getDeclaredField("theUnsafe").get(null);
     6         } catch (Exception e) {
     7             throw new Error();
     8         }
     9     }
    10 
    11     Field[] fields = ObjectA.class.getDeclaredFields();
    12     for (Field field : fields) {
    13       System.out.println(field.getName() + "---offSet:" + UNSAFE.objectFieldOffset(field));
    14     }

    输出结果为:

    str---offSet:24
    i1---offSet:12
    b1---offSet:20
    b2---offSet:21
    i2---offSet:16
    obj---offSet:28
    b3---offSet:22

    我们同样可以算得对象实际占用的内存大小:

    Size(ObjectA) = Size(对象头(_mark)) + size(oop指针) + size(排序后数据区)  =  8 + 4 + (28+4-12)  =  32.

     

    我们再回过头来,看我们在通过代码获取对象所占内存大小之前的预估值40。比我们实际算出来的值多了8个字节。通过Unsafe打印的详细信息,我们不难想到这其实是由hotspot创建对象时的排序决定的:

    HotSpot创建的对象的字段会先按照给定顺序排列,默认的顺序为:从长到短排列,引用排最后: long/double –> int/float –> short/char –> byte/boolean –> Reference。

    所以我们重新计算对象所占内存大小得:

    Size(ObjectA) = Size(对象头(_mark)) + size(oop指针) + size(排序后数据区)
    Size(ObjectA) = 8 + 4 + 4(int) + 4(int) + byte(1) + byte(1) + 2(padding) + 4(String) + 4(ObjectB指针)
    Size(ObjectA) = 32

    与上面计算结果一致。

     

    Deeper---深入分析的一个例子:

    以下代码摘抄自原链接:

      1 package test;
      2 
      3 import java.lang.reflect.Array;
      4 import java.lang.reflect.Field;
      5 import java.lang.reflect.Modifier;
      6 import java.util.ArrayList;
      7 import java.util.Arrays;
      8 import java.util.Collections;
      9 import java.util.HashMap;
     10 import java.util.IdentityHashMap;
     11 import java.util.List;
     12 import java.util.Map;
     13 
     14 import sun.misc.Unsafe;
     15 
     16 public class ClassIntrospector {
     17 
     18     private static final Unsafe unsafe;  
     19     /** Size of any Object reference */  
     20     private static final int objectRefSize;  
     21     static {  
     22         try {  
     23             Field field = Unsafe.class.getDeclaredField("theUnsafe");  
     24             field.setAccessible(true);  
     25             unsafe = (Unsafe) field.get(null);  
     26 
     27             // 可以通过Object[]数组得到oop指针究竟是压缩后的4个字节还是未压缩的8个字节
     28             objectRefSize = unsafe.arrayIndexScale(Object[].class);  
     29         } catch (Exception e) {  
     30             throw new RuntimeException(e);  
     31         }  
     32     }  
     33 
     34     /** Sizes of all primitive values */  
     35     private static final Map<Class<?>, Integer> primitiveSizes;  
     36 
     37     static {  
     38         primitiveSizes = new HashMap<Class<?>, Integer>(10);  
     39         primitiveSizes.put(byte.class, 1);  
     40         primitiveSizes.put(char.class, 2);  
     41         primitiveSizes.put(int.class, 4);  
     42         primitiveSizes.put(long.class, 8);  
     43         primitiveSizes.put(float.class, 4);  
     44         primitiveSizes.put(double.class, 8);  
     45         primitiveSizes.put(boolean.class, 1);  
     46     }  
     47 
     48     /** 
     49      * Get object information for any Java object. Do not pass primitives to 
     50      * this method because they will boxed and the information you will get will 
     51      * be related to a boxed version of your value. 
     52      *  
     53      * @param obj 
     54      *            Object to introspect 
     55      * @return Object info 
     56      * @throws IllegalAccessException 
     57      */  
     58     public ObjectInfo introspect(final Object obj)  
     59             throws IllegalAccessException {  
     60         try {  
     61             return introspect(obj, null);  
     62         } finally { // clean visited cache before returning in order to make  
     63                     // this object reusable  
     64             m_visited.clear();  
     65         }  
     66     }  
     67 
     68     // we need to keep track of already visited objects in order to support  
     69     // cycles in the object graphs  
     70     private IdentityHashMap<Object, Boolean> m_visited = new IdentityHashMap<Object, Boolean>(  
     71             100);  
     72 
     73     private ObjectInfo introspect(final Object obj, final Field fld)  
     74             throws IllegalAccessException {  
     75         // use Field type only if the field contains null. In this case we will  
     76         // at least know what's expected to be  
     77         // stored in this field. Otherwise, if a field has interface type, we  
     78         // won't see what's really stored in it.  
     79         // Besides, we should be careful about primitives, because they are  
     80         // passed as boxed values in this method  
     81         // (first arg is object) - for them we should still rely on the field  
     82         // type.  
     83         boolean isPrimitive = fld != null && fld.getType().isPrimitive();  
     84         boolean isRecursive = false; // will be set to true if we have already  
     85                                         // seen this object  
     86         if (!isPrimitive) {  
     87             if (m_visited.containsKey(obj))  
     88                 isRecursive = true;  
     89             m_visited.put(obj, true);  
     90         }  
     91 
     92         final Class<?> type = (fld == null || (obj != null && !isPrimitive)) ? obj  
     93                 .getClass() : fld.getType();  
     94         int arraySize = 0;  
     95         int baseOffset = 0;  
     96         int indexScale = 0;  
     97         if (type.isArray() && obj != null) {  
     98             baseOffset = unsafe.arrayBaseOffset(type);  
     99             indexScale = unsafe.arrayIndexScale(type);  
    100             arraySize = baseOffset + indexScale * Array.getLength(obj);  
    101         }  
    102 
    103         final ObjectInfo root;  
    104         if (fld == null) {  
    105             root = new ObjectInfo("", type.getCanonicalName(), getContents(obj,  
    106                     type), 0, getShallowSize(type), arraySize, baseOffset,  
    107                     indexScale);  
    108         } else {  
    109             final int offset = (int) unsafe.objectFieldOffset(fld);  
    110             root = new ObjectInfo(fld.getName(), type.getCanonicalName(),  
    111                     getContents(obj, type), offset, getShallowSize(type),  
    112                     arraySize, baseOffset, indexScale);  
    113         }  
    114 
    115         if (!isRecursive && obj != null) {  
    116             if (isObjectArray(type)) {  
    117                 // introspect object arrays  
    118                 final Object[] ar = (Object[]) obj;  
    119                 for (final Object item : ar)  
    120                     if (item != null)  
    121                         root.addChild(introspect(item, null));  
    122             } else {  
    123                 for (final Field field : getAllFields(type)) {  
    124                     if ((field.getModifiers() & Modifier.STATIC) != 0) {  
    125                         continue;  
    126                     }  
    127                     field.setAccessible(true);  
    128                     root.addChild(introspect(field.get(obj), field));  
    129                 }  
    130             }  
    131         }  
    132 
    133         root.sort(); // sort by offset  
    134         return root;  
    135     }  
    136 
    137     // get all fields for this class, including all superclasses fields  
    138     private static List<Field> getAllFields(final Class<?> type) {  
    139         if (type.isPrimitive())  
    140             return Collections.emptyList();  
    141         Class<?> cur = type;  
    142         final List<Field> res = new ArrayList<Field>(10);  
    143         while (true) {  
    144             Collections.addAll(res, cur.getDeclaredFields());  
    145             if (cur == Object.class)  
    146                 break;  
    147             cur = cur.getSuperclass();  
    148         }  
    149         return res;  
    150     }  
    151 
    152     // check if it is an array of objects. I suspect there must be a more  
    153     // API-friendly way to make this check.  
    154     private static boolean isObjectArray(final Class<?> type) {  
    155         if (!type.isArray())  
    156             return false;  
    157         if (type == byte[].class || type == boolean[].class  
    158                 || type == char[].class || type == short[].class  
    159                 || type == int[].class || type == long[].class  
    160                 || type == float[].class || type == double[].class)  
    161             return false;  
    162         return true;  
    163     }  
    164 
    165     // advanced toString logic  
    166     private static String getContents(final Object val, final Class<?> type) {  
    167         if (val == null)  
    168             return "null";  
    169         if (type.isArray()) {  
    170             if (type == byte[].class)  
    171                 return Arrays.toString((byte[]) val);  
    172             else if (type == boolean[].class)  
    173                 return Arrays.toString((boolean[]) val);  
    174             else if (type == char[].class)  
    175                 return Arrays.toString((char[]) val);  
    176             else if (type == short[].class)  
    177                 return Arrays.toString((short[]) val);  
    178             else if (type == int[].class)  
    179                 return Arrays.toString((int[]) val);  
    180             else if (type == long[].class)  
    181                 return Arrays.toString((long[]) val);  
    182             else if (type == float[].class)  
    183                 return Arrays.toString((float[]) val);  
    184             else if (type == double[].class)  
    185                 return Arrays.toString((double[]) val);  
    186             else  
    187                 return Arrays.toString((Object[]) val);  
    188         }  
    189         return val.toString();  
    190     }  
    191 
    192     // obtain a shallow size of a field of given class (primitive or object  
    193     // reference size)  
    194     private static int getShallowSize(final Class<?> type) {  
    195         if (type.isPrimitive()) {  
    196             final Integer res = primitiveSizes.get(type);  
    197             return res != null ? res : 0;  
    198         } else  
    199             return objectRefSize;  
    200     }  
    201 }

    下面来分析ObjectC所占内存大小:

     1 package test;
     2 
     3 public class IntrospectorTest {
     4     private static class ObjectC {  
     5         ObjectD[] array = new ObjectD[2];  
     6     }  
     7 
     8     private static class ObjectD {  
     9         int value;  
    10     }  
    11     
    12     public static void main(String[] args) throws IllegalAccessException {
    13         final ClassIntrospector ci = new ClassIntrospector();  
    14         ObjectInfo res = ci.introspect(new ObjectC());  
    15         System.out.println( res.getDeepSize() );  
    16     }
    17 }

    代码输出为:40。

    下面我们来分析下ObjectC的内存布局:

    ShallowSize(ObjectC) = Size(对象头) + Size(oop指针) + Size(内容) + Size(对齐)

    ShallowSize(ObjectC) = 8 + 4 + 4(ObjectD[]数组引用) =16

    Size(ObjectD[] arr) = 8(数组对象头) + 4(oop指针) + 4(数组长度) + 4(ObjectD[0]对象引用) + 4(ObjectD[1]对象引用) = 24

    因为arr没有具体赋值,所以此时具体引用的为null,不占用内存。否则需要再次计算ObjectD的内存最后想加。

    所以总共得到:Size(ObjectC) = ShallowSize(ObjectC) + Size(ObjectD[] arr)  = 40。

     

    参考链接:

    http://blog.csdn.net/antony9118/article/details/54317637
    https://www.cnblogs.com/licheng/p/6576644.html

    转载于:https://www.cnblogs.com/Kidezyq/p/8030098.html

    展开全文
  • RTTI(Run-Time Type Identification) 运行时类型识别,最先出现在C++里,引入这机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象实际类型Java中的RRTI则是源于《Thinking in ...

    一、引言

    什么是RTTI

    RTTI(Run-Time Type Identification) 运行时类型识别,最先出现在C++里,引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。

    Java中的RRTI则是源于《Thinking in Java》一书,可以在程序运行时发现和使用类型信息。这使得我们从只能在编译期执行面向类型的操作中解脱出来。主要有两种方式:一是传统的RRTI,它假定我们在编译期已知道了所有类型(如new对象时必须定义好类型);但并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程),这就需要第二种方式:反射机制,它允许我们在运行时发现和使用类型的信息。

    为什么需要RTTI

    按字面意思,我们在某些时候需要知道类的信息并使用它。具体看下面这个例子,我们定义了一个基类Animal及通用行为eat(),派生出的具体类有Dog、Cat和Brid,见下图。
    在这里插入图片描述
    在面向对象编程中基本目的是:让代码只操纵对基类(这里是Animal)的引用。为了实现这点,通常我们在创建具体类的对象时,都将其向上转型为对应的父类,然后在其余的代码中都使用这个父类,这是为了方便以后扩展,例如你可以向下面这样编码:

    abstract class Animal {
    	void eat() {
    		System.out.println("吃");
    	}
    }
    
    class Dog extends Animal {
    	void eat() {
    		System.out.println("狗吃骨头");
    	}
    }
    
    class Cat extends Animal {
    	void eat() {
    		System.out.println("猫吃小鱼");
    	}
    }
    
    class Brid extends Animal {
    	void eat() {
    		System.out.println("鸟吃虫子");
    	}
    }
    
    public class AnimalTest {
    	public static void main(String[] args) {
    		List<Animal> aList = Arrays.asList(
    			new Dog(), new Cat(), new Brid() 
    		);
    		for (Animal animal : aList) {
    			animal.eat();
    		}
    	}
    }
    

    Output:

    狗吃骨头
    猫吃小鱼
    鸟吃虫子
    

    在这个例子中,创建 Dog、Cat、Brid对象放入List <Animal>中时会向上转型,同时也丢失了它们的具体类型,对于List而言,它们都是Animal类的对象。这时,假如我们需要知道某个泛化引用的确切类型,该怎么办呢?例如,我们只有骨头来喂食动物,猫和鸟并不吃骨头,怎么才能知道这个Animal对象是狗呢?或者我们想要拍动物在天空的照片,这时必须筛选出鸟类,因为其它动物不会飞。

    使用RTTI,通过它可以查询某个Animal引用所指向的对象的确切类型,然后选择你需要的或者剔除你不要的。

    二、深入理解Class对象

    Class类的概念

    想要理解RTTI在Java中的工作原理,首先得知道类型信息在运行时是如何表示的。Java用Class类来表示运行时的类型信息,首先必须明确,Class类跟Java API中定义的String、Integer等类以及我们自己定义的类是一样的,是一个实实在在的类,只不过名字特殊点,在JDK的java.lang包中。

    那么Class类到底有什么作用呢?是什么的抽象,其实例又表示什么呢?

    对于我们自己定义的类,我们用类来抽象现实中的某些事物,比如我们定义名为Dog的类来抽象现实中的狗,然后可以实例化这个类,用这些实例来表示一条黑狗、一条黄狗、我家的狗、你家的狗等等。我们还用Cat类来抽象现实中的猫,用Brid类来抽象现实中的鸟。那么,Dog、Cat、Brid这三个类之间有没有共同特征了,可不可以对这三个类进行抽象呢?

    当然可以,我们都知道所有的class都是Object的子类,都有类名,有hashcode,可以判断类型属于class、interface、enum还是annotation。另外可以定义一些方法,比如获取某个方法、获取类型名等等。这样就封装了一个表示类型的类 — Class,用来提取这些类的一些共同特征,表示对这些类(或接口)的抽象。而Dog、Cat、Brid这三个类就分别是Class类的对象。也就是说,每个类都有一个Class对象,即每当我们编写并且编译一个新类,就会产生一个对应的Class对象,被保存在一个同名.class文件(编译后的字节码文件)里。

    下面我们来分析一下Class类的源码:

    //前一个Class表示这是一个类的声明,第二个Class是类的名称,
      <T>表示这是一个泛型类,并实现了四种接口。
    public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement {
      
        //定义了三个静态变量
        private static final int ANNOTATION= 0x00002000;
        private static final int ENUM      = 0x00004000;
        private static final int SYNTHETIC = 0x00001000;
    
        //定义了一个名为registerNatives()的本地方法,并在静态块中调用:
        private static native void registerNatives();
        static {
            registerNatives();
        }
    
        // 私有构造函数,只能由JVM调用,创建该类实例
        private Class(ClassLoader loader) {
            classLoader = loader;
        }
        
        /*如果Class对象是一个Java类,返回class full_classname,即class 包名.类名;
          比如上面例子的List,返回的就是class java.util.List;
          如果是接口,将class改成interface;
          如果是void类型,则返回void;
          如果是基本类型,返回基本类型。*/
    	public String toString() {
            return (isInterface() ?"interface " : (isPrimitive() ? "" : "class")) + getName();
        }
    

    注意:Class类的构造器是private的,这意味着我们无法用new关键字得到一个Class对象。为了生成一个类的Class对象,必须通过运行Java虚拟机(JVM)中的类加载器子系统。

    从上我们可以总结出:

    • Class类的作用是运行时提供或获得某个对象的类型信息;
    • Class类也是类的一种,只是名字和class关键字高度相似;
    • Class类的对象表示你创建的类的类型信息,比如你创建一个Dog类,那么,Java编译后就会创建一个包含Dog类型信息的Class对象;
    • Class类只有私有构造函数,因此对应的Class对象不能像普通类一样,以 new 操作符的方式创建,只能通过JVM加载。
    • 一个class类有且只有一个相对应的Class对象(无论创建多少个实例对象,在JVM中都只有一个Class对象),如下图所示:
      在这里插入图片描述

    Class对象的加载

    那么JVM是如何加载这个类的?

    当程序创建第一个对类的静态成员的引用时,JVM中的类加载器子系统会将类对应的Class对象加载到JVM中。这个证明构造器也是类的静态方法,尽管构造器前并没有用static关键字修饰。因此,当我们使用new操作符创建一个类的实例对象时,也会被当作对类的静态成员的引用。

    可以看出,Java一门动态加载的语言,Java中的类在需要时才会被加载。也就是说,我们编写出的Java程序,在它们开始运行之前并非被完全加载到内存的,其各个部分是在需要时才加载。因此,在我们需要用到某个类时,类加载器首先检查这个类的Class对象是否已被加载,如果没有加载,默认的类加载器就会先根据类名查找.class文件。在这个类的字节码文件被加载时,它们要接受验证,以确保其没有被破坏、并且不包含不良Java代码(这是java众多安全检测机制中的一个),检测通过后Class对象就被载入内存了,可以被用来创建这个类的所有实例对象。下图表示了一个类加载的过程:
    在这里插入图片描述

    • 第一阶段(加载):类加载器根据类名找到此类的.Class文件,并把这个文件包含的字节码加载到内存中,生成Class对象。

    • 第二阶段(链接):又分为三个步骤,分别是:

      (1) 验证阶段:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

      (2) 准备阶段:正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,这些变量所使用的内存都将在方法区中进行分配。

      (3)解析阶段:虚拟机将常量池内的符号引用替换为直接引用的过程。

    • 第三阶段(初始化):类中静态属性和初始化赋值,以及静态块的执行。

    Class对象的获取方式

    Java主要提供了三种方式来获取一个实例对象对应的Class对象:

    1. Class.forName():

    这个方法是Class类的一个static成员方法。Class对象和其他对象一样,我们可以获取并操作它的引用,forName()就是取得Class对象的引用的一种方法,该方法允许我们无需通过持有该类的实例对象引用而去获取Class对象。

    	try {
    	  		//"com.yang"是包名
    	 		Class c1 = Class.forName("com.yang.Dog");
    		} 	catch (ClassNotFoundException e) {
    		         e.printStackTrace();
     	   }
    

    注意:如果Class.forName()没有找到你要加载的类,会抛出ClassNotFoundException异常。因此,在调用forName()方法时,需要向上面一样,给出一个ClassNotFoundException异常捕获。

    1. getClass():

    通过new一个对象,用这个对象调用getClass()方法来获取Class的引用。这个方法属于根类Object的一部分,将返回表示该对象类型的Class引用。

    	Dog dog = new Dog();
    	Class c2 = dog.getClass();
    
    1. 类字面常量:
    	//字面常量的方式获取Class对象
    	Class c3 = Dog.class;
    

    用类字面常量的方式来生成Class对象的引用,在编译时会受到检查(因此不需要置于try语句块中来捕获异常),相对前面两种方式显得更简单、更安全。

    采用字面常量的方式不仅可以应用于普通的类,也可以应用在接口、数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析。另外,因为基本数据类型有着对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下:

    Class对象TYPE字段
    boolean.classBoolean.TYPE
    char.classCharacter.TYPE
    byte.classByte.TYPE
    short.classShort.TYPE
    int.classInteger.TYPE
    long.classLong.TYPE
    float.classFloat.TYPE
    double.classDouble.TYPE
    void.classVoid.TYPE

    一般建议使用.class的形式,这样可以保持与普通类一致。

    上面我们分析了类加载的三个步骤,初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常熟静态域进行首次引用时才执行,而使用“.class”来创建Class对象时,触发的是加载阶段,并不会触发最后阶段类的初始化,下面引用《Java编程思想》中的例子来说明这点:

    import java.util.*;
    
    class Initable {
      //静态成员常量,编译期就确定值
      static final int staticFinal = 47;
      //静态成员常量,运行期才确定值
      static final int staticFinal2 =
        ClassInitialization.rand.nextInt(1000);
      //静态初始化块
      static {
        System.out.println("Initializing Initable");
      }
    }
    
    class Initable2 {
      //静态成员变量
      static int staticNonFinal = 147;
       //静态初始化块
      static {
        System.out.println("Initializing Initable2");
      }
    }
    
    class Initable3 {
      //静态成员变量
      static int staticNonFinal = 74;
      //静态初始化块
      static {
        System.out.println("Initializing Initable3");
      }
    }
    
    public class ClassInitialization {
      public static Random rand = new Random(47);
      public static void main(String[] args) throws Exception {
        //字面常量方法获取Class对象
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");
        //不触发类初始化
        System.out.println(Initable.staticFinal);
        //会触发类初始化
        System.out.println(Initable.staticFinal2);
        //会触发类初始化
        System.out.println(Initable2.staticNonFinal);
        //forName()方法获取Class对象
        Class initable3 = Class.forName("Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
      }
    }
    

    Output:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74
    

    根据运行结果可以看出:

    1. 初始化有效地实现了尽可能的“惰性”,通过.Class语法来获取Initable类的Class对象时没有触发初始化,通过Class.forName()方式来获取Initable3类的Class对象时就进行了初始化。
    2. 调用Initable.staticFinal变量时,只输出了“47”,并没有打印“Initializing Initable”,说明也没有触发初始化,这是因为staticFinal值是“编译期静态常量”,在编译时其值“47”存储到了NotInitialization常量池中,对常量Initable.staticFinal的引用实际都被转化为NotInitialization类对自身常量池的引用了。如果将一个域只设置为static或final,如Initable2.staticNonFinal,还是会触发初始化。

    (类型转换和反射将后续更新)

    展开全文
  • java中类和对象的概念

    千次阅读 2021-02-26 09:37:31
    (这部分对于java来说是一个非常重要的知识)今天心情很好,来学习一下java中的类和对象Java是什么?Java是一门面向对象的编程语言(OOP),想要理解Java,首先要理解类(Class)和对象(Object)这两个概念。Java中的类...
  • java一个对象占用多少字节?

    千次阅读 2019-06-26 12:32:41
    最近在读《深入理解Java虚拟机》,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 1、JAVA 对象布局 在 HotSpot虚拟机中,对象在内存中的...
  • 如何在java打印所有枚举值?

    千次阅读 2021-03-09 06:41:35
    现在,打印你只是使用的东西像所有枚举值:// Note: enum name changed to comply with Java naming conventionsfor (GeneralInformation info : EnumSet.allOf(GeneralInformation.class)) {Syste...
  • 深入理解Java类型信息(Class对象)与反射机制

    万次阅读 多人点赞 2017-05-01 23:19:19
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解
  • 一个Java对象和Hashmap对象占用多大内存

    万次阅读 多人点赞 2019-12-03 21:47:35
    1、JAVA 对象布局1.1对象头(Header):1.2实例数据(Instance Data)1.3对齐填充(Padding)2、Java数据类型有哪些2.1基础数据类型内存占用如下2.2引用类型 内存占用如下:2.3字段重排序3、验证3.1有一个Fruit类...
  • java打印byte数组

    千次阅读 2021-03-21 09:25:57
    JAVA字节转换 字节(Byte)简称:"B",字位(Bit)简称“b“, 1 字位(Bit)=1 二进制数, 1 字节=8 字位=8 二进制数, 1 汉字=2 两字节=......“Java is Good!; InputStream input = new ByteArrayInputStream...
  • java面向对象

    万次阅读 多人点赞 2018-08-21 16:51:59
    包括面向对象概念、类与对象的关系、封装、构造函数、this关键字、static关键字、单例设计模式、继承、多态、内部类、异常、包等java基础知识。 1、面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是...
  • Java打印对象和toString方法

    万次阅读 2017-04-12 09:56:04
    1、打印对象和toString方法:toString方法是系统将会输出该对象的“自我描述”信息,用以告诉外界对象具有的状态信息。 2、Object 类提供的toString方法总是返回该对象实现类的类名 + @ +hashCode值。   二 打印...
  • Java对象写入文件中

    千次阅读 2021-03-06 03:50:20
    对象的序列化指将一个Java对象写入IO流中,与此对应的反是,对象的反序列化则指从IO流中恢复该Java对象。如果需要需要让某个对象支持序列化机制,则必须让它的类是可序列化的(serializable)。为了让某个...
  • 深入理解Java类型信息(Class对象)

    千次阅读 多人点赞 2019-05-19 19:36:01
    RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象类型和类的信息。这里分两种: ...
  • Java的基本类型

    万次阅读 2021-12-17 21:25:42
    文章目录前言Java 虚拟机的 boolean...Java 则不同,它引进了八基本类型,来支持数值计算。Java 这么做的原因主要是工程上的考虑,因为使用基本类型能够在执行效率以及内存使用两方面提升软件性能。 今天,我们就来了
  • 深入理解Class对象 ...认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thin...
  • java中常见类型的转换以及方法总结

    千次阅读 2022-03-26 13:19:18
    char是表示的是字符,定义的时候用单引号,只能存储一个字符。例如; char=‘d’. String表示的是字符串,定义的时候用双引号,可以存储一个或者多个字符。例如:String=“we are neuer”。 char是基本数据类型,而...
  • java打印日志的几种方式

    千次阅读 2021-01-25 11:06:33
    、简单介绍五种 最简单的方式,就是system.println.out(error) ,这样直接在控制台打印消息了; Java.util.logging ; 在JDK 1.4 版本之后,提供了日志的API ,可以往文件中写日志了; log4j , 最强大的记录日志的...
  • java输出变量数据类型At SitePoint we’re always looking to expand the range of topics we cover. Lately, we’ve set our sights on exploring the world of Java. If you’re a strong Java developer who ...
  • Java 泛型,你了解类型擦除吗?

    万次阅读 多人点赞 2017-08-05 22:32:18
    泛型是 Java 中一个很小巧的概念,但同时也是一个很容易让人迷惑的知识点,它让人迷惑的地方在于它的许多表现有点违反直觉。文章开始的地方,先给大家奉上一道经典的测试题。List&lt;String&gt; l1 = new ...
  • 说实话真的挺绕的。。。 /* ... * 一个子类对象类型可以向上转换成... * 一些数据成员和方法父类中未必会有,但如果是一个父类对象引用实际引用的是一个子类对象, * 则可以强制将这个父类对象引用转换成子类对
  • Java-面向对象

    千次阅读 2020-02-14 16:14:48
    软件生命周期: 软件生命周期: 软件的产生直到报废的整个过程. 软件生命周期内有:问题定义, 可行性分析, 总体描述, 系统设计,编码, 调试和测试, 验收与运行, 维护升级到废弃等...需求分析阶段是一个很重要的阶段,...
  • Java中的对象是什么?

    千次阅读 2020-07-14 12:45:19
    Java种面向对象的编程语言,它将世界视为具有属性和行为的对象的集合。 Java的面向对象版本非常简单,它是该语言几乎所有内容的基础。 因为它对Java非常重要,所以我将对幕后内容进行一些解释,以帮助任何不熟悉...
  • Java中将对象转换成String的三种方法

    万次阅读 2022-01-27 15:35:37
    因此在强转之前最好做个类型的检查,就能有效避免异常 Object o = 2; if (o instanceof String) { System.out.println((String) o); }else { System.out.println("此类型无法强转"); } //输出结果为:此类型无法强转...
  • 在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...
  • Java中常见异常打印输出方式

    千次阅读 2021-03-08 07:57:56
    Java中异常打印输出的常见方法总结前言Java异常是在Java应用中的警报器,在出现异常的情况下,可以帮助我们程序猿们快速定位问题的类型以及位置。但是一般在我们的项目中,由于经验阅历等多方面的原因,依然有若干的...
  • java对象作为参数传递给一个方法,到底是值传递,还是引用传递? pdd:所谓java只有按值传递:基本类型 值传递;引用类型,地址值传递,所以叫值传递。  当主函数的变量,对象(主本)传递到副函数时,传递的...
  • java打印对象和toString方法

    万次阅读 2014-11-17 23:01:50
     toString()方法是一个非常特殊的方法,是一个“自我描述”方法,该方法通常用于实现当程序员直接打印对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。  Object类提供的...
  • Java面向对象 - 类与对象

    千次阅读 多人点赞 2021-01-16 10:07:03
    第1关:什么是类,如何创建类 ...人是一个 “类”,小明就是人的 “对象” ,女生/男生是一个类,你的女朋友/男朋友就是一个对象,这个对象的属性有:名字,性别,年龄;行为有:吃饭、睡觉、学习等。 在Jav
  • JVM通过加装、连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用。类型的生命周期如下图所示: 装载和连接必须在初始化之前就要完成。 类初始化阶段,主要是为类变量赋予正确的初始值。这里的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 225,189
精华内容 90,075
关键字:

java打印一个对象的实际类型