首先我来给你举个例子:一个抽象的乐器类abstract Instrument它里面有一个抽象的方法abstract void play();(提醒一下,没有方法体)。
然后有其它的几个不同的乐器(比如Wind、Percussion、Stringed)来继承了前面的乐器抽象类,你应该知道,继承一般要重写基类的方法并实现,所以Wind、Percussion、Stringed都需要去重写play方法。
比如Wind重写后是这样的public void paly() {Systemoutprintln("这是Wind的play()")}。Percussion重写后是这样的public void paly() {Systemoutprintln("这是Percussion的play()")}。Stringed重写后是这样的public void paly() {Systemoutprintln("这是Stringed的play()")}。
你实例化类Wind、Percussion、Stringed的对象,它们的引用分别为W,P,S。
此时你这样调用Wplay();Pplay();Splay();
出来的结果分别是:这是Wind的play()这是Percussion的play()这是Stringed的play()
你看,这样就实现了多态。接口和抽象类有点类似,实现接口,然后去实现接口的中定义的方法。
首先,我们先写一段大家可能不怎么写或者认为不可能的代码:一个类中,几个类型都是private类型,没有public方法,如何对这些属性进行读写 *** 作,看似不可能哦,为什么,这违背了面向对象的封装,其实在必要的时候,留一道后门可以使得语言的生产力更加强大,对象的序列化不会因为没有public方法就无法保存成功吧,OK,我们简单写段代码开个头,逐步引入到怎么样去测试对象的大小,一下代码非常简单,相信不用我解释什么:
import javalangreflectField;
class NodeTest1 {
private int a = 13;
private int b = 21;
}
public class Test001 {
public static void main(String []args) {
NodeTest1 node = new NodeTest1();
Field []fields = NodeTest1classgetDeclaredFields();
for(Field field : fields) {
fieldsetAccessible(true);
try {
int i = fieldgetInt(node);
fieldsetInt(node, i 2);
Systemoutprintln(fieldgetInt(node));
} catch (IllegalArgumentException e) {
eprintStackTrace();
} catch (IllegalAccessException e) {
eprintStackTrace();
}
}
}
}
代码最基本的意思就是:实例化一个NodeTest1这个类的实例,然后取出两个属性,分别乘以2,然后再输出,相信大家会认为这怎么可能,NodeTest1根本没有public方法,代码就在这里,将代码拷贝回去运行下就OK了,OK,现在不说这些了,运行结果为:
26
42
为什么可以取到,是每个属性都留了一道门,主要是为了自己或者外部接入的方便,相信看代码自己仔细的朋友,应该知道门就在:fieldsetAccessible(true);代表这个域的访问被打开,好比是一道后门打开了,呵呵,上面的方法如果不设置这个,就直接报错。
看似和对象大小没啥关系,不过这只是抛砖引玉,因为我们首先要拿到对象的属性,才能知道对象的大小,对象如果没有提供public方法我们也要知道它有哪些属性,所以我们后面多半会用到这段类似的代码哦!
对象测量大小的方法关键为java提供的(15过后才有):javalanginstrumentInstrumentation,它提供了丰富的对结构的等各方面的跟踪和对象大小的测量的API(本文只阐述对象大小的测量方法),于是乎我心喜了,不过比较恶心的是它是实例化类:suninstrumentIntrumentationImpl是sun开头的,这个鬼东西有点不好搞,翻开源码构造方法是private类型,没有任何getInstance的方法,写这个类干嘛?看来这个只能被JVM自己给初始化了,那么怎么将它自己初始化的东西取出来用呢,唯一能想到的就是agent代理,那么我们先抛开代理,首先来写一个简单的对象测量方法:
步骤1:(先创建一个用于测试对象大小的处理类)
import javalanginstrumentInstrumentation;
public class MySizeOf {
private static Instrumentation inst;
/
这个方法必须写,在agent调用时会被启用
/
public static void premain(String agentArgs, Instrumentation instP) {
inst = instP;
}
/
直接计算当前对象占用空间大小,包括:当前类及超类的基本类型实例字段大小
引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小
但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小
用来测量java对象的大小(这里先理解这个大小是正确的,后面再深化)
/
public static long sizeOf(Object o) {
if(inst == null) {
throw new IllegalStateException("Can not access instrumentation environment\n" +
"Please check if jar file containing SizeOfAgent class is \n" +
"specified in the java's \"-javaagent\" command line argument");
}
return instgetObjectSize(o);
}
}
步骤2:上面我们写好了agent的代码,此时我们要将上面这个类编译后打包为一个jar文件,并且在其包内部的META-INF/MANIFESTMF文件中增加一行:Premain-Class: MySizeOf代表执行代理的全名,这里的类名称是没有package的,如果你有package,那么就写全名,我们这里假设打包完的jar包名称为agentjar(打包过程这里简单阐述,就不细说了),OK,继续向下走:
步骤3:编写测试类,测试类中写:
public class TestSize {
public static void main(String []args) {
Systemoutprintln(MySizeOfsizeOf(new Integer(1)));
Systemoutprintln(MySizeOfsizeOf(new String("a")));
Systemoutprintln(MySizeOfsizeOf(new char[1]));
}
}
下一步准备运行,运行前我们准备初步估算下结果是什么,目前我是在32bit模式下运行jvm(注意,不同位数的JVM参数设置不一样,对象大小也不一样大)。
(1) 首先看Integer对象,在32bit模式下,class区域占用4byte,mark区域占用最少4byte,所以最少8byte头部,Integer内部有一个int类型的数据,占4个byte,所以此时为8+4=12,java默认要求按照8byte对象对其,所以对其到16byte,所以我们理论结果第一个应该是16;
(2) 再看String,长度为1,String对象内部本身有4个非静态属性(静态属性我们不计算空间,因为所有对象都是共享一块空间的),4个非静态属性中,有offset、count、hash为int类型,分别占用4个byte,char value[]为一个指针,指针的大小在bit模式下或64bit开启指针压缩下默认为4byte,所以属性占用了16byte,String本身有8byte头部,所以占用了24byte;其次,一个String包含了子对象char数组,数组对象和普通对象的区别是需要用一个字段来保存数组的长度,所以头部变成12byte,java中一个char采用UTF-16编码,占用2个byte,所以是14byte,对其到16byte,24+16=40byte;
(3) 第三个在第二个基础上已经分析,就是16byte大小;
也就是理论结果是:16、40、16;
步骤4:现在开始运行代码:运行代码前需要保证classpath把刚才的agentjar包含进去:
D:>javac TestSizejava
D:>java -javaagent:agentjar TestSize
16
24
16
第一个和第三个结果一致了,不过奇怪了,第二个怎么是24,不是40,怎么和理论结果偏差这么大,再回到理论结果中,有一个24曾经出现过,24是指String而不包含char数组的空间大小,那么这么算还真是对的,可见,java默认提供的方法只能测量对象当前的大小,如果要测量这个对象实际的大小(也就是包含了子对象,那么就需要自己写算法来计算了,最简单的方法就是递归,不过递归一项是我不喜欢用的,无意中在一个地方看到有人用栈写了一个代码写得还不错,自己稍微改了下,就是下面这种了)。
import javalanginstrumentInstrumentation;
import javalangreflectArray;
import javalangreflectField;
import javalangreflectModifier;
import javautilIdentityHashMap;
import javautilMap;
import javautilStack;
public class MySizeOf {
static Instrumentation inst;
public static void premain(String agentArgs, Instrumentation instP) {
inst = instP;
}
public static long sizeOf(Object o) {
if(inst == null) {
throw new IllegalStateException("Can not access instrumentation environment\n" +
"Please check if jar file containing SizeOfAgent class is \n" +
"specified in the java's \"-javaagent\" command line argument");
}
return instgetObjectSize(o);
}
/
递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
/
public static long fullSizeOf(Object obj) {//深入检索对象,并计算大小
Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
Stack<Object> stack = new Stack<Object>();
long result = internalSizeOf(obj, stack, visited);
while (!stackisEmpty()) {//通过栈进行遍历
result += internalSizeOf(stackpop(), stack, visited);
}
visitedclear();
return result;
}
//判定哪些是需要跳过的
private static boolean skipObject(Object obj, Map<Object, Object> visited) {
if (obj instanceof String) {
if (obj == ((String) obj)intern()) {
return true;
}
}
return (obj == null) || visitedcontainsKey(obj);
}
private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
if (skipObject(obj, visited)) {//跳过常量池对象、跳过已经访问过的对象
return 0;
}
visitedput(obj, null);//将当前对象放入栈中
long result = 0;
result += sizeOf(obj);
Class <>clazz = objgetClass();
if (clazzisArray()) {//如果数组
if(clazzgetName()length() != 2) {// skip primitive type array
int length = ArraygetLength(obj);
for (int i = 0; i < length; i++) {
stackadd(Arrayget(obj, i));
}
}
return result;
}
return getNodeSize(clazz , result , obj , stack);
}
//这个方法获取非数组对象自身的大小,并且可以向父类进行向上搜索
private static long getNodeSize(Class <>clazz , long result , Object obj , Stack<Object> stack) {
while (clazz != null) {
Field[] fields = clazzgetDeclaredFields();
for (Field field : fields) {
if (!ModifierisStatic(fieldgetModifiers())) {//这里抛开静态属性
if (fieldgetType()isPrimitive()) {//这里抛开基本关键字(因为基本关键字在调用java默认提供的方法就已经计算过了)
continue;
}else {
fieldsetAccessible(true);
try {
Object objectToAdd = fieldget(obj);
if (objectToAdd != null) {
stackadd(objectToAdd);//将对象放入栈中,一遍d出后继续检索
}
} catch (IllegalAccessException ex) {
assert false;
}
}
}
}
clazz = clazzgetSuperclass();//找父类class,直到没有父类
}
return result;
}
}
Eclipse中测试正常。最简单的一个做法:
你可以把上述所有代码保存在一个Interfacejava文件中,然后编译这个类、运行这个类即可。
出现找到主类的错误,绝大部分是自己动手编译、运行时因为package相关问题出的小错造成的,也可能是CLASSPATH环境变量中忘记配置 "" 造成的。一般不是代码的问题。
以上就是关于java中继承于多态 抽象类和接口的例子全部的内容,包括:java中继承于多态 抽象类和接口的例子、如何准确计算Java对象的大小、各位路过的Java高手,求帮忙!编译通过,但是提示找不到或无法加载主类Interface等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)