Java基础知识:面向对象

1.类

Java类里,包含属性,方法,构造函数,初始化块,局域变量,内部类等成员,每种成员可以被各种修饰符修饰。其实被static修饰符修饰的成员,被称为静态成员(类成员),而没有被static修饰的成员,被称为实例成员。

1)静态成员(类成员)

静态成员属于整个类,而不属于单个对象。类成员被static关键字修饰的静态属性,静态块,静态方法,静态内部类等。

对static关键字而言,有一条非常重要的规则:类成员(静态属性,静态初始化块,静态方法,静态内部类)不能访问实例成员(实例属性,实例初始化块,实例方法,实例内部类)。因为类成员是属于类的,类成员的作用域比实例成员的作用域大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况,如果允许类成员访问实例成员将会引起大量错误。

(1)静态属性

静态属性属于整个类。当系统第一次准备使用该类时,系统会为该静态属性在JVM的方法区中分配内存空间,静态属性开始生效,直到该类被卸载,该类的静态属性所占用的内存才被系统的垃圾回收机制回收(Perm区的major GC)。静态属性生存范围几乎等同于该类的生存范围。当类初始化完成后,静态属性也被初始化完成。

静态属性既可以通过类来访问,也可以通过类的对象来访问。但通过类的对象来访问静态属性时,实际上并不是访问该对象所拥有的属性,因为当系统创建该类的对象时,系统不会再为该静态属性分配内存,也不会再次对静态属性进行初始化,也就是说,对象根本不拥有对应类的静态属性。通过对象访问静态属性只是一种假象,通过对象访问的依然是该类的静态属性。可以这样理解:当通过对象访问静态属性时,系统会在底层转换为通过该类来访问静态成员。

PS:C#不允许通过对象访问静态属性,对象只能访问实例属性;静态属性必须通过类来访问。

由于对象实际上并不持有静态属性,静态属性由该类持有,同一个类的所有对象访问静态属性时,实际上访问的是该类持有的静态属性。因此,可看到同一个类的所有实例共享同一块内存区(静态属性存放在JVM方法区的类信息里)。

(2)静态方法

静态方法也是属于类的,通常直接使用类作为调用者来调用类方法,但也可以通过类的对象来调用类方法。与静态属性类似,即使使用对象来调用方法,效果也与采用类调用类方法完全一样。

当使用实例来访问类成员时,实际上,依然是委托给该类来访问类成员,因此即使某个实例为null,它也可以访问它所属类的类成员。

例如:

package com.demo3;

public class NullAccessStatic {
	static void test() {
		System.out.println("static修饰的静态方法");
	}

	public static void main(String[] args) {
		// 定义一个NullAccessStatic变量,其值为null
		NullAccessStatic nas = null;
		// null对象调用所属类的静态方法
		nas.test();
	}

}

输出结果:

static修饰的静态方法

PS:如果一个null对象访问实例成员(包括成员和方法),会引发NullPointerException异常。

(3)静态初始化块

静态初始化块也是类成员的一种,静态初始化块用于执行类初始化动作,在类的初始化阶段,系统会调用该类的静态初始化块来对类进行初始化。一旦类的初始化结束,静态初始化块将永远不会得到执行的机会。

2)实例成员

创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例。大多时候,定义一个类就是为了重复创建该类的实例,同一个类的多个实例具有相同的特征,而类则定义了多个实例的共同特征。从某个角度来看,类定义的是多个实例的特征,因此类不是一种具体存在,实例才是具体存在。

(1)对象、引用、指针

有这样一行代码:

Person p=new Person();

这行代码创建了一个Person实例,也叫Person对象,这个Person实例被赋给p变量。这行代码中实际产生了两个东西:一个p变量,一个Person实例。Person实例被存放在JVM中的堆内存区中,用来存储Person的实际数据信息;而p变量实际上是一个引用,它被存放在JVM的线程栈内存中,指向实际的Person实例。

实际上,java里的引用就是C里的指针,只是Java语言把这个指针封装起来,避免开发者进行繁琐的指针操作。当一个实例被创建成功后,这个实例将保存在堆内存中,java程序中不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象,也就是说,不管数组还是对象,都只能通过引用访问它们。Java线程栈内存中,不会存放对象,只会存放基本数据类型和引用。

