Java面向对象之——封装

简介: Java面向对象之——封装

前言

我们知道Java是一门面向对象的语言,对于面向对象的程序有三大特性:封装、继承、多态。我们上期了解了类和对象,在类和对象阶段,主要研究的就是封装特性。

一、封装的概念

举个例子:我要看电视,只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口。让用户可以与电视机进行交互即可。


二、何为封装?

何为封装呢?简单来说就是套壳屏蔽细节。封装的理念是高内聚、低耦合。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。

要了解封装就需要了解访问权限,要了解访问权限需要了解包,那么下面我们就先来讲一下——Java包


三、封装拓展——包

🍑1、包的概念

在面向对象体系中,提出了一个软件包的概念,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式。包(package)相当于文件夹对于文件的作用。用于管理类、用于解决类的重命名问题。

🍑2、导入包中的类

Java 中已经提供了很多现成的类供我们使用。例如Date类:可以使用 java.util.Date; 表示导入 java.util 这个包中的 Date类。Arrays类:可以使用import java.util.Arrays;表示导入java.ulil这个包中的Arrays类。

📝方式1:写全路径导入

public class Test3 {
    public static void main(String[] args) {
        java.util.Date date=new java.util.Date();
    }
}

虽然这样也可以导入,但是这种写法比较冗余,不建议。

📝方式2:使用import导入

import java.util.Date;
public class Test3 {
    public static void main(String[] args) {
        Date date=new Date();
    }
}

📝方式3:使用 import 包路径. * 导入包中的其他类,

需要注意的是:这种导入方式不是将包中的所有类都导入进来,而是需要谁就导入谁。

import java.util.*;
public class Test3 {
    public static void main(String[] args) {
        Date date = new Date();
    }
}

虽然这样也可以导入包中的类,但是我们更建议显式的指定要导入的类名,指定类名可以使导入类更清楚明确。

当我们这样导入,也可能出现冲突的情况。如:java.util包和java.sql包下都有Date类,如果我们使用这种导入方式,会导致编译器不知道应该从哪个包中导入Date类,将会报错。

📝方式4:使用import static导入包中静态的方法和字段。

这种导入方式了解即可,不建议使用。

import static java.lang.Math.*;
public class Test3 {
    public static void main(String[] args) {
        double x = 30;
        double y = 40;

// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        //省略类名Math
        double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
    }
}

🍑3、自定义包

基本规则

  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
  • 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.zijie.demo1 ).
  • 包名要和代码路径相匹配. 例如创建 com.zijie.demo1 的包, 那么会存在一个对应的路径com/zijie/demo1 来存储代码.
  • 如果一个类没有 package 语句, 则该类被放到一个默认包中.

注: 如果在源文件中没有定义包,那么类、接口、枚举和注释类型文件将会被放进一个无名的包中,也称为默认包。学了包之后,写项目时都要加包,尽量不要使用默认包!

下面就演示一下在IDEA中创建包:

🍑4、常见的包

  1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
  2. java.lang.reflect:java 反射编程包;
  3. java.net:进行网络编程开发包。
  4. java.sql:进行数据库开发的支持包。
  5. java.util:是java提供的工具程序包。(集合类等) 非常重要
  6. java.io:I/O编程开发包。

四、访问限定符

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:

注解:

  1. protected主要是用在继承中,继承部分详细介绍
  2. default权限指:什么都不写时的默认权限
  3. 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性

目前来说,我们就先介绍public、private、默认修饰符:

🍑1、public修饰符

public修饰的成员在哪里都能访问,它的权限是四种修饰符中最大的。这里就不过多展示了。

🍑2、private修饰符

🍑3、默认修饰符(缺省)

🍑4、protected修饰符

------------------ (继承部分讲解)------------------

五、再谈封装

上面提到:封装是对类细节的隐藏,提供公开接口来进行和数据的交互。通过搭配上述的修饰符这一特点体现的更为明显。

📝如:对学生类使用private进行封装

class Student {
    //属性
    private String name;
    private int age;
    //方法
    public void show() {
        System.out.println("姓名:"+name+" 年龄:"+age);
    }

    //构造方法
    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;
    }
}

public class Test2 {
    public static void main(String[] args) {
        Student student =new Student("张三",20);
        student.show();

        student.setName("李四");
        student.setAge(18);
        System.out.println(student.getName());
        System.out.println(student.getAge());
    }
}

