2. 面向对象(第五天)

一、 什么是面向对象?

1."面向对象"的概述

  • “面向对象”是一种思考方式,更早的“面向过程”也是一种思考方式。
  • “面向对象”是基于“面向过程”的,但它更强调解决问题所需的功能(或者说行为、动作),而不是解决的过程。
  • “面向对象”的思想随着计算机程序设计发展而出现,更好地满足了现代开发复杂的需求。

2.与“面向过程”的对比

思考一下,如何把一只大象放进冰箱吧?

根据“面向过程”的思想,可以化解为三个步骤,过程是: 打开冰箱门 → 把大象放进去 → 关闭冰箱门。

可以看出,整个思路强调的是“怎样去做”,注重行为、功能、动作,

事件本身的解决过程很贯穿始终。

但我们如果不想放大象了,想放一条鱼进去,难道又要把这事件过程的整个代码再写一次?

要知道无论放什么,这个过程的行为始终都是一样的,只是放进去的东西不一样而已。

无论打开、关闭、存放,都是冰箱的行为,

所以,我们只要操作冰箱的就可以了。

由此可见,我们可以把冰箱看作一个对象,

定义冰箱这个对象并且让它具备“打开、放东西、关闭”这样的功能,

这样,我们再放东西进冰箱,只要适当的调整即可。

这就是“面向对象”的思想,可以根据需要适应变化,不用像“面向过程”基本上要推倒重来。

再思考一下,如果两个师傅分别用“面向对象”和“面向过程”的思想做一个首饰盒,会发生什么?

“面向过程”师傅,客户说做什么样子,他就直接根据要求做一个完整的整体出来,

也不准备好做首饰盒的工具,需要什么工具就拿什么工具。

“面向对象”的师傅,他会根据客户的要求进行分析,做成图纸跟客户确认,

并且将平时常用的工具准备好后,再来根据确认好的图纸分块制作首饰盒,

最后各块完成才可并到一起。日常生活不少这样的例子,哪个师傅生意会更好,显而易见。

其实,甚至你目前浏览器右上角上的“最小化、最大化、关闭”按钮,也是运用了“面向对象”的思想。

“面向对象”的思想,将复杂的问题变简单了,让代码从面向多种的功能转为面向一个对象即可。

3.三个特性

  • 封装性:将“对象”的属性和行为看成一个整体,除了使“对象”变成一个独立的程序设计单位外,还起到了隐蔽信息的作用。
  • 继承性:将具有一般特性的“对象”归类看作基本类,如果在其之上又有特殊特性的“对象”,只要将它在基本类上进行扩展即可。(车 —— 汽车、自行车)
  • 多态性:两种,一个是方法重载(方法的名字相同,传入参数不一样 —— 即允许完成一个“对象”的行为,可以有不一样的前提条件);一个是对象多态(父类对象与子类对象互换,完成不同的功能)。

4.怎样运用“面向对象”的思想?

名词提炼法分析事件存在哪些对象,例如:人开门,提炼“人”和“门”两个对象;

根据提炼找对象用,如果没有就自己创建。

Q:用“面向对象”的思想,解释一下软件公司为什么要招聘程序员去干活?(提示:提高工作效率)

二、 类与对象的关系

1.“类与对象”的关系

  • 类:某类群体、某类对象一些基本特征的抽象。
  • 对象:类具体的个体。
  • 关系:提取对象的共性内容,对其进行抽象,即可将多个对象描述为一个类。
  • 例子:对于张三、李四这样的个体对象,想要描述他们,可提取他们的共性(姓名、年龄、性别、学习java的行为),然后进行描述,即他们属于“人”类。

在Java中:就是class关键字定义类,然后具体对象就是对应Java在堆内存中用new关键字建立的实体。

再一个例子:“类与对象”,就类似于“生产汽车的图纸”和“生产出来的汽车”,图纸规定了汽车的基本组成信息。

2.类的定义

描述事物,其实就是在描述事物的属性和行为。

因此,简单理解,其实 类 = 属性(某类对象的基本信息) + 方法(又称为行为,是这类对象的操作行为);

属性和 方法又共同称为类中的成员,成员变量和成员方法。

3.对象的创建和使用

(1)    对象的创建

public class TestPerson
{
	public static void main(String[] args){
		Person p = new Person();
		p.tell();
	}
}

