面向对象
1.面向对象概述
面向对象是相对于面向过程而言的,所以,理解面向对象,先要理解面向过程是什么。
面向过程(Procedure Oriented)就是通过分析要解决的问题,并拆分成若干个步骤,然后按照这些步骤依次进行,比如做一道菜,那就需要依次进行买菜、洗菜、切菜、炒菜、装盘这些步骤,这就是面向过程的思想。
面向对象(Object Oriented,OO)则是把构成问题的事务按照一定规则划分为多个独立的对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
面向对象编程(Procedure Oriented Programming,OOP)的本质:以类的方式组织代码,以对象的组织封装数据。
2.面向对象特性
2.1 封装
(1)封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现细节。比如玩手机,我们并不需要知道手机内部到底是怎么运作的,就可以使用手机。
(2)程序的设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法给外部使用。
(3)封装的优点:
- 良好的封装可以降低耦合度。
- 保护数据,可以对成员变量进行更精确的控制。
- 隐藏代码,实现细节。
- 增强系统的可维护性。
public class Student {
// 使用private对变量进行修饰,达到封装的目的
// 属性私有
private String name; // 姓名
private int age; // 年龄
private char sex; // 性别
// 使用public对方法进行修饰,以便外部能够通过使用get、set方法对属性进行操作
// 方法公有
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
// 对setAge()方法添加判断条件,外部看不到
public void setAge(int age) {
if (age < 120 && age > 0) {
this.age = age;
}else {
age = 0;
}
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
2.2 继承
(1)继承主要描述的是类与类之间的关系,继承可以在无需重新编写原有类的情况下,对原有类的功能进行扩展。
(2)继承的关键字是extends。Java中类只有单继承,没有多继承,即一个子类(派生类)有且仅能继承一个父类(基类、超类)。
/*动物类(父类)*/
public class Animal {
private String name;
private double weight;
public void eat() {
System.out.println("吃饭ing");
}
}
/*狗类(子类)*/
public class Dog extends Animal {
public void run() {
System.out.println("跑步ing");
}
}
/*测试类*/
public class Test {
public static void main(String[] args) {
// 创建一个对象
Dog dog = new Dog();
// 调用动物类的eat()方法
dog.eat();
// 调用狗类的run()方法
dog.run();
}
}
/*
输出结果:
吃饭ing
跑步ing
*/
2.3 多态
(1)多态指的是在一个类中定义的属性和方法被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特性。
(2)多态的三个前提
- 存在继承或实现关系
- 子类重写父类方法
- 父类引用指向子类对象(如Father father = new Son();)
(3)好处和弊端
- 好处:提高程序的扩展性。定义方法时,使用父类型作为参数,以后使用的时候,就可以使用不同具体的子类型参与操作。
- 弊端:不能使用子类特有方法。
/*动物类*/
public class Animal {
public int age = 2;
public void eat() {
System.out.println("吃东西");
}
}
// 子类(狗类)继承父类(动物类)
public class Dog extends Animal {
public int age = 3;
public int weight = 20;
// 重写父类(动物类)方法
@Override
public void eat() {
System.out.println("狗吃狗粮");
}
// 子类特有方法
public void lookHouse() {
System.out.println("狗狗看家");
}
}
/*测试类*/
public class Test {
public static void main(String[] args) {
/*
向上转型:
从子类到父类,父类引用指向子类对象
*/
Animal dog = new Dog();
// 多态是方法的多态,属性没有多态性,因此下面输出为:2
System.out.println(dog.age);
// 子类重写父类方法,执行子类方法,因此下面输出为:狗吃狗粮
dog.eat();
// 不能调用子类特有的方法
dog.lookHouse(); // 报错:Cannot resolve method 'lookHouse' in 'Animal'
/*
向下转型:
从父类到子类,父类引用转为子类对象
*/
((Dog)dog).lookHouse();
}
/*
运行结果:
2
狗吃狗粮
狗狗看家
*/
编译看左边,执行看右边:
父类变量引用指向子类对象时,该变量的成员变量和静态方法和父类保持一致,而对于非静态方法,编译时与父类一致,运行时则与子类一致。
3.类与对象的关系
类是对某一类事物的抽象描述,包括属性(成员变量)和行为(成员方法)。比如人,有身高、体重等属性,也有吃、喝、玩等行为,将这些共同的属性和行为提取出来就可以形成一个类,所以一个类可以简单理解为就是一个模板。
对象用于表示现实中该类事物的个体,是类的实例化、具体化,并且一个类可以对应多个对象。比如猫猫、狗狗、鸡鸡、鸭鸭等就是动物类的对象。当然,猫又分为黑猫、白猫、大猫、小猫,所以,猫也可以单独作为一个类。
类是抽象的,对象是具体的
public class Demo {
public static void main(String[] args) {
// 通过new创建一个对象
Animal animal = new Animal();
// 给该对象赋值
animal.name = "小一";
animal.weight = 20.20;
// 调用该对象的方法
animal.eat(); // 小一正在吃饭!现在是20.2kg。
}
}
// 定义一个动物类
class Animal {
// 定义两个属性
String name;
double weight;
// 定义一个行为(方法)
public void eat() {
System.out.println(name + "正在吃饭!现在是" + weight + "kg。");
}
}
4.构造方法
构造方法,也称为构造器,是一种专门用来创建对象的方法,其功能主要是完成对象的初始化,与其他方法一样也可以重载。
public class Person {
String name;
int age;
// 无参构造方法
public Person() {
}
// 有参构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
注意:
- 构造方法必须和所在类的名字一模一样。
- 构造方法不需要写返回类型,也不能写void。
- 如果没有编写任何构造方法,编译器会自动创建一个无参构造方法(可以通过IDEA反编译查看.class文件)。
- 如果编写了至少一个构造方法,那编译器就不会自动创建无参构造方法,若需要使用无参构造方法,则需要手动添加。
拓展1:值传递和引用传递
值传递:指在调用函数时将实际参数复制一份传递给函数,这样在函数中对参数进行修改,将不会影响到实际参数。
/*值传递*/
public class Demo {
public static void main(String[] args) {
int num = 2;
System.out.println(num); // 输出结果:2
change(num); // 并没有改变实际参数
System.out.println(num); // 输出结果:2
}
// 无返回值
private static void change(int a) {
a = 20;
System.out.println(a); // 输出结果:20
}
}
引用传递:指在调用函数时将实际参数的地址传递(而非值)到函数中,这样在函数中对参数所进行的修改,将影响到实际参数。
/*引用传递*/
public class Demo {
public static void main(String[] args) {
Student student = new Student();
// 输出结果:null今年0岁!
System.out.println(student.name + "今年" + student.age + "岁了!");
change(student);
// 输出结果:小一今年18岁!
System.out.println(student.name + "今年" + student.age + "岁了!");
}
// 无返回值
private static void change(Student student) {
student.name = "小一";
student.age = 18;
}
}
// 定义Student类
class Student {
String name;
int age;
}
拓展2:方法重写
方法重写用于继承关系中,即子类重写父类的方法(名称相同,方法体不同)。当子类需要父类的功能,而子类又有自己的特有内容时,就可以通过方法重写实现,这样,子类即继承了父类的功能,又有自己的特有内容。
/*人类*/
public class Person {
// 父类方法
public void sleep (String name) {
System.out.println(name + "在睡觉!");
}
}
/*学生类*/
public class Student extends Person{
// 重写父类方法
public void sleep(String name) {
System.out.println(name + "在玩手机");
// 继承父类方法
super.sleep(name);
}
}
/*测试类*/
public class Test {
public static void main(String[] args) {
// 创建对象,调用方法
Person person = new Person();
person.sleep("小一");
System.out.println("-------------");
Student student = new Student();
student.sleep("小二");
}
/*
运行结果:
小一在睡觉!
-------------
小二在玩手机
小二在睡觉!
*/
注意事项:
- 方法名必须相同
- 参数列表必须相同
- 修饰符的权限范围只增不减(public>protected>default>private)
- 被static、final、private修饰的方法不能被重写
注解:@Override
作用:帮助检查重写方法的方法声明的正确性。