通过这个例子,我们或许可以看到到封装在类中的体现,通过使用private对Student中成员进行隐藏,在提供一些对外的接口如,setName、getName等实现对外的交互。如需要对封装的成员进行操作,调用接口方法即可。


六、static成员

🍑1、再谈学生类——static的引入

学生有属性:姓名,年龄,身高,体重……这里仅以姓名年龄属性举例:

class Student {
    //属性
    public String name;
    public int age;

    //构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Test2 {
    public static void main(String[] args) {
        Student student1=new Student("张三",17);
        Student student2=new Student("李四",18);
        Student student3=new Student("王五",19);
    }
}

现在假设这三个同学是来自于同一个班级的,那么我们能否加一个普通成员变量来统一保存学生的班级信息呢?答案显然是不行的。为了实现这种功能,于是引出了static关键字。

之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。

🍑2、static修饰成员变量

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。

学习了static关键字之后我们可将成员变量分为:

1、静态成员变量(类变量)

2、非静态成员变量(普通成员变量)

我们再回到之前的问题,现在我们想要统一保存学生的班级信息只需要添加一个static修饰的成员变量即可:

class Student {
    //属性
    public String name;
    public int age;
    public static String classRome="软件1班";  
    //构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Student student1=new Student("张三",17);
        Student student2=new Student("李四",18);
        Student student3=new Student("王五",19);
        //使用类和对象都可以访问static修饰的成员变量
        System.out.println(Student.classRome);
        System.out.println(student1.classRome);
    }
}

此时我么再次调试:

结合代码功能、运行和调试足以说明【静态成员变量】具有以下特性:

  1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
  2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
  3. 类变量存储在方法区当中
  4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)

🍑3、static修饰成员方法

一般类中的数据成员都设置为private,而成员方法设置为public,那设置之后,Student类中classRoom属性如何在类外访问呢?这里就引出了静态方法。Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。学习了static关键字之后我们将成员方法分为:

1、静态成员方法(类方法)

2、非静态成员方法(普通成员方法)

class Student {
    //属性
    private String name;
    private int age;
    private static String classRome="软件1班";
    
    public static String getClassRome() {
        return classRome;
    }

    public static void setClassRome(String classRome) {
        Student.classRome = classRome;
    }
}

public class Test2 {
    //使用静态方法访问静态变量
    public static void main(String[] args) {
        System.out.println(Student.getClassRome());
        Student.setClassRome("软件2班");
        System.out.println(Student.getClassRome());
    }
}

🍑4、静态方法特性

  1. 不属于某个具体的对象,是类方法
  2. 可以通过对象调用,也可以通过类名。静态方法名(…)方式调用,更推荐使用后者。
  3. 不能在静态方法中直接访问任何非静态成员变量,无法传递this引用。
class Student {
   public int age;
}

public class Test2 {
    //在静态方法中不能直接访问非静态方法
    public static void main(String[] args) {
        //System.out.println(this.age);//不能使用this引用
    }
}
  1. 静态方法中不能直接调用任何非静态方法,因为非静态方法有this参数,在静态方法中无法传递this引用。
class Student {
    public void test() {
        System.out.println("test()");
    }
}
public class Test2 {
    //在静态方法中不能直接访问非静态方法
    public static void main(String[] args) {
//        Student.test();//错误
        //在静态方法中访问非静态方法只能new对象
          Student stu1=new Student();
          stu1.test();
    }
}
  1. 普通成员方法内部是可以使用静态的成员方法和成员变量的。
class Student {
   private static int age=10;
   private static String name="张三";
   
   private static void print() {
       System.out.println("test()");
   }
   
   public void test() {//这样写是可以的,不会报错
       Student.age=10;
       Student.name="李四";
       Student.print();
   }
}

🍑5、static成员变量初始化

(1)默认初始化

class Student {
   public static int age;//默认赋值为0
   public static String name;//默认赋值为null
}

同普通成员变量,如果静态成员变量创建时不赋初值,将被赋默认值。赋默认值规则同普通成员变量。

(2)就地初始化

class Student {
   public static int age=10;
   public static String name="张三";
}

像上面这种,在定义时直接给出初始值的赋值方式为就地初始化。

