前言
大家好,我还是那个不会打拳的程序猿,今天给大家讲解的是关于封装以及Java中关于静态中的静态方法、静态成员变量、静态代码块等的详解,非常适合初学者或者复习使用。
目录
1.封装
1.1封装的概念
面向对象的三大特性为:封装、继承、多态。我们刚学习完类和对象,此时主要研究的就是封装。何为封装呢?就是把代码包起来屏蔽一些细节。
比如:一台主机这样一个复杂的设备,它给用户提供的就是:开关机、插键盘、连接显示器、USB接口等。来实现用户与计算机进行交互。但实际上:主机真正工作的是CPU、显卡、内存等一些硬件。
对于计算机的使用者而言,不必关心内部的核心部件。我们只要知道怎么开机、怎么通过鼠标键盘与计算机进行交互即可。因此计算机厂商在出厂时,在外面套上壳子,实现内部的部件隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,这就是封装的一个概念。
封装:将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开接口进行交互。
1.2访问限定修饰符
Java中主要通过访问权限来实现封装效果,在类中我们可以将数据以及封装数据的方法结合在一起来实现隐蔽效果,而实现这个效果我们会用到四种访问限定修饰符:
NO | 范围 | private | default | protected | public |
---|---|---|---|---|---|
1 | 同一包中的同一类 | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
2 | 同一包中的不同类 | 不能访问 | 可以访问 | 可以访问 | 可以访问 |
3 | 不同包中的子类 | 不能访问 | 不能访问 | 可以访问 | 可以访问 |
4 | 不同包中的非子类 | 不能访问 | 不能访问 | 不能访问 | 可以访问 |
那么这四个访问限定符是什么意思呢?:
- private,隐蔽的理解为只有自己才能看到的
- public,公共的理解为任何人都能看到或用到的
- default,什么都不写时的默认权限(包访问权限)
- protected,主要是用在继承中,继承部分我们再讲
首先,我们来看private的用法,private的只能在当前类里面使用,出了那个类就不能使用。因此被它修饰的成员变量或者方法是私有的。
class Dog {
private String name;
public int age;
public void sleep() {
System.out.println("睡觉");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "旺财";
dog.age = 10;
dog.sleep();
}
}
运行后输出:
以上代码,我创建了一个狗类,里面的name用了private修饰,因此导致我在main方法里面没有访问name的权限,最后导致运行不通过。因此,当我们创建一个类后,我不想让别人看见一些数据这个时候我们可以用到private来修饰这些数据,从而达到这样一个效果。
当然,如果我们想初始化被private修饰的成员变量的,有以下一种初始化方法:
(1)构造方法
class Dog {
private String name;
public int age;
public void sleep() {
System.out.println("睡觉");
}
public Dog(String name,int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name+age);
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("旺财",19);
dog.show();
dog.sleep();
}
}
运行后输出:
以上代码,使用了带参的构造方法实现了从main方法设置name和age的值。我们在通过show方法来打印出这些值,来实现最终的效果。那么构造方法可以为无参的构造方法与有参的构造方法,我们可以通过快捷键来生成:在编译器内右击点击Generate然后再点击Constructor,来创建有参或无参的构造方法,具体设置多少可由你自己决定。
(2)提供一些公开的接口
class Dog {
private String name;
private int 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 void show() {
System.out.println(name+age);
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.setAge(18);
dog.setName("旺财");
dog.show();
}
}
运行后输出:
以上代码中,Dog类提供了一些公开的接口。使得我们可以进行传参等操作。那么可能有些朋友就不理解了,那你又要对这些数据进行隐藏又要给它们提供一些公开的接口。这不是脱裤子放屁多此一举吗?不是这样的,我们在类中的数据是隐蔽的它是非常有细节的。上述的代码是非常的简单,等你写了一段非常复杂的代码后,你就不想把这些细节分享给别人了。因此你可以提供一个公共的接口使得别人可以用但不能得到,就是这个意思。
提供公开的接口,我们也可以通过编译器来自主创建,步骤为:右击点击Generate,再点击Getter、Setter或者Getter and Setter即可创建你想要的接口。
1.3封装包
1.3.1包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的代码,我们分别把这些代码放在不同的文件夹里面。
那么在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
1.3.2导入包中的类
Java中提供了很多现成的类供我们使用,例如Date类,我们可以使用java.util.Date导入java.util这个包中的Date这个类。
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
//实现一个毫秒级的时间戳
System.out.println(date.getTime());
}
}
当然这样去写非常的麻烦,因此我们可以使用import语句来导入包,一般这段代码在最上方编写。
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
那么如果需要使用java.util中的其他类,我们可以使用import java.util.*来导入所有的包,这个*代表了所有的类。当然这个包不是导入就在里面了,只有你要用的时候这个类才会被引用。
import java.util.*;
public class Test {
public static void main(String[] args) {
int[] arr={1,2,3};
Date date = new Date();
System.out.println(Arrays.toString(arr));
System.out.println(date.getTime());
}
}
这个*就像mysql中的*一样代表着所有数据,但是我们更建议要指定的去导入类名,否则会出现冲突的情况:
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
Date date= new Date();
System.out.println(date.getTime());
}
}
因此,我们要导入明确的类名,不然就会向上方那样报出错误。
注意: import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要.import 只是为了写代码的时候更方便. import 更类似于 C++ 的 namespace 和 using。
1.3.3自定义包
定义规则:
- 在文件的最上方加上一个 package语句指定该代码在哪个包中
- 包名需要尽量指定成唯一的名字,通常会用到公司域名的颠倒形式如:com.bailan.www
- 包名要和代码路径相匹配,例如创建 com.bailan.www 的包,那么会存在一个对应的路径com/bailan/www来存储相应代码
- 如果一个类没有package 语句,那么该类被放到一个默认包中
操作步骤:
1.在IDE中新建一个包:右击src->new->package
2.在弹出对话框后,在对话框中以域名倒着的模式输入例如com.bailan.www
就会出现以下三个包:
3.右击src在Open In中找到Explorer点击即可
最后显示出你文件夹下目录:
1.3.4常见的包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包
2.static修饰的成员
2.1一个学生类
我们创建一个学生类,并在main方法里实例化三个对象stu1、stu2、stu3,每个对象都对应着自己的成员信息,如:
class Student {
private String name;
private int age;
private String classRoom;
public Student(String name,int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name+age);
}
}
public class Test {
public static void main(String[] args) {
Student stu1 = new Student("学生1,",18);
Student stu2 = new Student("学生2,",20);
Student stu3 = new Student("学生3,",23);
stu1.show();
stu2.show();
stu3.show();
}
public static void main1(String[] args) {
Dog dog = new Dog();
dog.show();
}
}
运行后输出:
假设以上三个学生都是一个班级的学生,那么他们的教室也就一样了。既然在同一个教室,那能否给类中再加一个成员变量,来保存同学上课时的教室呢?答案是不行的。因此,我没有给上面的classRoom赋值。因为现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。
因此在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
class Student {
private String name;
private int age;
private static String classRoom="Java赛道";
public Student(String name,int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name+"今年"+age+"岁来自"+classRoom);
}
}
public class Test {
public static void main(String[] args) {
Student stu1 = new Student("学生1,",18);
Student stu2 = new Student("学生2,",20);
Student stu3 = new Student("学生3,",23);
stu1.show();
stu2.show();
stu3.show();
}
运行后输出:
以上代码中的classRoom我们用到static修饰,使得它称为静态成员,也可以称为类成员,但不属于某个具体的对象,是所有对象所共享的。静态成员在方法区存储着因此就像公共厕所一样每个人都能使用一下,如下图所示:
2.2static修饰成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。public或其他修饰的成员变量为非静态变量。静态成员变量特性:
- 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
- 即可以通过对象反问,也可以通过类名访问,但一般跟推荐使用类名访问
- 类变量存储在方法区中
- 生命周期伴随着类(类的加载而创建,类的卸载而销毁)
class Student {
public static String classRoom="Java赛道";
}
public class Test {
public static void main(String[] args) {
Student stu = new Student();
//静态成员直接通过类名访问
System.out.println(Student.classRoom);
//静态成员通过对象访问
System.out.println(stu.classRoom);
}
运行后输出:
通过调试方式运行上述代码,在监视窗口中可以看到,静态成员变量并没有存储到某个具体的对象中。
2.3static修饰成员方法
一般类中的数据成员都设置为public,而这些成员被设置为private之和,Student如何在类外访问呢?
class Student {
private static String classRoom="Java赛道";
}
public class Test {
public static void main(String[] args) {
Student stu = new Student();
System.out.println(Student.classRoom);
}
}
运行后输出:
那static修饰了方法应该如何访问呢?Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。因此我们提供一个静态方法的接口就好了。
class Student {
private static String classRoom="Java赛道";
public static String getClassRoom() {
return classRoom;
}
}
public class Test {
public static void main(String[] args) {
Student stu = new Student();
System.out.println(Student.getClassRoom());
System.out.println(stu.getClassRoom());
}
}
运行后输出:
以上代码,提供了一个getClassRoom的静态方法,从而我们可以在main方法中使用对象或者类名来引用这个静态方法。静态方法的特性:
- 不属于某个具体的对象,是类中方法
- 可以通过对象调用,也可以通过类型调用
- 不能在静态方法中访问任何非静态成员变量
- 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,而静态方法中无法使用this参数
特性1-2,在上方已经演示过了。下面我们分别来看特性3和特性4:
特性3:
class Student {
public int age = 12;
public static String getClassRoom() {
age = age+1;
}
}
运行后显示:
特性4:
class Student {
public void show() {
}
public static String getClassRoom() {
show();
}
}
运行后显示:
2.4static成员变量初始化
静态成员变量的初始化分为两种:直接初始化和静态代码块初始化。
(1)直接初始化
class Student {
public static int age = 18;
private static String getClassRoom = "Java赛道";
}
(2)静态代码块初始化
具体的实现,请看下方讲解!
3.代码块
3.1代码块的概念及分类
代码块就是{}里面定义的一段代码称为代码块。那么代码定义的位置以及关键字,我们分类为以下几种代码块:
- 普通代码块
- 构造代码块(非静态代码块/实例代码块)
- 静态代码块
- 同步代码块(多线程部分再了解)
3.2普通代码块
普通代码块就是定义在方法内的一个代码块,它就是有一个{}组成的模块,里面可以设置变量也可以打印语句,一般用的不多,在此我们只做一个了解。
public class Test {
public static void main(String[] args) {
System.out.println("Hello World");
//普通代码块
{
int num = 20;
System.out.println("这是一个普通代码块");
}
}
}
运行后输出:
3.3构造代码块
构造代码块就是在类里面方法外的代码块,它也叫也叫:实例代码块\非静态代码块。构造代码块一般用于初始化实例成员变量。
class Student {
//这些是成员变量
public String name;
public int age;
//这是一个构造方法
public Student() {
}
//实例化代码块/构造代码块/非静态代码块
{
this.name = "Java";
this.age = 99;
System.out.println("Hello World");
}
public void show() {
System.out.println(name+" "+age);
}
}
public class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.show();
}
运行后输出:
当我们这样去做:
class Student {
//这些是成员变量
public String name;
public int age;
//这是一个构造方法
public Student(String name) {
this.name = name;
}
//实例化代码块/构造代码块/非静态代码块
{
this.name = "Java";
this.age = 99;
System.out.println("Hello World");
}
public void show() {
System.out.println(name+" "+age);
}
}
public class Test {
public static void main(String[] args) {
Student stu = new Student("C++");
stu.show();
}
}
运行并输出:
我们发现,代码块里面设置的Java被C++覆盖了,因为我们的构造方法是最后执行的,因此无论你把构造方法反正程序上面或者下面,它都会最后被执行。因此实例代码块{}里面的Java被覆盖了。
3.4静态代码块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
class Student {
//这些是成员变量
public String name;
public int age;
public static String classRoom;
//这是一个构造方法
//实例化代码块/构造代码块/非静态代码块
{
this.name = "Java";
this.age = 99;
System.out.println("Hello World");
}
//静态代码块
static {
classRoom = "Java";
System.out.println("Hello Java");
}
}
public class Test {
public static void main(String[] args) {
Student stu = new Student();
}
}
运行后输出:
注意:
- 静态代码块不管生成多少个对象,其只会执行一次
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
- 实例代码块只有在创建对象时才会执行
本期博文到这里就结束了,感谢你的耐心观看。
下期预告:继承