序言
举个大象装进冰箱的例子:
假设有一头大象,想把这头大象装进冰箱之中,于是有人就想出了如下两种解决这个问题的方案。
思路一:
- 打开冰箱门
- 把大象塞进冰箱
- 把冰箱门关上
思路二:
首先,通过分析,发现在大象装进冰箱的例子中,有大象,冰箱这样两个实体。那么,就可以有两个具体的对象:大象和冰箱。而后每一个对象都有自己的独立的功能和属性。那么这个问题就按下面的方式解决:
- 冰箱的门打开
- 大象自己走进冰箱
- 冰箱门关上
对于上面的两种思路,思路一就是一个面向过程的思想去解决问题,它强调的是每一个功能的步骤。思路二则是面向对象的思想,首先分析有几个实体,可以抽象出几个具体对象,而后是每个对象有什么样的功能,最后是各个对象各司其职,一起完成每个任务。
Java是一门完全面向对象的语言,对于初学者说起面向对象可能理解煞是费力。那么什么是对象,什么是面向对象呢?
编程界有两种编程思想,一种是面向过程的编程,一种是面向对象的编程。
面向过程: 就是面向程序中的函数。在使用面向过程编写程序时,会分析整个功能的流程,参与书写功能每个流程的代码。开发人员参与了整个功能的开发分析,属于参与者
面向对象:对象就是实体(在java中被关键字new创建的都是实体) ,其实就是面向使用new创建出来的实体(对象)。对象中封装了实现功能的代码。 在使用面向对象编写程序时,会先去找有没有存在已经解决问题的类(类中封装解决问题的函数),存在,则直接调用类中的功能解决问题。不存在,则再使用面向过程的思想解决问题。
一、面向对象的概述
面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能和隐藏的实现部分。面向对象其实就是对实体的抽象,包括实体的功能和属性,如:抽象出一个人,那么人对于的功能有:吃睡学....,属性有:age,high,wight,name....
从上面的分析中:我们可以得出一个结论,面向对象强调的是功能。而面向对象强调的对象(对象出来了之后,然后再去考虑对象的功能和属性)。
- 类(class)
类是构造对象的模板和蓝图。也就是说类是对一个事物的抽象,抽象出一个具体的实体,而每个实体具有自己的属性和功能。那么就可以将这些包装在一个类里面。因此类就成为了此实体的一个模板。当需要这个模板的对象时,我们只需要通过模板进行构造,设置每一个对象的属性,那么这就是一个个的具体的实例
- 封装(encapsulation)
在上面的类的叙述中,我们知道了类可以将一个实体的属性和功能进行包装在一个类中。那么如果将一个实体中的某些属性值设置为对外不可见,外部不能直接的使用这个属性,只能通过该实体的某些方法进行访问得到。这样的行为就叫做封装。
封装的实现的关键点就在于绝对不能让类中的方法直接地访问其他类的实例。封装给对象赋予了黑盒的特征。
- 对象
对象其实就是类的实例,也就是一个具体的实体,该实体具有自己特有的属性和功能。如一个学生类,现在new出一个名为张三的学生。那么张三就是一个对象。
对象的主要特性:
- 对象的行为:可以对对象施加哪些操作(其实就是方法)
- 对象的状态:当施加方法时,对象如何响应(方法的调用方式,类名调用或是对象调用,也就是静态或者非静态)
- 对象的标识:如何分辨同一个类new出来的对象。每个new出来的对象的地址一定不同
- 类之间的关系
常见的关系有:
- 依赖关系:一个类的方法可以操作另一个类的对象
- 聚合关系:一个类的对象包含了另一个类的对象,如Person类对象包含了Date类的对象。
- 继承关系:一个类中从另一个类中获得了一些方法,这种关系叫做继承
二、 对象的使用
通过上面的分析,面向对象的编程就是要使用对象去完成对应功能。而要想使用对象,那么就必须先构造对象,并指定其初始对象。而在Java语言中构造对象是使用类的构造方法通过new运算符进行实现的。
- 对象变量
对象变量只是一个以某一个类定义的一个变量而已,它没有任何的指向,里面存的值只是一个null值,也就是说和int a;的a一样,只是的变量名而已,在栈区建立的一块内存空间的一个名字而已。如下图所示:
- 对象
对象则是一个实体,它里面包含着该对象的一些属性值,它是通过某一个实体抽象出来的class的构造方法new出来的依据具体的实例(也就是一个实体),如new Date();,那么这个就是一个Date类的对象,保存在堆区。如下图所示:
- 引用对象
引用对象其实就是将一个类new出来的对象的地址,赋值给该类声明的一个变量,使该该变量引用该类的对象(也就是说使该类变量指向该类new出来的对象),具体见下图:Date brithday = new Date();
- 注意:
- 每一次new出来的对象均是一个独立的对象,也就是每new一次,在堆区都会创建一个Date的对象,它们都有自己独立的内存单元保存自己的数据。因此每个new出来的对象的地址是不同的。
- Date brithday;,这个只是定义了一个Date类的对象变量而已,它不是一个对象,实际上也没有引用对象,但是它可以引用Date类型的对象和Date的子类对象。
- 对于没有初始化的对象变量(也就是没有引用对象的对象变量)是不可以调用任何对象的方法的,这会产生编译错误。因为对象变量没有引用对象,那么存的就是null,而null是空,一个空对象变量是没有任何指向的。
- 在Java中,任何对象变量的值都是对存储在另一个地方的一个对象的引用(这其实可以理解成C中的指针,但又不同于指针)
- 代码示例:
public class Test1 {
public static void main(String[] args) {
// 创建一个对象变量
Object obj1 = null;
// 将对象变量引用一个对象
obj1=new Object();
// 创建另一个对象变量并引用对象
Object obj2 = new Object();
// 对象变量调用Object类的方法
boolean bool = obj1.equals(obj2);
// 打印hours的值
System.out.println("true:表示是同一对象; false:表示 不是同一变量 ----->" + bool);
//打印两个对象的地址
System.out.println(obj1);
System.out.println(obj2);
}
}
三、用户自定义类
上面的内容介绍了面向对象的基本思想,面向对象解决问题的本质就是抽象实体创建类,通过类构造对象调用对象的功能去解决实际的问题。那么如何创建自己的类呢?见下面的详细介绍:
- 通过代码分析自定义类:
Student类Code:
public class Student {
private int age;
private String name;
private String gender;
// 默认构造
public Student() {
}
// 带参数构造
public Student(int age, String name, String gender) {
this.age = age;
this.gender = gender;
this.name = name;
}
//年龄增加函数
public void addAge() {
age = age + 1;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
调用Code:
public class StudentDemo {
public static void main(String[] args) {
Student student = new Student();
student.setAge(20);
System.out.println("student1 age:" + student.getAge());
Student student2 = new Student(1,"Geore","男");
student2.addAge();
System.out.println("student2 age:" + student2.getAge());
}
}
运行截图:
- 分析a)的源码:
- 自定义类的格式:
class ClassName{
// 成员变量
field1
field2
field3
// 构造方法
constractor1
constractor2
// 成员方法
method1
method2
}
- 自定义一个类一般由成员变量,成员方法,构造方法和更改器(setXxx),访问器(getXxx)组成
- 对于成员变量,一般把成员变量设为私有的,即用private修饰
- 对于构造方法,当用户没有自定义构造方法时,会自动创建一个默认的构造方法(及无参构造),当创建了构造方法时,那么默认构造将消失,如果想保留默认构造则需自定义一个无参构造。
- 构造方法的用途:
- 构造类的对象(也就是实例化类)
- 带参构造可以初始化成员变量
- 对于私有的成员变量(private修饰的),在本类是可以访问的,但是在外部类是不可以访问的,如何访问和设置私有变量
- 通过更改器和访问器进行私有成员变量的操作
- 通过构造方法进行设置
- 上面Code中的this,this是一个关键字,它表示的是自身对象,也就是Student的实例化对象。(如果某个Student对象调用了某个方法,那么方法中的this就是这个Student对象)
- 构造方法的特点
- 与类同名
- 每个类至少有一个构造方法
- 构造方法可以设为私有的(用于创建单例模式或者静态类)
- 构造方法可以带参
- 构造方法总是和new操作一起调用
- 将成员变量设为私有的目的:实现封装,防止外部随意的修改成员变量的值。
- 隐式参数和显示参数
如下面这段代码:
//年龄增加函数
public void addAge() { age = age + 1; }
代码中我们看到,age在进行+1操作,但是我们知道,对于一个类来说,这个年龄的增加,应是对某一个类的对象的年龄+1,但是在上面的Code中却没有对象,那么这个年龄+1是对谁操作的呢?
其实这里隐藏了一个参数,如下代码:(其实是隐藏了this这个参数)
public void addAge() {
this.age = this.age + 1;
}
我们来看调用这个addAge方法的调用者,student2.addAge();,student2调用的addAge(),那么通过上面的this的解释,知道谁调用方法,那么方法的this就是那个对象。因此代码有可以表示成如下形式:
public void addAge() {
student2.age = student2.age + 1;
}
那么这个就是student2就是隐藏的参数。
四、 封装
封装(Encapsulation)是面向对象方法的重要原则,就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。通俗的说,Java的封装其实就是将类的某些属性和成员方法设为私有的,禁止外部的访问。这里对于这些私有的属性和方法就相当于隐藏在了一个黑盒里,对外不可见。如果要访问则需通过该类提供的更改器和访问器或者某些特殊方法访问。
- 优点:
- 防止对象域遭到外界的破坏
- 保护数据的安全性(外部不能随意访问,反射机制除外)
- 封装的时候不要编写含有返回引用可变对象的访问器方法,这样会破坏封装性。如下代码:
class Student{
private Date date;
public Date getDate(){
return date;
}
}
上面这段代码将在调用getDate的时候,返回一个Date类的对象引用,使得该Student类的date变量指向的不是Student对象存储的Date的引用,而是直接指向Date对象。如下图:
五、 域的初始化
在Java中一个好的习惯是对定义的变量要及时的进行初始化,那么对类的成员变量的初始化就叫做域的初始化。域的初始化分为以下四种种:
- 默认域初始化
如果在构造器中没有显示的赋给域初值,且在定义域的时候也没有给它相应的初始化值,那么这些域就会自动地赋为默认值:数值为0,布尔为false,对象引用为null。
这种初始化的方式,不推荐。使用默认域初始化是一种不好的编程习惯。
- 构造初始化
无参构造初始化,一般在无参构造的方法体中给出适当的初值,如下代码:
public Student() {
this.name = "name";
this.age = 0;
this.gender = "gender";
}
有参构造初始化,如下代码:
public Student(int age, String name, String gender) {
this.age = age;
this.gender = gender;
this.name = name;
}
- 显示初始化
显示初始化,就是在声明中赋值,如下代码:
private int age = 0;
private String name= "name";
private String gender = "gender";
- 初始化块初始化
在Java中还有一种机制,称为初始化块,在这些初始化块中对域进行初始化称为初始化块。其中初始化块又分为两种,如下
对象块
- 对象块就是由一对{......}包含的语句块,且这个语句块放置在类中,而不是类的方法中
- 对象块的启动,只有在创建类的对象的时候,才会执行对象块。
- 在加载类的时候,对象块是不会执行的。
- 代码展示:
public class Teacher { private int age; private String name; private String gender; //演示对象块 { this.age = 20; this.name = "Peter"; this.gender = "男"; System.out.println(age + " : " + name + " : " + gender); } public static void name() {} }
public static void main(String[] args) { // 通过调用静态方法,加载Teacher类,而加载类时候,对象块不会调用 System.out.println("加载类时候,对象块不会调用, 不会有任何输出结果: "); Teacher.name(); //加载类 System.out.println(); // 创建对象的时候,对象块调用 System.out.println("创建对象的时候,对象块调用, 有输出结果: "); Teacher teacher = new Teacher(); //创建对象 }
运行图:
静态块
- 用static修饰的对象块就是静态块,且这个语句块放置在类中,而不是类的方法中
- 静态块的启动是在加载类的时候,就开始执行。
- Code演示:
public class Teacher {
private static int age;
private static String name;
private static String gender;
//演示对象块
static{
age = 20;
name = "Peter";
gender = "男";
System.out.println(age + " : " + name + " : " + gender);
}
public static void name() {}
}
public static void main(String[] args) {
// 通过调用静态方法,加载Teacher类,而加载类时候,对象块会调用
System.out.println("加载类时候,静态块调用,有输出结果: ");
Teacher.name(); //加载类
System.out.println();
// 创建对象的时候,对象块调用
System.out.println("创建对象的时候,静态块不会调用,无输出结果: ");
Teacher teacher = new Teacher(); //创建对象
}
扩展知识点:
- 类加载的时机:
- 调用该类的静态方法
- 使用该类的静态成员变量
- Java的初始化方式很多,所以列出构造过程比较的混乱,因此Java有一套调用构造的具体的处理步骤:
- 所有数据域被初始化为默认值
- 按照在类的声明中出现的次序,一次执行所有域初始化语句和初始化块
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器
- 执行这个构造器的主体
- Java中的消息:
- 调用对象的方法就是给对象发送了一个消息
- 一个对象如果能够接受某种消息,就意味着该对象对外部提供了某种服务
- Java的访问修饰符表: