【JavaSE】类与对象(static 代码块 内部类 toString)超详解,听不懂你来打我

1.static关键字

1.1 static修饰成员变量

我们前面写过Student类中,每个实例化的学生都有各自的姓名,成绩,年龄等,这些信息来对不同的对象进行描述,但对于一个班一个学校的同学来说,他们具有同样的学校,班级,每次创建对象都为这些成员分配内存未免有些做无用功,浪费空间。

这样的话,我们可以将这种每个对象共享的属性用static修饰,在Java中被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。 话不多说,上代码演示。

class Student {
    static String classNum;
    String name;
    int age;
    float score;
}

public class TestDemo {
    public static void main(String[] args) {
        Student student1 = new Student();
        Student student2 = new Student();
        Student student3 = new Student();
    }
}

我们定义学生类,其中班级我们用static修饰,来调试看看。鼠标点击要打断点的位置,debug走起。
在这里插入图片描述
哇! 对象中只有name,age 和 score,没有classNum。

这就是为什么静态成员又叫类成员的原因了,它不属于任何对象,所有对象共享,它是属于类的。 那它既然是属于类的,怎么调用它呢?对象能调用嘛?

类成员的调用既可以 类名.成员名 调用,也可以通过 对象名.成员名 调用,不过我们更建议使用 类名.成员名来调用。

class Student {
    static String classNum;
    String name;
    int age;
    float score;
}

public class TestDemo {
    public static void main(String[] args) {
        Student student1 = new Student();
        Student.classNum = "711班";
        System.out.println(Student.classNum);       //通过类名调用
        System.out.println(student1.classNum);      //通过对象调用
    }
}

如图所示:
在这里插入图片描述

那既然static修饰的成员变量不属于对象,而我们知道对象都是在堆上的,那static修饰的成员变量是创建在哪的呢?

1.类变量存储在方法区当中
2.静态成员变量在类加载时便创建,在类销毁时销毁,生命周期伴随类的一生

说明一下,JDK7及以前,HotSpot(Java虚拟机)中存储在方法区,JDK8及之后,类变量存储在Java堆中, 但实际上,只是把方法区实现在堆上了而已。简单画个图帮助大家理解。
在这里插入图片描述

1.2 static修饰成员方法

既然成员变量可以是静态的,那按理说成员方法也可以用static来修饰。

Java中,被static修饰的成员方法称为静态成员方法,同样静态成员方法是类的方法,不是某个对象所特有的静态成员一般是通过静态方法来访问的。

不说空话,实诚人就得上代码!演示!

class Student {
    static String classNum;
    String name;
    int age;
    float score;
    public static void func() {
        System.out.println("这是一个静态成员方法");
        System.out.println(Student.classNum);
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Student.func();	
        //与类变量相似,静态方法通过类名直接调用,也可以通过对象调用
        //更推荐类名调用
    }
}

在这里插入图片描述
这里我们注意一个细节,我们并没有创建Student的对象就直接调用了func方法,这说明静态成员方法是不依赖对象存在的。不用创建对象就能直接用哦。

注意
1.静态方法中没有隐藏的this引用参数,因此不能在静态方法中访问任何非静态成员变量。
2.静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用。

在这里插入图片描述

这是个什么意思?好麻烦啊!为什么静态方法不能调用非静态的成员变量和成员方法呢???

这样想,静态方法可是不依赖于对象存在的,不需要创建对象就能用,但是,非静态的成员变量和方法,要使用他们就必须要实例化对象,也就是必须要有对象才能使用!! 我要调用静态方法,不需要对象,执行的时候你却告诉我需要new对象才能使用,这不大骗子吗? 编译器无语,给人整尴尬了…所以,这么做是不合法的!!

静态方法也不能重写(后面介绍)。

1.3 static成员变量初始化

普通成员变量一般会在构造方法中进行初始化, 静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性

那静态成员变量在哪初始化呢?

静态成员变量的初始化分为两种:就地初始化静态代码块初始化

就地初始化就是在定义时直接赋初值。

至于静态代码块初始化,我们得先来了解什么是代码块。

2. 代码块

2.1 代码块的概念

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:

1.普通代码块
2.构造代码块
3.静态代码块
4.同步代码块(暂时无需了解)

2.2 普通代码块

定义在方法中的代码块,其实就是在方法中把一段代码拿{}括起来罢了,比较鸡肋,用的少。

public class Main{
	public static void main(String[] args) {
		{ //直接使用{}定义,普通方法块
			int x = 10 ;
			System.out.println("x1 = " +x);
		}
		int x = 100 ;
		System.out.println("x2 = " +x);
	}
}

2.3 构造代码块/示例代码块

定义在类中的代码块(不加修饰符)。构造代码块一般用于初始化实例成员变量。

我们来代码实操一下。

class Student {
    static String classNum;
    String name;
    int age;
    float score;
    //来写一个构造代码块
    {
        name = "张三";
        age = 18;
        score = 60.5f;
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Student student = new Student();
        System.out.println(student.name);
        System.out.println(student.age);
        System.out.println(student.score);
    }
}

