黑马毕向东Java课程笔记(day06[5-10]):面向对象(第二部分)——帮助文档doc+静态代码块+对象的初始化与调用+单例设计模式

本文详细介绍了Java中帮助文档的制作,包括javadoc的使用和注意事项。接着讲解了静态代码块的概念,强调了其在类加载时的执行特性。此外,探讨了对象的初始化过程和构造代码块的区别。最后,讨论了单例设计模式的两种实现方式及其内存管理。通过对这些概念的深入理解,有助于提升Java编程能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、帮助文档的制作javadoc
1.1、先举一个例子来说明
  首先我们定义一个工具类“ArrayTool”,并将这个类放在“G:\java\lkj”目录之下,然后我们在“G:\java1”目录之下创建一个“Demo”类来调用“ArrayTool”,如下图:

public class Demo {
	public static void main(String[] args) {

		int[] arr = {3,4,5,6};
		int max = ArrayTool.getMax(arr);/
		System.out.println(max);
	}	
}

  出现如下图所示的错误
在这里插入图片描述
  由于我们调用的“ArrayTool.java”类并不在当前目录之下,因此找不到这个类,那么我们设置路径将这个类的路径“G:\java\lkj”添加到我们的系统环境变量中,以便虚拟机可以找到该类,如下图
在这里插入图片描述
  显示无法加载主类,这个时候在当前路径“G:\java1”之下明明就有“Demo.class”类,为什么还运行不了?
在这里插入图片描述
  因为我们之前将路径设置为“G:\java\lkj”,没有在路径后面加上“;”符号,因此虚拟机在这个路径下找到“ArrayTool.class”之后,并不会回到当前目录之下找“Demo.class”类加载,因此我们必须将路径设置为“G:\java\lkj;”,加上“;”表示在设置的路径查找不到再回到当前路径下查找,或者是设置为“.;G:\java\lkj”,表示先在当前路径下查找,找不到再到我们设置的路径下面查找。最好设置为后面一种。2类结果如下所示:
G:\java\lkj;
.;G:\java\lkj
  上面的实验也说明,我们如果要使用某一个功能,与其相关的类必须全部放在同一个文件夹之下(如下图),不然就得将所需类的“.class”文件的路径添加到环境变量中,这样程序才能运行。
在这里插入图片描述
  另一方面,如果我们获得的某个类的“.java”文件,还要在其路径下对其编译,并将其编译后获得的“.class”文件的路径添加到环境变量中才可以运行,并回到我们主函数所在类的路径之下,编译并运行主函数,这样才不会出错。如我们在路径“G:\java1”下放主函数类“Demo.java”,在路径“G:\java”下放工具类“ArrayTool.java”,按上面的步骤,如下图

G:\>cd java
//进入java文件夹编译ArrayTool.java
G:\java> javac ArrayTool.java
//将ArrayTool.class的位置添加到环境变量
G:\java>set classpath=.;G:\java
G:\java>cd\
G:\>cd javal
//进入java1文件夹,编译Demo.java文件
G:\java1>javac Demo.java
G:\java1>java Demo
//成功运行
6

  但是,就算把“ArrayTool.class”给你,你将其路径添加到环境变量中并加载,由于你不知道这个类的具体功能,你也没办法使用它,差个说明书!

1.2、帮助文档的制作
  java的说明书通过文档注释来完成,以后,我们每编写一个功能都必须使用文档注释对其进行说明注释。如下,我们对ArrayTool工具类进行文档注释。我们编辑一个包含下面代码的ArrayTool类。
  首先,我们输入下面语句

javadoc -d myhelp -anthor -version ArrayTool.java
//其中,“-d”表示生成目录的位置,如果我们所写的文件夹不存在,系统会帮助我们创建,后面跟着作者、版本信息,最后是要创建帮助文档的“ArrayTool.java”文件。

  报错!!一个类如果想生成帮助文档,这个类必须是public类型(或者说protected类型)。
在这里插入图片描述
  我们修改错误后,帮助文档自动生成
在这里插入图片描述
  我们只需要看index.html文件即可。文档想要体现的,是我们准备对外暴露的内容,只能生成用public与protected修饰的内容。
