【Java】Java学习进度_W2

Java第二周的学习,一般是到周末整理,结果碰到了清明假期,慵懒了三天,现在继续整理。

第一周主要学习了环境搭建,以及一些规范、规则,和流程控制。今天继续上周的话题,先从循环说起,补充一下上周的细节。

1. 上周补充

1.1 补充内容1

对于如下的一段程序:

class Liucheng 
{ 
	public static void main(String[] args) 
	{ 
		for(int x = 0; x<3; x++) 
		{ 
			System.out.println(x); 
		} 
		System.out.println(x); 
	} 
}

此时会编译错误,如下图所示,这是因为,在for循环中的x当for循环结束之后,x就不在内存中了。


而对于下面这一段程序,

int y = 0; 
while(y<3) 
{ 
	System.out.println(y); 
	y++; 
} 
System.out.println(y);

此时,就可以编译通过,并能够运行。

1.变量自己的作用域,对于for来讲,如果将用于控制循环的增量定义在for语句,那么该变量只在for语句有效,for语句执行完毕,该变量在内存中被释放;

2.for和while可以进行互换,如果需要定义循环变量,用for更合适;

3.如果循环后,还要用循环中的变量,则用while。

有下面一段程序,可以看出在for循环中,各个语句的执行顺序

class Liucheng 
{ 
	public static void main(String[] args) 
	{ 
		int x = 1; 
		for (System.out.println("a"); x<3; System.out.println("c")) 
		{ 
			System.out.println("d"); 
			x++; 
		} 
	} 
}

编译,执行如下图所示。


1.2 补充内容2

break跳出内循环,跳出最近的循环

在下面一段程序中,

class breakDemo
{
	public static void main(String[] args)
	{
		for(int x = 0; x<5; x++)
		{
			for(int y = x; y<5; y++)
			{
				if(y%2 == 1)
					break;
				System.out.println("x= "+x+". y="+y);
			}
		}
	}
}

编译执行,结果会如下图所示。


也可以增加下面程序段中的w和q,以控制每次跳出哪一个循环。

class breakDemo
{
	public static void main(String[] args)
	{
		w:for(int x = 0; x<5; x++)
		{
			q:for(int y = x; y<5; y++)	
			{
				if(y%2 == 1)
					break w;
				System.out.println("x= "+x+". y="+y);
			}
		}
	}
}

结果为


continue只能用于循环结构,继续循环,结束本次循环,继续下一次循环。如果把上上段程序中的break换成continue,那么输出结果就是下面了。


注意点,在break和continue后面不能再有其他的语句,会编译不通过。

if(x%2==1)
{
	continue;
	System.out.println(x);
}

编译时,会有下面错误。


2. 函数/方法

这是比较重要的一部分。

2.1 函数定义

如何定义一个函数呢?

①既然函数是一个独立的功能,那么先明确该功能的运算结果是什么,这是在明确函数的返回值类型

②再明确在定义该功能的过程中,是否需要未知的内容参与运算,这是在明确函数参数列表(类型,以及个数)。

格式就是:

修饰符 返回值类型 函数名(参数类型 形参1,参数类型 形参2,...)
{
	执行语句;
	return 返回值;
}

比如,

public static int getResult(int num)
{
	return num*3+6;
}
当函数运算后没有具体的返回值类型,用个特殊的关键字来标识,这个关键字就是void,此时函数中的return语句可以不写。

举例。

	//需求:判断两个数是否相同
	//思路:1.明确功能的结果,结果是boolean
	//      2.功能是否需要未知内容参与运算,有,两个整数
	public static boolean compare(int a,int b)
	{
		//具体思路1
/* 		if(a == b)
			return true;
		else
			return false;  */
		//具体思路2
		// return (a==b)?true:false;
		//具体思路3
		return a == b;
	}
在这里,具体采用了三种思路,思路1是最平常容易想到的,但是比较冗余;思路2,采用了条件语句,以为会比较炫酷呢;思路3,应该是以后的一种常态。

这里再注意一点,返回值类型为void的函数不可以打印输出;

2.2 重载overload