class Person
{
	String name;
	int age;

	public void tell(){
		System.out.println("我的名字叫"+name+",我今年"+age+"岁.");
	}
}
 
上面是一个简单的创建Person类对象p的代码,

可以看到,Java中通过new关键字创建Person的对象。

那么这个创建的过程,在内存中的情况是怎样的呢?详见下图:

通过上图可以看出,创建一个Person对象,

实际上在栈内存保存的是其对应堆内存的访问地址,而不是真正的对象。

过程可拆分为:声明对象(Person p = null;)  → 实例化对象(p = new Person();)。

(2)    对象的使用

要使用对象,实际上也就是操作某个对象的属性或者方法,一般是这样操作的:

访问属性:对象名称.属性名

访问方法:对象名称.方法名

对应上面创建Person p的例子,也就是

p.age;
p.tell();

备注:要使用对象,则必须实例化,否则会提示错误,抛出异常NullPointerException。

三、 成员变量和局部变量

直接上代码,请看:

class Car {
	int num = 4;
	String color = "红色";
	void run() {
		System.out.println(color + " ... " + num);
	}
	
	public static void main(String[] args) {
		//可以,但Demo类的main方法才会作为程序入口
	}
}

class Demo {
	public static void main(String[] args) {
		Car c = new Car();
		c.color = "blue";
		c.run(); //blue ... 4
		Car c1 = new Car();
		c1.run();  //红色 ... 4
	}
}

上面的程序中,发现Car类没有主函数。主函数的功能是什么?

为了保证一个类的独立运行。而当类不需要独立运行,就可以不写主函数。

只要建立类的对象,就能使用类里面的东西。

程序中num、color这样定义在函数外面的变量,是成员变量。成员变量与局部变量有什么区别呢?

  • 作用范围不一样。成员变量作用于整个类中,局部变量作用于函数中,或者说语句中。
  • 在内存中的位置不一样。成员变量在堆内存中,因为对象的存在而存在于内存中。局部变量则存在栈内存中。

Q:能不能再car类中又写个主函数?

A:可以,但一个程序有一个入口就行,不需要搞多个入口。

四、 匿名对象的应用

匿名顾名思义就是没有名字,匿名对象是对象的简化形式,有两种使用情况:

  • 当对对象方法仅进行一次调用时
  • 匿名对象可以作为实际参数进行传递

“凡是简化的都有局限性”,匿名对象有其应用场景但也有其弊端。

class Car {
	int num = 4;
	String color = "红色";
	void run() {
		System.out.println(color + " ... " + num);
	}
}

class Demo {
	public static void main(String[] args) {
		new Car().num = 5;
		new Car().color = "blue";
		new Car().run();//红色 ... 4
	}
}

在上面的代码中,“new Car().num = 5;”这句刚执行完,

下一个要执行时,它就变垃圾了,因为没人用。

只有run()方法那句打印了还有其意义。

匿名对象使用方式一:

当对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。如果对一个对象进行多个成员调用,必须给这个对象起个名字。

需求:汽车修配厂,对汽车进行改装,将来的车都改成黑色,三个轮胎。

class Car {
	int num = 4;
	String color = "红色";

	void run() {
		System.out.println(color + " ... " + num);
	}
}

class Demo {
	public static void main(String[] args) {
		//Car q = new Car();
		//show(q);
		show(new Car()); //匿名对象作为参数传递
	}

	public static void show(Car c) {
		c.num = 3;
		c.color = "black";
		c.run();
	}
}

匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。

对象在内存中的情况如下图所示:

五、 封装

1.封装的定义:

隐藏对象的属性和实现细节,仅仅对外提供公共访问方式;是“面向对象”的一大特性。

2.封装的好处:

将变化隔离,便于使用,提高重用性,提高安全性。

3.封装的原则:

将不需要对外提供的内容都隐藏起来。把属性都隐藏,提供公共方法对其访问。

4.封装的类似例子:

在实际生活中,电脑的USB接口和耳机孔等插口,都可以看作封装的例子。

5.封装的具体代码实现:

class Person{

private int age;

public void setAge(int a){
age = a;
}

public int getAge(){
return age;
}

void speak(){
System.out.println("age="+age);
}

}

class TestPerson{

public static void main(String args[]){
Person p = new Person();
//p.age = -20;//编译出错
p.setAge(20);
p.speak();
}

}

