面向对象_static_代码块_final

2016.08.21

static

概述

  • 某些特定的数据在内存空间里只有一份
  • 例如:所有的中国人都有一个国家的名称,每个中国人都共享这个国家名称,不必在每个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
  • static:静态的
  • static可以用来修饰:属性、方法、代码块、内部类,不可以修饰构造器。

静态变量

  • 使用static来修饰的属性,称作静态变量
  • 静态变量随着类的加载而加载,可以通过类.静态变量的方式进行调用。
  • 静态变量的加载早于对象的创建
  • 由于类只会加载一次,则静态变量在内存中也只有一份,存在方法区的静态域中。

分类

  • 属性按照static分类,可以分为静态属性 和 非静态属性(实例变量)
  • 实例变量:我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改其中一个对象的非静态属性时,都不会导致其他对象中同样的属性值的修改。
  • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过的。
类变量实例变量
yesno
对象yesyes

举例

public class StaticTest {
	public static void main(String[] args) {
		// 静态变量也叫类变量,是根据类的加载而加载
		// 所以静态变量比实例变量加载快
		Chinese.nation = "中国";
		System.out.println(Chinese.nation);// 中国

		Chinese c1 = new Chinese();
		c1.age = 18;
		c1.name = "张三";

		Chinese c2 = new Chinese();
		c2.age = 28;
		c2.name = "李四";

		c1.nation = "CHN";
		c2.nation = "CHINA";
		System.out.println(Chinese.nation);// CHINA
		System.out.println(c1.nation);// CHINA
		System.out.println(c2.nation);// CHINA

	}
}

class Chinese {
	// 实例变量
	String name;
	int age;
	// 类变量、静态变量
	static String nation;
}

内存解析

20200819104227

静态方法

  • 使用static修饰的方法:静态方法
  • 随着类的加载而加载,通过类.静态方法的方式进行调用。
    | | 静态方法 | 非静态方法 |
    | ---- | ---- | ---- |
    | 类 | yes | no |
    |对象 | yes | yes |
  • 静态方法中,只能调用静态方法属性属性。
  • 非静态方法中,静态\非静态的方法和属性都可以调用。
  • 注意点:
    • 在静态方法内,不能使用thissuper关键字
    • 关于静态属性和静态方法的使用,都从生命周期的角度去理解

举例

public class StaticTest {
	public static void main(String[] args) {
		
		// 类调用静态方法,yes
		Chinese.show();
		// 类调用非静态方法,no
		// Cannot make a static reference to the non-static method eat() from the type
//		Chinese.eat();

		// 对象调用静态方法,yes
		Chinese c3 = new Chinese();
		c3.show();

		// 对象调用非静态方法,yes
		c3.eat();

	}
}

class Chinese {
	// 实例变量
	String name;
	int age;
	// 类变量、静态变量
	static String nation;

	// 非静态方法
	public void eat() {
		System.out.println("中国人吃中餐");
		// 非静态方法中调用静态属性,yes
		System.out.println(nation);// nation省略了Chinese.

		// 非静态方法中调用非静态属性,yes
		age = 10;

		// 非静态方法中调用静态方法,yes
		show();
	}

	// 静态方法
	public static void show() {
		System.out.println("我是中国人");
		//静态方法中调用静态属性,yes
		System.out.println(nation);
		//静态方法中调用非静态属性,no
		//Cannot make a static reference to the non-static field age
//		System.out.println(age);
		//静态方法中调用静态方法,yes
		show2();
		//静态方法中调用非静态方法,no
		//Cannot make a static reference to the non-static method eat() from the type Chinese
		//eat();

	}

	// 静态方法
	public static void show2() {
		System.out.println("I'm Chinese");
	}
}

总结

  1. 在开发中,如何确定一个属性是否要声明为static
    • 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
  2. 在开发中,如何确定一个方法是否要声明为static
    • 操作静态属性属性的方法,通常设置为static的。
    • 工具类中的方法,习惯是声明为static的。例如Math\Arrays\Collections等。

练习

编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。 编写主类,使用银行账户类,输入、输出3个储户的上述信息。
考虑:哪些属性可以设计成static属性。

public class Account {
	private int account;// 账户
	private String password;// 密码
	private double balance;// 余额
	private static double interestRates = 0.2;// 利率
	private static double minBalance = 200000;// 最小余额
	private static int initAccount = 1001;// 初始化账号

	public Account() {
		account = initAccount++;
	}

	public Account(String password) {
		this();
		this.password = password;
	}

	public Account(String password, double balance) {
		this(password);
		this.balance = balance;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	public int getAccount() {
		return account;
	}

	@Override
	public String toString() {
		return "Account [account=" + account + ", password=" + password + ", balance=" + balance + "]";
	}

}

public class AccountTest {
	public static void main(String[] args) {
		Account a1 = new Account();
		Account a2 = new Account("951591");
		Account a3 = new Account("155789", 6666.66);
		System.out.println(a1.toString());
		System.out.println(a2.toString());
		System.out.println(a3.toString());
	}
}

20200819113622

static的应用:单例设计模式