同一个类中,允许存在一个以上的同名函数,只有它们的参数个数或者参数类型不同即可。
那什么时候用呢?
当定义的功能相同,但是参与运算的未知内容不同,这是就定义一个函数名称。

重载与返回值类型没有关系。

public static int add(int a, int b)
{
	return a+b;
}
public staic int add(int a, int b; int c)
{
	return add(a,b)+c;
}
当然,光是这样,函数还并没有介绍完,这里只是介绍一下格式,以后学习的过程中,还会继续用到函数,定义函数。

3. 数组

3.1 数组定义

格式1:
元素类型[] 数组名 = new 元素类型[元素个数或者数组长度];
例如,
int[] arr = new int[5];
格式2:
元素类型[] 数组名 = new 元素类型[]{元素1,元素2,....}
例如,
int[] arr = new int[]{3,4};
这里说一下内存结构,包括栈和堆。
栈内存:数据使用完毕,会自动释放;
堆内存:
1.实体;
2.都有默认值;(int→0;double→0.0;float→0.0f;boolean→false)
3.垃圾回收机制。
new在堆内。
当想不指向地址时,可以使用
int[] x = new int[3];
x = null;
如果执行了x=null,那么中间new[3]就是垃圾。
现在区分一组概念。如果执行下面的一段代码,是否会产生垃圾?
int[] x = new int[3];
int[] y = x;
y[1] = 89;
x[1] = 77;
x = null;
这个时候,两个引用指向同一个数组,第一个语句,在内存中如上图所示,第二个语句,产生下面的结果
第三个语句,使得[1] = 89,第四个语句使得[1]=77。最后一个语句,使得x不指向任何地址,但是此时y指向这个数组,这个数组仍然不是垃圾。
数组和数是不同的,对于下面这段代码
class Test
{
	public static void main(String[] args)
	{
		int a = 5;
		int b = a;
		b= 8;
		System.out.println(a);
		System.out.println(b);
	}
}

输出结果为:
常见问题:
int[] arr = new int[3];
System.out.println(arr[3]);
此时编译无错误提示,编译只检查语法错误,运行时出错,索引出界;

int[] arr = new int[3];
arr = null;
System.out.println(arr[0]);
空指针异常,当引用任何指向值为null的情况,该引用还在用于操作实体。

3.2数组操作

3.2.1获取数组元素

这个时候会使用到length函数。见下面代码:

int[] arr = new int[]{4,5,7,89,0};
for(int x=0;x<arr.length;x++)
{
	if(x != arr.length-1)
	{
	System.out.print(arr[x]+",");
	}
	else
	{
		System.out.print(arr[x]);
	}
}

3.2.2排序

(1)选择排序

代码如下:

public static void selectSort(int[] arr)
{
	for(int i = 0; i<arr.length-1; i++)
	{
		for(int j = i+1; j<arr.length; j++)
		{
			if(arr[i]>arr[j])
			{
				int temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp; 
			}
		}
	}
}
内循环结束一次,最值出现在左边位置上。

(2)冒泡排序

代码如下:

