1.封装
1.1封装的概念
java做为一门面向对象的语言,具有基本的三大特性:封装,继承和多态。在类与对象的第二篇文章中我来讲解一下封装这一特性。这也是类和对象这个概念最直接的体现。何为封装?顾名思义,是将一堆部件(实体)组合在一起,再用某种方法将其封存,整体包装在一起。简单来说就是套壳屏蔽细节。比如我们生活中的汽车就是封装这种思想的体现。正常汽车的内部包含了很多零件,如发动机,底盘,离合器等。然而汽车的使用者并不需要 知晓每个部件的运行原理与使用方法,相反还可能损害零件。因此使用一个外壳将汽车套壳生产出来,保留一些接口与使用者进行交互,如方向盘,刹车等等。 同样的对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件。对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。
封装的通俗定义:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。java实现封装:类+访问权限。
1.2访问限定符
不难得出结论,类这一概念可以将数据和操作数据的方法进行有机结合,既字段和成员方法。那么我们还需要访问权限来达到隐藏对象属性和实现细节这一目的。因此需要使用访问限定符,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
| private | default | protected | public |
---|
同一包中同一类 | 能 | 能 | 能 | 能 | 同一包中不同类 | 否 | 能 | 能 | 能 | 不同包中的子类 | 否 | 否 | 能 | 能 | 不同包中非子类 | 否 | 否 | 否 | 能 |
通过上表我们可以发现四种修饰符基本是按照其名字的含义来进行使用范围界定的。其中private指私有的,也是访问权限要求最高的,只有同一类中可以使用。而default为默认权限,在使用类时如果不使用修饰符则视为default,只能在同一个包中进行使用。protected多应用于继承场景,可以用于继承的子类中。而public则意为公共的,都可以访问的。 访问权限不仅可以限定成员的可见性,也可以限定类的可见性。 一般情况下设置成员变量为private,成员方法为public。
1.3 封装之–包
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。同样的,java中的包就像一个专门的仓库,比如一个武器库,里面存放的是各种各样的武器,既他们都有共同的一个特点。包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。 那么如何导入包中的内容呢?下面介绍三种方式。第一种方式是显式的指定全路径的包名,在java中已经写好了很多类供我们使用,比如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导入Date类,然后可以直接定义对象:
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.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
所以建议初学者在调用包时采用显式的定义方式,写全名称不要偷懒。上图的代码需要补全具体的包名与类名:
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导入包中静态的方法和字段:
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要. import 只是为了写代码的时候更方便. import 更类似于 C++ 的 namespace 和 using。 关于自定义包: 规则:
- 在文件的最上方加上一个 package 语句指定该代码在哪个包码在哪个包中.
- 包名需要尽量指定成唯一的名字。
- 包名要和代码路径相匹配. 例如创建 day0925.demo1 的包, 那么会存在一个对应的路径 day0925/demo1 来存储代码.
- 如果一个类没有 package 语句, 则该类被放到一个默认包中。
实现步骤如下: 1.在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包。在弹出的对话框中输入包名, 例如day0925.demo01.
2.在新建的包中,定义自己的类,如Test类。 3.此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了: 同时在包中的Test.java文件中第一句会自动出现一条package语句:package day0925.demo01。 常见的包如下:
- 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静态成员变量
当我们在定义类时,首先会去定义成员变量。成员变量是用来描述对象的属性。比如下面给出的动物类:
public class Animal{
String name;
int age;
String color;
String ancestor;
}
其中,名称,年龄以及颜色(毛发)都是用来描述具体所创建的对象。比如我们定义猫这个对象,它就会拥有自己的这三个字段。换言之,每个对象都会有属于自己的独立的成员变量。但是祖先这个变量是用来描述所有动物的,它是广义的说法。显然不适合以这种方式来定义。因此,当我们想要定义一个成员变量来描述类时,而非描述一个具体的对象,这个属性是属于类的,我们只需将这个字段用static来修饰即可。 static修饰的成员变量称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。具有以下的特性:
- 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
- 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
- JDK7及以前,HotSpot(Java虚拟机)中存储在方法区,JDK8及之后,类变量存储在Java堆中
- 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)。
public class Animal{
String name;
int age;
String color;
String ancestor=“cell”
public static void main(String[] args){
Animal cat=new Aniaml();
System.out.println(cat.ancestor);
System.out.println(Animal.ancestor);
}
以上代码可以证明一些特性。
2.2static修饰成员方法
在我们之前的学习中,类中的成员变量都是需要加修饰符的,可以是private,也可以是public。**那么使用static修饰的成员变量应该如何在类外访问呢?**先来看以下代码:
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306";
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.classRoom);
}
}
编译失败:
Error:(10, 35) java: classRoom 在 extend01.Student 中是 private 访问控制
在Java中,**被static修饰的成员方法称为静态成员方法,**是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。
public class Student{
private static String classRoom = "Bit306";
public static String getClassRoom(){
return classRoom;
}
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.classRoom);
}
}
Bit306
静态方法的特性:
- 不属于某个具体的对象,是类方法
- 可以通过对象调用,也可以通过类名.静态方法名(…)方式调用,更推荐使用后者
- 静态方法没有隐藏的this引用参数,因此不能在静态方法中访问任何非静态成员变量
- 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用。
- 静态方法无法重写,不能用来实现多态。
2.3static成员变量的初始化
我们前面学习了使用构造方法来初始化非静态的成员变量。但是静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性。静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化。
2.3.1就地初始化
该种方法顾名思义,在定义变量的时候直接给出初始值。
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306";
}
2.3.2 静态代码块初始化
要了解此形式,我们需要先了解什么是代码块。我们且往下慢慢道来。
3.代码块和内部类
3.1代码块
3.1.1代码块分类
使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造块
- 静态块
- 同步代码块
下来我们按顺序进行讲解。
3.1.2普通代码块
第一个是普通代码块,即出现在方法内部的代码块。这种用法是比较少见的。
public class test{
public void method{
{
System.out.printlin("这是一个代码块");
}
}
}
3.1.2构造代码块
构造(代码)块:**定义在类中的代码块(不加修饰符)。也叫:实例代码块。**构造代码块一般用于初始化实例成员变量。
public class Student{
private String name;
private String gender;
private int age;
private double score;
public Student() {
System.out.println("I am Student init()!");
}
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
实例代码块优先于构造方法执行,因为编译完成后,编译器会将实例代码块中的代码拷贝到每个构造方法第一条语句前。而就地初始化的语句优先于代码块中的。既顺序为:就地初始化语句—代码块语句–构造方法语句。
3.1.3静态代码块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。和上方的构造块区别为在代码块前加上static修饰符。代码如下:
package day0925.demo01;
public class Test {
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
{
this.name = "bit";
this.age = 12;
this.gender = "man";
System.out.println("I am instance init()!");
}
static {
classRoom = "bit306";
System.out.println("I am static init()!");
}
public Test(){
System.out.println("I am Student init()!");
}
public static void main(String[] args) {
Test s1 = new Test();
Test s2 = new Test();
}
}
运行结果:
注意事项:
- 静态代码块不管生成多少个对象,其只会执行一次。
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的。Java代码在经过编译器编译之后,如果要运行必须先要经过类加载子系统加载到JVM中才能运行。在链接阶段第二步准备中会给静态成员变量开辟空间,并设置为默认值,在初始化阶段,会执行静态代码块中的代码。(了解:关于类加载过程后序JVM中会详细讲解)。
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次合并,最终放在生成的clinit方法中,该方法在类加载时调用,并且只调用一次。而实例代码块只有在构造对象的时候会使用。
3.2内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,称为内部类。内部类也是封装的一种体现。 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类。
public class A{
}
class B{
}
注:内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件。内部类可以定义在类或者方法内的任何位置。包括代码块内。根据内部类定义的位置不同,一般可以分为以下几种形式:
- 成员内部类(普通内部类:被static修饰的成员内部类。 静态内部类:未被static修饰的成员内部类)
- 局部内部类(不谈修饰符)、匿名内部类
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中使用最多的是匿名内部类。
3.2.1成员内部类
在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类。 普通内部类:即未被static修饰的成员内部类。
public class OutClass {
private int a;
static int b;
int c;
public void methodA(){
a = 10;
System.out.println(a);
}
public static void methodB(){
System.out.println(b);
}
class InnerClass{
int c;
public void methodInner(){
a = 100;
b =200;
methodA();
methodB();
c = 300;
System.out.println(c);
OutClass.this.c = 400;
System.out.println(OutClass.this.c);
}
}
public static void main(String[] args) {
OutClass outClass = new OutClass();
System.out.println(outClass.a);
System.out.println(OutClass.b);
System.out.println(outClass.c);
outClass.methodA();
outClass.methodB();
System.out.println("==========================");
OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
OutClass.InnerClass innerClass2 = outClass.new InnerClass();
innerClass2.methodInner();
}
}
注意事项:
- 外部类中的任何成员都可以被在普通内部类方法中直接访问
- 普通内部类所处的成员与外部类成员位置相同,因此也受public、private等访问限定符的约束
- 在内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
- 普通内部类对象必须在先有外部类对象前提下才能创建
- 普通内部类的非静态方法中包含了一个指向外部类对象的引用。
- 外部类中,不能直接访问内部类中的成员,如果要访问必须先创建内部类对象。
对于第五点,我们可以从字节码层面进行说明。 静态内部类: 被static修饰的内部成员类称为静态内部类。
public class OutClass {
private int a;
static int b;
public void methodA(){
a = 10;
System.out.println(a);
}
public static void methodB(){
System.out.println(b);
}
static class InnerClass{
public void methodInner(){
b =200;
methodB();
}
}
public static void main(String[] args) {
OutClass.InnerClass innerClass = new OutClass.InnerClass();
innerClass.methodInner();
}
}
注意事项:
- 在内部类中只能访问外部类中的静态成员
- 创建内部类对象时,不需要先创建外部类对象
- 成员内部类,经过编译之后会生成独立的字节码文件,命名格式为:外部类名称$内部类名称。
3.2.2局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。
public class OutClass {
int a = 10;
public void method(){
int b = 10;
class InnerClass{
public void methodInnerClass(){
System.out.println(a);
System.out.println(b);
}
}
InnerClass innerClass = new InnerClass();
innerClass.methodInnerClass();
}
public static void main(String[] args) {
}
}
注意事项:
- 局部内部类只能在所定义的方法体内部使用。
- 不能被public、static等修饰符修饰。
- 编译器也有自己独立的字节码文件,命名格式:外部类名字$x内部类名字.class,x是一个整数。
- 几乎不会使用。
关于匿名内部类,在后续介绍接口时,我会详细的进行描述。 至此,关于类和对象的内容基本介绍完毕。如有不足欢迎大家的批评与指正。谢谢大家。
作者:端履门没有门
|