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("转型失败!");
}
}
}

越是具体的对象,越是具有较多的特性,而越是抽象的对象具有的特性也就相对较小了。向下转型通常需要将对象进行强制转换,要特别注意,小心出错。

菜鸟笔记 -- Chapter 6.3 对象的更多相关文章

  1. 菜鸟笔记 -- Chapter 6 面向对象

    在Java语言中经常被提到的两个词汇是类与对象,实质上可以将类看作是对象的载体,它定义了对象所具有的功能.学习Java语言必须要掌握类与对象,这样可以从深层次去理解Java这种面向对象语言的开发理念, ...

  2. 菜鸟笔记 -- Chapter 6.4 面向对象的三大特性

    6.4.1  三大特性概述 面向对象的三大特性是Java中一个很重要的基本理念. 封装是面向对象的核心思想.将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的意思.采用 ...

  3. 菜鸟笔记 -- Chapter 6.2 类的构成

    在前面我们讲过高级开发语言大多由7种语法构成,但这是一个很空泛的概述,下,面我们仅就针对Java程序来说一下构成一个Java程序的几大部分,其中类是最小的基本元素.类是封装对象属性和行为的载体,而在J ...

  4. 菜鸟笔记 -- Chapter 4 Java语言基础

    在Chapter3中我们写了第一个Java程序Hello World,并且对此程序进行了分析和常见错误解析.那么我们有没有认真观察一下Java程序的基本结构呢?本节我就来聊一下Java程序的基本结构( ...

  5. 菜鸟笔记 -- Chapter 1 计算机从0到1

    进入20世纪第二个十年,计算机已经成为生活中一个必不可小的工具了,但我们真的了解计算机吗?计算机有哪些部分构成?不同的计算机又可以做什么样的事情呢?我们的PC和用来做加减乘除的计算器都属于计算机范畴吗 ...

  6. 菜鸟笔记 -- Chapter 11 格式化

    我们在String中介绍过它有一个格式化的方法,在其它很多地方,也都能看到格式化的操作,那么这节我们就来认真了解一下Java中的格式化操作. 我们在操作中涉及到的格式化有字符串的格式化和一些其它数据类 ...

  7. 菜鸟笔记 -- Chapter 6.4.3 多态

    6.4.3  多态 多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方 ...

  8. 菜鸟笔记 -- Chapter 6.4.2 详解继承

    6.4.2  详解继承 6.4.2.1  继承入门 继承使得程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性.基本思想是基于某个父类 ...

  9. 菜鸟笔记 -- Chapter 6.2.5 代码块

    6.2.5  代码块 在编程过程中我们通常会遇到如下这种形式的程序: package democlass; public class CodeBlock { { System.out.println( ...

随机推荐

  1. Java学习第二十天

    1:递归(理解) (1)方法定义中调用方法本身的现象 举例:老和尚给小和尚讲故事,我们学编程 (2)递归的注意事项: A:要有出口,否则就是死递归 B:次数不能过多,否则内存溢出 C:构造方法不能递归 ...

  2. [转]ASP.NET Core Exception Filters and Resource Filters

    本文转自:https://damienbod.com/2015/09/30/asp-net-5-exception-filters-and-resource-filters/ This article ...

  3. UML建模 - 用例和用例图

    用例描述 用例描述一般包括: 用例编号.用例概述(说明).前置(前提)条件.基本事件流.其他事件流.异常事件流.后置(事后)条件等.如下:  元素  描述  备注  用例编号  为用例制定一个唯一的编 ...

  4. Unity 基础

    Unity 基础是unity入门的关键.他将讲解Unity的界面, 菜单项,使用资源,创设场景,并发布版本. 当你读完这段,你将理解unity是怎么工作的,如何有效地使用它,并且完成一个基本的游戏. ...

  5. WebGrease—异常来自 HRESULT:0x80131040

    一.错误源: 未能加载文件或程序集“WebGrease, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一 ...

  6. JVM如何加载一个类的过程,双亲委派模型中有哪些方法

    1.类加载过程:加载.验证.准备.解析.初始化   加载   在加载阶段,虚拟机主要完成三件事: 1.通过一个类的全限定名来获取定义此类的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法 ...

  7. 【学习笔记】实用类String的基本应用即常用方法

    一.String类概述 在Java中,字符串被作为String类型的对象来处理. String类位于java.lang包中,默认情况下会自动导入到所有的程序中. 创建String对象的方法如下: St ...

  8. 从零开始编译属于你的FFmpeg

    一.前提: 编译FFmpeg可以是初学者,尤其是对C语言项目,Linux编译不熟悉的的初学者的一道门槛. 我曾经找过很多博客,文章,有些能编译成功,有些则不能.编译通过,能够运行也是云里雾里的.其实最 ...

  9. 编程提取字符串"Java is a programming language"中的各个单词,并打印输出。

    import java.lang.String; import java.util.StringTokenizer; public class StringGetWord{ /* 编程提取字符串&qu ...

  10. spring集成JPA的三种方法配置

    JPA是Java EE5规范之一,是一个orm规范,由厂商来实现该规范.目前有hibernate,OpenJPA,TopLink和EclipseJPA等实现 spring提供三种方法集成JPA:1.L ...