JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装


前面一篇文章《JavaSE 拾遗(3)——JavaSE 面向对象程序设计语言基础(3) 》主要说了 java 应用程序的倒数第二层组成元素——函数,下面接着说 java 应用程序倒数第三层组成元素——类、对象,对象是类动态的概念,类的功能都要转化为对象才能实现。封装这里讲述了什么是封装,什么时候使用封装,如何定义一个类来实现封装。


先说一下面向对象的概念,面向对象是一种把这个世界看做为对象组成的思维方式。面向对象编程是把这个世界看做对象组成,并用面向对象的语言来描述我们对世界里的对象的认识的表达方式,我们认识这个对象的世界主要就是使用 简化、对比等抽象思维方式。面向对象语言中的封装、继承、多态就是这些思维方式的对应形式。

怎么学习面向对象编程

  • 掌握一种面向对象语言的语法——java
  • 掌握面向对象的思维方式原则——先整体,后局部;先抽象,后具体;先确定谁来做,后确定怎么做
  • 熟悉面向对象的设计原则——“开-闭”原则;里氏代换原则; 依赖倒转原则;合成/聚合复用原则; 迪米特法则; 接口隔离原则(看 这里 老外怎么解释OOD)
  • 掌握面向对象设计模式
和类、对象相关的知识点有
  • 封装
  • 继承
  • 多态

封装

指隐藏对象的属性和功能实现细节,仅对外提供公共访问方式,也就是指把功能和数据打包,包、类、private 修饰符、函数、接口等都是封装的具体形式。说的形象点,封装就是打包组合,打包组合和拆分是相辅相成存在的,意思是说封装表面上是在做组合打包的工作,但是潜在的也有拆分的工作,先拆分后组合再打包嘛。java 就是以类为封装的单位,进行封装。

类的也是封装的形式之一,下面说类相关的东西
  • 类和对象
  • 成员变量和局部变量
  • 匿名对象
  • 构造函数
  • 构造代码块
  • this 关键字
  • static 关键字
  • 内部类

类和对象

类的定义方法 class
类的抽取方法,名词提炼法,比如人关门,人、门就可以提取为类,关 是门的功能
对象的创建方法 new 类名()
类和对象的关系:类是用来表达对现实生活中事物的描述——就像建筑的图纸,对象是用来表达事物实实在在的个体——就像一栋实实在在的楼

成员变量和局部变量

(是什么)成员变量作用于整个类中,局部变量只能作用于函数或者语句块中
(能做什么)成员变量是类的组成元素,用来描述整个类中都要使用到的数据,局部变量是函数或者语句块的组成元素,配合表达式完成对数据的操作
(为什么)成员变量存储在 jvm 堆内存中,局部变量存在于 jvm java栈内存中

匿名对象

(是什么)直接创建,没有定义一个引用变量指向的对象
(能做什么)1.如果对象的方法只是用一次,那么使用匿名对象可以简化书写;2.将匿名对象作为实际参数进行传递也可以简化书写
所以匿名对象完全属于表达层的冗余,可以简化表达;在设计层上并不是必须存在;但是匿名对象细分对象创建的分类,使创建对象时,对对象作用的表达更清楚,一看到匿名对象的表达方式我们就知道这个对象要么作为函数参数进行传递,要么只使用一次。
(对象的创建,需要在堆内存中开辟空间存放对象的内容,在栈内存中开辟空间存放对象变量,这些都是需要在内存中开辟空间的,而且对象存在的时间可能会很长。使用匿名对象,就免去了在栈内存中开辟空间,可以节省内存空间,而且在使用之后, 对象就会立马被销毁,不会在内存中存在很长时间。所以说匿名对象可以简化代码,减小内存的开销。)

构造函数

(是什么)函数名与类名相同,不用定义返回值类型,不可以写 return 语句的函数,如果类中没有构造函数,编译器会加一个默认构造函数
(能做什么)给对象进行初始化,某些事物一创建就具备的特性或者执行的行为定义在构造函数中,一般为了提高可读性,一创建就执行的行为用另一个函数定义,构造函数只用来做初始化
(为什么)构造函数在对象一创建就运行

构造代码块

(是什么)构造代码块,定义在类成员位置上的代码块
(能做什么)构造代码块,定义类的不同对象实例共性的初始化内容,用来给所有的对象进行统一初始化,构造函数只给相应对象进行初始化
(为什么)构造代码块,在对象一创建就执行,而且先于构造函数执行
下面是一个演示程序
/**
需求:演示构造函数、构造代码块、默认初始化的先后顺序

思路:

步骤:
*/
public class Test{
	private int i = 8;

