菜鸟笔记 -- Chapter 6.3 对象
6.3 对象
Java是一门面向对象的程序设计语言,对象是由类抽象出来的,所有的问题都是通过对象来处理的,对象可以操作类的属性和方法解决相应的问题,所以了解对象的产生、操作和生存周期对学习Java语言是十分必要的。我们以后在探讨JVM的时候再进行对象生命周期的探讨,这里不做介绍。下面详细介绍对象在Java语言中的使用。
6.3.1 对象的创建
在上面我们介绍过对象是同一类事物抽象出来的一个特例,通过这个特例来处理这类事物出现的问题,在Java语言中通过new操作符来创建对象。在讲解构造方法的时候介绍过每实例化一个对象的时候就会自动调用一次构造方法,实质上这个过程就是创建对象的过程。准确地说,可以在Java语言中使用new操作符调用构造方法创建对象。语法格式如下:
ClassName name = new ClassName(); ClassName name = new ClassName(“构造参数A”,“构造参数B”,.....);
name对象被创建出来时,name对象就是一个对象的引用,这个引用在内存中为对象分配了存储空间,可以在构造方法中初始化成员变量,创建对象时,自动调用构造方法,也就是说Java语言中创建对象和初始化可以捆绑在一起进行.每个对象都是相互独立的,在内存中占据独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象变成了垃圾,由Java虚拟机自带的回收机制进行处理,处理后不能再被使用.Java中对象的创建除了new还有很多方法,我会在后边总结完反射作为一个Question补充;这里不多讲解:感兴趣参考下面链接:https://www.cnblogs.com/baizhanshi/p/5896092.html
6.3.2 访问对象的属性和方法
我们的类中定义的成员分为类成员和实例成员,上文讲的static修饰的属于类成员类成员直接通过类名调用,而实例成员则通过对象.成员名字来调用。下面我们来看一段代码,然后分析:
package cn.yourick.object; import javax.jws.Oneway; public class ObjectDemo { String name = "Youric"; static String address = "北京"; public static void main(String[] args) { ObjectDemo objectDemo1 = new ObjectDemo(); ObjectDemo objectDemo2 = new ObjectDemo(); System.out.println(objectDemo1.name+"--"+objectDemo1.address); objectDemo1.name = "YouricYou"; objectDemo1.address = "神都"; System.out.println(objectDemo1.name+"--"+objectDemo1.address); System.out.println(objectDemo2.name+"--"+objectDemo2.address); } }
我们发现虽然不同的两个对象调用同一个成员变量,结果却不相同,这是因为实例成员由实例自己携带,它属于实例自身。但类成员就不同了,类成员归属类所有,也就是可以被所有的实例共享,所以一个实例改变了类成员的话,那么所以实例指向的都改变了,通过图分析如下:
如上图所示,实例成员会由实例成员自己携带数据,所以操作也是基于自身进行操作的,不影响类本身和其它实例,而类成员则是基于类进行操作,改变了类本身的数据,自然也影响了其它实例;
6.3.3 对象的引用
我们在代码中创建对象,经过编译后会生成一个符号引用,在类加载时符号引用转换为直接引用,即在内存中分配空间,此时我们真正的表示符实质上是一个指向该内存的引用,所以我们一定要搞明白引用并不是对象,而是指向了对象的内存地址,但我们为了方便理解通常简单的说objectDemo1是ObjectDemo的对象.
如下表达式:A a1 = new A; 它代表A是类,a1是引用,a1不是对象,new A才是对象,a1引用指向new A这个对象。(即new A在内存中有一个内存空间,而a1只是一个指向,指向这个内存地址)。在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个 对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一 个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是 一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。再如:A a2;它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;再如:a2 = a1;它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。综上所述,可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。在后面的左右都是引用(a2 = a1)的“=”语句时,左右的引用同时指向了右边引用所指向的对象。再所谓实例,其实就是对象的同义词。
6.3.4 对象的比较
我们在开发中经常需要对对象进行比较,判断对象是否为同一个对象,判断对象有两种方式,分别为”==”和equals()方法。实质上这两种方式有着本质的区别,下面我们通过代码来演示和分析:
package cn.yourick.object; public class ObjectEquals { public static void main(String[] args) { test1(); System.out.println("---------------------"); test2(); System.out.println("---------------------"); test3(); } //比较一 public static void test1(){ String name1 = "Youric"; String name2 = "Youric"; String name3 = name1; String name4 = "youric"; System.out.println(name1.equals(name2)+"--"+(name1==name2)); System.out.println(name1.equals(name3)+"--"+(name1==name3)); System.out.println(name3.equals(name4)+"--"+(name3==name4)); } //比较二 public static void test2(){ String name1 = new String("Youric"); String name2 = new String("Youric"); String name3 = name1; String name4 = new String("youric"); System.out.println(name1.equals(name2)+"--"+(name1==name2)); System.out.println(name1.equals(name3)+"--"+(name1==name3)); System.out.println(name3.equals(name4)+"--"+(name3==name4)); } //比较三 public static void test3(){ ObjectEquals objectEquals1 = new ObjectEquals(); ObjectEquals objectEquals2 = new ObjectEquals(); ObjectEquals objectEquals3 = objectEquals1; System.out.println(objectEquals1.equals(objectEquals2)+"--"+(objectEquals1==objectEquals2)); System.out.println(objectEquals1.equals(objectEquals3)+"--"+(objectEquals1==objectEquals3)); } }
从上面的结果看,我们可能很莫名其妙,我们首先针对equals()和”==”分别说一下,”==”是一个判断运算符,它要求的是两个比较对象的hashcode返回要求一样,而每个对象都有自己唯一的hashcode返回,所以通常我们也管hashcode返回的作为地址,实际他和地址并不一样,只是因为它唯一所以我们用来形容他和对象地址的关系.下面是Object的hashcode()和搜集的hashcode()底层代码:http://blog.csdn.net/bluetjs/article/details/52610414
public native int hashCode();//可以看出这是一个本地方法,也就是C语言写就的
聊完”==”,聊一下equals(),我们知道Object是所有类的父类,那么也就意味着不管是我们自己写的类,还是JDK提供的类,它们中要么是继承了Object中的方法,要么是重写了Object中的方法.而equals()正是Object中的方法,源码如下:
public boolean equals(Object obj) { return (this == obj); }
通过这段代码,我们知道如果不重写equals(),那么equals()判断就只能是和”==”的结果一样了,而上面我们看到String的equals(),那么String的equals()又是如何的呢?
private final char value[]; public String(String original) { this.value = original.value; this.hash = original.hash; } public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
从上面我们看到,String完成了对equals()的重写,在equals()中有两个判读,第一个判断和”==”是一样的,如果==那么直接返回true,否则进行下一个判断,判断这个对象是String的对象吗?是的话,那么获取调用该equals()的字符串的长度,然后判断该字符串的长度和要比较的字符串对象的长度,如果相等的话,那么下面就是将字符串转换为char数组,然后挨个比较字符.所以字符串的equals()最低要求是内容相同即可,不要求地址一定相同.
通过上面的讲述,我们就明白上面各种结果的缘由了,下面我们讲几个对象的hashcode打印出来如下:
package cn.yourick.object; public class ObjectEquals { public static void main(String[] args) { test1(); System.out.println("---------------------"); test2(); System.out.println("---------------------"); test3(); } //比较一 public static void test1(){ String name1 = "Youric"; String name2 = "Youric"; String name3 = name1; String name4 = "youric"; System.out.println(name1.equals(name2)+"--"+(name1==name2)); System.out.println(name1.equals(name3)+"--"+(name1==name3)); System.out.println(name3.equals(name4)+"--"+(name3==name4)); System.out.println("name1:"+name1.hashCode()); System.out.println("name2:"+name2.hashCode()); System.out.println("name3:"+name3.hashCode()); System.out.println("name4:"+name4.hashCode()); } //比较二 public static void test2(){ String name1 = new String("Youric"); String name2 = new String("Youric"); String name3 = name1; String name4 = new String("youric"); System.out.println(name1.equals(name2)+"--"+(name1==name2)); System.out.println(name1.equals(name3)+"--"+(name1==name3)); System.out.println(name3.equals(name4)+"--"+(name3==name4)); System.out.println("name1:"+name1.hashCode()); System.out.println("name2:"+name2.hashCode()); System.out.println("name3:"+name3.hashCode()); System.out.println("name4:"+name4.hashCode()); } //比较三 public static void test3(){ ObjectEquals objectEquals1 = new ObjectEquals(); ObjectEquals objectEquals2 = new ObjectEquals(); ObjectEquals objectEquals3 = objectEquals1; System.out.println(objectEquals1.equals(objectEquals2)+"--"+(objectEquals1==objectEquals2)); System.out.println(objectEquals1.equals(objectEquals3)+"--"+(objectEquals1==objectEquals3)); System.out.println("objectEquals1:"+objectEquals1.hashCode()); System.out.println("objectEquals2:"+objectEquals2.hashCode()); System.out.println("objectEquals3:"+objectEquals3.hashCode()); } }
关于String的两种创建,我们在常见对象--String中再详细介绍;
6.3.5 对象的另类判断
在上面String类的equals()在我们发现有这么一条语句anObject instanceof String,这条语句是判断anObject 是否是String类的一个实例,instanceof是Java的一个关键字用于判断实例是否属于类;
我们在项目开发中会经常用到一个对象转型的问题,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前应该首先进行一下类型判断.下面通过一段代码来验证一下;
package cn.yourick.object; import javax.jws.Oneway; import cn.yourick.statickey.Test; public class ObjectInstanceof extends ObjectDemo{ public static void main(String[] args) { /** * 我们如果不判断,那么就会成为运行时异常 java.lang.ClassCastException */ //ObjectInstanceof objectInstanceof = (ObjectInstanceof) new ObjectDemo(); //通过instanceof关键字有效解决运行时异常 test(); } //测试一 public static void test(){ ObjectInstanceof objectInstanceof = new ObjectInstanceof(); ObjectDemo objectDemo = new ObjectDemo(); if(objectDemo instanceof ObjectInstanceof){ objectInstanceof = (ObjectInstanceof) objectDemo; System.out.println("转换成功!"); }else { System.out.println("转换失败!"); } } }
注意:实例不能是基本数据类型,null用操作符instanceof测试任何类型时都是返回false的;
6.3.6 向下转型和向上转型
在上面我们初次认识了类型转换,类型转换其实和继承时息息相关的,也是多态的一种体现,本人有了一定的Java基础,所以这里先做转型介绍,如果有疑问的可以先看后面的继承再回过头看转型,根据自己,不一而足.下面开始介绍:
对象类型的转换主要分为向上转换和向下转换。向上转型就是将子类对象赋值给父类对象,这是多态思想的体现,通过向上转型我们可以在父类中定义一个方法,它的子类重写方法,通过传递不同的子类可以实现该方法的不同实现。向上转型是由具体的向抽象的转变,所以总是安全的,不会存在问题。
向下转型则和向上转型截然相反,是将父类对象传递给子类对象,也即将将抽象的类转换为具体的类,这样的转换通常会出现问题,下面我们通过代码来体现:
package cn.yourick.object; public class ObjectChange extends ObjectInstanceof{ public static void main(String[] args) { ObjectChange objectChange = new ObjectChange(); objectChange.changeUp();//向上转型 objectChange.changeDown();//向下转型 } //重写父类方法 @Override public void test() { System.out.println("重写test()!"); } //向上转型 public void changeUp(){ ObjectInstanceof objectInstanceof = new ObjectChange(); objectInstanceof.test();//父类对象调用子类方法 } //向下转型,通常会出现错误 public void changeDown(){ ObjectInstanceof objectInstanceof = new ObjectInstanceof(); ObjectChange objectChange = new ObjectChange(); //向下转换,通常先用instanceof判断,避免出现运行时异常 if(objectInstanceof instanceof ObjectChange){ objectChange = (ObjectChange) objectInstanceof; System.out.println("转型成功!"); }else{ System.out.println("转型失败!"); } } }
越是具体的对象,越是具有较多的特性,而越是抽象的对象具有的特性也就相对较小了。向下转型通常需要将对象进行强制转换,要特别注意,小心出错。