通过上面的代码可以看出,private关键字将对象p的age属性进行了封装,

隐藏了age属性,仅仅提供了setAge()方法给用户操作,提高了安全性。

6.private关键字:

它是代表 私有 的权限修饰符(另外还有常见的public、默认的default和protected),

用于修饰类中的成员(成员变量,成员方法),

将属性私有化后,它只在本类中有效,类以外即使建立了对象也不能访问该成员,

 —— class Person中tell方法可以调用age,但在TestPerson中,调用p对象的age就会出错。

所以一般会提供getter()和setter()方法,用于访问私有属性。

另外,要注意,私有仅仅是封装的一种表现形式。不私有,也能实现封装。

7.对外提供访问方式的原因:

在访问私有属性的方法中对访问的数据进行操作,加入逻辑判断等语句,可提高代码的健壮性。

例如上面Person的setAge(),人是没有负数的岁数的,加上判断设置的年龄是否大于零,

才能保证数据符合现实中的情况。

8.程序的健壮性:

处理异常的能力,在异常中能够独立处理异常,并且把正确的答案输出。
例如:有一个程序能够下载一个文件到指定的路径,但是这个路径是不存在的,因此程序必须要处理这个情况。
这里就是程序的健壮性,让程序在更多的情况下正确运行。

六、 构造函数和构造代码块

1.什么是构造方法(构造函数)?

  • 构造方法,就是在实例化一个类的对象时,直接对对象属性进行赋值的方法。

  • 构造方法的作用:给对象的属性初始化。

2.构造方法的特点

  1. 构造方法名称与类名一致

  2. 定义构造方法,不用定义返回值类型。

  3. 构造方法里不能return 一个值(有第2点,这一点就显而易见了)。

3.默认的构造方法及重载

看本博文上面new Person()的例子程序,可以发现并没有定义Person类的构造方法,

因为当一个类没有定义构造方法时,系统会默认给该类加入一个空参数的构造方法。

class Person{

Person(){}

}

因为这个默认的构造方法并不能满足初始化的需要,

因此,很多时候需要对其进行重载,例如:

public Person(String name,int age){

this.setName(name);

this.setAge(age);

}

可以看出,这样比实例化对象后,再用setter方法赋值要简便不少。另外,还能多次重载。

并且要注意,如果类中已明确自定义构造方法,默认的构造方法将不再生成。

4.构造方法与普通方法的区别?

(1) 顺序上:构造方法和一般方法除了写法上有不同,在运行上也有不同。构造方法是在对象一建立就运行,给对象进行初始化,而一般方法是对象调用才执行,是给对象添加对象具备的功能。

(2) 次数上:一个对象建立,构造方法只运行一次,而一般方法可以被该对象调用多次。

什么时候定义构造方法呢?当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造方法中。

5.构造代码块

(1)  什么是代码块?

参考本博文上面的例子程序,用“{}”大括号扩起来的就是代码块。

(2)  代码块有哪几类?

  • 代码块有四类:普通代码块、构造代码块、静态代码块、同步代码块。
  • 普通代码块:直接在方法或语句中定义的代码块。
  • 构造代码块:直接写在类中的代码块。
  • 静态代码块:使用static关键字声明的代码块。
  • 同步代码块:略。在多线程部分再进行讲解。

(3)  构造代码块的作用?

  • 构造代码块中定义的是不同对象共性的初始化内容。
  • 作用是给对象进行初始化,用来定义共性的东西。
  • 对象一建立就运行,而且优先于构造方法执行。

(4)  和构造方法的区别

构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象进行初始化。

6.构造方法和构造代码块执行顺序的问题

public class TestPerson
{
	public static void main(String[] args){
		Person p1 = new Person("Tom",20);
		Person p2 = new Person("Lilei",21);
		Person p3 = new Person("Mary",22);

	}
}

class Person
{
	private String name;
	private int age;

	public Person(String name,int age){
		
		this.setName(name);
	    this.setAge(age);
		System.out.println(this.getName()+"的构造方法.");
	}

	public void setName(String name){
	this.name = name;
	}

	public String getName(){
	return this.name;
	}

	public void setAge(int age){
	this.age = age;
	}

	public int getAge(){
	return this.age;
	}

	{
	System.out.println(this.getName()+"的构造代码块.");
	}

