第三讲 面向对象
理解面向对象的概念
● 面向过程
在一个结构体中定义窗口的大小,位置,颜色,背景等属性,对窗口操作的函数与窗口本身的定义没有任何关系,如HideWindow,MoveWindow,MinimizeWindow,这些函数都需要接受一个代表要被操作的窗口参数,是一种谓语与宾语的关系。
● 面向对象
定义窗口时,除了要指定在面向过程中规定的那些属性,如大小,位置,颜色,背景等外,还要指定该窗口可能具有的动作,如隐藏,移动,最小化等。这些函数被调用时,都是以某个窗口要隐藏某个窗口要移动的语法格式来使用的,这是一种主语与谓语的关系。
● 面向对象的三大特征
▂封装
▂继承
▂多态
类与对象
类是对某一类事物的描述,是抽象的,概念上的定义;对象是实际存在的该类事物的每个个体,因而也称实例(instance)。
如果将对象比作汽车,那么类就是汽车的设计图纸。所以面向对象程序设计的重点是类的设计,而不是对象的设计。
类的定义
public class Person()
{
int age;
void shout()
{
System.out.println("I am age "+age);
}
}
● age是类的属性,也叫类成员变量
● shout是方法也叫类的成员函数。
● shout方法可以直接访问同一类中的age变量,如果一个方法中有与成员同名的局部变量,该方法中对这个
变量名的访问是局部变量,而不是成员变量。
在类中创建的成员变量会被初始化,局部变量不会被初始化。
对象的使用
创建新的对象之后,我们就可以使用“对象名.对象成员”的格式,来访问对象的成员(包括属性和方法)
class Person
{
int age;
void shout()
{
System.out.println("age="+age);
}
}
class TestPerson
{
public static void main(String[]args)
{
Person p1=new Person();
p1.age=20;
p1.shout();
Person p2=new Person();
p2.shout();
}
}
上面程序运行的内存布局如下:

