Java地基(八)-----静态、抽象、内部类


在这里插入图片描述

1.static: 静态的,加载到方法区的

static修饰符可以修饰属性、方法、代码块

静态方法中为什么不能使用this?

答:this是指向一个对象实例的,静态方法中没有实例变量,所以也就不能使用, 可以将该变量改为静态变量,然后直接使用,或者使用类名

1.1 区分:

静态:类名加方法名调用,也可以创建对象调用

  • 静态属性是属于类的。
  • 静态的属性和方法是在类加载的时候加载到方法区。
  • 类所有对象共享的
  • 类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值

非静态:创建对象后可以调用

  • 非静态属性,是属于对象的,一定要使用对象来访问,没有其他方式 。
  • 创建对象的时候才会加载到栈区。
  • 创建对象栈区开辟空间后,系统会自动给对象中的非静态属性做初始化赋默认值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9sAkThdT-1622597024843)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210531112731552.png)]

1.2 静态方法与非静态方法的相互使用:

静态方法中无法加载非静态属性和方法,但可以通过创建对象的方式访问。

  • 非静态的只有在创建对象的时候才会加载

  • 在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this)就不能访问类中的非静态属性和非静态方法。

非静态方法中可以加载静态和非静态的方法和属性

1.3初始化操作:

起因:类中的构造器,可以给非静态属性做初始化,但是不能给静态属性做初始化。

结果:

静态代码块:也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化的

  • 类加载的时候,自动执行
  • 静态代码块只会自动被执行一次

匿名代码块:

  • 它的作用是给非静态属性做初始化操作
  • 它能完成的工作,使用构造器也一样可以完成,使用的场景较少。
  • 构造器执行之前,自动执行。并且每次创建对象时,匿名代码块都会被自动执行。
1.4 完整的加载过程实例:静态的是自上而下的加载

jvm首先会扫描所有的静态类并加载到方法区中

之后至上而下给静态属性赋初始值,静态方法分配空间(方法区)

静态属性赋值显示赋值

加载子类前加载父类,因此执行父类的静态代码块,再执行子类的静态代码块

为子类堆区开辟空间,同时初始化子类非静态的属性赋默认值

调用父类的匿名代码块,父类构造器

对子类进行显示赋值(父类因没有创建对象因此没有显式赋值)

执行子类的匿名代码块,子类构造器

创建和初始化对象的过程:

Student s = new Student();

​ 以这句代码为例进行说明:

  • 对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间
  • 执行类中的静态代码块
  • 默认赋值:堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
  • 调用Student的父类构造器
  • 对Student中的属性进行显示赋值,例如 public int age = 20;
  • 执行匿名代码块
  • 执行构造器代码
  • =号赋值操作,把对象的内存地址赋给变量s
public class Person{
	private String name = "zs";
	public Person() {
		System.out.println("Person构造器");
		print();
	}
    public void print(){
		System.out.println("Person print方法: name = "+name);
	}
} 
class Student extends Person{
	private String name = "tom";
	{
		System.out.println("Student匿名代码块");
	}
    static{
		System.out.println("Student静态代码块");
	} 
    public Student(){
		System.out.println("Student构造器");
	} 
    public void print(){
		System.out.println("student print方法: name = "+name);
	}
    public static void main(String[] args) {
		new Student();
	}
}
运行结果:
    Student静态代码块
	Person构造器
	student print方法: name = null
	Student匿名代码块
	Student构造器
注意注意注意!!!!
  • 注意1,子类重写父类的方法,在创建子类对象的过程中,默认调用的一定是子类中重写后的方法

  • 注意2,非静态属性的

    显示赋值:是在父类构造器执行结束之后子类中的匿名代码块执行之前的时候。

    默认赋值父类构造器执行之前静态代码块执行之后

  • 注意3,以上代码中,因为方法的重写,会调用子类中重写后的print方法,同时该方法恰好是在父类构造器执行中调用的,而这个时候子类中的name属性还没有进行显示赋值,所以是输出结果为null

  • 注意4,如果此时在子类的匿名代码块中也输出name的值,那么就会显示tom,因为已经完成了属性的显示赋值

1.5 静态导入:JDK1.5及以上版本

在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名。

import static java.lang.Math.PI;
import static java.lang.Math.random;
public class Demo {
	public void test(){
		System.out.println(PI);
		System.out.println(random());
	}
}

2. final

2.1 修饰类

用final修饰的类不能被继承,也就是说这个类是没有子类的。

2.2 修饰方法

用final修饰的方法可以被子类继承,但是不能被子类的重写 。

2.3 修饰变量

局部变量:

用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错

非静态成员变量:

对于这个final成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:

  • 声明的同时赋值
  • 匿名代码块中赋值
  • 构造器中赋值,此时还有额外要求:类中出现的所有构造器都要赋值,否则报错

final修饰静态成员变量:

对于这个final静态成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:

  • 声明的同时赋值
  • 静态代码块中赋值

final修饰引用类型变量:

注意,此时final指的是,引用s的指向的对象不能改变,但是可以使用s来操作当前指向的对象属性和方法