	{
		i = 9;
	}

	public Test(){

		i =10;
	}

    public static void main(String[] args) { 
        System.out.println(new Test().i); 
    } 
} 
javap 反汇编后:
JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装_第1张图片
这个说明在 javac 处理之后,调用父类构造函数、默认初始化、构造代码块、构造函数 中的语句都放到了 init 函数中,其中构造默认初始化在最前面,其次是构造代码块,最后是构造函数中的语句。

this 关键字

(是什么)this关键字代表本类当前对象
(能做什么)this 的存在的目的就是方便在构建描述对象的类时候,仍然可以使用该对象这个概念,这样提供一个默认的自指的概念方便了程序设计。用处1:因为构建类的时候,类中的元素处于不同层次,所以可能有重名的现象,又类内部元素是可以互访,那么就需要 this 关键字来显示的指示元素属于类这个级别。
(为什么) 对象的成员方法在被函数使用的时候,首先 jvm 会先在 java 栈区压一个改方法的栈帧,保存在 java 栈帧的局部变量表的第一个位置的变量就是 this,接着在保存参数列表中的变量, 这也就动态实现了在该方法内部可以随意访问对象的成员,如果是是调用 static 方法,则不会在栈帧局部变量表第一个位置保存 this ,而直接保存参数列中的变量。
(画一个 栈帧中this -> java 堆中的对象的 Class 字节码引用-> 方法区 Class 实例 的图 来辅助说明 jvm 对非静态成员的的解析过程)
this语句, "this()" 在函数第一行可以直接代表调用构造函数
super关键字的原理:
/**
需求:演示 super 的jvm原理

思路:

步骤:
*/
public class Test extends Father{
	public int i = 10;

    public static void main(String[] args) { 
        new Test().println();
    }

	public void println(){
		System.out.println(super.i);
	}
}

class Father
{
	protected int i = 12;
}
javap 反汇编结果:
JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装_第2张图片
这说明使用 super 的时候,javac 直接翻译为父类成员的索引,如果是字段, jvm 在 resolution 字符索引的时候,再在java堆中取出父类字段。符号索引 + java 栈函数栈帧中的 this 关键字,就能是 jvm 动态解析符号索引。

static 关键字

static 是一个修饰符,用于修饰成员,使成员成为静态成员,可以直接用用类名使用
static 成员特点
  • 随着类的加载而加载
  • 优先于对象的存在
  • 被所有对象所共享
  • 可以直接被类名所调用
对象变量和类变量的区别:类变量随着类的加载而存放于类方法区中,实例变量随着对象的创建而存放于堆内存中
static使用注意:静态成员只能访问静态成员,静态方法中不可以有 this、super关键字
(能做什么)静态变量主要用来对对象共享数据的表达,所有对象共享数据,一个改变所有都改变,比如所有中国人共享国籍这个数据,其实用指向同一个引用的这种方法也可以实现,只是用 static 这种方法可读性更好。static 方法可以参考工具类,这是一个不依赖于对象的方法,也就是这种方法的功能是多个对象公用,且独立于对象的方法。

静态代码块:随着类的加载而执行,只执行一次,用于给类初始化
/**
需求:演示 static 的原理

思路:

步骤:
*/
public class Test{
	public static int i = 10;

	static{
		i = 11;
	}

    public static void main(String[] args) {
    }
}
JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装_第3张图片
对于static 字段的访问,jvm 提供专门的指令 putstatic getstatic。

对象创建时初始化过程:
  • 加载类对应的 *.class 文件,创建 Class 对象实例,但是保存在方法区
  • 执行该类 static 代码块,给类进行初始化
  • 在堆内存中开辟空间,分配内存地址
  • 在堆内存中建立对象特有属性,并进行默认初始化
  • 对属性进行显示初始化
  • 对对象进行构造代码块初始化
  • 对对象进行对应构造函数初始化
单例设计模式
饿汉式
/**
需求:实现饿汉式单例设计模式

思路:用 static 把对象创建封装在类内部

步骤:
1.将构造函数私有化
2.在类中创建一个本类对象
3.提供一个可以方法可以获取到该对象

*/
class Single
{
	private static Single single = new Single();