对象的比较
● “==”运算符与equals()方法的区别
“==”用来比较值是否相等,equals()方法用来比较两个对象的内容是否相等
● 怎样比较两个数组对象的内容是否相等
arrays.equals(ary1[],ary2[]);
匿名对象
● 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象,
如:new Person().shout();
● 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
● 我们经常将匿名对象作为实参传递给一个函数调用。
实现类的封装性
● 如果外面的程序可以随意修改一个类的成员变量,会造成不可预料的程序错误,就象一个人身高,不能被外部随意修改,只能通过各种摄取营养的方法去修改这个属性。
● 在定义一个类的成员(包括变量和方法)时,使用private关键字说明这个成员的访问权限,这个成员成了 类的私有成员,只能被这个类其他成员方法调用,而不能被其他类中的方法所调用。
● 为了实现良好的封装性,我们通常将类的成员变量声明为private,再通过public方法来对这个变量进行访问。对一个变量操作,一般都有读取和赋值操作,我们分别定义两个方法来实现这两种操作,一个是getXxx()方法(Xxx表示要访问的成员变量的名字),用来读取这个成员变量操作,另外一个是setXxx()用来对这个变量赋值。
● 一个类通常就是一个小的模块,我们应该让模块仅仅公开必须要让外界知道的内容,而隐藏其它一切内容。我们在进行程序的详细设计时,应尽量避免一个模块直接修改或操作另外一个模块的数据,模块设计追求强内聚(许多功能尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)。
class Person
{
private int age;
void shout()
{
System.out.println(age);
}
public void setAge(int x)
{
if(x<0)
return;
age=x;
}
public int getAge()
{
return age;
}
}
class TestPerson
{
public static void main(String[]args)
{
Person p=new Person();
p.setAge(18);
p.shout();
System.out.println(p.getAge());
}
}
构造函数的定义与作用
● 构造函数的特征
▁它具有与类相同的名称;
▁它不含返回值
▁它不能在方法中用return语句返回一个值
注意:在构造方法里不含返回值的概念是不同于“void”的,在定义构造方法时加了”voiid”,结果这个方法就不再被自动调用。
● 构造方法的作用:当一个类的实例对象刚产生时,这个类的构造方法就会被自动调用,我们可以在这个方法中加入要完成初始化工作的代码。
构造方法的重载
● 和一般的方法重载一样,重载的构造方法具有不同个数或不同类型的参数,编译器就可以根据这一点判断出用new 关键字产生对象时,该调用哪个构造方法了。产生对象的格式是:new 类名(参数列表);
● 重载构造方法可以完成不同初始化的操作,如:p3=new Person(“Tom”18);语句,会做这样几件事:创建指定类的新实例对象,在堆内存中为实例对象分配内存空间,并调用指定类的构造方法,最后将实例对象的首地址赋值给引用变量p3。
This引用句柄的应用
● 一个类中成员方法可以直接调用同类中的其他成员,其实我们在一个方法内部使用“this.其他成员”的引用方式和直接使用“其他成员”的效果是一样的,那this还有多大的作用呢?在有些情况下,我们是非得用this关键字不可的:
▁让类的成员变量名和对其进行赋值的成员方法的形参变量同名是必要的,这样的代码谁看了都能明白这两个变量是彼此相关的。
▁假设我们有一个容器类和一个部件类,在容器类的某个方法中创建部件类的实例对象,而部件类的构造方法要接收一个代表其所在容器的参数。
▁构造方法是在产生对象时被java系统自动调用的,我们不能在程序中象调用其他方法一样去调用构造方法。但我们可以在一个构造方法里调用其他重载的构造方法,不是用构造方法名,而是用this(参数列表)形式,根据其中的参数列表,选择相应的构造方法。
垃圾回收过程分析
● c++中的析构方法
● java中的finalize()方法
● System.gc的作用
Static静态变量
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。
编写使用静态变量统计一个类产生的实例对象的个数的程序。
class Person
{
private String name;
private static int count=0;
static String country="中国";
public Person()
{
System.out.println(name+":"+ ++count);
}
public Person(String name)
{
this.name=name;
System.out.println(name+":"+ ++count);
}
void shout()
{
System.out.println(name);
}
}
class TestPerson
{
public static void main(String[]args)
{
Person p=new Person("张三");
new Person();
p.shout();
new Person("李四").shout();
System.out.println(Person.country);
}
}
运行结果:
张三:1
null:2
张三
李四:3
李四
中国
Static静态方法
● 在静态方法里只能直接调用同类中其它的静态成员(包括变量和方法),而不能直接访问类中的非静态成员。这是因为,对于非静态的方法和变量,需要先创建类的实例对象后才可使用,而静态方法在使用前不用创建任何对象。
● 静态方法不能以任何方式引用this和super关键字。与上面的道理一样,因为静态方法在使用前不用创建任何实例对象,当静态方法被调用时,this所引用的对象根本就没有产生。
● main()方法是静态的,因此JVM在执行main方法时不创建main方法所在的类的实例对象,因而在main()方法中,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
单态设计模式
● 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
● 所谓类的单态设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样就不能用new操作符在类的外部产生类的对象了,但在类的内部仍可以产生该类的对象。因为在类的外部开始还无法等到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
class Chinese
{
static Chinese obj=new Chinese();
public static Chinese getInstance()
{
return obj;
}
private Chinese()
{
System.out.println("ss");
}
}
class TestChinese
{
public static void main(String[]args)
{
Chinese obj1=Chinese.getInstance();
Chinese obj2=Chinese.getInstance();
System.out.println(obj1==obj2);
//如果相等返回true,否则返回false.
}
}
运行结果:
ss
true
内部类
——在类中直接定义的内部类(嵌套类)
● 内部类(嵌套类)可以直接访问外部类(嵌套它的类)的成员,包括private成员,但是,内部类(嵌套类)的成员却不能被外部类(嵌套它的类)直接访问。
● 在内部类对象保存了一个对外部类对象的引用,当内部类的成员方法中访问某一变量时,如果在该方法和内部类中都没有定义过这个变量,内部类中对this的引用会被传递给那个外部类对象的引用。
● 如果用static修饰一个内部类,这个类就相当于是一个外部定义的类,所以static的内部类中可声明static成员,但是,非static的内部类中的成员是不能声明为static。Static的内部类不能再使用外层封装的非static的成员变量,这个道理不难想象,所以static嵌套类很少使用。
class Outer
{
int outer_i=100;
void test()
{
Inner inner=new Inner();
inner.display();
}
class Inner
{
void display()
{
System.out.println(outer_i);
}
}
public static void main(String[]args)
{
Outer outer=new Outer();
outer.test();
}
}
● 如果函数的局部变量(函数的参数也是局部变量)、内部类的成员变量、外部类的成员变量重名,我们应该按下面的程序代码所使用的方式来明确指定我们真正要访问的变量。
public class Outer
{
private int size;
public class Inner
{
private int size;
public void doStuff(int size)
{
size++;//引用的是doStuff函数的形参
this.size++;//引用的是Inner类中的成员变量
Outer.this.size++;//引用的是Outer类中的成员变量
}
}
}
内部类如何被外部引用
class Outer
{
private int size=100;
public class Inner
{
public void doStuff()
{
System.out.println(++size);
}
}
}
public class TestInner
{
public static void main(String[]args)
{
Outer outer=new Outer();
Outer.Inner inner=outer.new Inner();
inner.doStuff();
}
}
在方法中定义的内部类
● 嵌套类并非只能在类中定义,也可以在几个程序块的范围之内定义内部类。例如,在方法中,或甚至在for循环体内部,都可以定义嵌套类。
● 在方法中定义的内部类只能访问方法的fianl类型的局部变量,用final定义的局部变量相当于是一个常量,它的生命周期超出方法运行的生命周期。