【Java基础】static & main方法 & 代码块 & final & 抽象类与抽象方法 & 接口interface & 内部类

面向对象(下)

关键字:static

为什么需要有static

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

class Circle{
    private double radius;
    public Circle(doubler adius){
        this.radius=radius;
    }
    public double findArea(){
        returnMath.PI*radius*radius;
    }
}
//创建两个Circle对象
Circle c1=newCircle(2.0);	//c1.radius=2.0

Circle c2=newCircle(3.0);	//c2.radius=3.0
  • Circle类中的变量radius是一个实例变量(instancevariable),它属于类的每一个对象,但不能被同一个类的不同对象所共享。
  • 上例中c1的radius独立于c2的radius,存储在不同的空间。c1中的radius变化不会影响c2的radius,反之亦然。

如果想让一个类的所有实例共享数据,就用类变量!

如何使用

可以用来修饰的结构

主要用来修饰类的内部结构:属性、方法、代码块、内部类

static修饰属性

是否使用static修饰,分为:静态属性 vs 非静态属性(实例变量)

  • 静态变量:static修饰的变量,我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量后,其他对象调用此静态变量是也修改过了的。
  • 实例变量:我们创建了类的多个对象,每个对象都独立的拥一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。

说明:

  • 静态变量随着类的加载而加载。可以通过类.静态变量的方式进行调用
  • 静态变量的加载要早于对象的创建。
  • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

静态变量的内存解析

static修饰方法

static修饰的方法叫做静态方法

  • 没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类方法
  • 在静态方法内部只能访问静态属性或静态方法,不能访问非静态的结构。
  • 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super

什么时候用

  • 操作静态属性的方法,通常设置为static的
  • 工具类中的方法,习惯上声明为static的。

面试题

静态方法为什么不能调用非静态成员?

这个需要结合 JVM 的相关知识,主要原因如下:

  1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
  2. 在类的非静态成员不存在的时候静态成员就已经存在了,此时调用内存中还不存在的非静态成员,属于非法操作。

静态方法和实例方法有何不同?

调用方式

在外部调用静态方法时,可以使用 类名.方法名 的方式,也可以使用 对象.方法名 的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象

不过,需要注意的是一般不建议使用 对象.方法名 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。

因此,一般建议使用 类名.方法名 的方式来调用静态方法。

public class Person {
    public void method() {
      //......
    }

    public static void staicMethod(){
      //......
    }
    public static void main(String[] args) {
        Person person = new Person();
        // 调用实例方法
        person.method();
        // 调用静态方法
        Person.staicMethod()
    }
}

访问类成员是否存在限制

静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。

理解main()

  • 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
  • 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。

类的成员之四:代码块

类的成员之四:代码块(初始化块)(重要性较属性、方法、构造器差一些)

代码块的作用:用来初始化类、对象的信息

分类:代码块要是使用修饰符,只能使用static

分类

是否有static修饰

静态代码块:

  • 随着类的加载而执行,而且只执行一次
  • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
  • 静态代码块的执行要优先于非静态代码块的执行
  • 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构

非静态代码块:

  • 随着对象的创建而执行
  • 每创建一个对象,就执行一次非静态代码块
  • 可以在创建对象时,对对象的属性等进行初始化
  • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
  • 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法

按所处位置分

分类 位置 作用
局部代码块 在方法里面 给变量限定生命周期,局部代码块的变量在执行结束后会被Java回收
构造代码块 在类的成员位置 在每次执行构造方法前先执行构造代码块,可以将多个构造方法中的相同的代码放到构造代码块中,对对象进行初始化
静态要骂快 类成员位置 一般用于给类初始化,被静态修饰的代码块仅执行一次

优先级为: 静态代码块 > 构造代码块 > 构造方法;

class Block{
	{
		System.out.println("我是构造代码块"); //每次被新对象调用时都执行,优先级在静态代码块之后
	}
	static {
		System.out.println("我是静态代码块");  //只在第一次调用的时候执行,优先级最高
	}
	public void method() {
		System.out.println("我是成员方法"); 	
	}
	public Block() {
		super();
		System.out.println("我是无参构造方法");
	
	}
	
}
public class ARRTEXT {
	public static void main(String[] args) {
		{
			String a="我是局部代码块";
			System.out.println(a);
		}
		//System.out.println(a);  报错局部代码块内的变量在代码块外不能访问,代码块执行完后就会被回收
		System.out.println();
		Block b=new Block();
		b.method();
		System.out.println();
		Block c=new Block();
		c.method();	
		  
	}
}
/*
输出为:
我是局部代码块

我是静态代码块
我是构造代码块
我是无参构造方法

我是成员方法
我是构造代码块
我是无参构造方法
我是成员方法
 * */

