章节索引
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!
=========================================================
码字不易,点个赞再走吧!收藏不迷路! 持续更新中…