文章目录
- 一、介绍
- (1)封装性
- (2)权限修饰符
- 二、案例
- (1)案例1
- 三、练习
- (1)练习1
- (2)练习2
- (3)练习3
- (4)练习4
面向对象特征之一:封装性
一、介绍
(1)封装性
- 为什么需要封装性?
- 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
- 我要开车,我不需要懂离合、油门、制动等原理和维修也可以驾驶。
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要 遵循“ 高内聚、低耦合 ”。 高内聚、低耦合是软件工程中的概念,也是UNIX 操作系统设计的经典原则。
内聚,指一个模块内各个元素彼此结合的紧密程度;耦合指一个软件结构内不同模块之间互连程度的度量。内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
内聚:把特定的内容封装到不同的类里面,把类里面特定的功能封装到不同的方法当中。
耦合:相互之间要调用的时候,只对外暴露需要用的,不需要使用的细节就不对外暴露了。
理论上:
高内聚
:类的内部数据操作细节自己完成,不允许外部干涉;低耦合
:仅暴露少量的方法给外部使用,尽量方便外部调用。
所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对 象开放,向没必要开放的类或者对象隐藏信息。
通俗的说:把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
(2)权限修饰符
- 如何实现数据封装?
2.1 权限修饰符
Java规定了4种权限修饰,分别是:private
、缺省
、protected
、public
2.2 作用
我们可以使用4种权限修饰来修饰类及类的内部成员。当这些成员被调用时,体现可见性的大小。
类:只能使用
public
、缺省
修饰
类的内部成员:可以使用4种权限修饰
进行修饰。
比如,这里的类用的是“缺省
”:
class Animal{ //动物
}
这个类用的是“public
”:
public class AnimalTest {
public static void main(String[] args) {
Animal animal1=new Animal();
}
}
但是使用“private
”就不行,比如:
private修饰的是类的内部文件,此时AA就是一个类,它对自己可见,而且外边用不了,没有什么作用。
protected也一样,修饰的属性让子类的类能够看到,主要指的是成员,类自己加没有什么意义。
目前写的类是并列关系,都叫外部类,比如下面的AnimalTest类和Animal类:
public class AnimalTest {
public static void main(String[] args) {
Animal animal1=new Animal();
}
}
class Animal{ //动物
}
若在Animal类里面再写一个类,这个类就是内部类,内部类可以被四种权限修饰。(后边再说,不着急)
2.3 开发中4种权限使用频率的情况:
比较高:public、private
比较低:缺省、protected
- 封装性的体现
场景1:私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改
场景2:将类中不需要对外暴露的方法,设置为private.
场景3:单例模式中构造器private的了,避免在类的外部创建实例。(放到static关键字后讲)
二、案例
(1)案例1
看下面的代码:
package yuyi.exer01.exe01;
/**
* ClassName: AnimalTest
* Package: yuyi.exer01.exe01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 13:29
*/
public class AnimalTest {
public static void main(String[] args) {
Animal animal1=new Animal();
animal1.name="金蟾";
animal1.legs=4;
System.out.println("name="+animal1.name+",legs="+animal1.legs);
animal1.eat();
}
}
class Animal{ //动物
//属性
String name; //名字
int legs; //腿的个数
//方法
public void eat(){
System.out.println("动物觅食");
}
}
执行结果
⚡注意
不能直接在Animal类中写这种语句:System.out.println("动物");
类里面只能有属性和方法,而上面的语句既不算属性,也不算方法,只是一个执行语句,它只能放在方法里面。
class Animal{ //动物
//属性
String name; //名字
int legs; //腿的个数
System.out.println("动物"); //不能这样写
//方法
public void eat(){
System.out.println("动物觅食");
}
}
现在想给legs设置一个判断逻辑,只有正偶数的值才能被输出。
在Animal类中直接判断是不对的,如下:
但是属性可以在方法里面调用,所以我们可以在Animal类中写一个方法,在方法里面对legs进行逻辑判断。
//设置legs属性值
public void setLegs(int l){
if(l>=0&&l%2==0){ //腿的个数为正偶数
legs=l;
}else{
System.out.println("输入数据非法");
}
}
在给属性赋值的时候,不需要再直接调用属性了,可以通过刚才的setLegs方法来调用,如下:
//animal1.legs=4;
animal1.setLegs(4); //若输入的值是正偶数,就能正确输出结果
完整代码:
public class AnimalTest {
public static void main(String[] args) {
Animal animal1=new Animal();
animal1.name="金蟾";
//animal1.legs=4;
animal1.setLegs(4); //若输入的值是正偶数,就能正确输出结果
System.out.println("name="+animal1.name+",legs="+animal1.legs);
animal1.eat();
}
}
class Animal{ //动物
//属性
String name; //名字
int legs; //腿的个数
/*不能直接在这里判断
if(legs>=0){
}*/
//方法
public void eat(){
System.out.println("动物觅食");
}
public void setLegs(int l){
if(l>=0&&l%2==0){ //腿的个数为正偶数
legs=l;
}else{
System.out.println("输入数据非法");
}
}
}
输出结果:
但是现在我们还是可以直接来调用属性,现在只不过是多了一种方式判断逻辑而已。
那么如何让它彻底不能通过调用属性来给leg赋值?
可以在定义属性的时候,在前面加一个权限修饰符private
:(表示将这个属性私有化了,就是将它隐藏起来了,仅限在类的内部看到)
private int legs;
此时外界(出了Animal类)就看不到legs属性了。
所以直接调用legs属性就不被允许了。如下:
同时这里的legs也不能用了:
那怎么办呢?
可以在Animal类中再写一个方法:
//获取legs属性值
public int getLegs(){
return legs;
}
这个方法的权限是public
,使用范围还是很大的。
现在就可以调用了:
System.out.println("name="+animal1.name+",legs="+animal1.getLegs());
此时编译器看到的就是这个方法的返回值类型(int类型的变量),所以就可以使用了。
整体代码:
package yuyi.exer01.exe01;
/**
* ClassName: AnimalTest
* Package: yuyi.exer01.exe01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 13:29
*/
public class AnimalTest {
public static void main(String[] args) {
Animal animal1=new Animal();
animal1.name="金蟾";
//因为legs声明为private,是私有的,出了Animal类之外就不能调用了
//animal1.legs=4;
//只能通过setLegs(),间接对legs属性进行赋值
animal1.setLegs(4); //若输入的值是正偶数,就能正确输出结果
//System.out.println("name="+animal1.name+",legs="+animal1.legs);
System.out.println("name="+animal1.name+",legs="+animal1.getLegs());
animal1.eat();
}
}
class Animal{ //动物
//属性
String name; //名字
private int legs; //腿的个数
/*不能直接在这里判断
if(legs>=0){
}*/
//方法
public void eat(){
System.out.println("动物觅食");
}
//设置legs属性值
public void setLegs(int l){
if(l>=0&&l%2==0){ //腿的个数为正偶数
legs=l;
}else{
System.out.println("输入数据非法");
}
}
//获取legs属性值
public int getLegs(){
return legs;
}
}
输出结果:
通过这个例子可以看到,为什么需要将一些必要的结构隐藏起来,因为不希望外边的用户可以随意的调用它,就需要将它私有化。如果外边想用,那就提供相应的方法,在方法里面对私有化的属性进行其他的设置。
权限修饰符的使用,就是封装性的体现。
👻总结
在上述案例中,我们给Animal的对象的legs属性赋值。在实际的常识中,我们知道legs不能赋值为负数的。但是如果
直接调用属性legs,是不能加入判断逻辑的。
那怎么办呢?
将legs属性私有化(
private
),禁止在Animal类的外部直接调用此属性。提供给legs属性赋值的setLegs()方法,在此方法中加入legs赋值的判断逻辑if(legs >= 0 && legs % 2 ==0)
将此方法暴露出去,使得在Animal类的外部调用此方法,对legs属性赋值。提供给legs属性获取的getLegs()方法,此方法对外暴露。使得在Animal类的外部还可以调用此属性的值。
若是在类的外部不想让用户获取legs的值,可以不写getLegs()方法,或者将它的权限修饰符设置为private,如下:
private int getLegs(){
return legs;
}
此时getLegs方法对外也不暴露了,在Animal方法外面也就不能调用它了:
三、练习
(1)练习1
🌋题目描述
创建程序,在其中定义两个类:Person和PersonTest类。定义如下:
用setAge()设置人的合法年龄(0~130),用getAge()返回人的年龄。
在PersonTest类中实例化Person类的对象b,调用setAge()和getAge()方法,体会Java的封装性。
🤺代码
package yuyi.exer01.exe01;
/**
* ClassName: Person
* Package: yuyi.exer01.exe01
* Description:
*创建程序,在其中定义两个类:Person和PersonTest类。定义如下:
* 用setAge()设置人的合法年龄(0~130),用getAge()返回人的年龄。
* 在PersonTest类中实例化Person类的对象b,调用setAge()和getAge()方法,体会Java的封装性。
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 15:02
*/
public class Person {
//属性
private int age; //不对外暴露
//设置age属性
public void setAge(int agenumber){
if(agenumber>=0&&agenumber<=130){
age=agenumber;
}else{
System.out.println("输入的年龄有误");
}
}
//获取age属性
public int getAge(){
return age;
}
}
class PersonTest{
public static void main(String[] args) {
//创建Person实例1
Person b=new Person();
//b.age=21; //编译不通过
b.setAge(22); //赋值
System.out.println(b.getAge()); //获取值
}
}
⚡输出结果
👻注意
可以将这两个方法合并吗?如下:
现在两个功能合并在一起了,Age方法的耦合度更高,不建议这样来写!
比如之前已经有了一个Person对象,现在我只想看看是多少岁,如果是左边的两种方法,我就可以通过getAge看一下是多少岁。
但如果是右边的Age方法,此时我需要传进一个参数,然后又返回相同的数值,这就违背初衷了。
每一个方法就只单纯的完成一个功能,在实际需要的时候再去组合这些方法完成特定的操作即可。不要刻意合并!!!
(2)练习2
🌋题目描述
【自定义图书类】
设定属性包括:
书名bookName,
作者author,
价格price;
方法包括:
相应属性的get/set方法,
图书信息介绍等。
🤺代码
【book.java】
package yuyi.exer01.exe01;
/**
* ClassName: book
* Package: yuyi.exer01.exe01
* Description:
* 【自定义图书类】
* 设定属性包括:
* 书名bookName,
* 作者author,
* 价格price;
* <p>
* 方法包括:
* 相应属性的get/set方法,
* 图书信息介绍等。
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 15:39
*/
public class book {
//属性
private String bookName; //书名
private String author; //作者
private double price; //价格
//方法
//1.1设置书名
public void setbookName(String b) {
//...
bookName = b;
}
//1.2获取书名
public String getbookName() {
return bookName;
}
//2.1设置作者
public void setauthor(String a) {
//...
author = a;
}
//2.2获取作者
public String getauthor() {
return author;
}
//3.1设置价格
public void setprice(double s) {
//...
price = s;
}
//3.2获取价格
public double getprice() {
return price;
}
//4.获取图书信息
public String showInfo(){
return "bookName:"+bookName+",author:"+author+",price:"+price;
}
}
【bookTest.java】
package yuyi.exer01.exe01;
/**
* ClassName: bookTest
* Package: yuyi.exer01.exe01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 15:54
*/
public class bookTest {
public static void main(String[] args) {
book book1=new book();
book1.setbookName("西游记");
book1.setauthor("吴承恩");
book1.setprice(30.46);
System.out.println(book1.showInfo());
}
}
可以使用Ctrl+Alt+L来调整代码格式(idea编译器)
此时我们看到的就是对外暴露的方法。
从后期来看,只要不是一些常量的话,一般都将属性私有化,对外都是暴露方法,通过方法对属性进行修改。
⚡输出结果
👻注意
每创建一个对象,类里面的属性就会各自持有一份。
现在“封装性”考虑的只是控制这些属性、方法在被访问的时候权限的大小而已。不会影响各个对象属性方法之间的关系,都会各自一份属性方法。
(3)练习3
🌋题目描述
【普通员工类】
(1)声明员工类Employee,
- 包含属性:姓名、性别、年龄、电话,属性私有化
- 提供get/set方法
- 提供String getInfo()方法
(2)在测试类的main中创建员工数组,并从键盘输入员工对象信息,最后遍历输出
🤺代码
【Employee.java】
package yuyi.exer01.exe01;
/**
* ClassName: Employee
* Package: yuyi.exer01.exe01
* Description:
*【普通员工类】
* (1)声明员工类Employee,
* - 包含属性:姓名、性别、年龄、电话,属性私有化
* - 提供get/set方法
* - 提供String getInfo()方法
* (2)在测试类的main中创建员工数组,并从键盘输入员工对象信息,最后遍历输出
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 16:06
*/
public class Employee {
//属性
private String name;
private char sex;
private int age;
private String number;
//方法
//1.1设置姓名
public void setname(String na){
//...
name=na;
}
//1.2获得姓名
public String getname(){
return name;
}
//2.1设置性别
public void setsex(char se){
//...
sex=se;
}
//2.2获得性别
public char getsex(){
return sex;
}
//3.1设置年龄
public void setage(int ag){
//...
age=ag;
}
//3.2获得年龄
public int getage(){
return age;
}
//4.1设置电话
public void setnumber(String nu){
//...
number=nu;
}
//4.2获得电话
public String getnumber(){
return number;
}
//String getInfo()
public String getInfo(){
//return getname()+"\t"+getsex()+"\t"+getage()+"\t"+getnumber();
return name+"\t"+sex+"\t"+age+"\t"+number;
}
}
【EmployeeTest.java】
package yuyi.exer01.exe01;
import java.util.Scanner;
/**
* ClassName: EmployeeTest
* Package: yuyi.exer01.exe01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 18:03
*/
public class EmployeeTest {
public static void main(String[] args) {
//Employee ie=new Employee();
//创建Employee[] 假设只有两个员工
Scanner scan=new Scanner(System.in);
Employee[] ie=new Employee[2];
for (int i = 0; i < ie.length; i++) {
ie[i]=new Employee();
System.out.println("--------添加第"+(i+1)+"个员工--------");
System.out.print("姓名:");
String name=scan.next();
System.out.print("性别:");
char sex=scan.next().charAt(0);
System.out.print("年龄:");
int age=scan.nextInt();
System.out.print("电话:");
String number=scan.next();
//给指定的employee对象的各属性赋值
ie[i].setname(name);
ie[i].setsex(sex);
ie[i].setage(age);
ie[i].setnumber(number);
}
//遍历员工列表
System.out.println("---------------员工列表----------------");
System.out.println("编号\t姓名\t性别\t年龄\t电话");
for (int i = 0; i < ie.length; i++) {
System.out.println((i+1)+"\t"+ie[i].getInfo());
}
System.out.println("---------------员工列表完成----------------");
}
}
运行输入:
⚡输出结果
(4)练习4
🌋题目描述
测试权限修饰,先忽略protected。
🤸测试
在包test1里面创建一个类Order
:
ackage yuyi.exer01.exe01.test1;
public class Order {
//声明不同权限的属性
private int orderPrivate;
int orderDefault; //缺省权限
public int orderPublic;
//声明不同权限的方法
private void methodPrivate(){
}
void methodDefault(){
}
public void methodPublic(){
}
}
①在当前类里面
在类的某一个方法内,上面的都可以调用。方法里面可以调属性,调方法。(如下面的test方法)
package yuyi.exer01.exe01.test1;
public class Order {
//声明不同权限的属性
private int orderPrivate;
int orderDefault; //缺省权限
public int orderPublic;
//声明不同权限的方法
private void methodPrivate(){
}
void methodDefault(){
}
public void methodPublic(){
}
public void test(){
//调用属性
orderPrivate=1;
orderDefault=2;
orderPublic=3;
//调用方法
methodPrivate();
methodDefault();
methodPublic();
}
}
权限再小,在当前类中也能用。
②当前包里面,当前类外面
在当前test1包里面新建一个类OrderTest
,调用Order类里面的结构。
public class OrderTest {
public static void main(String[] args) {
//Order类的权限是public,在各个包里面都能看得见
//因为是同一个包下,所以不用import
Order order=new Order();
}
}
即使现在的Order类是缺省修饰的,也可以被调用。因为缺省权限是本包内有效。如下:
class Order{
//...
}
接下来测试调用属性和方法:
package yuyi.exer01.exe01.test1;
/**
* ClassName: OrderTest
* Package: yuyi.exer01.exe01.test1
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/22 0022 19:45
*/
public class OrderTest {
public static void main(String[] args) {
//Order类的权限是public,在各个包里面都能看得见
Order order=new Order();
//调用属性
order.orderPublic=1;
order.orderDefault=2;
//order.orderPrivate=3; //不可以调用
//调用方法
order.methodDefault();
order.methodPublic();
//order.methodPrivate(); //不可以调用
}
}
- 调用属性
- 调用方法
private修饰符只能在本类里面调用,出了类就不行了。
缺省可以在本包内调用,不会局限于类里面。(缺省就是default,但是现在不这么用了,因为容易和switch…case语句里面的default弄混)
③不同包里面
在不同包下面创建文件,现在在test2包下创建OrderTest文件:
接下来想看一下test1包下的Order类里面的属性和方法在包test2内的OrderTest类中能不能调用。
若此时Order类被缺省修饰,就不能在包test2内的OrderTest类中创建对象了,因为缺省修饰的只能在当前包内使用。
class Order{
//...
}
可以看到,如果现在创建Order对象是不可以的:
所以,Order类的权限要大一点:
public class Order{
//...
}
此时不会报错了,但是导包是必须的,如下:
看一下测试代码:
package yuyi.exer01.exe01.test2;
import yuyi.exer01.exe01.test1.Order;
/**
* ClassName: OrderTest
* Package: yuyi.exer01.exe01.test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/23 0023 0:04
*/
public class OrderTest {
public static void main(String[] args) {
Order order=new Order();
//调用属性
order.orderPublic=1;
//order.orderPrivate=2; //不可以
//order.orderDefault=3; //不可以
//调用方法
order.methodPublic();
//order.methodPrivate(); //不可以
//order.methodDefault(); //不可以
}
}
只有Public
修饰可以:
出了本包之后,不能调用Default和Private。
总结:
代码敲的比较快,如果文章哪里有误,欢迎指正。