目录
1. 封装
1.1 封装的概念
1.2 访问限定符
2. 包
2.1 包的概念
2.2 导入包中的类
2.3 自定义包
2.3.1 包的基本规则
2.3.2 创建一个包
2.4 包访问权限——defualt
3. 何为封装
3.1 private 关键字
4. static 成员
4.1 对学生类的进一步思考
4.2 static 修饰成员变量
4.3 static 修饰成员方法
4.4 private static 修饰的成员变量
4.5 对静态成员变量的初始化
4.5.1 普通代码块
4.5.2 构造块
4.5.3 静态块
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。这也算一种封装。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
Java 中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java 中提供了四种访问限定符:
范围 | private | default | protected | public | |
1 | 同一包中同一类 | ✔ | ✔ | ✔ | ✔ |
2 | 同一包中不同类 | ✔ | ✔ | ✔ | |
3 | 不同包中的子类 | ✔ | ✔ | ||
4 | 不同包中的非子类 | ✔ |
这里对 default 说明一下,defualt 表示默认的意思,并不是真的在成员变量前加一个default的单词,而是什么都不加,默认权限为同一包同一类以及同一包中不同类。而 public:可以理解为一个人的外貌特征,谁都可以看得到。private:只有自己知道,其他人都不知道。protected 主要是用在继承中,继承部分详细介绍。
为了更好地了解 private、default 及public 的区别,下面介绍包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
在 Java 中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一 个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
Java 中已经提供了很多现成的类供我们使用,例如 Date 类:可以使用 java.util.Date 导入 java.util 这个包中的 Date 类。
public static void main(String[] args) {
java.util.Date date4 = new java.util.Date();
//得到一个毫秒级的时间戳
System.out.println(date4.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.* 。但不是把 util 包里面所有类都导入,而是需要用到谁就导入哪个类。
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是我们更建议显式的指定要导入的类名,否则还是容易出现冲突的情况。
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
编译报错,所以最好使用完整的类名:
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
可以使用 import static 导入包中静态的方法和字段,但不建议如此使用
在计算一个数的 n 次方的时候,使用了 Math.pow(x , 10),发现这里并没有写包,也能使用。
//导入了 Math 类的 所有的静态方法 当然 是用的时候 取的
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
1. 在文件的最上方加上一个 package 语句指定该代码在哪个包中
2. 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.baidu.demo1 )
3. 包名要和代码路径相匹配. 例如创建 com.baidu.demo1 的包, 那么会存在一个对应的路径 com/baidu/demo1 来存储代码
4. 如果一个类没有 package 语句, 则该类被放到一个默认包中
每次新建一个项目的时候,都会在 src 里创建类。那么 src 就是 IDEA 默认的一个自定义包。那我们该如何创建一个自己的包呢?
右击 src,NEW 处,点 Package。弹出 New Pacage 小方框, 在里面填入域名的颠倒形式,比如 com.baidu.www。
紧接着右击新建包,在 New 处 ,新建一个新的类即可。此时在 src 的文件下就可以看见三个文件了,分别是 com 、baidu 以及 www。
在 src 处再创建一个包,com.baidu.www2 。打开 baidu 文件发现有 www,以及 www2,这就是两个不同的包。
package com.baidu.www;
class Size{
int size1 = 99;
}
public class Text {
int size2 = 999;
public void show(){
System.out.println(size2);//同一个包 同一个类使用
}
public static void main(String[] args) {
Size sz = new Size();
System.out.println(sz.size1);//同一个包 不同类使用
}
}
package com.baidu.www2;
public class Text2 {
public static void main(String[] args) {
com.baidu.www.Text text = new com.baidu.www.Text();
System.out.println(text.size2);
}
}
在包 www2 中导入包 www1 中的 Text 类,想打印默认权限的 size2,编译报错。因为 size2 是default 的,不允许其他包访问。
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 编程开发包。
除了第一个包,后面的包需要程序员手动 import。
对类的某些属性及方法用 private 来修饰,从而对外隐藏这些细节,达到封装的目的。而被 private修饰的变量及方法,权限的变小了,只能在类中使用。
class Person{
private String name;
private int age;
private void eat(){
System.out.println("吃饭!");
}
}
public class Text {
public static void main(String[] args) {
Person person1 = new Person();
System.out.println(person1.name);
}
}
上述代码中,name ,age 以及方法 studentId 都被private修饰,只能在 Person 类中访问,所以在Text 类中访问 name 的时候,编译会报错。
那么 private 关键字修饰的成员变量,该如何初始化?—— 使用构造方法
可以自己写,但也可以让 IDEA 自己生成:右击,选择 Generate -> Constructor ,按住 Ctrl 可选择自己想要的变量。
class Person{
private String name;
private int age;
private void eat(){
System.out.println("吃饭!");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void show(){
System.out.println(name+" "+age);
}
}
public class Text {
public static void main(String[] args) {
Person person1 = new Person("吹画",33);
person1.show();
}
}
那如果后续使用过程中,想要修改 name 和 age 的值,又该如何呢?
同样右击,选择 Generate -> Getter and Setter , 同样 Ctrl 选择想要的变量,就能生成多个 Getter 与 Setter 方法。
class Person{
private String name;
private int age;
private void eat(){
System.out.println("吃饭!");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void show(){
System.out.println(name+" "+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 Text {
public static void main(String[] args) {
Person person1 = new Person("吹画",33);
person1.show();
person1.setName("翠花");
person1.setAge(35);
System.out.println(person1.getName());
System.out.println(person1.getAge());
person1.show();
}
}
一个班的学生,姓名、年龄、学号等都是不一样的,可是班级呢?大家都是一个班的,如果按照以前的写法,每个学生对象会把类定义的所有成员变量都保存一份实例变量。那么如果一个班级有 45 人,那么 “X年X班” 这一信息会被重复保存45次,这么做,难道不是很浪费空间吗?所以这就有了static 关键字。
要表示学生的班级,这个属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。在 Java 中,被 static 修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
static 修饰的成员变量,称为静态成员变量,也叫类变量。特点有以下几条:
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中,这是静态成员变量的最大特点!
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁
class Student{
private String name;
private int age;
private String gender;
public static String highShool = "三年一班";
//初始化
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
//修改及类外访问
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
public class Text {
public static void main(String[] args) {
Student student1 = new Student("张帅",17,"男");
Student student2 = new Student("莉丝",16,"女");
Student student3 = new Student("荷包",19,"男");
System.out.println(student1.highShool);
System.out.println(student2.highShool);
System.out.println(student3.highShool);
System.out.println(Student.highShool);
}
}
在 Text 中实例化了三个学生,通过对象的引用可以访问到班级,但是会有警告,而如果类名访问,则一切正常。由于 highShool 是类变量,前面说了,是不属于任何对象的,那么是不是在没有实例化对象之前,就可以访问呢?重写 main 方法如下:
public class Text {
public static void main(String[] args) {
System.out.println(Student.highShool);
}
}
输出
三年一班
甚至给一个极端的例子,来更好地看清成员变量的这个特性:
public class Text {
public static void main(String[] args) {
Student student = null;
System.out.println(student.getName());
//System.out.println(student.highShool);
}
}
第一条打印,引发了空指针异常,而第二条打印,编译通过。
所以,静态的成员变量,可以借助类名直接访问,而不需要通过对象的引用来访问。
那么到这里总结一下,成员变量分为静态成员变量以及非静态成员变量。
Java 中,被 static 修饰的成员方法称为静态成员方法,不是某个对象所特有的。同成员变量一样,成员方法也分为两类,一是静态成员方法,亦叫类方法,一是非静态成员方法。
class Student{
......
......
......
public static void doClass(){
System.out.println("上课啦,同学们!");
}
}
public class Text {
public static void main(String[] args) {
Student.doClass();
}
}
同样的,静态成员方法不属于任何对象,因此在没有实例化对象之前,就可以通过类名来访问。
对于静态成员方法,有以下几点需要注意:
1. 同一个类中,静态成员方法可以互相直接调用
public class Text {
public static void func(){
System.out.println("烦死了!");
}
public static void func2(){
func();
//或者写成 Text.func();
System.out.println("嗷嗷嗷!");
}
public static void main(String[] args) {
func2();
//Text.func2();
}
}
2. 静态成员方法不可以直接调用非静态成员方法,一定得是通过对象的引用才能调用。
public class Text {
public void func(){
System.out.println("烦死了!");
}
public static void func2(){
System.out.println("嗷嗷嗷!");
}
public static void main(String[] args) {
Text funcc = new Text();
funcc.func();
func2();
}
}
3. 在静态变量方法中,不能使用非静态的成员变量。
非静态的成员变量是属于对象的,要知道,前面代码中调用静态变量方法的时候,根本连对象都没有实例化,直接用类名调用。只要是非静态的数据成员,只能通过对象的引用调用。
class Student{
......
......
......
public static void fun(){
// System.out.println(this.name); //静态方法里不能使用this
// System.out.println(name);
//静态变量方法里得这样调用非静态成员变量
Student student = new Student("666",88,"女");
System.out.println(student.name);
}
}
public class Text {
public static void main(String[] args) {
Student.fun();
}
}
4. 而非静态方法可以直接调用静态方法
public void doClass(){
System.out.println("上课啦,同学们!");
fun();
}
public static void fun(){
// System.out.println(this.name);
// System.out.println(name);
//静态变量方法里得这样调用非静态成员变量
Student student = new Student("666",88,"女");
System.out.println(student.name);
}
同前面仅有 private 修饰的成员变量一样,修改或取得这个值,也用到了 Getter 和 Setter 方法。
这里再创建了一个城市类:
class CityInChina{
private String province;
private double longitude;
private double latitude;
private static String country = "中国";
public CityInChina(String province, double longitude, double latitude) {
this.province = province;
this.longitude = longitude;
this.latitude = latitude;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public static String getCountry() {
return country;
}
public static void setCountry(String country) {
CityInChina.country = country;
}
}
public class Text {
public static void main(String[] args) {
System.out.println( CityInChina.getCountry());
}
输出
中国
在上面的代码中,我们发现,静态变量成员 country 访问权限为private ,其 Setter 及 Getter 方法 也是 static 修饰的,这样在调用的时候,可以用类名调用。
是不是又有同学有想法,说能不能就是不要 static ,那么后续要访问的时候,要得 new 一个对象,反而不方便了。
1. 就地初始化
private static String classRoom = "1102A";
2. 直接不写,编译器设置成默认值
private static String classRoom;
3. 使用Getter 和 Setter 来初始化
public static String getCountry() {
return country;
}
public static void setCountry(String country) {
CityInChina.country = country;
}
4. 通过代码块来进行赋值
在本小节中,重点讲解通过代码块来进行赋值。
代码块的分为:普通代码块、构造块(非静态代码块、实例代码块)、静态块以及同步代码块。
public class Text {
public static void main(String[] args) {
func2();
}
public static void func2(){
System.out.println("aaaaaaaaaa");
System.out.println("vvvvvvvvvvvvvv");
System.out.println("嗷嗷嗷!");
{
//这块代码的执行 不需要条件
int a = 10;
System.out.println("定义在方法内部的代码块!普通代码块!");
}
}
}
在类里边,方法外边的代码块,又或者说在类里面,方法外面的变量,都是实例代码块或非静态代码块。初始化非静态的数据成员,会比构造函数更先执行
class CityInChina{
......
......
......
{
System.out.println("非静态代码块/实例代码块/构造代码块!->初始化非静态的数据成员");
province = "GuangDong";
longitude = 113.281;
latitude = 23.1252;
System.out.println(province);
System.out.println(longitude);
System.out.println(latitude);
}
}
public class Text {
public static void main(String[] args) {
CityInChina BeiJing = new CityInChina("BeiJing",116.20,39.56);
System.out.println(BeiJing.getProvince());
System.out.println(BeiJing.getLongitude());
System.out.println(BeiJing.getLatitude());
}
}
输出
非静态代码块/实例代码块/构造代码块!->初始化非静态的数据成员
GuangDong
113.281
23.1252
BeiJing
116.2
39.56
实际编译过程,是将构造代码块的内容拷贝到构造方法的前方,再依次执行。
4.5.1 和 4.5.2 在这里知识介绍语法,但在实际应用中,还是使用构造方法初始化非静态成员变量。
使用 static 定义的代码块称为静态代码块。一般用于初始化静态成员变量。
class CityInChina{
......
......
public CityInChina(){
System.out.println("不带参数的构造方法!");
}
static {
System.out.println("静态代码块-》初始化静态的数据成员/ 提前准备一些数据");
}
{
System.out.println("非静态代码块/实例代码块/构造代码块!->初始化非静态的数据成员");
}
}
public class Text {
public static void main(String[] args) {
CityInChina ShanXi = new CityInChina();
}
}
输出
静态代码块-》初始化静态的数据成员/ 提前准备一些数据
非静态代码块/实例代码块/构造代码块!->初始化非静态的数据成员
不带参数的构造方法!
可以看到,执行顺序为 静态代码块 -> 构造块 -> 构造方法,不论静态代码块放在方法中的哪里,都会先执行。
class CityInChina{
......
......
......
public static void fun(){
System.out.println("啊啊啊啊!");
}
}
public class Text {
public static void main(String[] args) {
CityInChina.fun();
}
}
输出
静态代码块-》初始化静态的数据成员/ 提前准备一些数据
啊啊啊啊!
由此可知,只要类被加载,静态代码块就会执行。而实例代码块得在创建对象之后,才会执行。
public class Text {
public static void main(String[] args) {
CityInChina.fun();
CityInChina.fun();
}
}
输出:
静态代码块-》初始化静态的数据成员/ 提前准备一些数据
啊啊啊啊!
啊啊啊啊!
可以看到,静态代码块只执行了一次。同样地,实例化多个对象,静态代码块也只会执行一次。
需要注意的是:
1. 静态代码块不管生成多少个对象,其只会执行一次
2. 静态成员变量是类的属性,因此是在 JVM 加载类时开辟空间并初始化的
3. 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)