类变量和类方法
1、基本介绍
- 类变量 (静态变量)可以被所有实例共享
实例child1和child2都共享count空间
静态空间在堆还是在方法区里,取决于jdk版本
jdk8以前,在方法区静态域里
jdk8以后,在堆里
但是不管static变量在哪里,总有这样的共识:
①static变量是同一个类所有对象共享的
②static类变量,在类加载的时候就生成了
-
如何访问类变量
类名.类变量名
或者 对象名.类变量名 -
什么时候需要用类变量
当所有对象都需要共享一个变量时,就可以考虑使用类变量 -
类变量与实例变量的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。 -
加上static成为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
-
静态变量在类加载时就初始化
即使没有创建对象,只要类加载了,就可以使用类变量了 -
类变量的生命周期随着类的加载开始,随着类消亡而销毁
对象销毁了,静态变量仍然存在
注意:类变量的访问,必须遵守相关的访问权限
比如 private static,无法在本类外访问
2、静态方法
- 当方法使用了static修饰,该方法就是静态方法
- 静态方法可以调用静态变量
- 什么时候适合用类方法
如果我们希望不创建实例,也可以调用某个方法(即当作工具来使用),则适合用静态方法 - 类方法和普通方法都是随着类的加载而加载,将信息结构存储在方法区:
- 类方法中无this的参数(类方法不用创建对象)
- 普通方法中隐含着this的参数
-
类方法中不允许使用和对象有关的关键字,比如this和super
-
类方法(静态方法)只能访问静态变量或静态方法
-
普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)
小结:静态方法,只能访问静态的成员,非静态方法,可以访问静态成员和非静态成员(必须遵守访问权限)
3、作业
作业1
public class Test {
public static void main(String[] args){
System.out.println("total=" + Person.getTotalPerson()); //0,因为此时还没有创建对象,没创建对象就不会调用构造器
Person p1 = new Person(); //创建对象,调用了构造器
System.out.println("total=" + Person.getTotalPerson()); //1
}
}
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson(){
//id++; //错误,静态方法不能访问非静态变量
return total;
}
public Person(){ //构造器也是非静态方法
total++;
id = total;
}
}
作业2
public class Test {
public static void main(String[] args){
Person.setTotalPerson(3);
new Person();
System.out.println("total=" + Person.getTotal()); //4
}
}
class Person {
private int id;
private static int total = 0;
public static int getTotal(){
return total;
}
public static void setTotalPerson(int total){
this.total = total; //错误,static方法中不能用this
Person.total = total;
}
public Person(){ //构造器也是非静态方法
total++;
id = total;
}
}
main方法深入理解
main方法的形式:public static void main(String[] args){}
1、为什么是public
main方法是虚拟机调用 (java虚拟机和main方法不在同一个包)==》解释为什么是public
Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
2、为什么是static
java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
3、怎么理解String[] args
main方法接收String类型的数组参数,该数组中保存执行java命令时传递给运行的类的参数
args如何传参:
1)命令行:
java 运行的程序(类名) 参数1 参数2 参数3
2) idea中
右上角运行模块倒三角 -> Edit Configurations -> Program arguments ->
4、 注意事项
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
- 不能直接访问该类中的非静态成员,必须创建该类的实例对象,再去调用
代码块
1、基本介绍
代码块又称初始化块,属于类中的成员。
没有方法名,没有返回,没有参数,只有方法体。
不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。(静态代码块在类加载时就会调用一次,且仅调用一次)(普通代码块在创建对象时就会调用一次,可调多次)
2、基本语法
[修饰符]{
代码
};
3、注意
- 修饰符可写可不写。如果写,只能写static
- 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块
- 代码块内容可以为任意逻辑语句(输入、输出、方法调用、循环、判断等)
- ;号可以写上也可以省略
4、对代码块的理解
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化操作
- 如果多个构造器都有重复的语句,则可以整合到代码块中,提高复用性
public class Test {
public static void main(String[] args) {
Movie aa = new Movie("aa");
Movie bb = new Movie("bb", 30);
Movie cc = new Movie("cc", 40, "Leo");
}
}
class Movie {
private String name;
private double price;
private String director;
//下面三个构造器都有相同的语句,冗余
//可以把相同的语句,放入到一个代码块中
//这样,当我们不管调用哪个构造器,创建一个新对象,就会调用一次代码块中的内容
public Movie(String name) {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始");
this.name = name;
}
public Movie(String name, double price) {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始");
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始");
this.name = name;
this.price = price;
this.director = director;
}
}
整合后:
public class Test {
public static void main(String[] args) {
Movie aa = new Movie("aa");
Movie bb = new Movie("bb", 30);
Movie cc = new Movie("cc", 40, "Leo");
}
}
class Movie {
private String name;
private double price;
private String director;
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始");
};
//下面三个构造器都有相同的语句,冗余
public Movie(String name) {
System.out.println("构造器Movie(String name)被调用");
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
this.name = name;
this.price = price;
this.director = director;
}
}
- 代码块的调用优先于构造器
5、使用细节(类加载)⭐⭐⭐⭐⭐
1) static代码块只执行一次,普通代码块可执行多次
static代码块也叫静态代码块。作用就是对类进行初始化,随着类加载而执行。并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。
2) 类在什么时候被加载⭐⭐⭐
① 创建对象实例时(new一个对象)
② 创建子类对象实例,父类也会被加载(且父类先加载)
③ 使用类的静态成员时(静态属性,静态方法)(如果static和final一起用,则类不会被加载)
public class Test {
public static void main(String[] args) {
//类什么时候被加载
//1. 创建对象实例
AA aa = new AA();
System.out.println("================");
//2. 创建子类实例,父类也被加载,并且先加载父类再加载子类
CC cc = new CC();
System.out.println("================");
//3.使用类的静态成员(属性方法)时
System.out.println(DD.n1);
System.out.println("================");
//4.所有静态代码块只执行一次
System.out.println(BB.n1);
}
}
class AA {
//静态代码块
static {
System.out.println("AA的静态代码块被执行");
}
}
class BB {
public static int n1 = 999; //静态属性
static {
System.out.println("BB的静态代码块被执行");
}
}
class CC extends BB{
static {
System.out.println("CC的静态代码块被执行");
}
}
class DD {
public static int n1 = 999; //静态属性
static {
System.out.println("DD的静态代码块被执行");
}
}
输出:
3) 普通代码块,在创建对象实例时被调用
创建对象时普通代码块会被隐式调用。被创建一次就会调用一次。
如果只是使用类的静态成员时,普通代码块不会执行。
**普通代码块与类创建无关**!只有在创建对象实例(new)才会调用普通代码块
public class Test {
public static void main(String[] args) {
//创建多个对象
//创建对象时,同时进行类加载。原先没调用过静态代码块,这里先调用静态代码块
DD dd = new DD();
DD dd1 = new DD();
System.out.println("================");
//使用类的静态成员(属性方法)时,没有创建对象,不会调用普通代码块
System.out.println(DD.n1);
}
}
class DD {
public static int n1 = 999; //静态属性
static {
System.out.println("DD的静态代码块被执行");
};
{
System.out.println("DD的普通代码块被执行");
};
}
输出:
小结:
① static代码块是类加载时执行,只会执行一次
② 普通代码块是在创建对象时调用,创建一次,调用一次
③ 类加载的3种情况(new、子父类、调用静态成员)
4) 创建一个对象时,在一个类的调用顺序⭐⭐⭐⭐⭐⭐
① 调用静态代码块和静态属性初始化。
静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用
优先级一样,谁在前面,谁先调用
public class Test {
public static void main(String[] args) {
A a = new A();
}
}
class A {
private static int n1 = getN1();
static {
System.out.println("A静态代码块01");
}
public static int getN1() {
System.out.println("getN1被调用...");
return 100;
}
}
//输出:
getN1被调用...
A静态代码块01
n1 和static优先级一样,n1在前面,所以n1先调用
② 调用普通代码块和普通属性的初始化
普通代码块和普通属性初始化调用的优先级一样,如果多个普通代码块和多个普通属性初始化,则按定义顺序调用
③ 调用构造方法
package A;
public class Test {
public static void main(String[] args) {
A a = new A();
}
}
class A {
private int n2 = getN2();
private static int n1 = getN1();
public A(){
System.out.println("A()构造器01");
}
{
System.out.println("A普通代码块01");
}
static {
System.out.println("A静态代码块01");
}
public static int getN1() {
System.out.println("getN1被调用...");
return 100;
}
public static int getN2() {
System.out.println("getN2被调用...");
return 200;
}
}
输出:
顺序:static属性或代码块 -> 普通属性或代码块 -> 构造器
5) 构造器的最前面隐含了super()和、调用普通代码块和初始化普通属性
所以普通代码块优先于构造器执行
而静态static相关属性和代码块,在类加载时就执行完毕,所以优先于普通代码块和构造器
public A(){
//这里有隐藏的两个执行要求:
//(1)super(); //在没有this和其他super构造器时默认
//(2)调用本类普通代码块,初始化普通属性
System.out.println("A构造器被调用")
}
6) 创建一个子类对象实例时,调用顺序⭐⭐⭐⭐⭐
核心:父类构造器优先于子类普通代码块(父类122)
原理:
-
先类加载(先加载父类再加载子类)
-
然后创建对象,先创建子类对象,调用子类构造器,但构造器中隐含调用父类构造器
-
父类构造器,需要先调用本类普通代码块,初始化普通属性
-
接着才是执行本类构造器里剩余的内容
-
此时父类构造器执行完毕,返回子类构造器,重复上面34两步
package A;
public class Test {
public static void main(String[] args) {
//1 先进行类加载:先加载父类,后加载子类
//1.1 n1getVal01 1.2 A静态代码块 1.3 n3getVal03 1.4 B静态代码块
//2 再创建对象:从子类构造器开始->super()->super()中同样隐藏了super()和普通代码块
B b = new B();
}
}
class A {
private static int n1 = getVal01();
static {
System.out.println("A的一个静态代码块");//(2)
}
{
System.out.println("A的普通代码块"); //(5.4)=>(5)
}
public int n2 = getVal02(); //(6.1)
public static int getVal01(){
System.out.println("getVal01"); //(1)
return 10;
}
public int getVal02(){
System.out.println("getVal02");//(6.2)=>(6)
return 10;
}
public A(){
//隐藏了
//super() (5.2)
//本类普通属性及代码块 (5.3)
System.out.println("A的构造器");//(7)
}
}
class B extends A{
private static int n3 = getVal03();
static {
System.out.println("B的一个静态代码块");//(4)
}
{
System.out.println("B的普通代码块");//(8.2)=>(8)
}
public int n4 = getVal04();//(9.1)
public static int getVal03(){
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04(){
System.out.println("getVal04");//(9.2)=>(9)
return 10;
}
public B(){
//隐藏了
//super() (5.1)
//本类普通属性及代码块(8.1)
System.out.println("B的构造器");//(10)
}
}
输出:
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用静态也可以调用普通成员
package A;
public class Test {
public static void main(String[] args) {
}
}
class C {
private int n1 = 100;
private static int n2 = 200;
private void m1(){}
private static void m2(){}
static {
System.out.println(n1); //报错
System.out.println(n2); //ok
m1();//报错
m2();//ok
}
{
System.out.println(n1); //ok
System.out.println(n2); //ok
m1();//ok
m2();//ok
}
}
6、 课堂练习
输出:
静态成员sam初始化
static块执行
sam1成员初始化
Test默认构造器被调用
单例设计模式
1、什么是单例模式
采取一定方法,保证在整个软件系统中,对某个类只能存在一个对象实例。并且该类只提供一个取得其对象实例的方法
2、饿汉式
- 构造器私有化 =》 防止直接new一个对象
- 类的内部创建对象
- 向外暴露一个静态的公共方法 getInstance
public class Test{
public static void main(String[] args) {
// GirlFriend xm = new GirlFriend("小美");
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
}
}
//类,GirlFriend
//只能有一个女朋友
class GirlFriend {
private String name;
//内部创建一个类(为了能够使用static方法,这里必须加上static修饰符)
private static GirlFriend gf = new GirlFriend("小美美");
//构造器私有化
private GirlFriend(String name) {
this.name = name;
}
//提供公共的static方法,返回gf对象(方法必须静态,因为不能通过创建对象来调用方法)
public static GirlFriend getInstance(){
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
饿汉式特点:在还没有使用对象时,对象就已经创建好了
3、懒汉式
- 构造器私有化 => 防止直接new一个对象
- 定义一个static静态属性对象
- 提供一个public的static方法,可以返回一个GirlFriend对象
public class Test{
public static void main(String[] args) {
GirlFriend gf = GirlFriend.getInstance();
System.out.println(gf);
//后续再调用getInstance方法时,不会再重新创建一个对象(即gf != null)、
GirlFriend gf2 = GirlFriend.getInstance();
System.out.println(gf2);
System.out.println(gf == gf2); //true
}
}
//类,GirlFriend
//只能有一个女朋友
class GirlFriend {
private String name;
//2. 定义一个static静态属性对象
private static GirlFriend gf;
//1. 构造器私有化
private GirlFriend(String name) {
this.name = name;
}
//提供公共的static方法,可以返回一个GirlFriend对象
public static GirlFriend getInstance(){
if(gf == null) {//
gf = new GirlFriend("小美");
}
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式特点:在使用对象时才会创建对象,后面再次调用时,会返回上次创建的对象
4、饿汉式vs懒汉式
final关键字
1、final关键字的特点:
- final修饰的类不能被继承
- 父类某个方法被final修饰,不能被子类重写
- final修饰的类属性,其值不能被修改
- final修饰的局部变量不能被修改
2、final使用注意事项
-
final修饰的属性又叫常量,一般用XX_XX_XX来命名
-
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一
① 定义时:如public final double TAX_RATE = 0.08
② 在构造器中
③ 在代码块中 -
如果final修饰的属性是静态的,则初始化的位置只能是
① 定义时
② 在静态代码块
(不能在构造器中赋值)(因为构造器在创建对象时才会被调用,而类加载比创建对象更先一步)
class A {
public static final double TAX_RATE = 0.08;
public static final double TAX_RATE2;
static {
TAX_RATE2 = 0.33;
}
}
-
final类不能继承,但是可以实例化对象
-
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
-
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法了(无法继承,自然也就无法重写)
-
final和static往往搭配使用,效率更高。不会导致类加载(单独使用static,调用静态属性或方法时,类会被加载)
public class Test{
public static void main(String[] args) {
System.out.println(A.i);
}
}
class A {
public static final int i = 1;
static {
System.out.println("类被加载");
}
}
//输出:
1
- 包装类(Integer、Double、 Float、Boolean等都是final),String也是final类
3、练习
- 判断正误
public int addOne(final int x){ //true
++x; //错,不能修改final修饰的值
return x + 1; //true,没有修改x的值
}
抽象类
1、抽象类引入
在父类中定义一些没有实现的方法,由子类去实现
2、抽象类的使用
用abstract修饰父类中不确定的方法 =》 这个方法就是抽象方法
当一个类中存在抽象方法,其类必须用abstract修饰(变成抽象类)
抽象方法没有方法体
3、注意事项
- 抽象类不能实例化(抽象类是一个结构和功能“不完整”的类,如果强行创建对象,不能实现相关功能)
- 抽象类不一定要包含abstract方法,即抽象类可以没有抽象方法
- abstract只能修饰类和方法,不能修饰属性和其他的
- 抽象类可以有任意类成员(抽象类的本质还是一个类)
- 抽象方法不能有主体,即不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
abstract class A {
public abstract void hi();
}
abstract class B extends A {
@Override
public void hi(){ //重写父类抽象方法,即实现这个抽象方法
}
}
- 抽象方法不能使用private、final和static来修饰,因为这些关键词都是和重写相违背的
- private :没有机会重写
- final:子类不能重写父类的该方法
- static:static修饰的方法可以直接被类所调用,而abstract修饰的抽象方法没有实现(无方法体),所以不能被调用
4、抽象类实践:模板设计
要求:
- 有多个类,完成不同的人物job
- 统计得到各自完成任务的时间
Test.java(包含AA、BB子类和main方法)
public class Test{
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
class AA extends Template{
//任务:1+2+...+1000000
@Override
public void job(){
long num = 0;
for(long i = 1 ; i <= 1000000 ; i++){
num += i;
}
}
@Override
public String getClassName(){
return "AA";
}
}
class BB extends Template{
//任务:1*2*...*10000
@Override
public void job(){
long num = 0;
for(long i = 1 ; i <= 1000000 ; i++){
num *= i;
}
}
@Override
public String getClassName(){
return "BB";
}
}
Template.java(包含统一的内容)
abstract public class Template {
public abstract void job(); //抽象方法
public abstract String getClassName();
public void calculateTime(){ //实现方法,调用了job抽象方法
//得到开始时间
long start = System.currentTimeMillis();
job(); //动态绑定机制,执行到此时,会从子类开始调用
//得到结束时间
long end = System.currentTimeMillis();
System.out.println(getClassName() + " 执行时间:" + (end - start));
}
}
输出: