目录
1. 封装
1.1 封装的概念
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。这也算一种封装。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
1.2 访问限定符
Java 中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java 中提供了四种访问限定符:
范围 | private | default | protected | public | |
1 | 同一包中同一类 | ✔ | ✔ | ✔ | ✔ |
2 | 同一包中不同类 | ✔ | ✔ | ✔ | |
3 | 不同包中的子类 | ✔ | ✔ | ||
4 | 不同包中的非子类 | ✔ |
这里对 default 说明一下,defualt 表示默认的意思,并不是真的在成员变量前加一个default的单词,而是什么都不加,默认权限为同一包同一类以及同一包中不同类。而 public:可以理解为一个人的外貌特征,谁都可以看得到。private:只有自己知道,其他人都不知道。protected 主要是用在继承中,继承部分详细介绍。
为了更好地了解 private、default 及public 的区别,下面介绍包的概念
2. 包
2.1 包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
在 Java 中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一 个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
2.2 导入包中的类
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);
}
}
2.3 自定义包
2.3.1 包的基本规则
1. 在文件的最上方加上一个 package 语句指定该代码在哪个包中
2. 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.baidu.demo1 )
3. 包名要和代码路径相匹配. 例如创建 com.baidu.demo1 的包, 那么会存在一个对应的路径 com/baidu/demo1 来存储代码
4. 如果一个类没有 package 语句, 则该类被放到一个默认包中
2.3.2 创建一个包
每次新建一个项目的时候,都会在 src 里创建类。那么 src 就是 IDEA 默认的一个自定义包。那我们该如何创建一个自己的包呢?
右击 src,NEW 处,点 Package。弹出 New Pacage 小方框, 在里面填入域名的颠倒形式,比如 com.baidu.www。
紧接着右击新建包,在 New 处 ,新建一个新的类即可。此时在 src 的文件下就可以看见三个文件了,分别是 com 、baidu 以及 www。
2.4 包访问权限——defualt
在 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 的,不允许其他包访问。
2.5 常见的包
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。
3. 何为封装
3.1 private 关键字
对类的某些属性及方法用 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();
}
}
4. static 成员
在对某一类实体的数据特征进行抽象的过程中,会遇到有些数据特征值对于每个对象都是完全独立的,有些则是所有对象共享的。例如,在对所有中国人实体进行抽象时,可以发现所有中国人都有国籍、姓名、年龄、性别等数据特征,它们都需要声明为对应的成员变量,但是其中的国籍值是所有对象共享的,都是“中国”,但是每个人的姓名、年龄、性别值是独立的、各不相同的。Java语言中规定,如果某个数据特征值是该类所有实体对象共享的,那么该数据特征对应的成员变量就用static修饰,称为静态变量或类变量。反之,如果某个数据特征值对于该类的每个实体对象都是独立的,那么该数据特征对应的成员变量就不能用static修饰,称为实例变量。
4.1 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);
}
}
第一条打印,引发了空指针异常,而第二条打印,编译通过。
所以,静态的成员变量,可以借助类名直接访问,而不需要通过对象的引用来访问。
那么到这里总结一下,成员变量分为静态成员变量以及非静态成员变量。
4.2 static 修饰成员方法
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);
}
4.4 private static 修饰的成员变量
同前面仅有 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 一个对象,反而不方便了。
4.5 对静态成员变量的初始化
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. 通过代码块来进行赋值
在本小节中,重点讲解通过代码块来进行赋值。
代码块的分为:普通代码块、构造块(非静态代码块、实例代码块)、静态块以及同步代码块。
4.5.1 普通代码块
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("定义在方法内部的代码块!普通代码块!");
}
}
}
4.5.2 构造块
在类里边,方法外边的代码块,又或者说在类里面,方法外面的变量,都是实例代码块或非静态代码块。初始化非静态的数据成员,会比构造函数更先执行
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 在这里知识介绍语法,但在实际应用中,还是使用构造方法初始化非静态成员变量。
4.5.3 静态块
使用 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. 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)