十二、面向对象思想
12.1 概述
面向过程:当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为,详细处理每一个细节。(强调步骤)
面向对象:当需要实现一个功能的时候,不关心具体的步骤,而是找一个已经具有该功能的人,来帮我做事儿。强调的是通过调用对象的行为来实现功能。(强调对象)
特点:
- 面向对象思想可以将复杂的事情简单化,并将我们从执行者变成指挥者。
- 面向对象的语言中,包含了三大基本特征,即封装、继承和多态。
12.2 类和对象
类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。(抽象的)
对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性和行为。(具体的)
关系:类是对象的模板,对象是类的实体。
12.3 类的定义
类的定义格式:
public class 类名{
//成员变量
//成员方法
}
类名:使用大驼峰命名。
成员变量:对应事物的属性。在类中,在方法外。
成员方法:对应事物的行为。
eg:
public class Student{
//成员变量
String name; //姓名
int age; //年龄
//成员方法
public void study(){
System.out.println("好好学习")
}
}
12.4 对象的使用
创建对象格式:
类名 对象名 = new 类名();
使用对象访问类中的成员:
对象名.成员变量; //使用成员变量
对象.成员方法(); //使用成员方法
eg:
import demo.student;
public class Demo02Student {
public static void main(String[] args) {
// 1. 导包。
// 2. 创建,格式:
// 类名称 对象名 = new 类名称();
// 根据Student类,创建了一个名为stu的对象
Student stu = new Student();
// 3. 使用其中的成员变量,格式:
// 对象名.成员变量名
System.out.println(stu.name); // null
System.out.println(stu.age); // 0
// 改变对象当中的成员变量数值内容
// 将右侧的字符串,赋值交给stu对象当中的name成员变量
stu.name = "小明";
stu.age = 18;
System.out.println(stu.name); // 小明
System.out.println(stu.age); // 18
// 4. 使用对象的成员方法,格式:
// 对象名.成员方法名()
stu.study();
}
}
注意:
- 导包:也就是指出需要使用的类,在什么位置。格式:import 包名称.类名称;
(对于和当前类属于同一个包的情况,可以省略导包语句不写。)- 如果成员变量没有进行赋值,那么将会有一个默认值,规则和数组一样。
成员变量的默认值
数据类型 | 默认值 | |
---|---|---|
基本类型 | 整数(byte, short, int, long) | 0 |
浮点数(float, double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
引用类型 | 数组,类,接口 | null |
12.5 对象内存图
只有一个对象的内存图:
在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。
两个对象使用同一个方法的内存图:
对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息 只保存一份,节约内存空间。
两个引用指向同一个对象的内存图:
使用对象类型作为方法的参数:
引用类型作为参数,传递的是地址值。
使用对象类型作为方法的返回值:
引用类型作为返回值,返回的是地址值。
12.6 成员变量和局部变量的区别
- 在类中的位置不同
成员变量:类中,方法外
局部变量:方法中或方法声明上(形式参数) - 作用范围不一样
成员变量:类中
局部变量:方法中 - 初始化值的不同
成员变量:有默认值
局部变量:没有默认值,必须先定义,赋值,最后使用 - 在内存中的位置不同
成员变量:堆内存
局部变量:栈内存 - 生命周期不同
成员变量:随着对象的创建而存在,随着对象的消失而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
12.7 匿名对象
概念:
创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。虽然是创建对象的简化写法,但是应用 场景非常有限。(没有变量名的对象)
格式:
new 类名(参数列表)
eg:
new Scanner(System.in);
应用场景:
- 创建匿名对象直接调用方法,没有变量名。
new Scanner(System.in).nextInt();
- 一旦调用两次方法,就是创建了两个对象,造成浪费。
new Scanner(System.in).nextInt();
new Scanner(System.in).nextInt();
注意:
一个匿名对象,只能使用一次。
- 匿名对象可以作为方法的参数和返回值。
//作为参数
class Test {
public static void main(String[] args) {
// 普通方式
Scanner sc = new Scanner(System.in);
input(sc);
//匿名对象作为方法接收的参数
input(new Scanner(System.in));
}
public static void input(Scanner sc){
System.out.println(sc);
}
}
//作为返回值
class Test2 {
public static void main(String[] args) {
// 普通方式
Scanner sc = getScanner();
}
public static Scanner getScanner(){
//普通方式
//Scanner sc = new Scanner(System.in);
//return sc;
//匿名对象作为方法返回值
return new Scanner(System.in);
}
}
十三、封装
13.1 概述
面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
原则:将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
13.2 private关键字
private
的含义:
- private是一个权限修饰符,代表最小权限。
- 可以修饰成员变量和成员方法,被修饰后的成员变量和成员方法只在本类中才能访问。
private
的使用格式:
private 数据类型 变量名
13.3 封装的步骤
- 使用
private
关键字来修饰成员变量。 - 对需要访问的成员变量,提供对应的一对
getXxx
方法、setXxx
方法。
eg:
public class Student {
private String name;
private int age;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
}
13.4 封装优化1—this关键字
this
的含义:
this代表所在类的当前对象的引用(地址值),即对象自己的引用。
this
是用格式:
this.成员变量名;
当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量,从而导致成员变量被隐藏,方法中的变量名无法访问到成员变量,所以只能使用this
关键字,来解决这个重名问题。
eg:
public class Student{
private String name;
private int age;
public void setNmae(String name){
//name = name;
this.name = nmae;
}
public String getName(){
return name;
}
public void setAge(int age){
//age = age;
this.age = age;
}
public int getAge(){
return age;
}
}
注意:
- 当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。
- 方法被哪个对象调用,方法中的
this
就代表那个对象。即谁在调用,this
就代表谁。- 方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。
13.5 封装优化2—构造方法
构造方法:当一个对象通过关键字new来创建时,其实就是在调用构造方法,构造方法用来初始化该对象,给对象的成员变量赋初始值。
构造方法的定义格式:
修饰符 构造方法名(参数列表){
//方法体
}
eg:
public class Student{
private String name;
private int age;
//无参数构造方法
public Student(){
}
//有参数构造方法
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
注意:
- 构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样。
- 构造方法不要写返回值类型,连void都不写。
- 构造方法不能return一个具体的返回值
- 如果没有编写任何构造方法,那么编译器将会默认给出一个无参数构造方法,方法体也什么事情都不做。
- 一旦编写了至少一个构造方法,那么编译器将不再提供无参数构造方法。
- 构造方法是可以重载的,既可以定义参数,也可以不定义参数。
13.6 标准代码—JavaBean
JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 set 和 get 方法。
public class ClassName{
//成员变量
//无参构造方法【必须】
//有参构造方法【建议】
//成员方法
//getXxx()
//setXxx()
}
eg:
public class Student {
//成员变量
private String name;
private int age;
//构造方法
public Student() {
}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
//成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
publicvoid setAge(int age) {
this.age = age;
}
publicint getAge() {
return age;
}
}
//测试类
public class TestStudent {
public static void main(String[] args) {
//无参构造使用
Student s= new Student();
s.setName("柳岩");
s.setAge(18);
System.out.println(s.getName()+"‐‐‐"+s.getAge());
//带参构造使用
Student s2= new Student("赵丽颖",18);
System.out.println(s2.getName()+"‐‐‐"+s2.getAge());
}
}
十四、继承
14.1 概述
继承:子类(派生类)继承父类(基类、超类)的属性和行为,使得子类对象具有与父类相同的属性、相同的行为,还可以拥有自己的内容,子类可以直接访问父类中的非私有的属性和行为。
好处:
- 提高代码的复用性。
- 类与类之间产生了关系,是多态的前提。
格式:
class 父类{
...
}
class 子类 extends 父类{
...
}
14.2 继承的特点
- Java只支持单继承,不支持多继承。
- Java支持多级继承。
- 一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类。
顶层父类是
Object
类,所有的类默认继承Object
,作为父类。
14.3 继承后的特点——成员变量
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
- 直接通过子类对象访问成员变量:
等号左边是谁,就优先用谁,没有则向上找。 - 间接通过成员方法访问成员变量:
该方法属于谁,就优先用谁,没有则向上找。
注意:
父类中的成员变量是非私有的,子类中可以直接访问。若父类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private
修饰成员变量,那么我们可以在父类中提供公共的getXxx
方法和setXxx
方法。
eg:
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() {
// 使用的是本类当中的,不会向下找子类的
System.out.println(num);
}
}
public class Zi extends Fu {
int numZi = 20;
int num = 200;
public void methodZi() {
// 因为本类当中有num,所以这里用的是本类的num
System.out.println(num);
}
}
public class Demo01ExtendsField {
public static void main(String[] args) {
Fu fu = new Fu(); // 创建父类对象
System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
// 等号左边是谁,就优先用谁
System.out.println(zi.num); // 优先子类,200
// System.out.println(zi.abc); // 到处都没有,编译报错!
// 这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi(); // 200
// 这个方法是在父类当中定义的,
zi.methodFu(); // 100
}
}
局部变量: 直接写成员变量名
本类的成员变量: this.成员变量名
父类的成员变量: super.成员变量名
public class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); // 30,局部变量
System.out.println(this.num); // 20,本类的成员变量
System.out.println(super.num); // 10,父类的成员变量
}
}
14.4 继承后的特点——成员方法
在父子类的继承关系当中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先用谁,如果没有则向上找。
重写(Override):也称为覆盖,在继承关系中,方法的名称一样,参数列表也一样。
特点:创建的是子类对象,则优先用子类方法。
注意:
- 必须保证父子类之间方法的名称相同,参数列表也相同。
@Override
:写在方法前面,用来检测是不是有效的正确覆盖重写。(这个注解就算不写,只要满足要求,也是正确的方法覆盖重写)- 子类方法的返回值必须小于等于父类方法的返回值范围。
小扩展提示:java.lang.Object
类是所有类的公共最高父类(祖宗类),java.lang.String
就是Object
的子类。- 子类方法的权限必须大于等于父类方法的权限修饰符。
小扩展提示:public
>protected
>(default)
>private
备注:(default)
不是关键字default
,而是什么都不写,留空。
eg:
public class Fu {
public void methodFu() {
System.out.println("父类方法执行!");
}
public void method() {
System.out.println("父类重名方法执行!");
}
}
public class Zi extends Fu {
public void methodZi() {
System.out.println("子类方法执行!");
}
@Override
public void method() {
System.out.println("子类重名方法执行!");
}
}
public class Demo01ExtendsMethod {
public static void main