	static{
	System.out.println("该类的静态代码块.");
	}
}

输出如下:

该类的静态代码块.
null的构造代码块.
Tom的构造方法.
null的构造代码块.
Lilei的构造方法.
null的构造代码块.
Mary的构造方法.

从这个例子程序我们可以看出,

静态代码块优先于main方法执行,并且只执行一次。

构造代码块优先于构造方法执行,每次实例化对象时,

都会先执行构造代码块,再执行构造方法。

关于静态代码块,还有一个有趣的小例子:

public class TestStatic
{
    static{
    System.out.println("HelloWorld!");
    System.exit(1);
    }
}

上面这个代码,还没找main函数,就已经打印推出了,不提示错误信息。

上面这些代码块的执行顺序要弄清楚,虽然平时不用,也就面试可能会考到。

7.构造方法的私有化

将构造方法私有化,可以控制实例化对象的产生。(可以是,多个引用全部指向一个对象)

在下面讲解单例设计模式的时候,再详细讲。

七、 this关键字及其运用

1.this关键字代表什么?

this关键字表示当前对象的引用,也就是当前正在调用类中方法对象的引用。

简单说:哪个对象在调用this所在的方法,this就代表那个对象。

public class TestThis
{
	public static void main(String[] args){
		Person p1 = new Person();
		System.out.println("main方法中p1对象的地址"+p1);
		System.out.println("getInfo()方法中p1对象的地址"+p1.getInfo());
		Person p2 = new Person();
		System.out.println("main方法中p2对象的地址"+p2);
		System.out.println("getInfo()方法中p2对象的地址"+p2.getInfo());
	}
}

class Person
{
	public Person getInfo(){
		return this;
	}
}

输出如下:

可以看出,对象p1、p2的地址分别是107077e和7ced01,

当调用各自对应的getInfo( )方法后,返回那个正在调用方法的对象的地址,

main和getInfo方法的地址相一致。

由此可见,this关键字表示当前对象的引用。

2.何时用this关键字调用属性?

当需要明确指出赋值的属性,避免局部变量和成员变量同名导致调用不清晰的问题。

例如下面的代码:

Person(String name){
name = name;
}

虽然用了具有含义的单词做变量名,还是避免不了赋值错误的情况,

因为代码块中两个name,实际上都是传入构造方法的局部变量name,

因此会产生这个Person对象name属性为null的情况。

(在变量名相同的情况下,会采用就近原则,先用局部变量,再用成员变量。)

3.何时用this关键字调用方法?

本类功能内部用到本类的时候,都是用this。

当需要对某个方法用重载的方式对原功能进行扩展时,也可以用this关键字,

省去再写原来代码的麻烦。

以比较特殊的构造方法来举例子(不封装了,仅突出重点):

class Person
{
    String name;
    int age;

    public Person(String name){
        this.name = name;
    }

    public Person(String name,int age){
        this(name);
        this.age = age;
    }

}


可以看出,Person类提供了两个构造方法,并且其中一个用this关键字调用了另外一个。

用this关键字调用构造方法要注意的两点:

要放第一行,因为构造方法在类的所有方法中,是优先被调用的。

要提供一个出口,不要每一个构造方法里都用this调用其它的构造方法。

只用this关键字,不要用类名,不要像下面这样:

	public Person(String name,int age){
		Person(name);
		this.age = age;
	}

4.this的练习:给Person类一个比较年龄的功能,看是否是同龄人。

public class PractiseThis
{
	public static void main(String[] args){
	 Person p1 = new Person("Tom",22);
	 Person p2 = new Person("Lilei",22);
	 Person p3 = new Person("Tom",21);
	 System.out.println("p1、p2是否同龄人:"+p1.compareTo(p2));
	 System.out.println("p1、p3是否同龄人:"+p1.compareTo(p3));
	}
}

class Person
{
	private String name;
	private int age;

	public Person(String name,int age){
		this.setName(name);
	    this.setAge(age);
	}

	public void setName(String name){
	this.name = name;
	}

	public String getName(){
	return this.name;
	}

	public void setAge(int age){
	this.age = age;
	}

	public int getAge(){
	return this.age;
	}

	public boolean compareTo(Person p){
		if(this==p){
		return true;
		}

		if(this.getAge()==p.getAge()){
		return true;
		}else{
		return false;
		}
	}

	
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值