在这里插入图片描述
  一个类中默认会有一个空参数的构造函数,这个默认的构造函数的权限和所属类一致。如果类被public修饰,那么默认的构造函数也带public修饰符。如果类没有被public修饰,那么默认的构造函数,也没有public修饰。总而言之,默认构造构造函数的权限是随着的类的变化而变化的。
  需要注意的是,我们自己写一个空参数的构造函数,不是默认构造函数,我们不写构造函数,那么空参数函数就是构造函数,其修饰符就随类而变化。如下面代码,我们写了 ArrayTool() {},但是这个只是空参数的构造函数,并不是默认构造函数,因此其不随ArrayTool类是public类型,相应生成帮助文档,必须将其设置为public类型

/**
 首先,是对类的描述:这是一个可以对数组进行操作的工具类,该类中提供了,获取最值,排序等功能。
 使用文档注释中特殊的表示,他们可以被文档注释工具所提取并识别
 @author lkj
 @version V1.1
 */
public class ArrayTool{//工具类
	/**
	将空参数的构造函数私有化,防止其他类创建本类对象
	 */
	private ArrayTool() {}
	
	// 对于每一个功能也必须使用文档注释进行描述 ,凡是public所描述地功能(方法)都可以用文档注释进行描述,因为他们都可以被文档注释工具所提取
	/**
	对功能的描述:获取一个整形数组中的最大值
	@param arr 接受一个int类型的数组
	@return 返回所传进数组的最大值
	 */
	public static int getMax(int[] arr) {
		int max = 0;
		for(int x=1; x<arr.length ;x++) {
			if(arr[x]>arr[max])
				max = x;
		}
		return arr[max];
	}
	
	/**
	对功能的描述:获取一个整形数组中的最小值
	@param arr 接受一个int类型的数组
	@return 返回所传进数组的最小值
	 */
	public static int getMin(int[] arr) {
		int min = 0;
		for(int x=1; x<arr.length ;x++) {
			if(arr[x]<arr[min])
				min = x;
		}
		return arr[min];
	}
	
	/**
	对一个int型数组进行冒泡排序
	@param arr 接受一个int类型的数组
	@return 返回排序完成的数组
	 */
	public static int[] bubbleSort(int[] arr) {
		for(int i=1; i<arr.length ;i++) {
			for(int j=0; j<arr.length-i ;j++) {
				if(arr[j]>arr[j+1])
					swap(arr[j],arr[j+1]);
			}
		}
		return arr;
	}
	
	/**
	对两个数进行位置置换
	@param num1  要置换的第一个数
	@param num2 要置换的第二个数
	 */
	private static void swap(int num1,int num2) {
		int temp = num1;
		num1 = num2;
		num2 = temp;
	}
}

  这里我们所描述的,就是程序的说明文档,也就是帮助文档(API,application program interface:应用程序接口)。接口就是对外暴露东西,也就是将你的程序中需要给其他人看的部分暴露给其他人使用,并对你的暴露出来的内容进行使用说明。
  如何在eclipse中生成帮助文档 添加链接描述
  需要注意,eclipse中一个java文档(就是一个界面中)只能有一个public类型的类,否则会报错!如果所创建的类都是public类型。则需要创建多个java文档。

2、静态代码块
2.1、静态代码块的介绍与说明
  如果在执行类时,希望先执行类的初始化动作,可以使用static定义一个静态区域。静态代码块随着类的加载而执行,并且只会执行一次。静态代码块优先于主函数执行。

public class AnyThting {
	static {
		//程序块
	}
}

  看如下代码:


class StaticCode{
	static
	{
		System.out.println("a");
	}
}
 class StaticCodeDemo {
	 //静态代码块初始化类
	static 
	{
		System.out.println("b");
	}
	public static void main(String[] args) {
		//StaticCodeDemo包含主函数,它是程序的入口,其会先进内存
		//因此先执行StaticCodeDemo类的静态代码块,对这个类进行初始化
		//随后创建StaticCode类对象进行,这个类进入内存,其静态代码块执行
		new StaticCode();
		new StaticCode();//又一次创建对象,类已经初始化在内存中了,再次创建对象并不会再次执行静态代码块
		System.out.println("over");
	}

	static
	{
		System.out.println("c");
	}
}

  结果如下:

