Java——面向对象——基础
概述
对象,就是指客观存在的事物,万物皆对象。
- 客观存在的任何一种事物,都可以看作为程序中的对象
- 使用面向对象思想可以将复杂的问题简单化
- 将我们从执行者的位置,变成了指挥者
面向对象和面向过程的思想对比
面向过程编程
是一种以过程为中心的编程思想,实现功能的每一步,都是自己实现的。
面向对象编程
是一种以对象为中心的编程思想,通过指挥对象实现具体的功能。
什么是类
类是对现实生活中一类具有共同属性和行为的事物的抽象。简单的理解的就是类是对事物,也就是对象的一种描述,可以将类理解为一张设计图,可以根据设计图,创造出具体存在的事物。
可以根据类创造对象。
类的组成
- 属性:该事物的各种特征,例如学生事物的属性,姓名、年龄毕业院校...
- 行为:该事物存在的功能,就是能够做的事情,例如学生的行为,学习、Java开发编程。
类和对象的关系
- 类:是对现实生活中一类具有共同属性和行为的事物的抽象,类就是对象的描述
- 对象:是能够看得到摸得着的真实存在的实体,对象就是类的实体
类的定义
说明
类的组成是属性和行为,属性在代码中可以通过成员变量(类中方法外的变量)来体现。行为在代码中可以通过成员方法来体现,和普通方法类似,去掉关键字static即可。
步骤
定义类 ——》 定义类的成员变量 ——》编写类的成员方法
代码实现如下
public class 类名 { // 成员变量 变量1的数据类型 变量1; 变量2的数据类型 变量2; ... // 成员方法 方法1; 方法2; ... }
比如创建一个Student学生类,学生类的成员变量有姓名(name)、年龄(age),成员方法有学习(study),如下
// 属性 姓名、年龄 // 成员变量 跟定义普通对象的格式一样,只不过位置发生了改变,类中方法外 String name; int age; // 行为 学习 // 成员方法 跟普通定义方法的格式一样,去掉了static关键字 public void study(){ System.out.println("学习"); }
这样,一个基本的类就定义好了。
对象的创建
参照已经定义好的类,我们可以在测试类中来创建对象,格式如下
类名 对象名 = new 类名();
例子
Student stu = new Student();
对象的使用
在测试类中使用对象的格式如下
// 使用成员变量 对象名.对象名 // 使用成员方法 对象名.方法名()
例子
System.out.println(stu.age); // 0 stu.study(); // 学习
这里需要注意的是,即使我们没有给对象的属性赋值,也还是可以拿到具体的值,这里叫做默认初始化值,不会编译出错,比如String 为 null, int 为 0。
也可以对属性进行赋值操作,如下
stu.name = "张三"; stu.age = 27; System.out.println(stu.name); // 张三 System.out.println(stu.age); // 27
对象的内存存储
有如下俩个类,一个Student类,一个TestStudent测试类
Student
public class Student { String name; int age; public void study(){ System.out.println("学习"); } }
TestStudent
public class TestStudent { public static void main(String[] args) { Student stu = new Student(); System.out.println(stu.age); // 0 stu.name = "张三"; stu.age = 27; System.out.println(stu.name); // 张三 System.out.println(stu.age); // 27 stu.study(); // 学习 } }
首先拥有主方法的测试类的字节码文件(TestStudent.class)加载进方法区,目前这个字节码文件中只有一个主方法main,主方法被虚拟机自动调用执行,进入到栈内存,第一句代码Student = stu...就是声明了一个对象类型的变量,由于内存中没有Student这个类,所以系统这一步也会把Student这个类的字节码文件(Student.class)加载进方法区,加载进来之后,成员变量和成员方法在方法区都是存在的,加载进来之后,继续 new Student(),有new就会进堆,自动在堆内存中开辟空间,产生地址,由于对象是根据类来创建的,所以类中存在的成员变量在堆内存当中也同时存在着这么一份,堆内存中的数据都有自己的默认初始化值,所以name,age都有了自己的默认初始化值,null和0,此时,成员方法是不会进入堆内存的,但是,堆内存中是会有那么一块地址是用于存在方法的引用地址的,将来可以通过这个地址找到方法区中的成员方法,此时就可以把堆内存中的地址赋值给stu了,所以如果此时打印stu,打印的就是地址值,接下来打印成员变量,就可以通过地址值找到堆内存中的这块内存,也可以根据name和age继续找到成员变量的值,null和0,并且改变它们。调用study方法是通过stu找到堆内存中的这块地址,然后再通过保存过的这个地址,找到方法区中的字节码文件的study方法,找到之后,把study方法加载进栈内存当中运行,执行完study里面的功能之后,就会从栈内存中弹栈消失。由于study()是主方法中的最后一句代码,study调用完毕之后,主方法也会从栈内存当中弹栈消失。至此,程序执行完毕。
注意
如果再次创建Student对象,是不会再次Student这个类的字节码文件(Student.class)加载进方法区的。
俩个引用指向同一个对象
情况如下,有俩个类
Student
public class Student { String name; int age; public void study(){ System.out.println("学习"); } }
TestStudent
public class TestStudent { public static void main(String[] args) { Student stu1 = new Student(); stu1.name = "张三"; Student stu2 = stu1; stu2.name = "李四"; System.out.println(stu1.name + "------" + stu2.name); // 李四------李四 } }
由于俩个引用指向了同一个内存空间,所以只要一个改变了对象,俩个再次取值看到的就是改动之后的结果了。
这里我可以把对象变量赋值为null来断开连接,如下:
stu1 = null; System.out.println(stu1.name); // NullPointerException(空指针异常)
如果设为null之后,再次通过stu1取属性值,就会提示空指针异常。此时不会影响另一个引用(stu2),我们还是可以通过stu2找到堆内存中的这块地址,如下
stu1 = null; // System.out.println(stu1.name); // NullPointerException(空指针异常) System.out.println(stu2.name); // 李四
如果之后又将stu2赋值为null,此时就没有引用能够找到堆内存中的这块地址内容了,这块内容就成了内存中的垃圾对象,垃圾对象会被Java的垃圾回收器在空闲的时候,自动进行清理。下面有描述。
垃圾回收
当堆内存中,对象或数组产生的地址,通过任何方式都不能被找到后,就会被判定为内存中的"垃圾","垃圾"会被Java垃圾回收器,空闲的时候,自动清理。有部分语言,"垃圾"是需要程序员手动清理的。
成员变量和局部变量
- 成员变量——就是类中方法外的变量,判断一个变量是不是成员变量,就看它是不是类中方法外的变量,就就可以了
- 局部变量——就是方法当中的变量
public class Student { String name; int age; public void study(){ int i = 0; System.out.println("学习"); } }
上面代码,name 和 age 就是成员变量,study方法中的 i 就是局部变量。
成员变量和局部变量有如下区别
区别 | 成员变量 | 局部变量 |
类中位置不同 | 类中方法外 | 方法内或者方法声明上(形参) |
内存中位置不同 | 堆内存 | 栈内存 |
生命周期不同 | 随着对象的存在而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的调用完毕消失 |
初始化值不同 | 有默认的初始值 | 么有默认的初始化值,必须先定义,赋值,才能使用 |
封装
private
private是一个访问修饰符,这个单词的意思是私有的,它的作用可以用来修饰成员(变量和方法)。被它修饰过的成员,只能在本类中进行访问。如下
public class Student { private String name; int age; public void show(){ System.out.println(name + "-----" + age); } }
上面Student类中的name成员变量就被private修饰符修饰过了。这样的话,其他的类就无法访问它了。
public class TestStudent { public static void main(String[] args) { Student stu = new Student(); stu.name = "张三"; // name在com.baidu.Student中是private访问控制 } }
使用访问修饰符修饰成员就是可以提高了数据的安全性,如果使用private,还需要提供如下的操作
- 提供"get变量名()"方法,用于获取成员变量的值,方法用public修饰
- 提供"set变量名(参数)"方法,用于设置成员变量的值,方法用public的值
public class Student { private String name; private int age; public String getName(){ return name; } public void setName(String n){ name = n; } public int getAge(){ return age; } public void setAge(int a){ age = a; } public void show(){ System.out.println(name + "-----" + age); } }
如此使用的直观影响就是在其他类中是无法通过"对象.变量名"的方式获取和修改成员变量了,但是也提高了安全性,比如用户在设置年龄的时候,我们在设置年龄的方法中(setAge),加以判断,必须在某个范围内,才能设置成功。如下
// Student.java public void setAge(int a){ if(a >= 0 && a <= 120){ age = a; }else{ System.out.println("您设置的年龄有误"); } } // TestStudent.java Student stu = new Student(); stu.setAge(130); // 您设置的年龄有误
总结:今天在定义成员变量时,都需要采用private修饰。再提供get、set方法,从而提高代码的安全性。
this
在Java中,当成员变量和局部变量重名时,Java会采用就近原则,如下
// Student.java public class Student { private int age = 10; public void show() { int age = 20; System.out.println(age); } } // ThisTest01.java public class ThisTest01 { public static void main(String[] args) { Student stu = new Student(); stu.show(); // 20 } }
这时,如果需要访问打印成员变量age,而不是局部变量age,就可以加上this关键字,如下
// Student .java public class Student { private int age = 10; public void show() { int age = 20; System.out.println(this.age); } } // ThisTest01.java public class ThisTest01 { public static void main(String[] args) { Student stu = new Student(); stu.show(); // 10 } }
所以,this关键字的作用就是可以调用本类的成员(变量、方法),解决成员变量和局部变量的重名问题。
this代表所在类的引用,方法被哪个对象调用,this就代表哪个对象。
构造方法
构造方法就是构建、创造对象的时候,所调用的方法。
它的作用就是用于给对象的数据(属性)进行初始化。
构造方法的书写格式有如下特点
- 方法名与类名相同,大小写也要一致
- 没有返回值类型,连void都没有
- 没有具体的返回值,不能由return带回结果数据
- 创建对象的时候调用,每创建一次对象,不能手动调用
以下就是一个最简单的构造方法
// Student.java public class Student { public Student(){ System.out.println("我是Student类的构造方法"); } } // TestStudent.java public class TestStudent { public static void main(String[] args) { Student stu = new Student(); // 我是Student类的构造方法 } }
根据Student类创建出来对象的时候,就会执行Student类的构造方法。
因为构造方法在创建对象的时候会自动调用一次,所以,我们可以让它额外帮我们初始化一下成员变量,如下就是构造方法的标准使用
// Student.java public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // TestStudent.java public class TestStudent { public static void main(String[] args) { Student stu = new Student("张三",27); System.out.println(stu.getName() + "——" + stu.getAge()); // 张三——27 } }
上面代码提供了一个空参构造方法和一个有参构造方法,这样我们我们创建类的时候就可以看情况初始化对象的属性。
构造方法也有一些注意事项,如下
- 如果没有定义构造方法,系统将会给出一个默认的无参构造方法
- 如果定义了构造方法,系统将不再提供默认的构造方法
- 如果自定义了带参构造方法,还要使用无参构造方法,就必须再写一个无参数 构造方法
- 还是建议今后无论是否使用,都手动书写无参数构造方法和带参数构造方法
JavaBean类
上面用于封装数据的Student类就称为JavaBean类。