  • 设计模式:是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
  • 单例设计模式:采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象的实例,并且该类只提供一个获取其对象实例的方法。
  • 单例模式,减少系统的性能开销。
  • 实现单例模式的方法有两种
    • 饿汉式:好处是没有线程安全问题;坏处是目前类加载就会创建对象,占用相应的内存。
    • 懒汉式:好处是什么时候用什么时候创建对象,相对节省内存;坏处是目前学到的是有线程安全问题的。
  • 实现步骤:
    • 私有化构造器
    • 创建当前类的对象的静态变量
    • 创建获取静态变量的方法

饿汉式

public class SingletonTest {
	public static void main(String[] args) {
		// 测试
		Bank b1 = Bank.getInstance();
		Bank b2 = Bank.getInstance();
		System.out.println(b1 == b2);// true
	}
}

class Bank {
	// 私有化构造器
	private Bank() {

	}

	// 创建当前类对象的静态变量
	private static Bank instance = new Bank();

	// 创建获取静态变量的方法
	public static Bank getInstance() {
		return instance;
	}
}

懒汉式

public class SingletonTest2 {
	public static void main(String[] args) {
		// 测试
		Order o1 = Order.getInstance();
		Order o2 = Order.getInstance();
		System.out.println(o1 == o2);// true
	}
}

class Order {
	// 私有化构造器
	private Order() {

	}

	// 声明当前类对象的静态变量
	private static Order instance = null;

	// 创建获取静态变量的方法
	public static Order getInstance() {
		// 目前这样写是有安全问题的,后期学到线程后在修改成没有线程安全问题的
		if (instance == null) {
			instance = new Order();
		}
		return instance;
	}
}

单例模式的应用场景

  • 网站计数器
  • 应用程序的日志应用
  • 数据库的连接池
  • 读取配置文件的类
  • android中的Application类
  • windows中Task Manager
  • windows中的Recycler Bin

理解main()方法

  1. main()可以作为程序的入口。
public class MainTest {
	public static void main(String[] args) {
		System.out.println("程序的入口");
	}
}
  1. main()可以作为普通的静态方法。
public class MainTest {
	public static void main(String[] args) {
		// 1.作为程序的入口
		//System.out.println("程序的入口");
		// 2.作为普通方法进行调用
		Person.main(new String[100]);
	}
}

class Person {
	public static void main(String[] args) {
		for (int i = 0; i < args.length; i++) {
			args[i] = "args_" + i;
			System.out.println(args[i]);
		}
	}
}

20200819145930

  1. main()可以作为与控制台的交互方式。
    首先编写一个类
public class MainTest2 {
	public static void main(String[] args) {
		for (int i = 0; i < args.length; i++) {
			System.out.println(args[i]);
		}
	}
}

String[] args可以再运行MainTest2.class时填写String数据给到程序。

  • CMD
    首先编译文件javac MainTest2.java
    然后java MainTest2 输入数据,这样就传递数据了。

20200819150710

  • Eclipse
    右击 → Run as → Run Configurations
    选中MainTest2字节码文件,选择arguments,输入数据即可

20200819150919

20200819150928

代码块

  1. 作用:用来初始化类、对象
  2. 格式:{ 内容 }
  3. 修饰符:如果要使用修饰,只能用static,即static { 内容 }
  4. 分类:
    • 静态代码块
    • 非静态代码块

静态代码块

  • 可以输出语句
  • 随着类的加载而执行
  • 只执行一次
  • 作用:加载类的信息
  • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
  • 静态代码块的执行优于非静态代码块的执行
  • 静态代码块能可以调用静态属性、静态方法,不可以调用非静态的结构

非静态代码块

  • 可以输出语句
  • 随着对象的创建而执行
  • 每创建一个对象,就会执行一次
  • 作用:可以在创建对象的同时,对对象的属性进行初始化
  • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
  • 非静态代码块可以调用静态属性、静态方法、非静态属性、非静态方法。

实例

public class BlockTest {
	public static void main(String[] args) {
		// 测试
		// 指挥执行一次静态代码块
		Person.nation = "CHN";
		// 每创建一个对象,就会执行一次非静态代码块
		Person p1 = new Person();
		Person p2 = new Person();
		System.out.println(p1.age);
	}
}

class Person {
	// 实例变量
	String name;
	int age;
	// 类变量(静态变量)
	static String nation;

	// 方法
	public void eat(String food) {
		System.out.println("爱吃:" + food);
	}

	// 静态方法
	public static void sleep() {
		System.out.println("睡觉了");
	}

	// 非静态代码块
	{
		System.out.println("非静态代码块1");
		age = 1;
	}
	{
		System.out.println("非静态代码块2");
		// 调用非静态属性
		age = 1;
		// 调用静态属性
		nation = "中国2";
		// 调用非静态方法
		eat("米饭");
		// 调用静态方法
		sleep();
	}