(3)使用构造方法初始化

class Student {
   public static int age;
   public static String name;

   public Student(int a,String n) {
       age=a;
       name=n;
   }
}

虽然使用构造方法也可以给静态变量赋值,但是静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性,而静态变量是所有对象共享的,每次new对象时都把static变量改了是不合适的。

(4)使用Setter方法

class Student {
   public static int age;
   public static String name;
   
   public static void setAge(int age) {
       Student.age = age;
   }

   public static void setName(String name) {
       Student.name = name;
   }

(5)使用静态代码块初始化

静态代码块是什么?我们接着往下看


七、代码块

🍑1、代码块概念以及分类

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:普通代码块构造块静态块同步代码块(后续讲解多线程部分再谈)

🍑2、普通代码块

普通代码块,是指定义在方法中的代码块。

//这是一个main方法
    public static void main(String[] args) {
        { //直接使用{}定义,普通方法块
            int x = 10;
            System.out.println("x1 = " + x);
        }
    }

🍑3、非静态代码块

非静态代码块也叫实例代码块、构造块,是定义在类中的代码块(不加修饰符)。构造代码块一般用于初始化非静态实例成员变量。

class Student {
   public int age;
   public String name;

    //构造代码块,实例化成员变量
   {
       age=10;
       name="李四";
   }
    public void show() {
        System.out.println("年龄:"+age+" 姓名:"+name);
    }
}
public class Test2 {
    public static void main(String[] args) {
        Student student1=new Student();
        student1.show();
  }
}

补充:

  1. 如果都是非静态的,那么定义顺序谁在后最后赋值结果就是哪个值。
  2. 可以简单认为,编译器编译好代码之后会把非静态代码块的东西放到构造方法的最前面。

🍑4、静态代码块

使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。

class Student {
    private String name;
    private String gender;
    private static String classRoom;

    // 静态代码块
    static {
        classRoom = "软件1班";
        System.out.println("I am static init()!");
    }

    public static void main(String[] args) {
        //静态代码块的加载不依赖于对象
        System.out.println(Student.classRoom);
        //静态代码块只执行1次
        Student s1 = new Student();
        Student s2 = new Student();
    }
}

注意事项:

  1. 静态代码块不管生成多少个对象,其只会执行一次
  2. 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
  3. 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
  4. 实例代码块只有在创建对象时才会执行

🍑5、执行顺序

1. 静态代码块先执行,并且只执行一次,在类加载阶段执行。

2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后执行构造方法。

class Demo {
    public String name;
    public int age;
    //构造方法
    public Demo(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("构造方法执行");
    }
    //实例代码块
    {
        System.out.println("实例代码块执行");
    }
    //静态代码块
    static {
        System.out.println("静态代码块执行");
    }
}

public class Test {
    public static void main(String[] args) {
        Demo demo=new Demo("张三",20);
        System.out.println("===========分隔线===========");
        Demo demo2=new Demo("李四",18);
    }
}


八、对象的打印

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        Student student=new Student("张三",18);
        System.out.println(student);
    }
}

当我们使用println打印时,如果直接将student引用作为参数传入println(student),我们能得到引用指向对象中的属性值吗?答案是不能的。我们将得到如下图的数值:

如果想要println默认打印对象中的属性该如何处理呢?答案:重写toString方法即可。也就是说对于你想输出一个对象的引用的值的时候,如果你没有自己写一个toString方法,那么就会调用Object这个类的方法,如果自己实现了,就调用自己的:

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

  //String方法重写
    @Override//这个是注解,注解有很多种,这只是其中1种
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", gender='" + age + '\'' +
                '}';
    }
    
    public static void main(String[] args) {
        Student student=new Student("张三",18);
        System.out.println(student);
    }
}

补充:对于上述提到的,方法重写以及Object的相关知识后期博客会详细介绍。


九、拓展:不学后悔的IDEA技巧

🍑1、快速生成构造方法

🍑2、快速生成Setter和Getter接口方法

🍑3、快速生成重写

IDEA中也有一个快速重写toString的技巧

其实使用IDEA还可以实现很多便捷的操作,大家可以在使用中慢慢摸索。




总结

