281-290封装/封装与构造器/继承/继承的细节

一、封装实现的步骤

封装实现的步骤:
1、将属性进行私有化,使得不能直接修改属性
2、提供一个公共的set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){
	// 加入数据验证的业务逻辑
	属性 = 参数名;
}
3、提供一个公共的get方法,用于获取属性的值
public XX getXxx() {
	return xx;
}

二、封装的快速入门

快速入门案例
看一个案例
那么在Java中如何实现这种类似的控制呢?
请大家看一个小程序Encap01.java,不能随便查看别人的年龄,工资等等隐私信息,并对设置的年龄进行合理的验证。年龄合理就设置,否则就给默认
年龄必须在1-120,年龄,工资不能直接看,name的长度在2-6之间。
public class Person {
	public String name;
	private int age;
	private double salary;
	private String job;
}

将Encapsulation01.java这个类写在com.hspedu.encap包中
image
已经私有化的就没有办法在class Person的外部进行修改了。
想要修改和访问需要提供set和get的方法
但是自己写set和get方法太慢了,这个东西也可以使用快捷键自动生成。

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

使用快捷键来自动生成set和get
输入Alt + insert,之前我更改成了Alt + i
输入之后会出现一个Getter and Setter
image
image
然后选中需要对哪些属性进行set和get
现在需要对代码进行完善,根据要求完善代码
现在我们可以
Person person = new Person();
person.setName("jack");
person.setAge(30);
person.setSalary(30000);
我们再写一个方法返回这个人的信息

public String info() {
	return "信息为name="+name+"age="+age+"salary"="+salary;
}
然后直接使用person调用info
System.out.println(person.info());

tip:这里老韩也讲了,要想要使用Alt + r这个快捷键来运行需要先配置一下主类,不然运行的就会使上一个运行的主类了。但是我是通过Ctrl+Shift+F10运行的没有这个问题,这个前面我也提过。
image
点击右上角可以配置当前主类
image
可以看到这个地方没有Encapsulation01.java,如果没有使用不了快捷键,可以鼠标右键点击的形式运行,后面就可以使用了。
image
因为现在年龄这里没有做逻辑处理,所以年龄这里我们可以随意设置。
有了方法之后就可以在年龄方法中增加相关的条件。
1、增加一个年龄的合理范围
2、增加提示信息,年龄需要在什么范围内
3、如果不满足条件就给一个默认的年龄。
image
还有一个工资不能直接查看
现在不能直接查看工资,使用get的方式查看工资,因为已经设置成了private
image
我们就可以在get方法中增加一些逻辑,返回薪水的时候可以增加对于当前对象的权限判断。
还有名字长度需要在2-6
找到setName方法,加入对数据的校验和检测
字符串有length()方法,返回字符串的长度
image
相当于增加了业务逻辑
我们可以在封装的同时改变一些封装的属性
相关的方法可以添加业务逻辑,这就是封装的好处。

三、封装与构造器

上一节在写Person类的时候并没有提供构造器
如果提供构造器这样的封装机制就没有了
首先提供一个无参构造器
再写一个三个属性的构造器
image
如果我们直接使用构造器指定属性,
Person smith = new Person("smith", 2000, 50000);

输入smith的信息
System.out.println(smith.info());
这个时候运行
image
这个时候直接通过构造器就绕过去了,如果希望再构造器中也可以进行一个检测,可以将set方法也写构造器中,这样仍然能够验证
image
这样就能控制住了

四、关于封装的课后练习

com.hspedu.encap:AccountTest.java
和Account.java
创建程序,在其中定义两个类:
Account和AccountTest类体会Java的封装性。
1、Account类要求具有两个属性:姓名(长度为2位3位或4位)、余额(必须>20)、密码(必须是六位),如果不满足,则给出提示信息,并给出默认值。
2、通过setXxx的方法给Account的属性赋值。
3、在AccountTest中测试
提示知识点:
String name = "";
int len = name.length();

tip
image
前面在写的时候有这种形参的提示
有的人不喜欢这个,这个如何去除呢?
image
直接将这个勾勾勾掉即可