	// 静态代码块
	static {
		System.out.println("静态代码块1");
	}
	static {
		System.out.println("静态代码块2");
		// 调用静态方法
		sleep();
		// 调用静态属性
		nation = "中国";
	}
}

练习

public class BlockTest2 {
	public static void main(String[] args) {
		new C();
		/*
		 * A的静态代码块
			B的静态代码块
			C的静态代码块
			A的非静态代码块
			A的无参构造器
			B的非静态代码块
			B的无参构造器
			C的非静态代码块
			C的无参构造器
		 */
	}
}

class A {
	public A() {
		System.out.println("A的无参构造器");
	}

	static {
		System.out.println("A的静态代码块");
	}

	{
		System.out.println("A的非静态代码块");
	}

}

class B extends A {
	public B() {
		System.out.println("B的无参构造器");
	}

	public B(String b) {
		this();
		System.out.println("B的有参构造器");
	}

	static {
		System.out.println("B的静态代码块");
	}

	{
		System.out.println("B的非静态代码块");
	}
}

class C extends B {
	public C() {
		System.out.println("C的无参构造器");
	}

	static {
		System.out.println("C的静态代码块");
	}

	{
		System.out.println("C的非静态代码块");
	}
}

总结:由父及子,静态显性。

赋值的先后顺序(完结篇)

目前有如下几种属性赋值的方式:

  1. 默认初始化值
  2. 显性初始化值
  3. 构造器初始化值
  4. 通过类.方法类.属性赋值
  5. 通过代码块赋值

赋值的先后顺序为:
默认初始化值 - 显性初始化值/代码块赋值(谁在前面谁先执行) - 构造器初始化值 - 类.方法类.属性赋值

final

  1. final:最终的
  2. 可以用来修饰:类、方法、变量。

final修饰类

  • 代表此类不可以被继承。
  • 比如StringSystem等类都是使用final来修饰
final class FinalA{
	
}

//The type FinalB cannot subclass the final class FinalA
//FinalA使用final修饰,代表此类不可以被继承
class FinalB extends FinalA{
	
}

final修饰方法

  • 代表此方法不能被重写
  • 例如Object类中getClass()方法使用final来修饰
class FinalA {
	// 使用final修饰此方法
	public final void eat() {

	}
}

class FinalB extends FinalA {
	//	- Cannot override the final method from 
	// FinalA
	public void eat() {
		
	}
}

final修饰变量

属性

  • 只可以使用,不可以重新赋值。
  • 可以考虑的赋值的位置为:显式初始化、代码块中赋值、构造器中赋值,其他的赋值方式不可以。
class FinalA {
	// 显式赋值
	final int a = 10;
	// 代码块赋值
	final int b;
	{
		b = 10;
	}
	// 构造器赋值
	final int c;

	public FinalA() {
		c = 10;
	}

}

局部变量

  • 只可以使用,不可以重新赋值。
	public void show() {
		final int aa = 10;
		//The final local variable aa cannot be assigned. It must be blank and not using a compound  assignment
		//错误
		//aa = aa+1;
	}

形参

  • 使用final修饰形参,当我们调用此方法的时候给常量形参附一个实参,一旦赋值,只可以使用,不可以重新赋值。
	public void show(final int cc) {
		//The final local variable cc cannot be assigned. It must be blank and not using a compound assignment
		//错误
		//cc = 20;
	}

static final

  • 用来修饰属性(全局常量)、方法。

Day14问题

  1. static修饰的属性,相较于实例变量,有些特别之处(>=三条)

    • 通过类.属性去调用
    • 随着类的加载而加载,在内存空间只有一个
    • 早于对象的创建
    • 类或者类的对象都可以调用
    • 放在方法区的静态域中。
  2. final可以用来修饰哪些结构,分别表示什么意思

    • 类:final修饰的类不有子类
    • 方法:final修饰的方法不能被子类重写
    • 变量:
      • 属性:常量,赋值后不能修改,赋值的位置:显式赋值、代码块赋值、构造器赋值
      • 局部变量:常量,赋值后不能修改
      • 形参:带final的形参,在调用方法过程中赋值,赋值后不能修改。
  3. 代码实现单例模式的饿汉式

    • 好处不存在线程安全问题
    • 坏处占用一定内存
public class A{
	private static A instance = new A();
	public static A getInstance(){
		return instance;
	}
}
  1. 代码实现单例模式的懒汉式
    • 好处用的时候再去加载,减少内存
    • 坏处此代码存在线程安全问题,后期学完线程在修改成没有现成安全的代码
public class A{
	private static A instance = null;
	public static A getInstance(){
		if(instance == null){
			instance = new A();
		}
		return instance;
	}
}
  1. 类的属性复制位置有哪些?先后顺序为何?
    • 默认值
    • 显式赋值/代码块赋值(谁在前谁先复制)
    • 构造器赋值
    • 通过对象.方法对象.属性赋值。