成员变量赋值的执行顺序

由父及子,静态先行

  • 最先执行的是父类的静态代码块,然后按等级执行子类的静态代码块
  • 按等级执行非静态代码块
  • 代码块的执行先于构造器
  • 按等级执行构造器
class Father {
	static {
		System.out.println("11111111111");
	}
	{	
		System.out.println("22222222222");
	}

	public Father() {
		System.out.println("33333333333");
	}
}

public class Son extends Father {
	static {
		System.out.println("44444444444");
	}
	{
		System.out.println("55555555555");
	}
	public Son() {
		System.out.println("66666666666");
	}

	public static void main(String[] args) { // 由父及子 静态先行
		System.out.println("77777777777");
		System.out.println("************************");
		new Son();
		System.out.println("************************");
		new Son();
		System.out.println("************************");
		new Father();
	}
}

输出结果在下方:

11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333

实际应用场景

控制变量的生命周期

局部代码块作用在方法当中,可以控制变量的生命周期

public void show(){
    {
        int a = 1
    }
    System.out.println(a)	//编译不通过,变量a的作用域只在代码块中
}

在程序中当我们定义完成一个局部变量之后,并且在接下来的代码中不想再用到它时,那么就没必要让它在内存中继续占用空间,因此就有了局部代码块。

Java中的局部变量保存在虚拟机栈的局部变量表中,Java程序执行的过程就是局部变量传递的过程,所以局部变量表会占用大量资源,而与性能调优关系最为密切的部分也就是局部变量表,感兴趣的朋友可以去了解下JVM虚拟机,此处不做详细讲解。

统一初始化

构造代码块作用在类中,可以给类的部分字段统一初始化

public class Apple {
    private String size;

    //构造代码块  
    {
        System.out.println("构造代码块运行!");
        size = "E";
    }
}  

构造代码块与构造函数的区别是:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化

因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。所以理所当然的,构造代码块在构造函数之前执行。

静态代码块

作用有两个:给类的静态变量赋值;声明静态变量;

public class APP {
    static int x, y; // 静态变量

    static {
        x = 5; // 给静态变量x赋值
    }
    
     public static void main(String[] args) {
        System.out.println(x);	//5
    }
}

注意,如果静态代码块中的代码改为:

static {
    int  x = 5; 	// 生命静态变量
}

则只是在声明静态变量,而不是进行初始化,并且这个静态变量的作用域只在这个代码块中,最后的输出结果0

final关键字

final:最终的

可以用来修饰:类、方法、变量

  • final标记的类不能被继承。提高安全性,提高程序的可读性。

    • String类、System类、StringBuffer类
  • final标记的方法不能被子类重写。

    • 比如:Object类中的getClass()。
  • final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。

    • final标记的成员变量必须在声明时或在构造器中或代码块中显式赋值,然后才能使用。
      final double MY_PI = 3.14;
    • 被final修饰的变量,不可变的是变量的引用,而不是变量的内容

    • 例如final int stamp = xxx.getStamp();stamp内容在变,但引用即指向的地址没变

抽象类与抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

abstract修饰类:抽象类

抽象类本身不执行什么功能,只声明有哪些功能,具体功能实现都是在子类重写的方法中,所以抽象类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 ,所以抽象的使用前提:继承性

abstract修饰方法:抽象方法

  • 抽象方法只有方法的声明,没有方法体
  • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法。
    反证:若有抽象方法的类不是抽象类,则该类就可以实例化调用该抽象方法,错误
  • 若子类重写了父类中的全部抽象方法,则该子类可实例化;若子类没重写父类中的全部抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
  public abstract void Employee();   //抽象方法没有方法体

注意事项

  • abstract不能用来修饰:属性、构造器等结构
  • abstract不能用来修饰私有方法、静态方法、final的方法、final的类
  • 多态也是抽象的前,抽象类不能实例化对象,多态使其可以声明子类对象
abstract class GeometricObject{
	public abstract double findArea();
}
class Circle extends GeometricObject{
	private double radius;
	public double findArea(){
		return 3.14 * radius * radius;
	};
}