3. abstract:修饰符

抽象类中不一定有抽象方法,有抽象方法的一定时抽象类,抽象方法一定是要被继承实现的

抽象类和抽象方法的关系:

  • 抽象类中可以没有抽象方法
  • 有抽象方法的类,一定要声明为抽象类

存在的必要性:

  • 因为抽象类不能实例化对象,所以必须要有子类来实现它之后才能使用。这样就可以把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护。
  • 当又有一个具有相似的组件产生时,只需要实现该抽象类就可以获得该抽象类的那些属性和方法。
  • 有一些方法可以写,但没有意义,因此引入抽象

关于抽象类的一些疑惑?

  • 抽象类有构造器,便于子类继承
  • 抽象类的抽象方法,让别人使用的时候必须实现
  • 抽象方法去实现,普通方法被重写

4. interface:解决类的单继承不好扩展

4.1类型:

引用数据类型:类、数组、接口

接口并不是类,而是另外一种引用数据类型,接口的内部主要就是封装了方法和静态常量。

无构造器

4.2注意点:

abstract不能和final private static 连用:

private:
abstract修饰方法只有方法声明,没有方法实现。抽象方法的意义在于子类实现。而private修饰后为私有,子类不能继承,也就不能使用。所以二者冲突,不能共存。

final:
abstract修饰的类,该类中的方法子类继承之后需要重写的,可是final修饰的类不能被继承,也没子类,方法更不能得到重写。所以二者冲突,不能共存。

static:
static修饰的是静态方法,可以直接被调用;而abstract修饰的类中只有方法声明,没有方法实现,不能被直接调用。所以二者冲突,不能共存。

4.3 示例:

//使用interface关键字来定义接口
public interface Action {
	//接口中的静态常量
	public static final String OPS_MODE = "auto";
	//接口中的抽象方法
	public abstract void start();
	//接口的抽象方法
	public void stop();
}

接口里面的属性,默认就是public static final修饰的
接口里面的方法,默认就是public abstract修饰的
所以这些都可以省去不写的

4.4 实现

  • 类实现一个接口 和 类继承一个父类 的效果类似,格式相仿,只是关键字不同,实现使用 implements 关键字,而继承使用的是extends关键字
  • 一个类实现了接口,那么就要实现接口中所有的抽象方法,否则这个类自己就必须声明为抽象类
  • 一个类实现了多个接口,那么就需要把这多个接口中的抽象想法全都实现

4.5接口的继承

java中,类和类之间是单继承,接口和接口之间是多继承

示例:

//实现该接口的类,将具有run的功能
public interface Runable {
	void run();
}
//实现该接口的类,将具有fly的功能
interface Flyable{
	void fly();
} 
//实现该接口的类,将具有run的功能,fly的功能,以及Action接口独有的doSomething功能
interface Action extends Runable,Flyable{
	void doSomething();
} 
//实现类,实现Action接口,就必须要实现Action及其父接口中的所有抽象方法
class Demo implements Action{
	@Override
	public void run() {
	} 
    @Override
	public void fly() {
	}
    @Override
	public void doSomething() {
	}
}

4.6 接口的多态

  • 多态的前提是继承,必须要先有子父类关系才行,而类和接口之间的实现关系,其实也是继承的一种形式,所以在类和接口的实现关系中,也可以使用多态
  • 抽象方法的实现,也算是一种方法的重写,形式、语法完全一致。

子类与父类方法调用时的顺序关系:

当使用多态时子类和父类,调用方法时先查找父类,如果父类有方法,在查找子类是否重写,重写则输出子类,否则输出父类。

在编译的时候,编译器会先检查引用 子类所属的类型父类中,是否存在当前要调用的方法
,如果没有那么就直接编译报错。

5.内部类

类中可以嵌套接口,接口的内部也可以嵌套其他接口。
例如,参考java.util.Map接口中的内部接口Entry

类型:

成员内部类
静态内部类
局部内部类
匿名内部类 (关键)

注意点

注意,成员内部类中,不能编写静态的属性和方法
注意,当前这个代码,编译成功后,会生成俩个class文件,一个对应外部类,一个对应内部类
编译生成的俩个class文件的名字分别为:
MemberOuterClass.class
MemberOuterClass$MemberInnerClass.class

5.1成员内部类和外部类的相互访问 :

成员内部类访问外部的属性和方法 :

//访问当前run方法中的参数name
System.out.println(name);
//访问内部类自己的属性name
System.out.println(this.name);
//访问外部类的非静态属性
System.out.println(MemberOuterClass.this.name);
//访问外部类的静态属性
System.out.println(MemberOuterClass.age);
//访问外部类的非静态方法
MemberOuterClass.this.run();
//访问外部类的静态方法
MemberOuterClass.go();

外部类访问成员内部类的属性和方法 :

//需要创建内部类对象,然后才可以访问
MemberInnerClass t = new MemberInnerClass();
System.out.println(t.name);
System.out.println(t.age);