b
c
a
over

  ** 如果我们不创建类的对象,仅仅是使用类名调用方法,但是只要类有加载到栈内存中**,其静态代码块依然会被加载,如下代码


class StaticCode{
	static
	{
		System.out.println("a");
	}
	public static void show() {
		System.out.println("show run");
	}
}
 class StaticCodeDemo {
	public static void main(String[] args) {
		//没有创建StaticCode类的对象,但是这个类要加载,依然会执行静态代码块
		StaticCode.show();
	}
}

  结果如下:

a
show run

  再看如下代码:


class StaticCode{
	static
	{
		System.out.println("a");
	}
}
 class StaticCodeDemo {
	public static void main(String[] args) {
		//创建StaticCode类的变量,但是该变量不指向任何东西,因此该类不加载进内存
		StaticCode s = null;
	}
}
//结果为不显示a

  总结:只有类中有内容被使用,这个类才会加载进内存,才会执行静态代码块!

2.2、静态代码块与构造代码块的对比(见黑马视频6.7)
  构造代码块作用:给对象进行初始化。对象一建立就运行,而且优先于构造函数执行。
  和静态函数的区别:静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。 基于静态代码块可以给所有对象统一初始化,它可以用来定义对象共性内容。

{
	//构造代码块
}

  区别:
(1)静态代码块是对类初始化,只在类加载的时候执行一次;构造代码块是对对象进行初始化,每创建一个对象,构造代码块就会执行!

class StaticCode{
	//无参数的构造函数
	StaticCode()
	{
		System.out.println("b");
	}
	//静态代码块
	static
	{
		System.out.println("a");
	}
	//构造代码块
	{
		System.out.println("c");
	}
	//一个int类型参数的构造函数
	StaticCode(int x)
	{
		System.out.println("d");
	}	
}
 class StaticCodeDemo {
	public static void main(String[] args) {
		//先执行类的加载,执行静态代码块,给类初始化
		//接下来创建对象,执行构造代码块,给所有对象初始化
		//接下来执行构造函数,给对应对象初始化
		new StaticCode(4);
	}
}
//结果是:a c d

  再注意静态成员与非静态成员的访问方式!创建对象时代码执行的优先级:静态代码块>构造代码块>构造函数
在这里插入图片描述
3、对象的初始化过程
  先看如下代码

class Person
{
	private String name = "haha";
	private int age;
	private static String country = "cn";
	//构造函数
	Person(String name,int age)
	{
			this.name = name;
			this.age = age;
			System.out.println(this.name+"..."+this.age);
	}
	//构造代码块
	{
		System.out.println(this.name+"..."+this.age);
	}
	
}
public class Demo1 {

	public static void main(String[] args) {
		Person p = new Person("lkj",20);
	}

}
//结果是
haha...0//构造代码块先于构造函数执行,而虚拟机对成员变量隐式赋值赋值先于构造函数赋值
lkj...20

  创建对象过程总结:Person p = new Person(“lkj”,20);
1)因为new用到了Person.class类,所以会先找到Person.class文件并加载到内存中。
2)执行该类中的static代码块,如果有的话,给person.class类进行初始化。
3)在堆内存中开辟空间,分配内存地址。
4)在堆内存中建立对象的特有属性,并进行默认初始化。
5)对属性进行显式初始化(如果有的话)。
6)对对象进行构造代码块初始化。
7)对对象进行对应的构造函数初始化。
8)将内存地址赋值给栈内存中的p变量。