本期主要探讨了面向对象三大特性之一的封装,重点介绍了Java包的概念、引入了静态属性static、详细介绍了Java中的三种代码块、最后拓展了一下IDEA中的小技巧,本章篇幅较长,笔者尽量做到句句重点,如有不足,敬请斧正。我们下期再见!


相关文章
|
2月前
|
安全 Java 编译器
Java的封装详解
封装和多态是面向对象编程(OOP)的重要概念。封装通过私有属性和公共方法实现数据隐藏和保护,使类的内部细节对外部不可见;多态则通过方法重载和重写实现同一方法在不同对象上的不同表现形式,增强了代码的灵活性和可维护性。两者结合使用,可以使Java程序更加安全、灵活且易于维护。
|
2月前
|
Java
Java的封装详解
封装是Java中实现数据隐藏和保护的核心机制。它通过将对象的状态和行为结合并限制外部直接访问,确保类的内部细节对外不可见,仅能通过公共方法访问和修改对象状态。封装带来了数据隐藏、提高代码可维护性和增强安全性等好处。在Java中,封装主要通过将属性设为私有并提供getter和setter方法来实现。这种方式不仅保护了数据完整性,还允许在修改类内部实现时不影响外部代码,从而提升程序的健壮性和可读性。
|
8天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
9 2
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
2月前
|
SQL Java 编译器
Java——类与对象(封装)
封装是面向对象编程中的概念,指将数据(属性)和相关操作(方法)组合成独立单元(类),使外部无法直接访问对象的内部状态,只能通过提供的方法进行交互,从而保护数据安全。例如,手机将各种组件封装起来,只暴露必要的接口供外部使用。实现封装时,使用`private`关键字修饰成员变量,并提供`get`和`set`方法进行访问和修改。此外,介绍了包的概念、导入包的方式及其注意事项,以及`static`关键字的使用,包括静态变量和方法的初始化与代码块的加载顺序。
40 10
Java——类与对象(封装)
|
2月前
|
安全 Java 数据安全/隐私保护
Java 封装怎么理解
封装是Java中的一种重要机制,它将对象的状态(数据)和行为(方法)打包在一起并控制外部访问权限,以保护数据不被随意修改。封装的主要目的包括数据保护、接口设计和增强模块性。通过使用`private`、`protected`及`public`等访问控制修饰符,结合getter和setter方法,可以有效隐藏对象内部实现细节。下面是一个简单的`BankAccount`类示例,展示了如何通过封装保护类的内部状态,确保数据安全和一致性,简化类的使用。理解封装有助于编写高质量代码和设计优秀程序架构。
|
2月前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
32 4
|
2月前
|
Java 数据安全/隐私保护
Java 封装详解
在 Java 中,封装是面向对象编程的关键特性,通过将对象的状态(数据)和行为(方法)结合并利用访问控制保护数据,防止外部随意访问和修改。主要特点包括访问控制(如 `private` 和 `protected`)、数据隐藏及方法暴露(如 getter 和 setter)。封装的优点在于保护数据、隐藏实现细节、易于维护以及提高代码可读性。下面是一个简单的 `Person` 类封装示例,展示了如何通过 getter 和 setter 控制对类内部状态的访问,并进行合法性检查。总结而言,封装有助于构建清晰、易用且可维护的代码结构,是编写高质量 Java 程序的重要原则。
|
2月前
|
安全 Java 开发者
Java修饰符与封装:理解访问权限、行为控制与数据隐藏的重要性
Java中的修饰符和封装概念是构建健壯、易维护和扩展的Java应用程序的基石。通过合理利用访问权限修饰符和非访问修饰符,开发者能够设计出更加安全、灵活且高效的代码结构。封装不仅是面向对象编程的核心原则之一,也是提高软件项目质量和可维护性的关键策略。
16 1
|
2月前
|
Java
接口和抽象类【Java面向对象知识回顾②】
本文讨论了Java中抽象类和接口的概念与区别。抽象类是不能被实例化的类,可以包含抽象和非抽象方法,常用作其他类的基类。接口是一种纯抽象类型,只包含抽象方法和常量,不能被实例化,且实现接口的类必须实现接口中定义的所有方法。文章还比较了抽象类和接口在实现方式、方法类型、成员变量、构造方法和访问修饰符等方面的不同,并探讨了它们的使用场景。
接口和抽象类【Java面向对象知识回顾②】