在其他类中使用这个内部类:
如果这个成员内部类不是private修饰的,那么在其他类中就可以访问到这个内部类

MemberOuterClass moc = new MemberOuterClass();
MemberInnerClass mic = moc.new MemberInnerClass();

意义:

在对事物进行抽象的时候,若一个事物内部还包含其他事物,就可以考虑使用内部类这种结构。
例如,汽车(Car)中包含发动机(Engine) ,这时, Engine 类就可以考虑(非必须)使用内部类来描述,定义在Car类中的成员位置。这样设计,既可以表示Car和Engine的紧密联系的程度,也可以在Engine类中很方便的使用到Car里面的属性和方法

5.2 静态内部类和外部类的相互访问:

注意的点在其他类中使用这个内部类:

import com.briup.sync.StaticOuterClass.StaticInnerClass;
public class Test {
	public static void main(String[] args) {
        //不需要通过外部类创建内部类对象,直接创建内部类对象
		StaticInnerClass sic = new StaticInnerClass();
		sic.run("tom");
	}
}
  • 静态内部类中访问不了外部类中的非静态属性和方法
  • 这个内部类需要import导入,并且是 外部类.内部类 的形式导入。
  • 在创建对象的时候,直接使用这个静态内部类的名字即可: new 静态内部类对象(); ,不再需要依赖外部类对象了。

5.3 局部内部类和外部类的相互访问:

局部内部类访问外部的属性和方法 :

//将String name 改为别的名字 cride
public void sayHello(String name){
   
/* 局部内部类 声明开始 */
    class LocalInnerClass{
		private String name;
		public void test(String name){
			//访问当前test方法中的参数name
			System.out.println(name);
			//访问内部类自己的属性name
			System.out.println(this.name);
			/*注意,sayHello方法的参数name,无法访问,因为实在没有办法表示了,和内部类方法的形参一样了,换成其他名字后,就可以访问了,不要叫name就行*/
            System.out.println(cride);
			//访问外部类的非静态属性
			System.out.println(LocalOuterClass.this.name);
			//访问外部类的非静态方法
			LocalOuterClass.this.run();
			//访问外部类的静态属性和方法
			System.out.println(LocalOuterClass.age);
			LocalOuterClass.go();
		}
	} 
    /* 局部内部类 声明结束 */
}

局部内部类中,访问当前方法中的变量,这个变量必须是final修饰的 :

在JDK1.8中,一个局部变量在局部内部类中进行访问了,那么这个局部变量自动变为final修饰

public void sayHello(final String name){
	final int num = 1;
	/* 局部内部类 声明开始 */
	class LocalInnerClass{
		public void test(){
			System.out.println(name);
			System.out.println(num);
			//编译报错,final修饰的变量,只能赋值一次
			//name = "tom";
			//num = 2;
		}
	} 
    /* 局部内部类 声明结束 */
}

外部类访问局部内部类的属性和方法 :

public void sayHello(String name){
	/* 局部内部类 声明开始 */
	class LocalInnerClass{
		private int num;
		public void test(){
		}
	} 
    /* 局部内部类 声明结束 */
	//创建局部内部类对象
	LocalInnerClass lic = new LocalInnerClass();
	//对象访问属性
	System.out.println(lic.num);
	//对象调用方法
	lic.test();
}

5.4 匿名内部类: 接口和继承

在普通的代码中,使用一个接口的步骤如下:

  • 声明一个类,去实现这个接口
  • 实现这个接口中的抽象方法(重写)
  • 在其他代码中,创建这个类的对象
  • 调用类中实现(重写)后的方法

使用匿名内部类,就可以把这个过程给给简化了,让我们更加方便的调用到实现(重写)后的方法!

格式:

父类或者接口类型 变量名 = new 父类或者接口(){
	// 方法重写
	@Override
	public void method() {
		// 执行语句
	}
};
//调用实现(重写)后的方法
变量名.method();

匿名内部类因为没有类名:

  • 匿名内部类必须依托于一个父类型或者一个接口
  • 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
  • 匿名内部类中无法定义构造器

示例:

public interface Algorithm{
	void sort(int[] arr);
} 
class Test{
	//使用指定算法,对数组arr进行排序
	public void sort(int[] arr,Algorithm alg){
		alg.sort(arr);
	}
} 
public static void main(String[] args){
	Test t = new Test();
	int[] arr = {4,1,6,3,8,5,9};
	Algorithm alg = new Algorithm(){
		public void sort(int[] arr){
			//使用当前需要的排序算法
			//例如,这里简单的使用Arrays工具类中的排序方法
			java.util.Arrays.sort(arr);
		}
	};
	t.sort(arr,alg);
}

5.5 选择

内部类的选择:
假设现在已经确定了要使用内部类,那么一般情况下,该如何选择?

  1. 考虑这个内部类,如果需要反复的进行多次使用(必须有名字)
    • 在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类
    • 在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类
  2. 考虑这个内部类,如果只需要使用一次(可以没有名字)
    • 选择使用匿名内部类
  3. 局部内部类,几乎不会使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值