在Account.java类中,为了封装,将三个属性设置成为private
创建姓名,余额,密码
private String name;
private double balance;
private String pwd;
通过Alt + insert / Alt + i
快速生成get和set方法
Getter and Setter
三个属性都是private的,都需要设置get和set,选中三个属性
已经私有了只能在本类中使用
首先写姓名的逻辑:
public void setName(String name) {
	if(name.length() >= 2 && name.length() <= 4) {
		this.name = name;
	} else {
		System.out.println("姓名要求(长度为2位3位或4位),默认值 无名");
		this.name = "无名";
	}
}
然后余额必须大于20
public void setBalance(double balance) {
	if(balance > 20) {
		this.balance = balance;
	} else {
		System.out.println("余额(必须>20)默认为0");
	}
}
然后密码必须是6位
public void setPwd(String pwd) {
	if(pwd.length() == 6) {
		this.pwd = pwd;
	} else {
		System.out.println("密码(必须是六位)默认密码是000000");
		this.pwd = "000000";
	}
}
这里还可以提供构造器,因为要封装,所以构造器需要使用方法来对类中的数据进行操作
// 提供两个构造器
Alt + insert / Alt + i
public Account() {}
public Account(String name, double balance, String pwd) {
	this.name = name;
	this.balance = balance;
	this.pwd = pwd;
}
我们需要封装需要更改成为封装的set方法
public Account(String name, double balance, String pwd) {
	this.setName(name);
	this.setBalance(balance);
	this.setPwd(pwd);
}
再在com.hspedu.encap下面创建一个用来测试的TestAccount.java
因为Account.java和这个类是在同一个包中的,是可以相互访问的
先在Account中写一个显示账号信息的方法
这里可以增加一个权限的校验
先判断身份是否合法,否则就无法查看
public void showInfo() {
	System.out.println("账号信息 name="+name+"余额="+balance+" 密码"+pwd);
}
在TestAccount中可以创建Account对象,这里不用引入,因为都是在相同的包中
//创建Account
Account account = new Account();
account.setName("jack");
account.setBalance(60);
account.setPwd("123456");
account.showInfo();
或者通过构造器来实现
不管怎么都能够控制住就行。

五、面向对象-继承

为什么需要继承
一个小问题,来看一个程序
com.hspedu.extend_包:extends01.java
提出代码复用的问题。
我们编写两个类,一个类是Pupil类(小学生),一个是Graduate(大学毕业生)。
问题:两个类的属性和方法有很多的是相同的,怎么办?
因为这里extend是一个关键字,所以这里使用一个下划线以示区别。
声明属性
编写方法

public String name;
public int age;
private double score;
public void setScore(double score) {
	this.score = score;
}
public void testing() {
	System.out.println("小学生 "+name+" 正在考小学数学...");
}
public void showInfo() {
	System.out.println("学生名 "+name+ " 年龄"+age+" 成绩"+score);
}
小学生类已经完成
再编写一个大学毕业生类就可以看到为什么需要继承
小学生有的一些属性和方法,大学生也有
有相同的地方,也有不一样的地方
public String name;
public int age;
private double score;
public void setScore(double score) {
	this.score = score;
}
public void testing() {
	System.out.println("大学生 "+name+" 正在考大学数学..");
}
public void showInfo() {
	System.out.println("学生名 "+name+" 年龄" + age + " 成绩"+score);
}
这里出现一个很麻烦的事情,大学生类和小学生类都有相同的地方,相同的地方非常多,不同的地方是一个是大学生一个是小学生,以后创建更多的学生类就不好了,比如专科生,研究生。
继承可以解决代码复用的问题。
在Extends01.java同包的类中进行测试
Pupil pupil = new Pupil();
pupil.name = "银角大王";
pupil.age = 10;
pupil.testing();
pupil.setScore(60);
pupil.showInfo();
然后创建一个大学生
System.out.println("========");
Graduate graduate = new Graduate();
graduate.name = "金角大王";
graduate.age = 22;
graduate.testing();
graduate.setScore(100);
graduate.showInfo();

六、继承的基本原理

继承的基本介绍和示意图
继承可以解决代码复用,让我们编程更加接近人类的思维,当多个类存在相同的属性和方法的时候,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
继承的基本语法
class 子类 extends 父类{
1、子类就会自动拥有父类定义的属性和方法
2、父类又叫做超类,基类
3、子类又叫做派生类
}

继承的示意图
image

七、快速入门

我们对Extends01.java进行改进,使用继承的方法,请大家注意体会使用继承的好处。
为了和前面的进行区别
在com.hspedu.extend_下面创建一个子包
improve_
创建一个Student类
使用Pupil和Graduate的父类
抽取原来大学生和小学生的共有的属性和方法

public String name;
public int age;
private double score;

public void setScore(double score){
	this.score = score;
}
public void testing() {
	System.out.println("大学生 "+ name + " 正在考大学数学...");
}  // 这个方法有一些不一样直接去掉,不写这个方法
public void showInfo() {
	System.out.println("学生名 "+name+" 年龄"+age+" 成绩"+score);
}
所以现在有三个共有的属性和两个共有的方法
创建一个Pupil类
只需要放一个不一样的方法
public void testing() {
	System.out.println("小学生 "+name+" 正在考小学数学...");
}
将这个小学生类继承学生类
public class Pupil extends Student
同理大学生
public class Graduate extends Student
也有对应的方法
public void testing() {
	System.out.println("大学生 "+name+" 正在考大学数学..");
}
虽然这里没有写name属性,但是我们通过extedn继承就能够使用这个属性了
将来有很多的子类就都可以继承这个父类,节省了很多的代码
同理创建一个Extends01.java编写测试代码
这里就通过了一个继承的机制来完成了冗余代码的复用。

