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 数组定义
元素类型[] 数组名 = 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.垃圾回收机制。
int[] x = new int[3];
x = null;
int[] x = new int[3];
int[] y = x;
y[1] = 89;
x[1] = 77;
x = null;
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]);
②空指针异常,当引用任何指向值为null的情况,该引用还在用于操作实体。int[] arr = new int[3]; arr = null; System.out.println(arr[0]);
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]);
则输出为:
格式2:
int[][] arr = new int[3][];
如果对上述代码执行:
System.out.println(arr);
System.out.println(arr[0]);
System.out.println(arr.length);
结果为:
格式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使用以上代码,则输出为:
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表示要存放说明书的文件夹,如果没有就会自动创建一个。之后,就
一个类默认会有一个空参数的构造函数,这个默认的构造函数的权限和所属类一致;如果类被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方法时才建立对象。
开发时,用饿汉式,防止创建多个对象。