一、面向对象简述。
面向对象是一种现在最为流行的程序设计方法,谈到面向对象就不得不提及面向过程。
这是两种不同的程序设计方法,常见的c语言用的是面向过程,而java、c++用的是面向对象。
对两种思想抽象地定义,
面向过程是围绕功能进行的,为每一个功能写一个函数,需要考虑其中的每一个细节,以步骤划分;
而面向对象则像是组装,先确定一个系统是由哪些对象组成,再分别去设计这些对象,将它们像零件一样组装起来形成有完整功能的系统。
再具体举一个常用的例子:用这两种方法分别实现“把大象放进冰箱”。
面向过程:肢解大象-打开冰箱-放入大象-关闭冰箱
面向对象:大象(肢解功能、移动功能)、冰箱(打开功能、装载功能、关闭功能)。
面向对象的三大特性。
(1)封装:保护内部的操作不被破坏;
(2)继承:在原本的基础之上继续进行扩充;
(3)多态:在一个指定的范围之内进行概念的转换。
二、类与对象的基本概念
类与对象时整个面向对象中最基础的组成单元。
类:是抽象的概念集合,表示的是一个共性的产物,类之中定义的是属性和行为(方法);
对象:对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同对象。
比如说:人类是一个类,每一个人是类中的一个对象,每个人都有身高,体重,性别,外貌等等的属性。
在编程设计中,我们需要定义一个类,再给对应的类定义具体对象。
三、类与对象的定义和使用
类定义范式:
class 类名称{
属性;
行为;
}
定义Person类:
class Person{
String name;//名字
int height;//身高
int age;//年龄
public void walk(){
System.out.print(name+"会走路");
} //走路
public void tell(){
System.out.print("名字:"+name+";"年龄:"+age);
}
}
类定义完成之后,无法直接使用。如果要使用,必须依靠对象,对象的产生格式(两种格式)如下:
(1)格式一:声明并实例化对象
类名称 对象名称 = new 类名(); //格式
Person person = new Person();//Person实例
(2)格式二:先声明对象,再实例化对象
类名称 对象名称 = null;
对象名称 = new 类名(); //格式
Person person = null;
person = new Person(); //Person实例
不同于int、String、char等基本数据类型,类属于引用数据类型,而引用数据类型在使用时需要用内存空间的分配和使用,关键字new就是用来分配内存的。
实例化一个对象后,可以用关键字"."对对象进行类的操作,比如定义属性具体值,使用方法。
对象名.属性名 = 具体值;
对象名.方法名();
person.age = 18;
person.walk();
接下来用在eclipse实践一下这两种实例化对象的方法。
格式一:
package com.alex.classtest;
class Person {
String name ;
int age ;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
Person per = new Person() ;// 声明并实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
运行结果:
姓名:张三,年龄:30
格式二:
package com.alex.classtest;
class Person {
String name ;
int age ;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
Person per = null;//声明对象
per = new Person() ;//实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
运行结果:
姓名:张三,年龄:30
我们可以发现,得到的结果是一样的。那么这两种方式的区别在哪呢?
说到这里就必须从内存的角度来分析。
首先,给出两种内存空间的概念:
(1)堆内存:保存对象的属性内容。堆内存需要用new关键字来分配空间;
(2)栈内存:保存的是堆内存的地址(在这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)。
任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。
上面两种对象实例化对象方式内存表示如下:
两种方式的区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。
如果使用了没有实例化的对象,则会程序运行结果会报错。
package com.alex.classtest;
class Person {
String name ;
int age ;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
Person per = null;//声明对象
//per = new Person() ;//实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
运行结果:
Exception in thread "main" java.lang.NullPointerException
at com.wz.classandobj.TestDemo.main(TestDemo.java:15)
错误信息表示的是“NullPointerException(空指向异常)”。是因为程序只声明了Person对象,但并没有实例化Person对象(只有了栈内存,并没有对应的堆内存空间),程序在编译的时候不会出现任何的错误,但是在执行的时候出现了上面的错误信息。
四、对象引用传递初步分析
引用传递定义上指的是同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。
也就是说同一个对象的属性内存被两个不同命名的对象共同使用。
看下面实例
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:" + name + ",年龄:" + age) ;
}
}
public class TestDemo {
public static void main(String args[]) {
Person per1 = new Person() ; // 声明并实例化对象
Person per2 = new Person() ;
per1.name = "张三" ;
per1.age = 20 ;
per2.name = "李四" ;
per2.age = 30 ;
per2 = per1 ;// 引用传递
per2.name = "王五" ;
per1.tell() ;
}
}
输出的结果是:
姓名:王五,年龄:20
在这个过程中,内存空间是这样变化的:
垃圾:指的是在程序开发之中没有任何对象所指向的一块堆内存空间,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)不定期的进行回收与空间的释放。
五、面向对象的封装性
在定义类时,在class或者属性变量之前加上修饰词public、private、default、protected表示它的被访问权限。
先看属性变量没有修饰词也就是默认(default)的时候,
class Book{
String title;
double price;
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();
book.title = "Quanta";
book.price = 99;
book.getInfo();
}
}
输出的结果是:
图书的名称:Quanta 图书的价格:99
当属性变量修饰词为private时:
class Book{
private String title;
private double price;
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();
book.title = "Quanta";
book.price = 99;
book.getInfo();
}
}
运行结果:
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
The field Book.title is not visible
The field Book.price is not visible
at com.wz.classandobj.TestDemo.main(TestDemo.java:16)
也就是在加入private修饰词后无法再Book类的外部直接调用它的属性了。
那么如何在类外部访问Book类内的private(私有)变量呢?这时候可以用到setter、getter方法。
为Book类定义属性相应的setter和getter方法,以Book类为例:
class Book{
private String title;
private double price;
public void setTitle(String title){
this.title = title;
}
public String getTitle(){
return title;
}
public void setPrice(double price){
this.price = price;
}
public double getPrice(){
return price;
}
public void getInfo(){
System.out.println("图书的名称:"+getTitle()+" 图书的价格:"+getPrice());
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();
book.setTitle("Quanta");
book.setPrice(99);
book.getInfo();
}
}
五、类中的构造方法
通常我们实例化对象的时候用的是这样的格式
①类名称 ②对象名称 = ③new ④类名称();
①类名称:规定了对象的类型。即:对象可以使用哪些属性和方法都是由类定义的;
②对象名称:如果需要使用对象,需要有一个名称,这是一个唯一的标记;
③new:分配新的堆内存空间;
④类名称():调用了名称和类名称相同的方法,这个地方就是我们所讲的构造方法。
实际上,构造方法一直在被我们调用,但我们并没有去定义它,为什么能够使用呢?这是因为在整个Java开发中,为了保证程序可以正常执行,即便用户没有定义任何构造方法,也会在程序编译后自动为类增加一个没有参数,方法名称与类名称相同,没有返回值的构造方法。
当我们创建一个类时,类中会默认生成一个无参构造方法(只是并没有显示,当我们用默认格式生成对象实例的时候便是调用了该构造方法)。
//无参,无返回值的构造方法
public Book() {
}
当我们给构造方法中加入一条打印语句后
class Book{
private String title;
private int price;
//无参,无返回值的构造方法
public Book() {
System.out.println("无参构造方法");
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();//实例化对象,等于调用Book构造方法
}
}
输出结果:
无参构造方法
自定义带参数的构造方法:
class Book{
private String title;
private int price;
public Book() {
System.out.println("无参的构造方法");
}
public Book(String title) {
this.title = title;
System.out.println("有一个参数的构造方法");
}
public Book(String title, double price) {
this.title = title;
this.price = price;
System.out.println("有俩个参数的构造方法");
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book1 = new Book();
book1.getInfo();
Book book2 = new Book("Quanta");
book2.getInfo();
Book book3 = new Book("Quanta",99);
book3.getInfo();
}
运行结果:
无参的构造方法
图书的名称:null 图书的价格:0
有一个参数的构造方法
图书的名称:Quanta 图书的价格:0
有俩个参数的构造方法
图书的名称:Quanta 图书的价格:99
可以看到通过默认构造方法创建的book1属性是null和0,而book2有title属性,book3有title和price;
注意:如果定义了带有参数的构造函数,则默认构造函数失效,原来的默认函数(没有参数的默认函数)需手动定义!
六、类中的匿名对象
没名字的对象称为匿名对象,对象的名字按照之前的内存关系来讲,在栈内存之中,而对象的具体内容在堆内存之中保存,这样,没有栈内存指向堆内存空间,就是一个匿名对象。
class Book{
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
System.out.println("有俩个参数的构造方法");
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
//匿名对象
new Book("Quanta",99).getInfo();
}
}
结果:
有俩个参数的构造方法
图书的名称:Quanta 图书的价格:99
可以看到不用命名对象而直接生成Book类的匿名对象并调用它的方法。
课后实践:编写一个Person类,
(1)Person类有私有属性姓名,性别,年龄,身高;
(2)有方法speak():输出语句“哈哈哈”;
(3)方法tell():输出姓名、性别、年龄、身高;
(4)对应的私有属性要有相应的setter、getter方法;
(5)自定义一个带有全部参数的构造方法;
main函数中要求用默认方式实例化一个对象,再用对应的set方法定义他的属性,另外用自定义的构造方法实例化一个带参数的对象,最后两个对象分别使用tell方法。