面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象来映射现实中的事物,使用对象的关系来描述事物之间的联系,这种思想就是面向对象。
面向对象是把构成问题的事务按照一定规则划分为多个独立的对象,然后通过调用对象的方法来解决问题。一个应用程序会包含多个对象,通过多个对象的相互配合来实现应用程序的功能,这样当应用程序功能发生变动时,只需要修改个别的对象就可以了,从而使代码更容易得到维护。面向对象的特点主要可以概括为封装性、继承性和多态性。
封装是面向对象的核心思想,将对象的属性(property)和行为(behavior)封装起来,不需要让外界知道具体实现细节。
继承主要描述的就是类与类之间的关系,通过继承,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展(extend)。
多态指的是在一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特性。
面向对象的编程思想,力图让程序中对事物的描述与该事物在现实中的形态保持一致。为了做到这一点,面向对象的思想中提出了两个概念——类和对象。
类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体。简而言之,类是模板(template),对象是实例(instance)。
在面向对象的思想中,最核心的就是对象。为了在程序中创建对象,首先需要定义一个类。类是对象的抽象,它用于描述一组对象的共同特征和行为,例如人都有姓名、年龄、性别等特征,还有学习、工作、购物等行为。
以面向对象的编程思想,就可以将某一类中共同的特征和行为封装起来,把共同特征作为类的属性(property),也叫成员变量(member variable),把共同行为作为类的方法(method),也叫成员方法(member method)。
创建Person类,包含两个属性:姓名(name)和人品(character、
),另外还包含一个自我介绍的方法speak()。注意:要求人品值在[1, 10]。
package net.hw.lesson09;
/**
* 功能:Person类
* 作者:华卫
* 日期:2020年4月25日
*/
public class Person {
String name;
int character;
public void speak() {
System.out.println("嗨,我叫" + name + ",人品值:" + character);
}
}
在Java中,定义在类中的变量被称为成员变量(member variable),定义在方法中的变量被称为局部变量(local variable)。如果在某一个方法中定义的局部变量与成员变量同名,这种情况是允许的,此时方法中通过变量名访问到的是局部变量,而并非成员变量。
public class Person {
String name = "李晓红"; // 类中定义的变量被称作成员变量
int character = 3; // 类中定义的变量被称作成员变量
public void speak() {
int character = 7; // 方法内部定义的变量被称作局部变量
System.out.println("嗨,我叫" + name + ",人品值:" + character);
}
}
分析: 上述代码中,speak()方法中所访问的变量character是局部变量,也就是说,当有另外一个程序来调用speak()方法时,输出的character值为7,而不是3。speak()方法中所访问的变量name是成员变量,当另一个程序来调用speak()方法时,输出的name值就是“李晓红”。
应用程序想要完成具体的功能,仅有类是远远不够的,还需要根据类创建实例对象。
类名 对象名称 = new 类名();
package net.hw.lesson09;
/**
* 功能:Person类
* 作者:华卫
* 日期:2020年4月25日
*/
public class Person {
String name;
int character;
public void speak() {
System.out.println("嗨,我叫" + name + ",人品值:" + character);
}
}
直接打印对象变量,输出的并不是对象的内容,而是对象名@哈希码。
类似的,大家可以参看Python代码:
如果要输出对象的内容,那么需要改写Person类的toString()方法,可以利用集成开发环境提供的快捷方式帮我们自动生成,如下GIF动画所示:
此时Person类的代码如下:
此时,再执行TestPerson,结果如下:
设置对象的属性与调用对象的方法:
package net.hw.lesson09;
/**
* 功能:测试Person类
* 作者:华卫
* 日期:2020年4月25日
*/
public class TestPerson {
public static void main(String[] args) {
// 实例化Person对象
Person p = new Person();
// 设置对象属性
p.name = "李晓红";
p.character = - 1;
// 输出Person对象
System.out.println(p);
// 调用对象方法
p.speak();
}
}
运行程序,查看结果:
显然这样做是不合理的,应该把属性私有化,统一地提供访问私有属性的公共方法,包括设置与读取私有属性值。在下一讲《Java讲课笔记10:类的封装》,我们会对Person类进行改进。
大家可以参看一下完成相同任务的Python代码:
Python类的__str__()方法就类似于Java类的toString()方法。
"""
创建类与对象
"""
class Person:
def setName(self, name):
self.name = name
def setCharacter(self, character):
self.character = character
def speak(self):
print("嗨, 我叫" + self.name + ",人品值:" + str(self.character))
def __str__(self):
return "Person {name='" + self.name + "', character=" + str(self.character) + "}"
if __name__ == "__main__":
p = Person()
p.name = "李晓红"
p.character = -1
print(p)
p.speak()
运行程序,查看结果:
name与character属性是Person类的对象属性,可以通过对象名.属性名的方式访问,也可以通过setXXX()方法来访问:
p.setName("李晓红")
p.setCharacter(-1)
在创建对象时,程序会占用两块内存区域,分别是栈内存和堆内存。其中Person类型的变量p被存放在栈内存中,它是一个引用,会指向真正的对象;通过new Person()创建的对象则放在堆内存中,这才是真正的对象。
小提示: Java将内存分为两种,即栈内存
和堆内存
。其中栈内存用于存放基本类型的变量和对象的引用变量(如Person p),堆内存用于存放由new创建的对象和数组。
对象引用.对象成员
new 类名().对象成员
成员变量可以不初始化,不同类型的成员变量有默认的初始值。但是要注意一点,局部变量是必须初始化的。
成员变量类型 | 初始值 |
---|---|
byte、short、int、long | 0 |
float、double | 0.0 |
char | 一个空字符,即’\u0000’ |
boolean | false |
引用数据类型 | null,表示变量不引用任何对象 |
{
Person p1 = new Person();
......
}
说明:变量p1引用了一个Person类型的对象,当这段代码运行完毕时,变量p1就会超出其作用域而被销毁,这时Person类型的对象将因为没有被任何变量所引用而变成垃圾。
{
Person p2 = new Person();
......
p2 = null;
......
}
说明:使用变量p2引用了一个Person类型的对象,接着将变量p2的值置为null,被p2所引用的Person对象就会失去引用,成为垃圾对象。
Java有垃圾自动回收机制。当然也可以调用System.gc()
方法来显式回收垃圾对象。
在Java中,针对类、成员方法和属性提供了四种访问级别,分别是private、default、protected和public。
如果类的成员被private访问控制符来修饰,则这个成员只能被该类的其他成员访问,其他类无法直接访问。类的良好封装就是通过private关键字来实现的。
如果一个类或者类的成员不使用任何访问控制符修饰,则称它为默认访问控制级别,这个类或者类的成员只能被本包中的其他类访问。
如果一个类的成员被protected访问控制符修饰,那么这个成员既能被同一包下的其他类访问,也能被不同包下该类的子类访问。
这是一个最宽松的访问控制级别,如果一个类或者类的成员被public访问控制符修饰,那么这个类或者类的成员能被所有的类访问,不管访问类与被访问类是否在同一个包中。
访问范围 | private | default | protected | public |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中 | √ | √ | √ | |
子类中 | √ | √ | ||
全局范围 | √ |
前面讲了,protected属性可以被不同包里该类的子类访问,下面我们演示如何访问。
按
组合键,可以查看Man类的继承层次结构:
然后在主方法里实例化Man对象,设置其属性name与character(都是父类Person里的protected属性),调用其方法speak(),如下图所示:
创建三角形类Triangle,包含a、b、c三个默认属性,一个公共方法getArea()。然后创建一个测试类TestTriangle,在其主方法里创建Triangle对象,设置对象属性,调用对象方法得到三角形面积。a、b、c暂时采用默认权限,下一讲我们再设置为private权限,再创建setters和getters来访问私有属性a、b、c。大家编写获取面积的方法getArea(),该方法有一个double类型的返回值。
创建一元二次方程类Equation,包含a、b、c三个默认属性,一个公共方法solve()。然后创建一个测试类TestEquation,在其主方法里创建Equation对象,设置对象属性,调用对象方法求解方程。a、b、c暂时采用默认权限,下一讲我们再设置为private权限,再创建setters和getters来访问私有属性a、b、c。大家编写求解方程的方法solve(),该方法没有返回值。