一 static关键字
1.PPT内容
1.static 关键字:用于修饰成员(成员变量和成员方法)
2.被修饰后的成员具备以下特点:
随着类的加载而加载
优先于对象存在
被所有对象所共享
可以直接被类名调用
3.使用注意
静态方法只能访问静态成员
静态方法不可以写this、super关键字
主函数是静态的
2.示例代码
public class staticDemo {
public static void main(String[] args) {
Person p = new Person();
p.name = "P";
p.show();
}
}
class Person {
String name;
String country = "CN";
public void show() {
System.out.println(name + " :: " + country);
}
}
如果Person都是中国人,那么他们的country其实都是CN,
但现在每次创建Person要在堆内存创建自己的、值为“CN”的成员变量,耗费内存,
因此,可以把country单列出来,所有新建的Person对象的country,都用同一块内存空间。
这时候,就可以用static关键字了。
例子:每个班级都有一个公用的饮水机(静态),每个人都用自己的杯子(不是静态)喝水
3.引入静态代码
/*
* 静态 : static
* 用法:是一个修饰符,用于修饰成员(成员变量和成员方法)。
* 当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,
* 还可以直接被类名调用,类名.静态成员。
* */
public class staticDemo {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "P";
p1.show(); // P :: CN
System.out.println(Person.country); // CN
}
}
class Person {
String name; //成员变量(实例变量)
static String country = "CN"; //静态成员变量(类变量)
public void show() {
System.out.println(name + " :: " + country);
}
}
“特有数据随着对象存储,要分清什么数据是对象特有的,
什么数据是对象公有的,以此定义成员变量和静态成员变量”
上面代码加入static修饰的成员变量,
是放在 方法区(也被称为共享区、数据区)。
4.静态的特点
在此以上面的示例代码进行说明。
- 随着类的加载而加载 :Person类一加载到内存,country成员变量就随之而开辟好空间了。也就是说,它也随着类的消失而消失,说明“它生命周期最长”。
- 优先于对象存在 :明确“静态先存在,对象后存在”
- 被所有对象所共享
- 可以直接被类名调用
5.实例变量和类变量的区别
(1)存放位置:
类变量随着类的加载而存在于方法区中;实例变量随着对象的建立而存在于堆内存中。
(2)生命周期:
类变量生命周期最长,随着类的消失而消失;实例变量生命周期随着对象的消失而消失。
6.静态使用注意事项
- 静态方法只能访问静态成员 ,非静态方法即可访问静态成员也可访问非静态成员。
- 静态方法不可以写this、super关键字 :因为静态优先于对象存在。
- 主函数是静态的
7.静态的利弊
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象都存储一份。可以直接被类名调用。
弊端:生命周期过长,访问出现局限性(静态虽好,但它只能访问静态)。
二 main方法
1.主函数(main方法)的相关概念
主函数:是一个特殊的函数,作为程序的入口,可以被JVM调用。
主函数的定义:
public : 该函数的访问权限是最大的。
static : 代表着主函数随着类的加载而存在。
void:主函数没有具体的返回值。(因为虚拟机在调,不需要返回值)
main:不是关键字,但是是一个特殊的单词,可以被JVM识别。
函数的参数(String[] args):函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组。
主函数是固定格式的:JVM识别,JVM只认 public static void main(String[] args) 这个主函数,重载也没用的。
只有"args"这四个字母可以改。args其实就是老外写的arguments的缩写。
public class MainDemo {
public static void main(String[] args) { // 虚拟机只要这个,最多把"args"改掉
System.out.println(args); // [Ljava.lang.String;@18a992f 证明虚拟机传了个数组
System.out.println(args.length); // 0
System.out.println(args[0]);// 如果没传参,就会java.lang.ArrayIndexOutOfBoundsException
// 没有args[0]
}
public static void main(int x) {// 能存在,重载。
}
}
主函数的args要么是个数组,要么是null。通过上机证明它是new String[0]。
JVM在调用主函数时,传入的是new String[0]。
我们可以再javac编译程序时,往主函数里面传数据,这样就可以打印传入的参数了。
下面的例子仅作参考,注意编译和运行的顺序,不过其实没什么实际用途:
class MainDemo {
public static void main(String[] args) {
String[] arr = { "1", "2", "3" };
MainTest.main(arr);
}
}
class MainTest {
public static void main(String[] args) {
for (int x = 0; x < args.length; x++) {
System.out.println(args[x]);
}
}
}
三 静态什么时候使用
什么时候使用静态?这个要学明白!
1.什么时候使用静态?
因为静态修饰的内容是成员变量和函数,所以要从两方面下手。
问:什么时候定义静态变量(类变量)呢?
答:当对象中出现共享数据时,该数据被静态所修饰;对象中的特有数据要定义成非静态存在于堆内存中。
问:什么时候定义静态函数呢?
答:当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以被定义成静态的。
2.示例代码
class Person {
String name;
static String country = "CN";
public static void show1() {
System.out.println("haha");
}
public void show2() {
System.out.println(name + " :: " + country);
}
}
public class mainDemo {
public static void main(String[] args) {
Person.show1();
Person p = new Person();
p.name = "P";
p.show2();
}
}
上面的代码,show1只是打印,没有用到成员变量,就可以使用static修饰了,
而show2()需要输出name和country,则不能用static修饰。
四 静态的应用 - 工具类
应用程序中的多个类都使用相同功能,就可以把功能全封装到一个类里面,
让这个类成为一个工具类,例如 : newTool().getMax()。
1.引入工具类的概念
每个应用程序中都有共性的功能,可以将这些功能进行抽取,进行封装。
/*
* 静态的应用
* */
public class ArrayTool {
public 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];
}
public 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];
}
// 选择排序
public void selectSort(int[] arr) {
for (int x = 0; x < arr.length - 1; x++) {
for (int y = x + 1; y < arr.length; y++) {
if (arr[x] > arr[y]) {
swap(arr, x, y);
}
}
}
}
// 冒泡排序
public void bubbleSort(int[] arr) {
for (int x = 0; x < arr.length - 1; x++) {
for (int y = 0; y < arr.length - x - 1; y++) {
if (arr[y] > arr[y + 1]) {
swap(arr, y, y + 1);
}
}
}
}
// 元素交换位置
private void swap(int[] arr, int a, int b) // 因为此函数没有提供数据,可以将此方法私有化
{
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 打印数组
public void printArray(int[] arr) {
System.out.print("[");
for (int x = 0; x < arr.length; x++) {
if (x != arr.length - 1)
System.out.print(arr[x] + ", ");
else
System.out.println(arr[x] + "]");
}
}
}
public class ArrayToolDemo {
public static void main(String[] args) {
int[] arr = { 3, 1, 87, 32, 8 };
ArrayTool tool = new ArrayTool();
int max = tool.getMax(arr);
System.out.println("Max: "+max);
}
}
就算你直接编译ArrayToolDemo.java,没有先编译ArrayTool.java,
它会在当前目录下找ArrayTool.class,没有就找.java文件编译,
还是没有才提示错误。
2.完善ArrayTool
虽然可以通过建立ArrayTool的对象来使用这些工具方法,对数组进行操作。
但依然有些问题。
一是,对象是用于封装数据的,但ArrayTool并未封装特有数据。
二是,操作数组的每个方法都没有用到ArrayTool对象中的特有数据。
为了让程序严谨,不需要对象,可以将ArrayTool中的方法设置成静态,直接通过类名调用即可。
//这个程序用来承上启下,仅作参考(ArrayTool构造方法是public,各种工具类方法是static)
public class ArrayToolDemo {
public static void main(String[] args) {
int[] arr = { 3, 1, 87, 32, 8 };
ArrayTool tool = new ArrayTool();
int max = tool.getMax(arr);
System.out.println("Max: " + max);
int min = tool.getMin(arr);
System.out.println("Min: " + min);
tool.printArray(arr);
tool.selectSort(arr);
tool.printArray(arr);
/** 其实你可以连ArrayTool的对象都不用创建 **/
ArrayTool.printArray(arr);
ArrayTool.bubbleSort(arr);
ArrayTool.printArray(arr);
}
}
将方法都静态后,可以方便使用。但仍然可以建立ArrayTool的对象,为了更加严谨,强制让该类不能建立对象。
可以私有化构造函数。对于那些只供内部使用的方法,也可以私有化。能隐藏的尽量隐藏,只暴露部分对外访问方法。
最后,对ArrayTool进行完善:
当考虑让程序更严谨时,如果不需要对象,可以将一个类中的成员设为静态的,直接通过类名调用即可。
将方法都静态后,可以方便于使用,但是该类还是可以被其他程序建立对象的,为了更严谨,
应该强制该类不能建立对象,可以将构造函数私有化就不允许建立对象了。
public class ArrayTool {
private ArrayTool() {
}
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];
}
// 之前的各种方法省略
}
五 帮助文档的制作
1.帮助文档准备知识
将ArrayTool.class文件发送给其他人,其他人只要将其设置到classpath路径下即可使用,例如:
set classpath = .;c:\JavaFiles
但是该类中定义了什么方法,对方不清楚,因为没有提供说明书。
所以需要制作程序的说明书,通过文档注释来完成。
这个文档(说明书)就是API文档(Application Programming Interface) ,说明了程序中各种对外的“接口”,即函数。
1. 类的上面注释类的描述信息。 一些单词可以被系统识别并提取后面的内容:@author作者名,@version 版本。
2. public的方法都需要用文档注释,因为需要对外提供访问。 @param参数,@return返回值。
使用javadoc工具获得文档,格式:javadoc –d myhelp(目录名)-author –versionArrayTool.java,没有目录系统自动建一个。
注意:想要给一个类建立文档说明,这个类必须是public修饰。
不写的话,权限不够,不能确定是否暴露。
只有共有的才能暴露,没有暴露的话也可以视为封装,不一定私有才封装。
java的说明书通过文档注释来完成。
格式:
/**
功能说明语句
@author 作者
@version 版本
@param 参数
@return 返回值
*/
每个类和凡是用public修饰的成员函数都要用文档注释给说明。
2.制作帮助文档
/**
* 这是一个可以对数组进行操作的工具类,该类提供了,获取最大值和最小值, 排序等功能。
*
* @author Linww
* @version V1.1
*
*/
public class ArrayTool {
/**
* 空参数的构造函数。
*/
public ArrayTool() {
}
/**
* 获取整形数组中的最大值:
* @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类型的数组
*/
public static void selectSort(int[] arr) {
for (int x = 0; x < arr.length - 1; x++) {
for (int y = x + 1; y < arr.length; y++) {
if (arr[x] > arr[y]) {
swap(arr, x, y);
}
}
}
}
/**
* 给int数组进行冒泡排序
* @param arr 接收一个int类型的数组
*/
public static void bubbleSort(int[] arr) {
for (int x = 0; x < arr.length - 1; x++) {
for (int y = 0; y < arr.length - x - 1; y++) {
if (arr[y] > arr[y + 1]) {
swap(arr, y, y + 1);
}
}
}
}
/**
* 给数组中的元素进行位置的置换
* @param arr 接收一个int类型的数组
* @param a 要置换的位置
* @param b 要置换的位置
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
/**
* 用于打印数组中的元素,打印形式:[element1,element2 ...]
* @param arr 接收一个int类型的数组
*/
public static void print(int[] arr) {
System.out.print("[");
for (int x = 0; x < arr.length; x++) {
if (x != arr.length - 1)
System.out.print(arr[x] + ",");
else
System.out.println(arr[x] + "]");
}
}
}
从index.html索引页面开始看。文档提供的是可以让外界访问的内容,所以私有化的方法不会出现在摘要中。只有public和pretected的函数才会被提取。
注意:
一个类中默认会有一个空参数的构造函数(看不见的),这个构造函数的权限和所属类一致,随类的变化而变化。
如果类被public修饰,你们默认的构造函数也带public修饰符,
如果类没有被public修饰,那么默认的构造函数,也没有public修饰符。
默认构造函数的权限是随着类的变化而变化的。
六 静态代码块
格式:
static
{
静态代码块的执行语句;
}
特点:
随着类的加载而执行,只执行一次,用于给类进行初始化的。
示例代码:
class StaticCode {
static {
System.out.println("a");
}
}
public class staticCodeDemo {
static {
System.out.println("b");
}
public static void main(String[] args) {
new StaticCode();
new StaticCode();
System.out.println("HelloWorld!");
}
static {
System.out.println("c");
}
}
输出:
b
c
a
HelloWorld!
在主函数前的静态代码块优先于主函数执行。
当一个类被new一个对象时,类中的构造代码块,静态代码块,构造函数
执行顺序是:静态代码块,构造代码块,构造函数。
静态代码块随着类的加载而加载,只能调用静态成员。
构造代码块(补充知识):
public class StaticTest {
static {
System.out.println("静态代码块!");
}
{
System.out.println("构造代码块!");
}
public static void main(String[] args) {
StaticTest st = new StaticTest(); // 在对象一建立的时候就调用
System.out.println("主函数!");
}
}
输出:
静态代码块!
构造代码块!
主函数!
在这段代码中,有一个构造代码块,这个代码块是在对象一建立的时候就调用,在函数体中,他的执行时间是看对象是什么时候建立的。
一般出题,会把构造代码块,静态代码块,构造函数放一起,问你那个程序输出是什么。
七 对象的初始化过程
class Person {
private String name;
private int age;
private static String country = "CN";
public Person(String name, int age) {
this.name = name;
this.age = age;
}
{
System.out.println(name+" .. "+age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void speak(){
System.out.println(this.name+" …… "+this.age);
}
public static void showCountry(){
System.out.println("country = "+country);
}
}
public class PersonDemo{
public static void main(String[] args){
Person p = new Person("张三",20);
}
}
输出:null .. 0
1.因为new用到了Person类,所以会先找到Person.class文件并加载到内存中,静态变量加载到方法区。同时,栈内存中分配空间给变量p。
2. 会执行该类中的静态代码块,如果有的话,同时给Person.class类进行初始化。
3. 在堆内存中开辟空间,分配内存地址。
4. 在堆内存中建立对象的特有属性(实例变量),并进行默认初始化。
5. 对属性进行显式初始化。
6. 对对象进行构造代码块初始化。
7. 对对象进行对应的构造函数初始化。
8. 将内存地址赋给栈内存中的p变量,p指向对象。
八 对象调用成员过程
Person p = new Person("张三",20);
主函数先在栈内存中开辟空间,产生p;然后是方法区加载数据,再就是堆内存加载数据。
方法区加载的内容:首先开辟Person的空间,加载静态变量、静态方法(静态区,被Person类调用)和非静态方法(非静态区,被对象调用)。
p.setName(“lisi”);
非静态方法调用过程,例如p.setName(“lisi”);
在栈内存中开辟空间,建立setName(name);内部有this.name,非静态的方法运行时都需要有属于某个对象,用this代替那个对象。
执行时,相当于将p的地址值赋给了this,this指向对象。Bill首先传递到执行语句中的name,再赋给栈内存中this.name,然后再赋给堆内存中的实例变量name。
运行完毕后释放。
静态方法的调用:直接在方法区内调用即可,使用的都是方法区内的数据,不涉及堆内存。
非静态成员前面省略的是this,静态成员省略的是类名。被调用才执行。
九 单例设计模式
设计模式 :解决某类问题的最佳方案。
偏重于思想,不偏重代码。Java中有23中模式,例如,单例设计模式。
也适用于其他语言,用于解决实际问题。模式的组合就是框架。
1.单例设计模式的概念
单例设计模式:解决 “让一个类在内存中只存在一个对象” 问题的方案。
需要保证对象唯一的方法:
1.为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象。
2.还为了让其他程序可以访问到该类对象,只好在本类中自定义一个对象。
3.为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
用代码体现这三步:
1.将构造函数私有化。本类中仍然可以使用。
2.在本类中创建一个本类对象。
3.提供一个方法可以获取该对象。
2.饿汉式
先初始化对象,称为“饿汉式”。
public class Single
{
private Single() {
}// 私有化使得外界无法访问
private static Single s = new Single();// 保证静态变量可以被静态方法调用,创建一个对象
public static Single getInstance()// 不能被对象调用,只能被类直接调用。
{
return s;
}
}
特点:Single类一进内存,就已经创建好了对象。
3.懒汉式
调用时才被初始化,称为“懒汉式”。
class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
}
}
}
return s;
}
}
特点:Single类进内存,对象还没有存在,只有调用了getInstance()方法时,才建立对象。
刚开始时不做,什么时候需要什么时候做。
synchronized是给该方法上锁,当有一个对象操作该方法时,别的对象就不能操作。
是为了防止两个对象同时操作该方法。
开发中一般用饿汉式,简单、安全。