目录
类变量
- 类变量也叫静态变量/静态属性
- 基本语法:访问修饰符 static 数据类型 变量名
- static变量会被类的所有对象共享
- jdk7以后,类变量存储在类实例的最后(类实例在堆中),jdk7及以前是存储在方法区的静态域
如何访问类变量?
- 类名.类变量名(更规范)
- 对象名.类变量名
类变量使用细节
- 类变量与实例变量(普通属性)区别:类变量为所有对象所共享,实例变量是每个对象独享的
- 加上static称为类变量或静态变量,否则为实例变量/普通变量/非静态变量
- static类变量在类加载的时候就初始化了,即使没有创建对象也能使用
类方法
- 类方法也叫静态方法
- 基本语法:访问修饰符 static 返回类型 方法名() { }
类方法的调用
- 类名.类方法名
类方法使用场景
当我们希望不创建对象也可以调用方法时,就可以使用静态方法。开发自己的工具类时,可以将方法定义为静态方法。
类方法细节
- 类方法和普通方法都是随着类的加载而加载,结构信息存储在方法区
- 类方法中无this的参数,普通方法中隐含着this的参数
- 类方法中不允许使用和对象有关的关键字,如this和super⭐
- 类方法(静态方法)只能访问类变量和类方法(静态成员)⭐
- 普通成员方法,既可以访问非静态成员,也可访问静态成员⭐
- 静态方法要访问静态成员,必须先实例化一个对象,用该对象访问
Exercise1
看看下段代码会输出什么
package com.lili.static_;
public class StaticExercise01 {
public static void main(String[] args) {
new Test().count();
new Test().count();
System.out.println(Test.count);
}
}
class Test {
static int count = 9;
public void count() {
System.out.println("count = " + (count++));
}
}
answer1:
count = 9
count = 10
11
Exercise2
先看看有没有错误的代码,如有就将错误代码注释后,再看看输出
package com.lili.static_;
public class StaticExercise02 {
public static void main(String[] args) {
System.out.println("total = " + Person.getTotalPerson());
Person person = new Person();
System.out.println("total = " + Person.getTotalPerson());
}
}
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
id++;
return total;
}
public Person() {
total++;
id = total;
}
}
answer2:
//id++; 静态方法不能访问非静态成员
输出:
total = 0
total = 1
Exercise3
先看看有没有错误的代码,如有就将错误代码注释后,看看total最后等于多少
package com.lili.static_;
public class StaticExercise03 {
public static void main(String[] args) {
P.setTotal(4);
new P();
}
}
class P{
private int id;
private static int total = 0;
public static void setTotal(int total) {
this.total = total;
P.total = total;
}
public P() {
total++;
id = total;
}
}
answer3:
//this.total++ 静态方法中不能使用和对象有关的关键字
total = 5;
main方法
- main方法是java虚拟机调用,所以main方法的访问权限必须是public
- java虚拟机在执行main方法时不必创建对象,所以main方法必须是static方法
- main方法中的String[] args参数,是在执行java命令时传递给所运行的类的参数,如:
java 执行的程序 参数1 参数2 参数3
idea中main动态传值
代码块(初始化块)
属于类的成员,类似于方法,将逻辑语句封装在{}中,但和方法不同,没有方法名,没有返回,没有参数,不用通过对象或类显示调用,而是在类加载或创建对象时隐式调用
基本语法
[修饰符]{
代码
};
- 修饰符可选,且只能为static
- 代码块分为静态代码块(static修饰)和普通代码块
- 分号 ; 可以省略
代码块的好处和应用场景
- 代码块相当于构造器的补充
- 如果多个构造器中有相同的重复语句,就可以写到代码块中,提高代码的重用性
- 每次调用构造器时,都会先调用代码块
代码块细节
- 静态代码块随类的加载而执行,普通代码块每创建一个对象就执行一次
类加载3种场景⭐⭐⭐
- 创建对象实例时
- 创建子类对象时,父类也会被加载
- 使用类的静态成员时
package com.lili.codeblock;
public class CodeBlockDetail01 {
public static void main(String[] args) {
System.out.println("类加载的情况");
// System.out.println("1 创建对象时");
// A a = new A();
// System.out.println("2 创建子类对象时,父类也会被加载,且先加载父类");
// B b = new B();
System.out.println("3 使用类的静态成员时");
System.out.println(A.n);
}
}
class B extends A{
static {
System.out.println("B的静态代码块被调用...");
}
}
class A {
public static int n = 999;
static {
System.out.println("A的静态代码块被调用...");
}
}
注意:
static代码块是在类加载时执行,只会执行一次;
普通代码块每创建一次对象就执行一次
- 创建一个对象时,一个类中的调用顺序:
①先进行静态属性的初始化和静态代码块的执行,优先级一样,谁先定义调用谁;
②再进行普通属性的初始化和普通代码块的执行,优先级一样,谁先定义调用谁;
③最后调用构造方法。
package com.lili.codeblock;
public class CodeBlockDetail02 {
public static void main(String[] args) {
AA aa = new AA();
}
}
class AA {
private int n2 = getN2();
private static int n1 = getN1();
public AA() {
System.out.println("构造器被调用...");
}
{
System.out.println("AA的普通代码块被调用...");
}
static {
System.out.println("AA的静态代码块被调用...");
}
public static int getN1() {
System.out.println("n1初始化...");
return 100;
}
public int getN2() {
System.out.println("n2初始化...");
return 200;
}
}
- 构造器的最前面其实隐含了super() 和 普通代码块的调用(默认调用父类无参构造器)
静态代码块和静态属性初始化在类加载时就执行,因此优先于普通属性初始化和普通代码块的调用
下段代码,看看会输出什么?
package com.lili.codeblock;
public class CodeBlockDetail03 {
public static void main(String[] args) {
BBB bbb = new BBB();
}
}
class AAA {
{
System.out.println("AAA的普通代码块...");
}
public AAA() {
System.out.println("AAA的构造器...");
}
}
class BBB extends AAA {
public BBB() {
//super();
//调用普通代码块
System.out.println("BBB的构造器...");
}
{
System.out.println("BBB的普通代码块...");
}
}
创建一个子类对象时,静态代码块和属性、普通代码块和属性、构造器的执行顺序如下:⭐⭐⭐
- 父类的静态代码块、静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块、静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 父类的构造器
- 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 子类的构造器
package com.lili.codeblock;
public class CodeBlockDetail04 {
public static void main(String[] args) {
//先进行类加载,再创建对象
//1.1父类静态属性和代码块 1.2子类静态属性和代码块
//2.1父类普通属性和代码块、构造器 2.2子类普通属性和代码块、构造器
D d = new D();
}
}
class C {
static {
System.out.println("C的静态代码块"); //1
}
private static int c1 = getC1();
public static int getC1(){
System.out.println("C的静态属性"); //2
return 100;
}
private int c2 = getC2();
public int getC2(){
System.out.println("C的普通属性"); //5
return 200;
}
{
System.out.println("C的普通代码块"); //6
}
public C() {
System.out.println("C的构造器"); //7
}
}
class D extends C {
private static int d1 = getD1();
static {
System.out.println("D的静态代码块"); //4
}
public static int getD1(){
System.out.println("D的静态属性"); //3
return 100;
}
private int d2 = getD2();
public int getD2(){
System.out.println("D的普通属性"); //8
return 200;
}
{
System.out.println("D的普通代码块"); //9
}
public D() {
//super()
//普通属性初始化、普通代码块
System.out.println("D的构造器"); //10
}
}
- 静态代码块只能直接调用静态成员,普通代码块可以直接调用任意成员
代码块习题
test1
answer:类加载(调用类的静态成员时)时会执行静态代码块,static代码块只会执行一次
in static block!
total = 100
total = 100
test2
answer:
单例设计模式
设计模式:是在大量实践基础上总结和理论化的优选的代码结构、编程风格和解决问题的思考方式。
- 单例设计模式:采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,且该类只提供一个获取对象实例的方法
- 单例模式主要有2种:饿汉式、懒汉式
饿汉式、懒汉式实现步骤:
- 构造器私有化(防止在类的外部创建对象实例)
- 类的内部创建对象
- 向外暴露一个静态的公共方法getinstance(不用创建对象就可以获取)
package com.lili.single_;
public class SingleTon01 {
public static void main(String[] args) {
GirlFriend g1 = GirlFriend.getInstance();
}
}
class GirlFriend {
//饿汉式
private String name;
//1.构造器私有化
private GirlFriend(String name) {
this.name = name;
}
//2.创建一个static属性对象
private static GirlFriend gf = new GirlFriend("gl");
//3.提供一个公共的静态方法getInstance
public static GirlFriend getInstance(){
return gf;
}
}
package com.lili.single_;
import javax.jws.soap.SOAPBinding;
public class SingleTon02 {
public static void main(String[] args) {
Cat cat = Cat.getInstance();
System.out.println(cat);
Cat cat2 = Cat.getInstance();
System.out.println(cat2);
System.out.println(cat == cat2);
}
}
class Cat {
private String name;
private static Cat cat;
//1.构造器私有化
//2.定义一个static属性对象
//3.提供一个public的static方法,返回对象
private Cat(String name) {
this.name = name;
}
public static Cat getInstance() {
if (cat == null) {
cat = new Cat("kitty");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
- 饿汉式:在类加载(3种情况)时就创建,即会有创建了但没有使用的情况,造成资源浪费!⭐
- 懒汉式:要使用时才创建,懒汉式只有用户调用getInstance方法时才会创建对象
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
- 在JavaSE标准类中,java.lang.Runtime是经典的单例模式(饿汉式)
final关键字
final(最后的、最终的)可以修饰类、属性、方法和局部变量
final使用场景:
- 当不希望类被继承时
- 当不希望父类的某个方法被子类重写/覆盖时
- 当不希望类的某个属性值被修改时(常量名一般全大写)
- 当不希望某个局部变量被修改时
package com.lili.final_;
public class Final01 {
public static void main(String[] args) {
E e = new E();
//e.PI = 8.0;
}
}
//不希望A类被继承
final class A {
}
//class B extends A {
//}
class C{
//不希望hi方法被重写
public final void hi(){
}
}
class D extends C{
// @Override
// public void hi() {
// super.hi();
// }
}
class E{
//不希望属性PI被修改
public final double PI = 3.14;
}
class F{
public void method(){
//不希望局部变量NUM被修改
final double NUM = 0.01;
//NUM = 0.99;
}
}
final使用细节
- final修饰的属性称为常量,一般用XX_XX_XX命名(下划线分隔)
- final常量必须赋初值(①定义时;②构造器中;③代码块中赋值)
- 若final修饰的属性是静态的,则初始化的位置只能是:①定义时;②静态代码块中赋值
- final类不能继承,但是可以实例化对象
- 非final类中含有final方法,该类是可以被继承的,只是final方法不能被重写
- 如果一个类已经是final类,没必要将类中的方法修饰成final方法
- final不能修饰构造器
- final和static往往搭配使用,效率更高,使用static属性时不会导致类加载,底层编译器做了优化⭐⭐
- 包装类(Integer, Double, Float, Boolean等都是final),String类也是final类
final应用实例
编写一个程序,能计算圆的面积,圆周率为3.14,赋值的3个位置都写一下
package com.lili.final_;
public class FinalExercise01 {
public static void main(String[] args) {
double r = 2;
Circle circle = new Circle(r);
System.out.println("半径为" + r + "的圆面积:" + circle.calArea());
}
}
class Circle {
private double radius;
//public final Double PI = 3.14; //定义时赋值
public final Double PI;
public Circle(double radius) {
this.radius = radius;
//PI = 3.14; //构造器中赋值
}
{
PI = 3.14; //代码块中赋值
}
public Double calArea() {
return PI * radius * radius;
}
}