在这里插入图片描述
我们看到在实例化对象后,student的各项值都不再是默认值了,说明我们的实例化代码块起作用了!

那它是怎么执行的呢?

实际上,在编译时,编译器会把我们的实例代码块拷贝一份放到每一个构造方法中,并且放在构造方法开头,优先于构造方法执行

测试一下!

class Student {
    static String classNum;
    String name;
    int age;
    float score;
    //来写一个构造代码块
    {
        name = "张三";
        age = 18;
        score = 60.5f;
        System.out.println("这是构造代码块");
    }
    Student() {
        System.out.println("这是Student的无参构造方法!");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Student student = new Student();
    }
}

看看运行结果!
在这里插入图片描述
果然示例代码块在构造方法之前执行了!

如果当我们有多个实例代码块时,会按照实例代码块的位置来依次执行!

2.4 静态代码块

定义:使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。 就是在代码块前加static修饰嘛。

感情都在代码里嗷铁汁!

class Student {
    static String classNum;
    String name;
    int age;
    float score;
    //来写一个构造代码块
    {
        name = "张三";
        age = 18;
        score = 60.5f;
        System.out.println("这是构造代码块");
    }
    //来写一个静态代码块
    static {
        classNum = "711班";
        System.out.println("这是静态代码块!");
    }
    Student() {
        System.out.println("这是Student的无参构造方法!");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Student student1 = new Student();
        System.out.println("==================");
        Student student2 = new Student();

    }
}

看一下运行结果。
在这里插入图片描述
哎呦,有意思,我的静态代码块放在了实例代码块的后面,可它却有优先于我的实例代码块执行了,而且,我第二次new对象时,它居然不执行了!

总结一下:

1.静态代码块不管生成多少个对象,其只会执行一次。
2.静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的。
3.Java代码在经过编译器编译之后,如果要运行必须先要经过类加载子系统加载到JVM中才能运行。在加载阶段:
在这里插入图片描述
在链接阶段第二步准备中会给静态成员变量开辟空间,并设置为默认值,在初始化阶段,会执行静态代码块中的代码。(了解:关于类加载过程后序JVM中会详细讲解)
4.如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次合并,最终放在生成
的< clinit>方法中,该方法在类加载时调用,并且只调用一次。
在这里插入图片描述
5.跟实例代码块不同的是,实例代码块只有在创建对象是才会执行,因为new对象必须调用构造方法,而构造方法中第一行就是实例代码块!静态代码块是类加载时执行的。

3. 内部类

3.1 内部类定义与分类

在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,
前者称为内部类,后者称为外部类。内部类也是封装的一种体现。

public class OutClass {
	class InnerClass{
	
	}
}
// OutClass是外部类
// InnerClass是内部类

注意,内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件

根据内部类定义的位置不同,一般可以分为以下几种形式:
1.成员内部类:

1.普通内部类
2.静态内部类(static修饰)

2.局部内部类
3.匿名内部类(日常开发中用的最多)

public class OutClass {
	// 成员位置定义:未被static修饰 ---> 普通内部类
	public class InnerClass1{
	}
	// 成员位置定义:被static修饰 ---> 静态内部类
	static class InnerClass2{
	}
	public void method(){
		// 方法中也可以定义内部类 ---> 局部内部类:几乎不用
		class InnerClass5{
		}
	}
}

3.2 普通内部类

普通成员内部类因为所处位置跟成员变量相同,所以叫成员内部类,而且同样能被public,private修饰。

上代码!

public class OutClass {  //外部类
	private int a;
	static int b;
	int c;
	public void methodA(){
		a = 10;
		System.out.println(a);
	}
	public static void methodB(){
		System.out.println(b);
	}
	// 成员内部类:未被static修饰
	class InnerClass{
		int c;
		public void methodInner(){
	// 在内部类中可以直接访问外部类中:任意访问限定符修饰的成员
			a = 100;
			b =200;
			methodA();
			methodB();
	// 如果外部类和内部类中具有相同名称成员时,优先访问的是内部类自己的
			c = 300;
			System.out.println(c);
	// 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
			OutClass.this.c = 400;
			System.out.println(OutClass.this.c);
		}
	}
}

这样我们就定义了一个外部类和内部类,那内部类是怎么实例化对象的呢?
It`s a question.

我们知道类的成员的访问或者赋初值都是要依赖于对象的,也就是有对象才有成员变量,那内部类作为一种特殊的成员,自然也是依赖于对象的,也就是必须要有内部类对象,才能实例化内部类!

我们试试new出一个内部类对象!

public class TestDemo {
    public static void main(String[] args) {
        //内部的创建 先创建外部类
        OutClass outClass1 = new OutClass();
        //再创建内部类 注意内部类类名前需要加 外部类 + .
        //毕竟内部类也是外部类的成员,访问成员就是通过类名 + ‘.’ 来执行的。
        OutClass.InnerClass innerClass1 = outClass1.new InnerClass();
        //需要注意这里new的位置,不能放在outclass前面
        //因为outclass已经实例化完成,我们需要在他内部再实例化一个对象
        innerClass1.methodInner();
        //内部类方法调用
    }
}

或者你也可以这样创建内部类对象:

	OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
//注意无论哪种方法都需要两次new,因为外部类内部类都需要分配空间

运行结果如下:
在这里插入图片描述
给大家分析一下:
在这里插入图片描述

1.内部类在使用成员变量时,先看看自己有没有,如果有就用自己的,如果没有才会向外寻找外部类中有没有,如果都没有,那编译器就报错了
2.如果就要访问外部的成员,我们需要加上外部类名.this.成员名
3.普通内部类的非静态方法中包含了一个指向外部类对象的引用
4.成员内部类,经过编译之后会生成独立的字节码文件,命名格式为:外部类名称$内部类名称
在这里插入图片描述

3.3 静态内部类

静态内部类就是用static修饰的内部类,它拥有静态成员的属性,是属于类的,所以它不依赖于对象存在。

上代码!

public class OutClass {
	private int a;
	static int b;
	public void methodA(){
		a = 10;
		System.out.println(a);
	}
	public static void methodB(){
		System.out.println(b);
	}
	// 静态内部类:被static修饰的成员内部类
	static class InnerClass{
		public void methodInner(){
			// 在内部类中只能访问外部类的静态成员
			// a = 100; // 编译失败,因为a不是类成员变量
			b =200;
			// methodA(); // 编译失败,因为methodB()不是类成员方法
			methodB();
		}
	}
}

我们说过静态成员不依赖于对象而存在,所以,静态内部类不需要外部对象就能实例化。
啊哈哈哈哈~ 代码来咯~(不是)

class OutClass {
    private int a;
    static int b;

    public void methodA() {
        a = 10;
        System.out.println(a);
    }

    public static void methodB() {
        System.out.println(b);
    }
    
    // 静态内部类:被static修饰的成员内部类
    static class InnerClass {
        public void methodInner() {
            // 在内部类中只能访问外部类的静态成员
            // a = 100; // 编译失败,因为a不是类成员变量
            b = 200;
            // methodA(); // 编译失败,因为methodA()不是类成员方法
            methodB();
        }
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        OutClass.InnerClass innerClass = new OutClass.InnerClass();
        //这里我们只需要一次new即可创建对象哦
        innerClass.methodInner();
    }
}

运行结果:
在这里插入图片描述

在静态内部类中只能访问外部类的静态成员,同样的道理,静态内部类不依赖于外部类对象存在,而外部类的普通成员需要创建对象初始化才有,不能做让编译器尴尬的事哦!

3.4 局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。

public class OutClass {
	int a = 10;
	public void method(){
		int b = 10;
		// 局部内部类:定义在方法体内部
		// 不能被public、static等访问限定符修饰
		class InnerClass{
			public void methodInnerClass(){
					System.out.println(a);
					System.out.println(b);
			}
		}
		// 只能在该方法体内部使用,其他位置都不能用
		InnerClass innerClass = new InnerClass();
		innerClass.methodInnerClass();
	}
}

3.5 匿名内部类

嗨嗨嘿,卖个关子,接口再讲。

4. toString方法介绍

我们在之前打印过对象的引用,有没有想过为什么打印出来是那个鬼东西?
在这里插入图片描述
我们在IDEA上鼠标光标移动到println上,按住Ctrl,左击点进去,就能看到它的源码。
在这里插入图片描述
在这里插入图片描述
进入源码后,往下翻,找到参数是object引用类型的。
在这里插入图片描述
啊,原来就是打印valueOf()方法的返回值啊,我们再看看valueOf是个什么东西,同样的方法,Ctrl,点进去。
在这里插入图片描述
三目操作符的结果,如果为空引用返回"null",否则返回由toString()方法,我们再来看看toString方法,走起。
在这里插入图片描述
看到这里我们就能理解为什么打印对象名会出现那串奇怪的东西啦。原来是调用了toString方法。

实际上,我们在开发中,都会自己重写一个toString方法来打印。关于什么是重写,后面再介绍, 这里只需要知道,println方法参数是引用时,会首先寻找自己类中的toString方法,如果没有,就会调用自带的Object.java中的toString方法。

教大家一个快捷键,体现IDEA强大之处的地方到了,打开我们的构造器!

在类空白处,按下鼠标右键,选择Generate

在这里插入图片描述

选择toString()
在这里插入图片描述> 按住Ctrl,选择需要的成员变量,点击ok。
在这里插入图片描述
好啦!这样我们就得到了一个toString方法,方法上面有一行@Override就是注释一下这是方法的重写,编译器自动加的暂时不用管。
在这里插入图片描述
来测试一下下面这段代码这次打印对象名会是什么。

class Student {
    String name;
    int age;
    float score;

    public Student(String name, int age, float score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

public class TestDemo3 {
    public static void main(String[] args) {
        Student student = new Student("张三", 18, 99.5f);
        System.out.println(student);
    }
}

在这里插入图片描述
嗯 ,有了toString方法我们便能用引用来打印类的内容啦,当然你也可以自己写喜欢的toString方法。

以后可以尽情偷懒惹!好耶! IDEA yyds!

=========================================================
码字不易,点个赞再走吧!收藏不迷路! 持续更新中…

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值