3. 面向对象(第六天)

一  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是给该方法上锁,当有一个对象操作该方法时,别的对象就不能操作。

是为了防止两个对象同时操作该方法。

开发中一般用饿汉式,简单、安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值