4、对象调用成员过程(见黑马视频6.8)
  注意,静态变量、常量、类相关信息(包括方法的代码)都存储在内存的方法区,而在创建对象的时候,相应的成员变量代码与成员方法的地址值会加载到堆内存中(见就业班-day06视频内存解析)。在加载类的时候,静态的方法与变量会比非静态的方法先加载进方法区内存。
  我们在写代码的时候,在同一个类中,非静态的变量与方法部分会省略“this.”,代表本类对象调用,但是因为在同一个类中无需使用对象来调用它,因此可以省略this;而静态部分省略“类名.”,代表类调用,但是因为在同一个类中无需使用类名来调用它,因此可以省略类名。
  不管是方法中的局部变量,还是形参中的局部变量,都是在栈内存中创建空间的!
  **注意视频内关于内存区域的分析!!!**由于内容较难以描述,以视频的解说为主!不理解的部分放慢多看几遍,内存部分的分析很重要!!!

5、单例设计模式
5.1、方式一
  设计模式:解决某一类问题最行之有效的方法。java中23种设计模式。我们今天讲解单例设计模式:解决一个类在内存只存在一个对象。
  为什么会有一个类在内存中只创建一个对象的现象,见黑马视频6.8中9分30秒处开始的解释,会有只创建一个对象的情况出现。
  如何保证对象唯一?
1)为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象;
2)还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象;
3)为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
  这三步怎么用代码体现呢?
1)将构造函数私有化;
2)在类中创建一个本类对象;
3)提供一个方法可以获取到该对象。
  原则:对于事物该怎么描述,还怎么描述。当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。

/*
这个是先初始化对象,称为:饿汉式。
特点:Single类一进内存,就已经创建好了对象。
*/
class Single{
	//第一步,将构造函数私有化,防止外界创建对象
	private Single() {}
	//在类中创建一个本类对象
	//另一方面,s是类中的成员变量,为了避免外界通过类名直接对其调用,我们将其私有化,这样就只能通过getInstance()方法访问
	private static final Single s = new Single();//将s设定为final更加严谨
	//提供一个方法可以获取到该对象,该方法外部类不能提供对象,只能通过类名调用,将方法设置为静态
	//静态方法只能直接访问静态成员,这里没办法通过对象访问,因此必须将对象s设置为静态
	public static Single getInstance() {
		return s;
	}
}
class SingleDamo {
	public static void main(String[] args) {
		//获取对象,通过这个方法,无论外界创建多少个对象,都指向内存中的同一个对象
		//这样便保证一个类在内存中对象的唯一性
		Single s = Single.getInstance();
	}
}

  那么我们在下面的代码中试验一下

class Single{
	private int num;
	public void setNum(int num) {
		this.num = num;
	}
	public int getNum() {
		return num;
	}
}
class SingleDamo {

	public static void main(String[] args) {
		Single s1 = new Single();
		Single s2 = new Single();
		s1.setNum(30);
		//因为s1与s2是不同的对象,s1改变num的值是针对它这个对象本身,而原来的num值并没有改变
		System.out.println(s2.getNum());
	}
}
//结果是
0
//说明s1\s2代表不同对象

  再看

class Single{
	private int num;
	public void setNum(int num) {
		this.num = num;
	}
	public int getNum() {
		return num;
	}
	
	private Single() {}	
	private static final Single s = new Single();		
	public static Single getInstance() {
		return s;
	}
}
class SingleDamo {

	public static void main(String[] args) {
		Single s1 = Single.getInstance();
		Single s2 = Single.getInstance();
		s1.setNum(30);
		System.out.println(s2.getNum());
	}
}
//结果是
30
//说明s1\s2代表同一个对象

5.2、方式二
  代码如下

/*
对象是方法被调用时才初始化,也叫做对象的延时加载,称为懒汉式。
Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
*/
class Single{
	private Single() {}	
	private static Single s = null;	//懒汉式s不能设置为空	
	public static Single getInstance() {
		if(s == null)
			s = new Single();
		return s;
	}
}
class SingleDamo {
	public static void main(String[] args) {
		Single s = Single.getInstance();
	}
}

  注意黑马视频6.10,里面讲解的方式1与方式2在内存中的不同操作过程!
  懒汉式与饿汉式差不多,开发的时候一般使用饿汉式,既先加载对象。因为饿汉式安全简单,懒汉式容易出现小问题,这种情况见6-10的11分10秒开始处视频。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值