如果堆内存中的对象没有任何变量指向该对象,那么程序将无法再访问该对象,这个对象也就变成了垃圾,Java里的垃圾回收机制会回收该对象,释放该对象占用的内存空间。

(2)this关键字

Java提供了一个this关键字,this关键字只能出现在实例块和实例方法中,static修饰的类成员不能使用this关键字,因此java语法规定:静态成员不能直接访问非静态成员。this关键字总是指向调用该方法的对象。this关键字最大的作用就是让类中一个方法,可以访问该类实例里的另一个方法或属性。

Java运行对象的一个成员直接调用另一个成员,可以省略this前缀。省略this前缀只是一种假象,虽然省略了this,但是this依然是存在的。

对于实例方法,可以根据python的处理方式,理解为:实例方法的形参列表里,默认隐藏了一个this形参。

其实,JVM内部是通过栈帧的默认规定实现this的。

示例:

package com.demo3;

public class Demo {
	static String staticName;
	String instanceName;

	public static void sayHello() {
		System.out.println("Static sayHello:  " + staticName);
	}

	public void sayWorld() {
		System.out.println("this  sayWorld: " + this.instanceName);
	}
}

通过javap反编译:

javap -c Demo.class

反编译结果 :

通过对比可以发现,获取“staticName”静态属性,使用的是getstatic指令,getstatic指令是指加载位于JVM方法区的静态属性到栈帧操作区的栈顶;而获取“this.instanceName”指令使用的是“aload_0”和getfield指令,“aload_0”指令用于加载栈帧局域变量区的第一个变量到栈帧操作区的栈顶,"getfield"指令用于弹出操作区栈顶引用,并获取该引用指向的堆中实例的属性,最后将属性压入操作区栈顶。

根据JVM栈帧的局域变量区的特性:实例方法对应栈帧的局域变量区的第一个变量是该实例自己的引用,即this;静态方法对应栈帧的局域变量区的第一个变量不是this,是其他局域变量。如图:

所以,在使用this的地方,在编译时,都被aload_0指令代替。

(3)super关键字

示例:

package com.demo3;

public class Demo {
	String instanceName;

	public void sayHello() {
		System.out.println("Demo.sayHello");
	}
}
package com.demo3;

public class Demo2 extends Demo {
	String instanceName;

	public void sayWorld() {
		System.out.println(this.instanceName);
		this.sayHello();
	}

	public void saySun() {
		System.out.println(super.instanceName);
		super.sayHello();
	}
}

用javap反编译Demo2.class文件:

javap -c  Demo2.class

获得方法反编译指令:

根据字节码指令可知:

在编译时,this关键字被aload_0指令替代;super也被aload_0指令替代,不过super后面的属性被指定为父类属性,super后面的方法被指定为父类方法,而且用invokespecial指令调用。

JVM提供了4条方法调用的字节码指令:

  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器<init>方法,私有方法和父类方法
  • invokevirtual:调用所有的虚方法
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。

只要能被invokestatic和invokespecial调用的方法,都是静态连接。只要能被invokevirtual和invokeinterface调用的方法,都是动态连接。除静态方法,实例构造器,私有方法,父类方法以外,其他方法称为虚方法。

JAVA非虚方法除了invokestatic和invokespecial以外,还有一种就是final修饰的方法,因为该方法无法被覆盖,这种被final修饰的方法是用invokevirtual指令调用的。

invokespecial和invokevirtual的主要区别是:

  • invokespecial静态绑定;invokevirtual动态绑定
  • invokespecial调用实例构造器<init>方法,私有方法和父类方法;invokevirtual主要调用除静态方法,实例构造器,私有方法,父类方法以外的实例方法。
  • 运行时,invokespecial选择方法基于引用的类型,而不是对象所属的实际类型。但invokevirtual则选择对象所属的实际类型。

getfield指令和invokespecial一样,也是基于引用的类型,而不是对象的所属实际类型。

综上所述,在使用super的地方,在编译时,都被aload_0指令代替,而且getfield获取父类属性,invokespecial调用父类方法;在使用this的地方,在编译时,都被aload_0指令代替,getfield指令获取本类属性,invokevirtual调用本类方法。

