前言
我们知道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、常见的包
java.lang
:系统常用基础类(String、Object),此包从JDK1.1后自动导入。java.lang.reflect:java
反射编程包;java.net
:进行网络编程开发包。java.sql
:进行数据库开发的支持包。java.util
:是java提供的工具程序包。(集合类等) 非常重要java.io
:I/O编程开发包。
四、访问限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
注解:
- protected主要是用在继承中,继承部分详细介绍
- default权限指:什么都不写时的默认权限
- 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性
目前来说,我们就先介绍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); } }
此时我么再次调试:
结合代码功能、运行和调试足以说明【静态成员变量】具有以下特性:
- 不属于某个具体的对象,是
类的属性
,所有对象共享的,不存储在某个对象的空间中 - 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用
类名访问
- 类变量存储在
方法区
当中 - 生命周期伴随
类的一生
(即:随类的加载而创建,随类的卸载而销毁)
🍑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、静态方法特性
- 不属于某个具体的对象,是
类方法
。- 可以通过对象调用,也可以通过
类名
。静态方法名(…)方式调用,更推荐使用后者。不能
在静态方法中直接访问任何非静态成员变量,无法传递this
引用。
class Student { public int age; } public class Test2 { //在静态方法中不能直接访问非静态方法 public static void main(String[] args) { //System.out.println(this.age);//不能使用this引用 } }
- 静态方法中
不能
直接调用任何非静态方法,因为非静态方法有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(); } }
- 普通成员方法内部是
可以
使用静态的成员方法和成员变量的。
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(); } }
补充:
- 如果都是非静态的,那么定义顺序谁在后最后赋值结果就是哪个值。
- 可以简单认为,编译器编译好代码之后会把非静态代码块的东西放到构造方法的最前面。
🍑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(); } }
注意事项:
- 静态代码块不管生成多少个对象,其只会
执行一次
- 静态成员变量是
类的属性
,因此是在JVM加载类时开辟空间并初始化的- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的
先后次序
依次执行(合并)- 实例代码块只有在
创建对象
时才会执行
🍑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中的小技巧,本章篇幅较长,笔者尽量做到句句重点,如有不足,敬请斧正。我们下期再见!