	private Single()
	{
	}

	public static Single getInstance()
	{
	    return single;
	}
}

class SingleDemo
{
	public static void main(String[] args) 
	{
		Single single = Single.getInstance();

		System.out.println("Hello World!");
	}
}
线程安全的懒汉式
/**
需求:实现线程同步的懒汉式单例设计模式

思路:用 static 把对象创建封装在类内部,延迟创建对象

步骤:
1.将构造函数私有化
2.提供一个可以获取到该类对象的方法,并既在第一次访问该方法的时候才创建对象,以后都是直接返回已经创建的对象

*/
class Single
{
	private static Single single =  null;

	private Single()
	{
	}

	public static Single getInstance()
	{
		if(single == null)
		{
			synchronized(Single.class)
			{
				if(single == null)
				{
					new Single();
				}
			}
		}
		return single;
	}
}

class SingleDemo
{
	public static void main(String[] args) 
	{
		Single single = Single.getInstance();

		System.out.println("Hello World!");
	}
}

内部类

内部类的意思就是将类的定义放在另一个类的内部。有时合理的内部类使用会使代码更加简洁,令程序更加巧妙。作为外部类的成员,内部类可以访问外部类私有的成员变量。

静态内部类不可以使用外部类的非静态成员。当内部类中的成员为静态时,内部类必须为静态的。

局部内部类只能访问 final 修饰过的局部变量,因为非 final 局部变量存在于 java 栈的栈帧的局部变量表中,而内部类对象存在于堆中,所以内部类对象的生存周期可能比非 final 局部变量要长。更详细的介绍为什么内部类只能访问 final 修饰的局部变量,看这里

           使 用匿名内部类应该注意:
           a) 匿名内部类不能有构造方法
           b) 匿名内部类不能定义任何静态成员、方法和类。
           c) 匿名内部类不能是public,protected,private,static。
           d) 只能创建匿名内部类的一个实例。
           e) 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
           f) 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

/**
需求:演示内部类

思路:成员访问的一些规则:
	 1.内部类 和 他的所属类 之间成员的相互访问,成员有重名的情况
     2.其他外面的类 访问内部类的成员

	 内部类定义在外部类成员位置上的时候,可以用成员修饰符修饰。private static
	 静态内部类只能使用外部类的静态成员。内部内既然作为成员,那么就很少对外开
	 放了。
	 当内部类定义了静态成员,该内部类必须是 static 的。

	 匿名内部类,是局部内部类的一种简写形式。


步骤:
*/
class OutsideClass
{
	private int v = 0;

	//1.成员内部类演示,private 成员类用得较多
	class InsideClass
	{
		int v = 1;

		void function()
		{
			int v = 2;
			System.out.print(v);
			System.out.print(this.v);
			System.out.print(OutsideClass.this.v);	//打印结果是 210
		}
	}

	//2.静态内部类演示
	static class StaticInsideClass
	{
		int v = 1;

		void function()
		{
			int v = 2;
			System.out.print(v);
			System.out.print(this.v);
			//System.out.print(OutsideClass.this.v);	//报错,只能访问外部类静态成员
		}
	}

	

	void function()
	{
		//3.局部内部类,只能访问外面 final 变量
		class LocalInsideClass
		{
			void function()
			{
				System.out.println("局部内部类");
			}
		}

		//4.匿名内部类演示,用得较多,Comparator 就有使用
		new OutsideClass()
		{
			void function()
			{
				System.out.println("匿名内部类");
			}
		}.function();
	}
}

class InsideClassMain
{
	public static void main(String[] args) 
	{
		// 这种外部使用内部内创建对象的情况基本不使用,因为内部类一般都封装为 private 成员类
		//OutsideClass.InsideClass part = new OutsideClass().new InsideClass();
		OutsideClass part = new OutsideClass();
		part.function();
	}
}
JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装_第4张图片
JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装_第5张图片
如图,javac 会给所有的非静态内部类自动的添加一个字段,用来保存外部类对象的引用,在内部类的对象中访问外部类对象字段的时候,会使用这个引用来访问,但是 在内部类中访问外部类字段,不是使用 getfeild setfeild 指令,而是调用外部类的 access$000 这个函数,这个函数却不是我们自己定义的。
关于内部类更详细的类容看 这里

你可能感兴趣的:(JavaSE 拾遗(4)——JavaSE 面向对象程序设计语言基础(4)...封装)