(4)方法参数传递

我们都知道:C 语言中函数参数的传递有:值传递,地址传递,引用传递这三种形式。但是在Java里,方法的参数传递方式只有一种:值传递。所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。

要说明这个问题,先要明确两点:

  • 1.引用在Java中是一种数据类型,跟基本类型int等等同一地位。
  • 2.程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

在运行JVM栈中,基本类型和引用的处理是一样的,都是传值。如果是传引用的方法调用,可以理解为“传引用值”的传值调用,即“引用值”被做了一个复制品,然后赋值给参数,引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。

(5)toString()方法

toString方法是一个非常特殊的方法,它来自与Object类,是一个“自我描述”方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。

Object类提供的toString方法总是返回该对象实现类的“类名[email protected]+hashCode”值,这个返回值并不能真正实现“自我描述”的功能,因此如果用户需要自定义类能实现“自我描述”功能,就必须重写Object类的toString()方法。

(6)equals和==

对于基本类型来说,==是比较两个值是否相等;对于引用类型来说,==是用来判断两个引用变量是否指向同一个对象。

equals方法是Object类的方法,equals函数主要用来定义“对象相等”的比较逻辑。默认是比较两个对象在堆中,是否是同一个指针(同一个对象), 即:return (this == obj);。基本类型的包装类,例如Integer、Float等等、String类,都将equals方法进行了重写。它们不再比较对象的堆地址是否相同,而是比较对象的内容是否相同。

equals方法与Object的方法hashCode()方法是相关联的。这主要设计到hash表(散列表)的相关知识。

给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字(key)的记录在表中的地址P,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数,P则为关键字key的hash值。

equals中涉及的比较逻辑就是key,而函数f()就是hashCode()方法,P则为hashCode()方法的返回值。所以根据散列表的特性可以得到:

  • 当两个对象调用equals函数,返回true时,则意味着关键字key相同,那么hashCode函数的返回值也应该相同。
  • 当两个对象调用equals函数,返回false时,则意味着关键字key不相同,那么hashCode函数的返回值可能相同。

因此,在重写equals函数时,也应该改变相应的hashCode方法,以适应特性。

3)局域变量

形参的作用域是整个方法,由方法调用时,指定值。主要被分配在线程栈的栈帧中。在方法结束时,自动销毁。形参最好在方法的执行过程中,不要被重新赋值。因为形参代表了实际的入口参数,最好不要轻易改变入口参数,可以用final关键字修饰。

方法局域变量和代码块局域变量的作用域是变量定义时,最近括号{}代码块之间。例如:

package com.demo3;

public class Test {

	public static void main(String[] args) {
		int a = 1;

		{
			int b = 1;
		}

		int[] array = new int[] { 1, 2, 3, 4, 5 };

		for (int i = 0; i < array.length; i++) {
			System.out.println(array[i]);
		}
	}

}

a的作用域是整个方法,b的作用域是{int b=1;}代码块,i的作用域是for的循环体。

Java允许局域变量和成员变量同名,如果方法里的局域变量和成员变量同名,局域变量会覆盖成员变量,如果需要在方法里引用被覆盖的成员变量,则可以使用this(对于实例属性)或类名(对于静态属性)作为调用者来限定访问成员变量。不过,大部分时候,应该尽量避免局域变量和成员变量同名。

4)初始化

待续

时间: 2024-10-07 22:22:49

Java基础知识:面向对象的相关文章

Java基础知识—面向对象(八)

概述 Java和C#都是面向对象语言,面向对象编程是目前高级语言习惯的编程模式,与C++编写过程编程而言,面向对象使用起来高效.灵活:面向对象的三个特征:封装.继承和多态. Java面向对象 1.类封装: 在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的.class 子类 extends 父类{}.implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔). 2.super 与

Java基础知识面向对象三大特性

面向对象三大特性:一 封装:概念:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式.好处:将变化隔离;便于使用;提高重用性;安全性.封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问.二 继承好处:1:提高了代码的复用性.2:让类与类之间产生了关系,提供了另一个特征多态的前提.注意:子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过supe

java基础知识回顾之面向对象