八、继承的细节

使用继承给编程带来的便利
1、代码的复用性提高了
2、代码的扩展性和维护性提高了

如果我们在A类中增加了一个方法,它所有的子类都会拥有这个方法,扩展性维护性增加。
将继承相关的细节写在
com.hspedu.extend_包:ExtendsDetail.java
1、子类继承了所有的属性和方法,但是私有属性和方法不能在子类中直接访问,要通过共有的方法去访问。
这里有一个父类Base
属性和方法如下:
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public Base() {
	System.out.println("Base()....");
}
public void test100() {
	System.out.println("test100");
}
protected void test200() {
	System.out.println("test200");
}
void test300() {
	System.out.println("test300");
}
private void test400() {
	System.out.println("test400");
}
再创建一个子类
public class Sub extends Base
继承父类
public Sub() {
	System.out.println("sub()...");
}
public void sayOk() {
	父类的非private属性和方法都能访问
	System.out.println(n1+" "+n2+" "+n3+" "+n4);  // 会发现n4爆红
	同理方法也一样
	test100();
	test200();
	test300();
	test400();
	可以看到test400也会爆红
	但是我们可以通过父类提供的公共方法访问
}
在父类提供一个public的方法
public int getN4() {
	return n4;
}
子类继承了,所以这里可以使用getN4
System.out.println("n4 = " + getN4());
同理在ExtendsDetail.java中编写测试代码
同理父类的私有方法也可以使用public的共有方法调用
public void callTest400() {
	test400();
}

我们可以利用debug工具证明的确是这样的。
image
image
程序执行到断点位置就会停下来,点击这个就可以往下走
image
将光标指向sub然后点击这个加号
image
会发现四个属性,通过这个debug工具可以认识到子类的确继承了。但是有的属性和方法直接访问不到需要通过其他折中的方式进行访问。
第二个细节
子类必须调用父类构造器完成父类的初始化
之前在Base父类中写了一个无参构造器
image
image
子类也写了一个
但是注意观察,当我们创建一个子类对象,这个子类对象是无参构造器,肯定会调用子类的无参构造器,走的是子类的无参构造器,但是会发现父类的无参构造器也被调用了。
image
在子类无参构造器中其实隐藏了一个super();的动作,这个地方虽然没有写,但是隐藏了这句话,会默认调用父类的无参构造器。
这个super();其实就是默认调用了父类的无参构造器,后面详细讲解super();
然后第三个细节
当我们创建子类对象的时候,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中使用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。

接下来在Sub子类中创建一个有参构造器
public Sub(String name) {
	System.out.println("子类Sub(String name)构造器被调用...");
}
Sub sub = new Sub();
System.out.println("===第二个对象===");
Sub sub2 = new Sub("jack");

有参构造器也同样如此
image
都会默认调用父类的无参构造器
然后如果父类没有提供无参构造器,则必须在子类的构造器中使用super去指定使用父类的哪个构造器完成对父类的初始化工作。

在Base父类中创建一个有参构造器
public Base(String name, int age) {
	System.out.println("父类Base(String name, int age)构造器被调用....");
}
然后注释掉无参构造器
如果这里自己写了一个构造器之后,原来java默认给的无参构造器没有了,所以这里我们将自己写的无参构造器注释之后并且写了一个有参数的构造器,所以java提供的默认无参构造器也没有了。
我们会发现子类中会报错

子类报错
image
这里没有一个默认的构造器所以报错
所以只能指定是带参数的构造器
image
image

第四个细节
如果希望指定去调用父类的某个构造器,则显示调用一下,即super(参数列表)
现在我们将原先创建的无参构造器的注释打开
然后再写一个构造器
image
等于是现在有了三个构造器
即子类中我们可以自己指定使用父类的哪个构造器,这里子类Sub也再创建一个构造器
调用父类无参构造器,可以写super();
或者什么都不写,因为默认就是这个
image
同理
image
image
然后是细节五:
Super在使用的时候必须放到构造器的第一行
image
细节六
super()和this()都只能放到构造器的第一行,因此这两个方法不能共同存在一个构造器中
image

posted @ 2025-05-06 21:16  请叫我虾  阅读(22)  评论(0)    收藏  举报