设计模式:模板方法(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
abstract class Template {
    publicfinalvoidgetTime() {
        longstart= System.currentTimeMillis();
        code();
        longend= System.currentTimeMillis();
        System.out.println("执行时间是:"+ (end-start));
    }
    public abstract void code();
}
class SubTemplate extends Template {
    publicvoidcode() {
        for(int i= 0; i< 10000; i++){
            System.out.println(i);
        }
    }
}

接口interface

为什么会有接口

 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方 

法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又
没有关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打
印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都
支持USB连接。

什么是接口

  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则

必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"

的关系。

  • 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都

要遵守

使用

  • JDK7及以前:只能定义全局常量和抽象方法
    • 全局常量(不仅包括基本数据类型变量,还有引用类型变量,例如类的对象定义为public static final,但是书写时,可以省略不写
    • 抽象方法:public abstract,接口中所有的方法都是抽象方法
  • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法默认方法

注意

  • 接口中不能定义构造器的!意味着接口不可以实例化

  • Java开发中,接口通过让类去实现(implements)的方式使用

    • 如果实现类覆盖了接口中的全部抽象方法,则此实现类就可以实例化
    • 果实现类没覆盖接口中全部的抽象方法,则此实现类仍为一个抽象类
  • Java类可以实现多个接口,弥补了Java单继承性的局限性

     class A extends B implements C,D,E
    

举例

接口的具体使用,体现多态性。接口,实际上可以看做是一种规范

class Computer{
	public void transferData(USB usb){//USB usb = new Flash();
		usb.start();
		System.out.println("具体传输数据的细节");	
		usb.stop();
	}		
}

interface USB{
	void start();
	void stop();
}

class Flash implements USB{

	@Override
	public void start() {
		System.out.println("U盘开启工作");
	}

	@Override
	public void stop() {
		System.out.println("U盘结束工作");
	}
	
}

class Printer implements USB{
	@Override
	public void start() {
		System.out.println("打印机开启工作");
	}

	@Override
	public void stop() {
		System.out.println("打印机结束工作");
	}
	
}

Java8中关于接口的新规范

Java 8中,你可以为接口添加静态方法和默认方法

静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中 找到像Collection/Collections或者Path/Paths这样成对的接口和类

默认方法:默认方法使用 default 关键字修饰。由于没有static修饰,所以默认方法通过实现类对象来调用。 我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。

Java8之前若接口新增一个抽象方法,则其实现类都必须实现该方法,如果接口的继承体系非常庞杂,例如有10000个类实现某个接口,那么如果在接口中新增一个方法,这10000个实现类都必须重写该方法,维护量太大

Java8有了默认方法之后,只需将新加的方法改为默认方法,那么实现类不用重写也能通过实现类对象调用。

注意事项

  • 接口中定义的静态方法,只能通过接口来调用,跟静态类类似。

  • 通过实现类的对象,可以调用接口中的默认方法。

  • 如果实现类重写了接口中的默认方法,则调用的是重写以后的方法

  • 类优先原则:如果一个类同时是子类和接口的实现类,且父类和接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。

  • 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法, 那么在实现类没重写此方法的情况下,报错接口冲突。 这就需要我们必须在实现类中重写此方法

  • 如何在子类(或实现类)的方法中调用父类、接口中被重写的方法

    	public void myMethod(){
    		method3();//调用自己定义的重写的方法
    		super.method3();//调用父类中声明的
    		//调用接口中的默认方法
    		CompareA.super.method3();
    		CompareB.super.method3();
    	}
    
    

类的成员五:内部类

是什么

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内 部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使 用内部类。

  • 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。

内部类的分类

  • 成员内部类(静态、非静态 ),与属性、构造器同级
  • 局部内部类(方法内、代码块内、构造器内)

成员内部类

  • 一方面,作为外部类的成员:
    • 可调用外部类的结构,包括用private修饰的私有成员
    • 可以被static修饰
    • 可以被4种不同的权限修饰
  • 另一方面,作为一个类
    • 可以定义属性、方法、构造器等。
    • 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
    • 可以被abstract修饰

如何创建成员内部类的对象

//创建静态的Dog内部类的实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();  //可直接用外部类调用 

//创建非静态的Bird内部类的实例(非静态的成员内部类):
Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();          //先创建外部类的对象
Person.Bird bird = p.new Bird();  //再用对象创建内部类的实例

实例化方式不同:

  • 静态内部类:不依赖于外部类的实例,直接实例化内部类对象
  • 非静态内部类:通过外部类的对象实例生成内部类对象

示例:

public class Outer {
	private int s = 111;
	public class Inner {
		private int s = 222;
		public void mb(int s) {
			System.out.println(s); // 局部变量s
			System.out.println(this.s); // 内部类对象的属性s
            System.out.println(Outer.this.s); // 外部类对象属性s 
        }
    }
            
	public static void main(String args[]) {
		Outer a = new Outer();
        Outer.Inner b = a.new Inner();
        b.mb(333);
	} 
}

局部内部类

  • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。

  • 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方 都不能使用该类。

  • 局部内部类可以使用外部类的成员,包括私有的。

  • 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局 部变量的声明周期不同所致。

  • 局部内部类和局部变量地位类似,不能使用权限修饰符

  • 局部内部类不能使用static修饰,因此也不能包含静态成员

posted @ 2021-03-26 16:49  至安  阅读(271)  评论(0)    收藏  举报