【JAVA类和对象介绍(完整版)】
1 面向对象概述
1.1 对象
现实世界中,随处可见的一种事物就是对象,对象是事物存在的实体,如人类、书桌、计算机、高楼大厦等。人类解决问题的方式总是将复杂的事物简单化,于是就会思考这些对象都是由哪些部分组成的。通常都会将对象划分为两个部分,即动态部分与静态部分。静态部分,顾名思义就是不能动的部分,这个部分被称为“属性”,任何对象都会具备其自身属性,如一个人,它包括高矮、胖瘦、性别、年龄等属性。然而具有这些属性的人会执行哪些动作也是一个值得探讨的部分,这个人可以哭泣、微笑、说话、行走,这些是这个人具备的行为(动态部分),人类通过探讨对象的属性和观察对象的行为了解对象。
1.2 类
类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。例如一个鸟类,鸟类封装了所有鸟的共同属性和应具有的行为,其结构如下图所示:
1.3 封装
封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,用户使用电脑,只需要使用手指敲击键盘就可以实现一些功能,用户无须知道电脑内部是如何工作的,即使用户可能碰巧知道电脑的工作原理,但在使用电脑时并不完全依赖于电脑工作原理这些细节。
1.4 继承
类与类之间同样具有关系,如一个百货公司类与销售员类相联系,类之间这种关系被称为关联。关联是描述两个类之间的一-般二元关系,例如一个百货公司类与销售员类就是一个关联,再比如学生类以及教师类也是一个关联。两个类之间的关系有很多种,继承是关联中的一种。
1.5 多态
多态性允许以统一的风格编写程序,以处理种类繁多的已存在的类以及相关类。该统一风格可以由父类来实现,根据父类统一风格的处理,就可以实例化子类的对象。由于整个事件的处理都只依赖于父类的方法,所以日后只要维护和调整父类的方法即可,这样降低了维护的难度,节省了时间。在提到多态的同时,不得不提到抽象类和接口,因为多态的实现并不依赖具体类,而是依赖于抽象类和接口。
2 类
类是封装对象的属性和行为的载体,在JAVA语言中对象的属性以成员变量的形式存在,对象的方法以成员方法的形式存在。
2.1 成员变量
在JAVA语言中对象的属性也称为成员变量。
例2.1.1 创建Book类,在该类中定义并使用成员变量。
public class Book {
private String name; //定义一个String类型的成员变量
public String getName() { //定义一个getName方法
int id = 0; //局部变量
setName("Java"); //调用类中的其他方法
return id + this.name; //设置方法返回值
}
private void setName(String name) {//定义一个setName方法
this.name = name; //将参数值赋予类中的成员变量
}
public Book getBook() {
return this; //返回Book类引用
}
}
2.2 成员方法
定义成员方法的语法格式如下:
权限修饰符 返回值类型 方法名(参数类型 参数名){
...//方法体
return 返回值;
}
一个成员方法可以有参数,这个参数可以是对象,也可以是基本数据类型的变量,同时成员方法有返回值和不返回任何值两种选择,如果主方法需要返回值,可以在方法体中使用return关键字,使用这个关键字后,方法的执行将被终止。
注意:JAVA语言中的成员方法没有返回值,可以使用void关键字表示。
在成员方法中可以调用其他成员方法和类成员变量,同时在成员方法中可以定义一个变量,这个变量为局部变量。
注意:如果一个方法中含有与成员变量同名的局部变量,则方法中对这个变量的访问以局部变量进行,例如:变量id在getName方法中的值为0,而不是成员变量中id的值。
类成员变量和成员方法也可以统称为类成员。
2.3 权限修饰符
Java中的权限修饰符主要包括private、public 和protected,这些修饰符控制着对类和类的成员变量以及成员方法的访问。如果一个类的成员变量或成员方法被修饰为private,则该成员变量只能在本类中被使用,在子类中是不可见的,并且对其他包的类也是不可见的。如果将类的成员变量和成员方法的访问权限设置为public,则除了可以在本类使用这些数据之外,还可以在子类和其他包中的类中使用。如果一个类的访问权限被设置为private,这个类将隐藏其内的所有数据,以免用户直接访问它。如果需要使类中的数据被子类或其他包中的类使用,可以将这个类设置为public访问权限。如果-一个类使用protected修饰符,那么只有本包内的该类的子类或其他类可以访问此类中的成员变量和成员方法。
注意:当声明类时不使用public、protected、private修饰符设置类的权限,则这个类预设为包存取范围,即只有一个包中的类可以调用这个类的成员变量或成员方法。
例2.3.1 在项目的com.lzw包下创建AnyClass类,该类使用默认的访问权限。
package com.lzw;
class AnyClass{
public void doString(){
...//主方法
}
}
在上述代码中,由于类的修饰符为默认修饰符,即只有一个包内的其他类和子类可以对该类进行访问,而AnyClass类中的doString(方法却又被设置为public访问权限,即使这样,doString(方法的访问权限依然与AnyClass类的访问权限相同,因为Java语言规定,类的权限设定会约束类成员的权限设定,所以上述代码等同于例2.3.1的代码。
**例2.3.1 **
package com.lzw;
class AnyClass{
void doString(){
...//主方法
}
}
2.4 局部变量
局部变量是在方法被执行时创建,在方法执行结束时被销毁。局部变量在使用时必须进行赋值操作或被初始化,否则会出现编译错误。
2.5 局部变量的有效范围
可以将局部变量的有效范围称为变量的作用域,局部变量的有效范围从该变量的声明开始到该变量的结束为止。局部变量的作用范围如图所示:
2.6 this关键字
this可以调用成员变量和成员方法,但这并不是Java语言中的最常规调用方式一 -使用 “对象.成员变量”或“对象.成员方法”进行调用(关于使用对象调用成员变量和方法的问题,笔者会在后续章节中进行讲解)。既然this关键字可以调用成员变量和成员方法,究竞this关键字与对象之间具有怎样的关系?事实上this引用的就是本类的一个对象。
this除了可以调用成员变量和成员方法外,还可以作为方法的返回值。
public Book getBook() {
return this; //返回Book类引用
}
3 类的构造方法
在类中除了成员方法之外,还存在一种特殊类型的方法,那就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。构造方法的特点如下:
构造方法没有返回值。
构造方法的名称要与本类的名称相同。
注意:在定义构造方法时,构造方法没有返回值,但这与普通没有返回值的方法不同,普通没有返回值的方法使用public void methodEx()这种形式进行定义,但构造方法并不需要使用void关键字进行修饰。
构造方法的定义语法格式如下:
public book(){
... //构造方法体
}
//public:构造方法修饰符
//book:构造方法的名称
在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,相应的成员变量也将被初始化。如果类中没有明确定义构造方法,编译器会自动创建一个不带参数的默认构造方法。
如果在类中定义的构造方法都不是无参的构造方法,那么编译器也不会为类设置一个默认的无参构造方法,当试图调用无参的构造方法实例化一个对象时,编译器会报错,所以只有在类中没有定义任何构造方法时,编译器才会被该类中自动创建一个不带参数的构造方法。
事实上,this不仅可以调用类的成员变量和成员方法,还可以调用类中的构造方法,看下面的实例:创建AnyThting类,在该类中使用this调用构造方法。
public class AnyThting {
public AnyThting(){ //定义无参构造方法
this("this 调用有参构造方法"); //使用this调用有参构造方法
System.out.println("无参构造方法");
}
public AnyThting(String name){ //定义有参构造方法
System.out.println("有参构造方法");
}
}
上述代码中可以看到有两个构造方法,在无参构造方法中可以使用this关键字调用有参的构造方法,但使用这种方式需要注意的是只可以在无参构造方法中的第一句使用this调用有参构造方法。
4 静态变量、常量和方法
在介绍静态变量、常量和方法之前首先需要介绍static关键字,因为由static修饰的变量、常量和方法被称作静态变量、常量和方法。有时,在处理问题时会需要两个类在同一个内存区域共享-一个数据。例如,在球类中使用PI这个常量,可能除了本类需要这个常量之外,在另外一个圆类中也需
要使用这个常量。这时没有必要在两个类中同时创建PI这个常量,因为这样系统会将这两个不在同一个类中定义的常量分配到不同的内存空间中。为了解决这个问题,可以将这个常量设置为静态的。
被声明为static的变量、常量和方法被称为静态成员。静态成员属于类所有,区别于个别对象,可以在本类或者其他类中使用类名.调用静态成员,语法如下:
类名.静态类成员
例4.1 创建StaticTest类,该类中的主方法调用静态成员并在控制台中输出:
public class StaticTest {
final static double PI = 3.1415; //在类中定义静态常量
static int id; //在类中定义静态变量
public static void method1() { //在类中定义静态方法
//do something
}
public void method2() {
System.out.println(StaticTest.PI); //调用静态常量
System.out.println(StaticTest.id); //调用静态变量
StaticTest.method1(); //调用静态方法
}
}
虽然静态成员也可以使用“对象.静态成员”的形式进行调用,但通常不建议用这样的形式,因为这样容易混淆静态成员和非静态成员。
在JAVA语言中对静态方法有两点规定:
(1)在静态方法中不可以使用this关键字。
(2)在静态方法中不可以直接调用非静态方法。
//注意:
//在JAVA中规定不能将方法体内的局部变量声明为static的,例如下面代码为错误的:
public class example {
public void method() {
static int i = 0;
}
}
如果在执行类时,希望先执行类的初始化动作,可以使用static定义一个静态区域,例如:
public class example{
static{
//some
}
}
当这段代码被执行时,首先执行static块中的程序,并且只会执行一次。
5 类的主方法
主方法是类的入口点,它定义了程序从何处开始;主方法提供对程序流向的控制,Java编译器通过主方法来执行程序。主方法的语法如下:
public static void main(String[] args){
//方法体
}
在主方法的定义中可以看到主方法具有以下特性:
1、主方法也是静态的,所以如要直接在主方法中调用其他方法,则该方法必须也是静态的。
2、主方法没有返回值。
3、主方法的形参为数组。其中args[O]~args[n]分 别代表程序的第一个参数到第n个参数,可以
使用args.length获取参数的个数。
6 对象
6.1 对象的创建
对象可以认为是在一类事物中抽象出某一个特例,通过这个特例来处理这类事物出现的问题,在Java语言中通过new操作符来创建对象。前文曾经在讲解构造方法中介绍过每实例化一个对象就会自动调用一次构造方法,实质上这个过程就是创建对象的过程。准确地说,可以在Java语言中使用new操作符调用构造方法创建对象。语法如下:
Test test = new Test();
Test test = new Test("a");
//Test:类名
//test:创建Test类对象
//new:创建对象操作符
//"a":创建方法的参数
注意:在java语言中对象和实例事实上是可以通用的。
例6.1 创建CreateObject类,在该类中创建对象并在主方法中创建对象:
public class CreateObject {
public CreateObject() { //构造方法
System.out.println("创建对象");
}
public static void main(String[] args) { //主方法
new CreateObject(); //创建对象
}
}
6.2 访问对象的属性和行为
当用户使用new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。前文已经提到过,对象的属性和行为在类中是通过类成员变量和成员方法的形式来表示的,所以当对象获取类成员,也就相应地获取了对象的属性和行为。
例6.2.1 创建TransferProperty类,在该类中说明对象是如何调用类成员的:
public class TransferProperty {
int i = 47; //定义成员变量
public void call() { //定义成员方法
System.out.println("调用call()方法");
for (i = 0; i < 3; i++) {
System.out.print(i + " ");
if (i == 2) {
System.out.println("\n");
}
}
}
public TransferProperty() { //定义构造方法
}
public static void main(String[] args) {
TransferProperty t1 = new TransferProperty(); //创建一个对象
TransferProperty t2 = new TransferProperty(); //创建另一个对象
t2.i = 60; //将类成员变量赋值为20
//使用第一个对象调用类成员变量
System.out.println("第一个实例对象调用变量i的结果:" + t1.i++);
t1.call(); //使用第一个对象调用类成员方法
//使用第二个对象调用类成员变量
System.out.println("第二个实例对象调用变量i的结果:" + t2.i);
t2.call(); //使用第二个对象调用类成员方法
}
}
如果希望成员变量不被任何一个对象改变,可以使用static关键字,上述代码改写成例6.2.2形式:
public class TransferProperty {
static int i = 47; //定义成员变量
public void call() { //定义成员方法
System.out.println("调用call()方法");
for (i = 0; i < 3; i++) {
System.out.print(i + " ");
if (i == 2) {
System.out.println("\n");
}
}
}
public TransferProperty() { //定义构造方法
}
public static void main(String[] args) {
TransferProperty t1 = new TransferProperty(); //创建一个对象
TransferProperty t2 = new TransferProperty(); //创建另一个对象
t2.i = 60; //将类成员变量赋值为20
//使用第一个对象调用类成员变量
System.out.println("第一个实例对象调用变量i的结果:" + t1.i++);
t1.call(); //使用第一个对象调用类成员方法
//使用第二个对象调用类成员变量
System.out.println("第二个实例对象调用变量i的结果:" + t2.i);
t2.call(); //使用第二个对象调用类成员方法
}
}
6.3 对象的引用
在JAVA语言中尽管一切都可以看作对象,但真正的操作标识符实质上是一个引用,语法如下:
类名 对象引用名称
//例如Book类的引用可以使用如下代码
Book book;
//通常一个引用不一定需要有一个对象相关联。引用和对象相关联的语法如下:
Book book = new Book();
//Book:类名
//book:对象
//new:创建对象操作符
注意:引用只是存放一个对象的内存地址,并非存放一个对象。严格地说,引用和对象是不同的,但是可以将这种区别忽略,如可以简单说book是Book类的一个对象,而事实上应该是book包含Book对象的一个引用。
6.4 对象的比较
在JAVA语言中有两种比较对象的方式,分别为“==”运算符与equals()方法,这两种方式有着本质的区别。
例6.4 创建Compare类,说明“==”运算符与equals()方法的区别:
public class Compare {
public static void main(String[] args) {
String c1 = new String("abc");
String c2 = new String("abc");
String c3 = c1;
//使用“==”运算符比较c2和c3
System.out.println("c2==c3的运算结果为:" +(c2 == c3));
//使用equals()方法进行比较
System.out.println("c2.equals(c3)的运算结果为:"+(c2.equals(c3)));
}
}
以上代码可以看出,“==”运算符与equals()方法比较的内容是不相同的,equals()方法是String类中的方法,它用于比较两个对象引用所指的内容是否相等,而“ ==”运算符比较的是两个对象引用的地址是否相等。
6.5 对象的销毁
每个对象都有生命周期,当对象的生命周期结束时,分配给该对象的内存地址需要被回收,JAVA拥有一套完整的垃圾回收机制。
以下两种情况会被java虚拟机视为“垃圾":
1、对象引用超过其作用范围,这个对象将被视为垃圾
2、将对象赋值为null
垃圾回收器只能回收那些由new操作符创建的对象,某些对象不是通过new操作符在内存中获取存储空间的,这种对象无法被垃圾回收机制所识别。
在JAVA中提供了一个finalize()方法,这个方法是Object类的方法,它被声明为protected,用户可以在自己的类中定义这个方法,如果用户在类中定义了finalize()方法,在垃圾回收时会首先调用该方法,在下一次垃圾回收动作发生时,才真正回收被对象占用的内存。
注意:需要明确的是,垃圾回收或finalize()方法并不保证一定会发生,如果JAVA虚拟机内存损耗待尽,它将不会执行垃圾回收处理。
由于垃圾回收不受人控制,具体执行时间不确定,所以finalize()方法也将无法执行。为此,java提供了System.gc()方法来强制启动垃圾回收器。