一 . 抽象类 什么是抽象类?很简单,就是用abstract关键字修饰,并允许包含未实现方法的类. 什么时候定义抽象类?在有些情况下,可能有些方法无法确定要怎么实现,这时就可以定义抽象类,无法实现的方法定义成抽象方法. 抽象类的特性: 1. 不能实例化,即不能创建对象,只能作为父类被继承. 2. 子类继承一个抽象类后,必须实现父类的抽象方法. 3. 抽象类中可以有抽象方法,也可以不包含抽象方法,但如果包含抽象方法,就必须定义成抽象类. public abstract class Shaoe{ p

Java基础知识:面向对象&类图

类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性.操作.关系的对象集合的总称.在系统中,每个类都具有一定的职责,职责指的是类要完成什么样的功能,要承担什么样的义务.一个类可以有多种职责,设计得好的类一般只有一种职责.在定义类的时候,将类的职责分解成为类的属性和操作(即方法).类的属性即类的数据职责,类的操作即类的行为职责.设计类是面向对象设计中最重要的组成部分,也是最复杂和最耗时的部分. 1.面向对象特性 1)抽象 2)继承 3)封装 4)多态 2.类图: 在软件系统

第1天:了解Java基础知识

Java的优势 1. 简单 不像C或者C++语言,Java中省去了对指针的操作.但是,Java中并没有省去指针,代替指针的是一种新的变量--引用,引用也是保存一个对象的内存地址. 2.方便 Java虚拟机自带垃圾回收器,能够自动回收内存资源.而C和C++语言,需要开发人员手动进行内存资源回收. 3.安全 不支持指针操作 4.平台无关性 Java语言是跨平台的,一次编译,到处运行. 而且,不同平台,C语言中数据类型所占的位数是不同的,而Java语言中,数据类型所占的位数是固定的. 5.面向对象 J

java基础知识回顾之---java String final类普通方法

辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /*     * 按照面向对象的思想对字符串进行功能分类.     *      *      * 1,获取:     * 1.1 获取字符串中字符的个数(长度).     *         int length();     * 1.2 取字符串中的某一个字符,其中的参数index指的是字符串中序数.字符串的序数从0开始到length()-1 .     *       

【小白的java成长系列】——Java基础知识

今天来说说java的基础知识,个人感觉都不知道要说啥的,还是为后面的内容做一些铺垫吧~ 今天主要说的都是java面向对象之前的基础知识,比如数据类型呀,表达式运算符呀~等等一系列的知识,下节来说说面向对象.今天这节我就不用程序来说明,直接用文字说明.因为个人感觉真木有啥好说的,这些程序后续都会说到的,比较简单,写写就会了的..好吧~开始了... 1. Java数据类型划分: 基本数据类型:都是一个个具体的值 数值型:表示具体的数字,所有的整数默认情况下都是int,所有的小数都是double型的

Java基础知识的三十个经典问答

Java基础知识的三十个经典问答 1.面向对象的特点 抽象: 抽象是或略一个主题中与当前目标的无关的因素,一边充分考虑有关的内容.抽象并不能解决目标中所有的问题,只能选择其中的一部分,忽略其他的部分.抽象包含两个方面:一是过程抽象:一是数据抽象. 继承 继承是一种联接类的层次模型,允许和鼓励类的重用,提供了一种明确的共性的方法.对象的一个新类可以从现有的类中派生,这叫做类的继承.心累继承了原始类 的特性,新类称为原始类的派生类或者是子类,原始类称为新类的基类或者父类.子类可以从父类那里继承父类的

java基础知识回顾之javaIO类--File类

File类是对文件系统中文件以及目录(文件夹)进行封装的对象,可以通过面向对象的思想来操作文件和目录(文件夹).File类保存文件或目录的各种元素的信息,包括文件名,文件长度,最后修改日期,是否可读,获取当前文件的路径名,判断指定文件是否存在,获得当前文件的列表,创建.删除文件目录等方法. /**     * 构造方法:File f = new File("file.txt");//file.txt 相对路径     *       File f1 = new File("c

Java基础知识回顾之七 ----- 总结篇

前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数据类型 基本数据类型主要有: byte.short.int.long.float.double.char.boolean 它们可以分为三类: 数值类型:byte.short.int.long.float.double 字符类型:char 布尔型:boolean 其中byte是8位,short是16位