public static void bubbleSort(int[] arr)
{
	for(int i = 0; i<arr.length-1; i++)
	{
		for(int j =0; j<arr.length-i-1; j++)
		{
			if(arr[j]>arr[j+1])
			{
				int temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
}
最值出现在最后一位,arr.length-i-1,其中-i是让每一次比较元素减少,-1是避免角标越界。

上面两种排序算法中,都用到了交换,因此可以单独写成一个函数调用。代码如下:

public static void swap(int[] arr, int a, int b)
{
	int temp = arr[a];
	arr[a] = arr[b];
	arr[b] = temp;
}

3.2.3查找操作

代码如下:

public static int getIndex(int[] arr, int key)
{
	for(int i=0; i<arr.length; i++)
	{
		if(arr[i] == key)
		{
			return i;
		}
	}
	return -1;
}
折半查找可以提高效率,但是前提是数组必须是已经排序好的。

代码如下:

public static int halfSearch(int[] arr, int key)
{
	int min = 0;
	int max = arr.length - 1;
	int mid = (min + max)/2;
	while(arr[mid] != key)
	{
		if(arr[mid]>key)
		{
			max = mid - 1;
		}
		else
		{
			min = mid + 1;
		}
		if(min > max)
		{
			return -1;
		}
		mid = (min + max)/2;
	}
	return mid;
}
另外一种形式就是如下:

public static int halfSearch2(int[] arr, int key)
{
	int min = 0,max = arr.length-1,mid;
	while(min<max)
	{
		mid = (min + max)>>1;
		if(key > arr[mid])
			min = mid + 1;
		else if(key<arr[mid])
			max = mid - 1;
		else
			return mid;
	}
	return -1;
}
这里,如果想在数组中插入一个值,可以将最后的return -1修改return min,如果此数组中存在与num相等的值,则返回索引,如果不存在,则返回插入到该数值中时的位置。

3.3二维数组

格式1:

int[][] arr = new int[2][3];
表示,arr有2个一维数组,每一个一维数组有3个元素。

如果对上述代码执行:

System.out.println(arr[0]);
则输出为:

其中,输出中的[表示为一维数组,I表示数组元素为int型,@后面的为地址值。

格式2:

int[][] arr = new int[3][];
如果对上述代码执行:

System.out.println(arr);
System.out.println(arr[0]);
System.out.println(arr.length);
结果为:

其中,[[表示arr为二维数组,I表示为int型。

格式3:

int[][] arr = {{1,2,3},{4,5,6}};
例如,如果这样定义:

int[] x,y[];
则,x是一维的,y是二维的。

4.例子

说完了函数与数组,下面举几个进制转换的例子。

4.1十进制→二进制

代码如下:

public static void toBin(int num)
{
	StringBuffer sb = new StringBuffer();
	while(num>0)
	{
		sb.append(num%2);
		num = num/2;
	}
	System.out.println(sb.reverse());
}

4.2十进制→十六进制

代码如下:

public static void toHex(int num)
{
	StringBuffer sb = new StringBuffer();
	for(int x=0; x<8; x++)//总共8个4位
	{
		int temp = num&15;
		if(temp>9)
		{
			sb.append((char)(temp-10+'A'));
		}
		else
		{
			sb.append(temp);
		}
		num = num>>4;
	}
	System.out.println(sb.reverse());
}
也可以用查表法,将所有的元素临时存储,建立对应关系,每一次&15后的值作为索引去查建好的表。

代码如下:

public static void toHex2(int num)
{
	char[] chs = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
	char[] arr = new char[8];
	for(int x=0; x<8; x++)
	{
		int temp = num&15;
		arr[x] = chs[temp];
		num = num>>4;
	}
	for(int i = 7; i>=0; i--)
	{
		System.out.print(arr[i]);
	}
	System.out.println();
}
如果对十进制162使用以上代码,则输出为:

每一次右移时,都会使得num变小,可以记住使得num开始为0的位置,倒序输出的时候,用此位置作为一个临界点。代码如下:

public static void toHex2None0(int num)
{
	char[] chs = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
	char[] arr = new char[8];
	int pos = 0;
	while(num != 0)
	{
		int temp = num&15;
		arr[pos++] = chs[temp];
		num = num>>4;
	}
	for(int i = pos-1; i>=0; i--)
	{
		System.out.print(arr[i]);
	}
}

4.3综合

上面的chs不管是二进制,还是八进制,还是十六进制,都能使用,因此考虑把所有的功能封装成一个函数,调用的时候修改参数即可,代码如下:

public static void trans(int num, int base, int offset)
{
	if(num == 0)
	{
		System.out.print(0);
	}
	char[] chs = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
	char[] arr = new char[32];//因为考虑到2进制,此时最大为32
	int pos = arr.length;
	while(num != 0)
	{
		int temp = num&base;
		arr[--pos] = chs[temp];
		num = num>>offset;
	}
	for(int i = pos; i<arr.length; i++)
	{
		System.out.print(arr[i]);
	}
}
例如,十进制→十六进制,直接调用trans(num,15,4),其中15表示进制减1,4表示每次右移的位置,因为每4个2进制位表示一个16进制数。

5.面向对象

5.1概念

面向过程,强调的是功能行为;

面向对象,将功能封装进对象,强调具备了功能的对象。是基于面向过程的。

名词提炼法:

例如,人开门

人
{
	开门(门)
	{
		门.开();
	}
}
门
{
	开(){}
}
开发就是找对象使用,若没有对象就新建一个。

5.2类与对象的关系

类就是对现实生活中事物的描述,class定义类。

对象就是这类事物实实在在存在的个体,Java在堆内存中使用new建立的实体。

//需求:描述汽车
class Car
{
	//描述颜色
	String color = "red";
	//描述轮胎数
	int num = 4;
	//运行行为
	void run()
	{
		System.out.println(color+"..."+num);
	}
}
class carDemo
{
	public static void main(String[] args)
	{
		//生产汽车,在Java中通过new操作符完成
		//其实就是在堆内存产生一个实体
		Car c = new Car();//c就是一个类类型变量,指向对象
	}
}
若想将汽车颜色修改为blue,则直接在c后面执行下面的代码即可:

//需求:将汽车颜色改为blue
c.color = "blue";
c.run();
在定义类时,就是在描述事物的属性和行为,共同组成类中的成员(成员变量和成员函数)。属性对应变量,行为对应函数(方法)。
下面看一下成员变量和局部变量的区别:

成员变量:作用于整个类,在堆内存中,因为对象的存在才在内存中存在;

局部变量:作用于函数中,在栈内存中。

对于上述代码,如下图:

匿名对象:对象的简化形式

如下代码:

new Car().num = 5;//匿名对象
new Car().color="blue";
new Car().run();
此段代码的作用不是使得轮胎数为5,并且颜色为blue。如下图:

当时执行为第二条时,第一条就是垃圾,当执行第三条的时候,第一和二条都是垃圾。
匿名对象的使用方式一:

当对对象的方法只调用一次时,可以用匿名对象,比较简化,如果对一个对象也进行多个成员调用,必须给这个对象起个名字;

匿名对方的使用方式二:

可将匿名对象作为实际参数进行传递

例如,对于下面这段代码:

//需求:汽车修配厂,对汽车进行改装
public static void show(Car c)
{
	c.num = 3;
	c.color = "black";
	c.run();
}
那么在main中,下面两段代码的结果是一致的。

Car c = new Car();
show(c);

show(new Car());

5.3封装Encapsulation

即,隐藏对象的属性和实现的细节,仅对外提供访问方式。之所以对外提供访问方式,因为可以在访问方式中加入逻辑判断等语句,增加代码的健壮性。

好处:将变化隔离;便于使用;提高重用性;提高安全性。

注意:私有仅仅是封装的一种表现形式

private:私有权限修饰符,用于修饰类中成员,私有只在本类中有效。

class Person
{
	int age;
	void speak()
	{
		System.out.println("age="+age);
	}
}
class PersonDemo
{
	public static void main(String[] args)
	{
		Person p= new Person();
		p.age = 20;
		p.speak();
	}
}
对于上面的代码,age如果赋值为-20,也可以。可以考虑将age私有化,类以外即使建立了对象,也不能直接访问。
class Person
{
	private int age;
	public void setAge(int a)
	{
		if(a>0 && a<130)
		{
			age=a;
		}
		else
		{
			System.out.println("error");
		}
	}
	public int getAge()
	{
		return age;
	}
	void speak()
	{
		System.out.println("age="+age);
	}
}
class PersonDemo
{
	public static void main(String[] args)
	{
		Person p= new Person();
		// p.age = 20;
		p.setAge(20);
		int a=p.getAge();
		p.setAge(-20);
	}
}
此时,就会使得更加安全。

5.4构造函数

特点:名字与类相同;不用定义返回值类型;不可以有return语句。

作用:将对象初始化。对象一建立,会调用与之对应的构造函数。

如果,在上面代码中age前加上以下代码:

Person()//构造函数
{
	System.out.println("person run");
}
则,运行main中时的p时会先打印person run。

小细节:当一个类中没有定义构造函数时,系统会默认给该类分配一个空参数的构造函数,注意上面的person(),不是默认的构造函数;当在类中定义构造函数后,默认的构造函数就没有效了。因为构造函数与类名一致,因此,不同的构造函数是重载的。

构造函数与一般函数的写法不同,运行也不同。构造函数在对象一建立就运行,给对象初始化只运行一次;一般函数对象调用才执行,给对象添加对象具备的功能可调用多次。

那什么时候给定构造函数呢?

当分析事物时,该事物存在具备一些特征或者行为,那么将这些内容定义在构造函数中。

构造函数代码块:作用给对象进行初始化,对象一建立就运行,而且优先于构造函数执行。定义的是不同对象共性的初始化内容。

区别:构造代码块是给所有对象进行统一初始化,构造函数是给对应的对象初始化。

例如以下代码:

Person()//构造函数
{
	System.out.println("person run");
}
{
	System.out.println("run");
}
第一个{}是构造函数,第二个是构造代码块,当建立对象时,输出结果为

5.5.this关键字

看上去是区别局部变量和成员变量同名的情况。

this,代表它所在函数所属对象的引用,简单说就是,哪个对象在调用this所在的函数,this就代表哪一个对象。

应用1:当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象,但凡本类功能内部使用了本类对象,都用this表示。

private String name;
private int age;
Person(String name)
{
	this.name = name;
}
//需求:定义一个函数,比较俩人年龄是否相等
public boolean compare(Person p)
{
	return this.age == p.age;
}
在构造函数的执行语句中,this.name指的是name属性,而=后面的name为值。

应用2:用于构造函数之间互相调用,且构造函数之间调用只能用this。

private String name;
private int age;
Person(String name)
{
	this.name = name;
}
Person(String name, int age)
{
	this(name);//this相当于对象
	this.age=age;
}
注意,this只能定义构造函数中的第一条语句,因为初始化要先执行。如果在this前面加入了其他语句,则会出现下面的错误:

5.6.static(静态)关键字

用法:

1.是一个修饰符,用于修饰成员(成员变量,成员函数);

2.被对象所共享;

3.当成员被静态修饰后,就多了一种调用方式,除了可以被对象调用外,还可以被类名调用,类名.静态成员。

例如,对于下面这段程序:

class StaticDemo
{
	public static void main(String[] args)
	{
		Person p = new Person();
		p.name = "lisi";
		p.show();
		System.out.println(Person.country);
	}
}
class Person
{
	String name;//成员变量,实例变量
	static String country = "CN";	
	public void show()
	{
		System.out.println(name+"..."+country);
	}
}
其输出为:


特点:

1.随类的加载而加载;也就是说,静态会随着类的消失而消失,它的周期最长。

2.优先于对象存在;静态先存在,对象后存在。

3.被所有对象所共享;

4.可以直接被类名调用。

static变量,又称为类变量。

实例变量与类变量的区别

①存放位置:类变量,随着类的加载而存在,在方法区中;实例变量随着对象的建立而存在堆内存中;

②生命周期:类变量生命周期最长,随着类的消失而消失;实例变量生命周期随着对象的消失而消失。

静态使用的注意事项:

①静态方法只能访问静态成员

非静态方法可以访问静态也可以访问非静态;

②静态方法不可以定义this,super关键字

因为静态优先于对象存在,所以静态方法中不可以出现this

③主函数是静态的

静态的利弊

①利:对对象的共享数据进行单独空间存储,节省空间,没有必要每一个对象中都存储一次,可以直接被类名调用;

②弊:生命周期过程;访问出现局限性(静态虽好,只能访问静态)。

例如,对于常见的代码如下:

class MainDemo
{
	public static void main(String[] args)
	{
		System.out.println(args[0]);
		System.out.println(args.length);
	}
}

主函数是一个特殊的函数,作为程序的入口,可以被JVM调用,主函数的定义是:

public:代表该函数访问权限是最大的;

static:代表主函数随着类的加载而存在;

void:主函数没有具体的返回值

main:不是关键字,可以被JVM识别

(String[] args):函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组

其中,主函数固定格式的,只有args可以修改,arguments,JVM在调用主函数时,传入的是new String[0]。
按下图执行上述代码时,输出为下图:

什么时候使用静态呢?

要从两个方面下手:因为静态修饰的内容有成员变量和成员函数。

①定义静态变量(类变量):当对象中出现共享数据时,该数据被静态所修饰,对象中的特有数据要定义成非静态,存在于堆内存中;

②静态函数:当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
静态应用:每一个应用程序中,都有共性功能,可以将这些功能进行抽取,独立封装,以便复用。
例如,可以将之前写过的查找,排序等函数封装到ArrayTool文件中。

class ArrayTool
{
	//求最大值
	public static int getMax(int[] arr)
	{
		int max = 0;
		for(int i= 0; i<arr.length; i++)
		{
			if(arr[i]>arr[max])
			{
				max = i;
			}
		}
		return arr[max];
	}
	//快速排序
	public static void selectSort(int[] arr)
	{
		for(int i = 0; i<arr.length-1; i++)
		{
			for(int j = i+1; j<arr.length; j++)
			{
				if(arr[i]>arr[j])
				{
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp; 
				}
			}
		}
	}
	//查找
	public static int getIndex(int[] arr, int key)
	{
		for(int i=0; i<arr.length; i++)
		{
			if(arr[i] == key)
			{
				return i;
			}
		}
		return -1;
	}
}
将上述文件保存为ArrayTool.java文件,将下面的代码保存为ArrayToolDemo.java文件,俩文件在同一路径下。

class ArrayToolDemo
{
	public static void main(String[] args)
	{
		int[] arr = {32,4,52,534};
		ArrayTool tool = new ArrayTool();//语句1,这两个文件保存在同一个路径
		int max = tool.getMax(arr);//语句2
		//int max = ArrayTool.getMax(arr);语句3
		System.out.println(max);
	}
}
通过编译ArrayToolDemo.java文件,可以发现在路径下同时出现了两个class文件。

虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作,发现了问题:

①对象是用于封装数据的,可是ArrayTool对象并未封装特有数据;

操作数组的每一个方法都没有用到ArrayTool对象中的特有数据

这时就考虑,让程序更严谨,是不需要对象的。可以将ArrayTool的方法都定义成static(上面代码都已经给出了static格式),直接通过类名调用,可以用语句3替代语句1和2。

将方法都静态后,方便于使用,但是该类还是可以被其他程序建立对象的,为了更为严谨,强制让该类不能建立对象,可以通过将构造函数私有化完成,即在ArrayTool的开端,加入以下代码即可:

private ArrayTool(){}//强制不能建立对象
接下来,将ArrayTool.class文件发送给别人,别人只需要把文件设置到classpath路径下就可以使用这个工具类了。在cmd下设置路径命令为set classpath=.;d:\。因为当前,我把.class文件放在d盘下。注意前面的.。

但是,该类中到底定义了多少个方法,对方不清楚,因为该类并没有使用说明书

5.7使用说明书

开始制作程序的使用说明书,Java的说明书通过文档注释完成。

注意,在制作说明书时,类必须是public的。

有以下格式:

/**
这是一个可以对数组进行操作的工具类,该类提供了获取最值,选择排序,查找,等功能
@author		maxinrui
@version	v1.1
*/
public class ArrayTool
{
	private ArrayTool(){}
	/**
	获取一个整形数组的最大值
	@param	arr接收一个int型的数组
	@return	会返回一个该数组的最大值
	*/
	public static int getMax(int[] arr)
	{
		int max = 0;
		for(int i= 0; i<arr.length; i++)
		{
			if(arr[i]>arr[max])
			{
				max = i;
			}
		}
		return arr[max];
	}
	/**
	对int型数组进行选择排序
	@param	arr接收一个int型的数组
	*/
	public static void selectSort(int[] arr)
	{
		for(int i = 0; i<arr.length-1; i++)
		{
			for(int j = i+1; j<arr.length; j++)
			{
				if(arr[i]>arr[j])
				{
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp; 
				}
			}
		}
	}
}
再在cmd中运行以下代码就可以制作出:

javadoc -d myhelp -author -version ArrayTool.java
其中,-d表示目录,myhelp表示要存放说明书的文件夹,如果没有就会自动创建一个。之后,就

从index.html开始就可以看了。
一个类默认会有一个空参数的构造函数,这个默认的构造函数的权限和所属类一致;如果类被public修饰,那么默认的构造函数也带public修饰符。

5.8.静态代码块

格式:

static
{
	静态代码块中的执行语句
}
特点:随着类的加载而执行,只执行一次,用于给类进行初始化,并优先于主函数。

对于一下代码块:

class StaticCode
{
	static
	{
		System.out.println("a");
	}
}
class StaticDemo2
{
	static
	{
		System.out.println("b");
	}
	public static void main(String[] args)
	{
		new StaticCode();
		new StaticCode();
		System.out.println("c");
	}
	static
	{
		System.out.println("d");
	}
}

输出结果为bdac(分行)。

如果在main中的语句1和2注释掉,使用下面的代码:

// new StaticCode();
// new StaticCode();
StaticCode s = null;
那么,此时"a"就不输出了。

思考,对于以下代码,其输出结果是什么?

class StaticCode
{
	StaticCode()
	{
		System.out.println("a");
	}
	//start of 1
	static
	{
		System.out.println("b");
	}
	//end of 1
	//start of 2
	{
		System.out.println("c");
	}
	//end of 2
	//start of 3
	StaticCode(int x)
	{
		System.out.println("d");
	}
	//end of 3
}
class StaticCodeDemo
{
	public static void main(String[] args)
	{
		new StaticCode(4);
	}
}
输出为bcd(分行)

其中,块1表示给类初始化,块2表示给对象初始化,块3表示给对应的对象初始化。

对于下面这一行代码(结合之前的程序看):

Person p = new Person("zhangsan",20);
该句话:

①因为new用到了Person.class,所以会先找到Person.class文件,并加载到内存中;

②执行该类中的static代码块,如果有的话,给Person.class类进行初始化;

③在堆内存中开辟空间,分配内存地址;

④在堆内存中建立对象的特有属性,并进行默认初始化;

⑤读属性进行显示初始化;

⑥对对象进行构造函数代码块初始化;

⑦对对象进行对应的构造函数初始化;

⑧将内存地址赋给内存中的p变量。

5.9.设计模式

解决问题行之有效的方法,Java中有23种设计模式。

单例模式:解决一个类在内存中只存在一个对象。
想要保证对象唯一,那么:

①为了避免其他程序也建立该类对象,先禁止其他程序建立该类对象;

②为了其他程序可以访问该类对象,只好在本类中自定义一个对象;

③为了方便其他程序对自定义的对象访问,可以对外提供一些访问方式;

针对以上三个问题,代码体现如下:

①将构造函数私有化,private;

②在类中创建一个对象;

③提供一个方法可以获取该对象;

如下代码:

class Single
{
	private Single(){}
	private static Single s = new Single();
	public static Single getInstance()
	{
		return s;
	}
}
class SingleDemo
{
	public static void main(String[] args)
	{
		Single ss = Single.getInstance();
	}
}
在类Single中,对于函数getInstance,因为调用方式有两种,一种是用对象,一种是类名,因为在单例模式中,其他程序没法建立对象,因此得用类名调用,而用类名调用时需要用static修饰,因此前面两个对象也必须是static。


下面给出一个完整的例子:

class Single
{
	private int num;
	public void setNum(int num)
	{
		this.num = num;
	}
	public int getNum()
	{
		return num;
	}
	//start of 3 steps
	private Single(){}
	private static Single s = new Single();
	public static Single getInstance()
	{
		return s;
	}
	//end of 3 steps
}
class SingleDemo
{
	public static void main(String[] args)
	{
		Single s1 = Single.getInstance();
		Single s2 = Single.getInstance();
		s1.setNum(23);
		System.out.println(s2.getNum());
	}
}
当需要将该事物的对象保证在内存中唯一时,就将上面代码中的3steps加入即可。上面代码的输出就是23。

先初始化对象,成为饿汉式,Single类进内存就已经创建了对象。
下面看一下懒汉式,对象是方法被调用时才初始化,也叫做对象的延时加载。

代码如下:

class Single
{
	private static Single s = null;
	private Single(){}
	public static Single getInstance()
	{
		if(s == null)
			s = new Single();
		return s;
	}
}
Single类进内存,对象还没有存在,只有调用了getInstance方法时才建立对象。

开发时,用饿汉式,防止创建多个对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值