首页 > 技术文章 > JavaSE模块二笔记

kyle-blog 2020-09-23 22:10 原文

一、类的执行流程和内存分析

  • 当类被执行的时候,会加载到内存中的方法区(方法区即存放class二进制文件的地方,方法区是数据共享的)。

  • 进入到方法区之后,会首先加载自己的静态成员。比如:private static String name, public static void main方法。将静态成员(包括变量和方法)放到静态区(也是属于方法区的一部分)。

  • 当创建对象(new,反射,或者其他方式)的时候,JVM会在堆内存中开辟一块内存空间,然后将成员变量复制一份到堆内存中,然后为其赋默认值。赋了默认值之后,就会开始执行构造方法,构造方法执行完毕,则对象创建完毕。

二、可变长参数

  • 格式:返回值类型 方法名(数据类型... 参数名)
  • 需要放到参数列表的末尾

三、参数传递的注意事项

  • 基本数据类型的变量作为方法的参数传递时,形参变量数据的改变不会影响实参变量的数值,因为两个变量有各自独立的内存空间。示例代码如下:

    public class ArgumentTest {
    	
    	public void show(int a) {
    		
    		a = 200;
    		System.out.println("a的值是:" + a);
    	}
    	
    	public static void main(String[] args) {
    		
    		int b = 10;
    		ArgumentTest at = new ArgumentTest();
    		at.show(b);  
    		System.out.println("b的值是:" + b);
    	}
    }
    
    //a的值是:200
    //b的值是:10
    // 从打印的结果来看,b的值并没有改变。所以在show方法内部,无论对形参a的值做什么改变,都不会改变实参b的值。
    // 相当于是把b的值复制了一份给形参a,然你不管怎么改a的值都不会影响到b的值。
    
  • 引用数据类型的变量作为方法的参数传递时,形参变量数据的改变会影响实参变量的数值,因为两个变量指向同一块内存空间。示例代码如下:

public class ArgumentTest {
	
	public void show2(int[] args) {
		args[0] = 200;
		System.out.println("show方法中:args[0] = " + args[0]);
	}
	
	public static void main(String[] args) {

		int[] arr2 = new int[]{10, 20};
		at.show2(arr2);
		System.out.println("main方法中:args[0] = " + arr2[0]);
	}
}

//show方法中:args[0] = 200
//main方法中:args[0] = 200
//可以看到在show2方法中对数组中的值改变之后,原始实参数组的值也发生改变了,因为他们指向同一块内存空间,都在堆内存

四、构造方法的注意事项

  • 当一个类中没有手动定义任何构造方法时,编译器会自动添加一个无参构造方法,叫做默认/缺省构造方法。

  • 如类中手动定义了构造方法,那么编译器则不再提供任何形式的构造方法。

五、递归(重点)

递归能简化代码,但是不容易理解。当递归会影响程序的性能时,这时候就需要使用递推了。

1. 费氏数列

编程实现费氏数列(斐波那契数列)中第n项的数值并返回

示例代码

/**
 *  TODO 使用递推的方式实现返回斐波那契列中第n项的数值
 *  1,  1,  2,  3,  5,  8,  13,...
 *  ia  ib  ic
 *	@author 凯尔王
 *	@date 2020年9月18日 下午8:58:45
 */
public class FeiBoNaQieTest {

	public int show(int n) {
		
		// 先让ia指向第一个位置,即ia = 1
		int ia = 1;
		// 再让ib指向第二个位置,即ib = 1
		int ib = 1;
		for (int i = 3; i <= n; i++) {
			// ic的值就是前两项的值的和
			int ic = ia + ib;
			// 然后更新ia和ib的位置,即向ia和ib分别向前移动一个位置
			ia = ib;
			ib = ic;
		}
		return ib;
	}
}

六、封装

1. 封装的概念

  • 通常情况下,可以在测试类中给成员变量赋值一些合法但是不合理的数值,无论是在编译阶段还是在运行阶段都不会报错或者给出提示,此时与现实生活不符。示例如下:
public class Student {
    int id;
    String name;
    
    public void show() {
        System.out.println("我的名字是:" + this.name);
    }
}

public class StudentTest {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.id = 101;
	    // 这里给名字的赋值就不合理了
        s1.name = "sddadasdda";
    }
}
  • 为了避免错误的发生,这时候就需要对成员变量进行密封包装处理,来隐藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装。示例代码如下:
public class Student {
    private int id;
    private String name;
    
    public void setId(int id) {
        // 这里面就可以进行合理值判断了
        this.id = id;
    }
    
    public void setName(String name) {
        // 这里面就可以进行合理值判断了
        // 如果名字的是英文,则提示用户重新输入。s
        this.name = name;
    }
    public void show() {
        System.out.println("我的名字是:" + this.name);
    }
}

七、static关键字(重点)

  • 使用static关键字修饰的成员变量表示静态含义,此时成员变量由对象层提升为类层级,也就是整个类只有一份并被所有对象共享
  • 该成员变量随着类的加载准备就绪,与是否创建对象无关。
  • 静态变量存在于内存中的方法区

1. 使用方式

  • 在非静态成员方法中既能访问非静态成员又能访问静态的成员。(成员:成员变量 + 成员方法,静态成员被所有对象共享)
  • 在静态成员方法中只能访问静态成员不能访问非静态成员。(成员:成员变量 + 成员方法,因为此时还没有创建对象)
  • 在开发过程中,只隶属于类层级并被所有对象共享的内容才可以使用static关键字修饰。

八、构造块和静态代码块(重点)

1. 构造块

1.1 构造块的使用

在类体中直接使用{}括起来的代码块

public class BlockTest {
    {
        Syetem.out.println("构造块")
    }
    
    public BlockTest() {
        System.out.println("=====构造方法体")
    }
}

注意事项:每一次创建一个对象都会执行构造块,会优先于构造方法体执行。

1.2 构造块的作用

  • 当需要在执行构造方法体之前做一些准备工作时,则将准备工作的相关代码写在构造块中即可。比如:对成员变量进行的统一初始化操作。
  • 当有多个构造方法的时候,如果多个构造方法体的内容有相同的代码,则可以将相同的代码提取出来放到构造块,可以避免代码的重复。

示例代码

/**
 * 演示构造代码块的作用
 * 小孩一生下来就要哭,在代码中的表示,即创建对象的时候就应该执行cry方法
 * 但是构造方法是有多个的,如果直接把哭的方法写在构造方法,那么就会造成代码的重复
 * 这个时候,构造代码块的作用就体现出来了,把cry方法写在构造代码块中,那么无论调用哪个
 * 构造方法,都会在这之前执行cry方法。
 */
public class BabyBorn {
    // 成员变量
    private String name;
    private int id;

    // 构造代码块!!!
    {
        cry();
    }

    // 构造函数
    public BabyBorn(String n, int i){
        this.name = n;
        this.id = i;
    }
    public BabyBorn(String n){
        // 这里只是初始化了name属性
        this.name = n;
    }
    public BabyBorn(int n){
        // 这里只是初始化了name属性
        this.id = n;
    }

    // 提供对应的setter和getter方法
    public void setName(String n){
        this.name = n;
    }
    public void setId(int n){
        this.id = n;
    }
    public String getName(){
        return this.name;
    }
    public int getId(){
        return this.id;
    }

    public void cry(){
        System.out.println("哭、哭、哭。。。。");
    }
    public void sleep(){
        System.out.println("睡觉...");
    }
}

/** 
* 测试类
*/
public class BabyBornTest {

    public static void main(String[] args) {

        BabyBorn bb = new BabyBorn("zhangsan", 16);

    }
}

2. 静态代码块

public class BlockTest {
    static {
        Syetem.out.println("静态代码块")
    }
    
    public BlockTest() {
        System.out.println("=====构造方法体")
    }
}

注意事项:静态代码块会随着类的加载而准备就绪,只执行一次,会优先于构造块执行。

九、单例类的实现

恶汉式和懒汉式

示例代码如下:

public class Singleton {

	// 1.对构造方法私有化,让外界无法随意构造对象。
	private Singleton() {};
	
	// 2.对构造方法私有化了之后,就必须在本类提供一个成员变量来接收Singleton对象的一个实例,
	// 为了防止外部获取 sn 时,对sn 就行随意修改,所以这里的成员变量也需要私有化
	private static Singleton sn = new Singleton();  // 饿汉式
	//private static Singleton sn = null;  // 懒汉式
    
	// 3.需要提供公有的get方法来获取sn,为了外部能够通过类名直接访问,所以该get方法需要用static修饰。
	public static Singleton getInstance() {
		return sn;  
        // 懒汉式
        //if(null == sn){
        //    sn = new Singleton();
        //}
        //return sn;
	}
}

public class SingletonTest {

	public static void main(String[] args) {
		
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		
		System.out.println(s1 == s2);  // true
	}
}

开发中推荐使用饿汉式

十、继承

1. 继承的特点

  • 子类不能继承父类的构造方法和私有方法。但私有成员变量可以被继承,只是不能直接访问。为什么不能继承构造方法呢?因为构造方法需要同类名相同,如果子类能够从父类继承构造方法,那么继承之后构造方法就同子类的类名不一样了,这就前后矛盾了。所以不能继承构造方法。

  • 无论用什么方式构造子类的对象时,都会自动调用父类的无参构造方法来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代码super()的效果。

2. 方法的重写

当从父类中继承下来的方法不满足子类的需求时,就需要在子类中重写一个和父类一样的方法来覆盖从父类中继承下来的版本。

3. 方法重写的原则(重点)

  • 要求方法名相同,参数列表相同以及返回值类型相同,从java5开始允许返回子类型。
  • 要求访问的权限不能变小,可以相同或者变大。
  • 要求方法不能抛出更大的异常

十一、再谈构造块与静态代码块

有继承关系时,各个代码块的执行顺序。

示例代码

public class SuperTest {

	{
		System.out.println("SuperTest类中的构造块");
	}
	
	static {
		System.out.println("SuperTest类中的静态代码块");
	}
	
	public SuperTest() {
		System.out.println("SuperTest类中的构造方法体");
	}
}

public class SubSuperTest extends SuperTest{

	{
		System.out.println("SubSuperTest类中的构造块");
	}
	
	static {
		System.out.println("SubSuperTest类中的静态代码块");
	}
	
	public SubSuperTest() {
		System.out.println("SubSuperTest类中的构造方法体");
	}
	
	public static void main(String[] args) {
		SuperTest st = new SubSuperTest();
	}
}
//执行结果如下:
//SuperTest类中的静态代码块
//SubSuperTest类中的静态代码块
//SuperTest类中的构造块
//SuperTest类中的构造方法体
//SubSuperTest类中的构造块
//SubSuperTest类中的构造方法体

// 可以这么理解,在SubSuperTest类中会默认有下面这两行代码
public SubSuperTest(){
    super();
}
// 所以在执行子类的构造方法之前,会先去执行父类的构造方法。

总结

  • 先执行父类的静态代码块,再执行子类的静态代码块。
  • 先执行父类的构造块,再执行父类的构造方法体。
  • 先执行子类的构造块,再执行子类的构造方法体。

通过代码说明类中成员变量、构造块、构造方法体的执行顺序

示例代码

public class SuperTest {

    private final int a;
    //private static final int a = 3; 如果是static final修饰·,则必须在定义的时候初始化。
    
	{
		System.out.println("SuperTest类中的构造块");
        a = 2;
	}
	
	static {
		System.out.println("SuperTest类中的静态代码块");
	}
	
	public SuperTest() {
		System.out.println("SuperTest类中的构造方法体");
        // this.a= 2; 这里赋值也是可以的
	}
    
    	public static void main(String[] args) {
		SuperTest st = new SuperTest();
         System.out.println(st.a);
	}
}

//执行结果如下:
//SuperTest类中的静态代码块
//SuperTest类中的构造块
//SuperTest类中的构造方法体
//2

对以上代码的说明

  • final修饰的成员变量必须给初始值,可以在定义的时候给初始值,也可以在构造方法体中或者构造代码块中给初始值。因为系统不会给final修饰的成员变量赋默认值,如果再不手动赋值,那肯定就不行了。
  • 说明成员变量的初始化会优先于构造代码块的初始化,如果成员变量都没有初始化好,又怎么能给其赋值呢。

十二、常用的访问控制符

修饰符 本类 同一个包中的类 子类 其他类
public 可以访问 可以访问 可以访问 可以访问
protected 可以访问 可以访问 可以访问 不能访问
默认 可以访问 可以访问 不能访问 不能访问
private 可以访问 不能访问 不能访问 不能访问

十三、final关键字(重点)

1. 使用方式

  • final关键字修饰类体现在该类不能被继承
  • final关键字修饰成员方法体现在该方法不能被重写,但是可以被继承
  • final关键字修饰成员变量体现在该成员变量必须初始化,并且不能被修改

2. 注意事项

  • final修饰的变量表示赋值之后不能再进行更改,系统赋默认值也算赋值,因此系统也不会赋默认值。

  • final修饰的成员变量必须给初始值,可以在定义的时候给初始值,也可以在构造方法体中或者构造代码块中给初始值。

  • 如果用static final同时修饰变量的话,则变量必须在定义的时候进行初始化。因为static变量属于类,在调用构造函数之前就已经被系统赋予默认值了。

    private static final int a = 3; //如果是static final修饰·,则必须在定义的时候初始化。
    
  • final修饰的引用类型变量,引用指向的对象不能改变,但是引用指向的对象里的内容可以改变。

    final A a = new A();
    a.value = 2;
    //这样是可以的,但是如果再从新赋值a就报错了:a = new A();
    
  • final修饰的成员变量一般都和static使用,因为 final修改的成员变量是无法被修改的,没有必要在每个对象里保存一份相同的成员变量。

  • final 不能和 abstract修饰类,抽象是用来实现的,final 不能被继承。

十四、类的加载和初始化过程(重点)

  • 先加载类的class文件到方法区
  • 然后将静态属性和静态方法加载到数据共享区
  • 在堆内存中开辟空间(new对象的时候)
  • 将变量加载到堆内存,并赋默认值。final修饰的成员变量系统不会赋默认值,需要手动显示赋值或者隐式赋值。
  • 构造块的初始化
  • 构造方法初始化

十五、类的初始化时机(扩展)

只有以下6种方式被看作程序对类或接口的主动使用

  • 创建类的实例。包括new关键字来创建,或者通过反射、克隆及反序列化方式来创建实例。
  • 调用类的静态方法,比如:getinstance()。
  • 访问某个类或接口的静态变量(不是静态常量),或者对该静态变量赋值。
  • 使用反射机制来创建某个类或接口对应的java.lang.Class对象。例如Class.forName("Test")操作,如果系统还未初始化Test类,这波操作会导致该Test类被初始化,并返回Test类对应的java.lang.Class对象。
  • 初始化一个类的子类,该子类所有的父类都会被初始化。
  • JVM启动时被标明为启动类的类(直接使用java.exe命令运行某个主类)。例如对于“java Test”命令,Test类就是启动类(主类),JVM会先初始化这个主类。

除了以上6种情况,其他方式都被看作成是被动使用,不会导致类的初始化。下面通过接个例子来验证:

public class A {
    
    public static final int a = 2*3;//a为编译时常量
    public static final String str = "haha";//str为编译时常量
    public static final int b = (int)(Math.random()*5);//b不是编译时常量
    
    static {
        System.out.println("init A");
    }   
}


public class ATest {
    public static void main(String[] args) {
        System.out.println(A.a); 
    }
}

// 输出结果为6
// 并没有输出init A,说明A类并没有被初始化,当JVM加载并连接A类时,不会在方法区内为它的编译时常量a分配内存。

public class ATest {
    public static void main(String[] args) {
    	System.out.println(A.b);
    }
}

// 输出结果为init A,    4
// 说明这时A类被初始化了

十六、多态

1. 多态的特点

  • 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法

    Person p = new Student();
    // p可以调用Person类中独有的方法
    
  • 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法

    Person p = new Student();
    // p不可以直接调用Student类中独有的方法,可以强转类型调用子类中特有的方法。
    
  • 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本

    Person p = new Student();
    p.show();
    // 编译阶段看Person类中是否有show方法,如果没有,则编译器报错。
    // 执行阶段,执行Student类中的show方法,如果Student类中没有show方法,就取Person类中找。
    // 总结一句话:编译看左边,运行看右边。
    
  • 对于父子类都有的非静态成员变量时,编译阶段调用父类中的变量,运行阶段也是调用父类中的变量。

    class Fu {
    	int num = 4;
    }
    class Zi extends Fu {
    	int num = 5;
    }
    class Demo {
    
        public static void main(String[] args) 	{
    		Fu f = new Zi();
    		System.out.println(f.num);
    		Zi z = new Zi();
    		System.out.println(z.num);
    	}
    }
    
    // 总结一句话:编译和运行看左边
    
  • 对于父子类都有的静态方法时,编译阶段调用父类中的方法,运行阶段也是调用父类中的方法。

    Person p = new Student();
    p.test();	// test为静态方法
    // 编译的时候看Person类中是否有test方法,运行的时候也是走的父类中的test方法。
    

总结

  • 对于非静态方法,编译看左边,运行看右边。
  • 对于非静态变量,编译看左边,运行看左边。
  • 对于静态方法,编译看左边,运行看左边。

2. 多态的使用场合

  • 通过方法的参数传递形成多态

    public static void draw (Shape s) {
        s.Show();
    }
    draw(new Rect(1,2,3,4));
    
  • 在方法体中直接使用多态的语法格式

    Account acc = new FixedAccount();
    
  • 通过方法的返回值类型形成多态

    Calenfar getInstance() {
        return new GregorianCalendar(zone, alocale)
    }
    

十七、抽象类(需要复习)

示例代码

public abstract class Employee {
	private String id;		// 员工编号
	private String name; 	// 员工姓名

	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	//工作方法(抽象方法)
	public abstract void work(); 
}

面试考点

  • 即使一个类中不含抽象方法,它也可以声明为抽象类;
  • 如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
  • 可以有构造方法的,由子类的super语句来调用,用于给抽象类中的成员初始化。
  • abstract不能和private共存,抽象就是用来继承的,而私有就是只能自己用,两者相矛盾。
  • abstract不能和final共存
  • abstract不能static共存,因为一旦加static我们就可以通过类名直接访问抽象方法,由于抽象方法没有方法体,没有任何意义,也不允许这样做。
  • 抽象类可以包含main方法,它只是一个静态方法,你可以使用main方法执行抽象类,但不可以创建任何实例。

十八、接口

1. 成员方法

全都是抽象方法,并且有固定的格式。

public interface MyAInterface{
    
    public abstract void show();
}

2. 成员变量

接口中无法定义普通的成员变量,必须定义为常量。

public interface MyAInterface{
    
    public static final int NAME = "zhangsan"; 
}

为什么必须是static final修饰的常量呢?

static:必须。因为接口是可以多继承的。如果一个类实现了两个接口,且两个接口都具有相同名字的变量,此时这个变量可以被实现类使用,那么如果不是static的,这个变量来自哪一个接口就会产生歧义,所以实现类使用接口中的变量必须通过接口名指定,也就只能定为static的。

看下面的例子:

public interface iface1 {
    int a = 10;
}

public interface iface2 {
    int a = 9;
}

public class impl implements iface1, iface2 {
    public static void main(String args[]){
        System.out.println(a);
    }
}

此时,会报编译错误,因为a有歧义。

final:我认为因为必须是static的,那么所有子类共享,而接口是一种抽象, 所以一个子类修改了值会影响到其他所有子类,因此就不应该允许子类修改这个值,所以定义为final。

十九、类和接口之间的关系

名称 关键字 关系
类和类之间的关系 使用extends关键字表达继承关系 支持单继承
类和接口之间的关系 使用 implements关键字表达实现关系 支持多实现
接口和接口之间的关系 使用extends关键字表达继承关系 支持多继承

二十、内部类

1. 内部类的分类

1.1 普通内部类

  • 定义格式:直接将一个类的定义放在另一个类的类体中
// 定义格式
修饰符 class 外部类 { 
	修饰符 class 内部类 {
		//其他代码
    }
}

// 访问方式:外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
  • 访问内部类和外部类中同名的成员变量的方式
public class OuterClass {
    private int cnt = 1;

    // 定义一个普通内部类,隶属于外部类的成员
    public class InnerClass {
        private int ic = 2;
        private int cnt = 3;

        // 内部类的构造方法
        public InnerClass() {
            System.out.println("普通内部类的构造方法!");
        }

        // 内部类的普通方法
        public void show(){
            System.out.println("内部类的show方法!");
            System.out.println("ic = " + ic);
        }

        public void print(int cnt) {
            System.out.println("传入的实参是:" + cnt);
            System.out.println("内部类中的成员变量cnt的值是:" + this.cnt);
            System.out.println("外部类中的成员变量cnt的值是:" + OuterClass.this.cnt);
        }
    }
}

public class OuterClassTest {

    public static void main(String[] args) {

        // 1.声明外部类中内部类的引用指向内部类的对象
        // 1.1.首先创建外部类对象
        OuterClass outerClass = new OuterClass();
        // 1.2.然后再用外部类对象去调用内部类对象
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
        innerClass.show();
        System.out.println("--------------------------------");
        innerClass.print(4);
    }
}
//打印结果
//传入的实参是:4
//内部类中的成员变量cnt的值是:3
//外部类中的成员变量cnt的值是:1

说明:普通内部类和普通类一样可以使用private、protected、final、abstract修饰

1.2 静态内部类

使用static关键字修饰的内部类,隶属于类层级。

// 定义格式
修饰符 class 外部类 { 
	修饰符 static class 内部类 {
		//其他代码
    }
}
// 访问方式:外部类名.内部类名 变量名 = new 外部类名.内部类名();

总结

  • 静态内部类不能直接访问外部类的非静态成员;

    public class StaticOuterClass {
    
        private int cnt = 1;
        private static int i = 2;
    
        public static class StaticInnerClass {
            private int sic = 3;
            private int cnt = 2; 
    
            public void show() {
                System.out.println("静态内部类中的show方法");
            }
            
            public void show2(int cnt) {
                System.out.println("传入的实参是:" + cnt);
                // 同名的成员变量访问方式
                System.out.println("内部类中的成员变量cnt的值是:" + this.cnt);
                // 访问外部类的静态成员变量的方式:可以直接使用i,因为静态内部类和静态变量i是同级关系。
                System.out.println("外部类中的静态成员变量i的值是:" + i);  
                // error:静态上下文中不能访问非静态成员
                System.out.println("外部类中的成员变量cnt的值是:" + cnt); 
            }
        }
    }
    
  • 静态内部类可以直接创建对象

  • 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。

1.3 局部内部类

直接将一个类的定义放在方法体的内部。

public class Party {				      // 外部类,聚会
	public void puffBall(){				  // 吹气球方法
		class Ball {		   		     // 内部类,气球,不能使用修饰符修饰
              public void puff(){
     			System.out.println("气球膨胀了");
              }
		}
		//创建内部类对象,调用puff方法
		new Ball().puff();
	}
}

总结

  • 局部内部类不能使用public、private、protected访问控制符和static关键字修饰符,跟在成员方法中的局部变量一样。

    public class AreaOuter {
    
    	private int cnt = 1;
    	
    	public void show() {
    		
    		// 局部变量,java8开始,默认理解为是final修饰的
    		int ib = 2;
    		// public int ib = 2; 报错
    		// private int ib = 2; 报错
    		
             // 局部内部类
    		//public class AreaInner { 报错
    		//private class AreaInner { 报错
    		final class AreaInner {
    			private int ia = 1;
    			
    			public AreaInner() {
    				System.out.println("局部内部类的构造方法");
    			}
    			
    			public void test() {
                      // 如果用到外部的变量ib,则ib为final修饰的
                      System.out.println("ib = " + ib);
    				 System.out.println("ia = " + ia);
    				 System.out.println("cnt = " + cnt);
    			}
    		}
    		
    		//调用方法:只能在方法体内部调用,声明局部内部类的引用指向局部内部类的对象
    		AreaInner areaInner = new AreaInner();
    		areaInner.test();
    	}
    }
    
    // 因为局部变量本身就是一个访问权限的设定。 只能在局部调用,也就是说局部变量的生命周期在{}之中除了这个方法外界是没办法访问你这个变量,所以不需要用任何修饰符修饰,比如private ,public protected,等。但是能加final。
    
    // 也不能加static,静态的关键词,因为static只能修饰成员变量和成员方法,在局部变量中用static修饰,又不能直接被类调用,而static关键字就是不直接过对象而用类就可以直接调用的,所以局部变量前不能加static关键字。
    
  • 局部内部类引用外部变量必须是final修饰的

1.4匿名内部类

没有名字的内部类

  • 格式
new 父类或接口(){
	//进行方法重写
};
  • 代码演示
//已经存在的父类:
public abstract class Person{
	public abstract void eat();
}
//方式一:使用Person类型的引用来接收匿名对象
Person  p = new Person(){
	public void eat() {
		System.out.println(“我吃了”);
}
};
p.eat();

// 方式二:不使用变量引用
new Person(){
	public void eat() {
		System.out.println(“我吃了”);
}
}.eat();		// 注意这一行.eat()之前都是在创建了接口的实现类对象

总结:匿名内部类可以理解为不用重新写一个类去实现抽象类,直接new,然后实现抽象方法即可。这样可以更加节省内存空间。

二十一、回调模式

1. 概念

回调模式是指:如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象。而该方法在运行的时候会调用到参数对象中所实现的方法。

2. 示例代码

public class AnonymousInterfaceTest {

    // 假设已有下面的方法,请问如何调用下面的方法?
    // AnonymousInterface ai = new AnonymousInterfaceImpl();
    // 接口类型的引用指向实现类型的对象,形成了多态
    public static void test(AnonymousInterface ai) {
        // 编译阶段调用父类版本,运行调用实现类重写的版本
        ai.show();
    }

    public static void main(String[] args) {

        //AnonymousInterfaceTest.test(new AnonymousInterface()); // Error:接口不能实例化
        AnonymousInterfaceTest.test(new AnonymousInterfaceImpl());
        
    }
}

public class AnonymousInterfaceImpl implements AnonymousInterface {
    @Override
    public void show() {
        System.out.println("这里是接口的实现类!");
    }
}

public interface AnonymousInterface {
    // 自定义抽象方法
    public abstract void show();
}

二十二、枚举类(熟悉)

1. 枚举的定义

  • 使用public static final修饰的常量描述较为繁琐,使用enum关键字来 定义枚举类型取代常量。

  • 枚举值就是当前类的类型,也就是指向本类的对象,默认是使用public static final 修饰的,因此采用枚举类型.的方式调用。

  • 枚举类可以自定义构造方法,但是构造方法的修饰符必须是private的,默认也是私有的。

  • 枚举类可以实现接口,不能继承类。

2. 枚举类的自定义实现代码

同单例类的实现相比较

/**
 * 自定义实现方向枚举类
 * 总共有四个方向:向上、向下、向左、向右
 * 实现方式按照单例模式的实现
 */
public class Direction {

    // 1.声明一个成员变量用于描述方向字符串
    // 为了达到必须初始化和不能更改的目的,所以用final修饰。
    private final String desc;

    // 2.声明本类类型的引用指向本类类型的对象
    // 使用static可以在外面用类名直接访问,final表示创建的对象不能被修改。
    // 严谨点说,这里本应该用private,但是这里为了方便,让外面可以直接使用,使用public。
    public static final Direction UP = new Direction("向上");
    public static final Direction DOWN = new Direction("向下");
    public static final Direction LEFT = new Direction("向左");
    public static final Direction RIGHT = new Direction("向右");

    // 3.私有化构造方法,此构造方法只能在本类使用
    private Direction(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}

3. Java官方的枚举类定义格式

同上面自定义的枚举类相对比

public enum DirectionEnum {

    // 1.声明本类类型的引用指向本类类型的对象,默认还是用public static final修饰的
    // 注意:枚举类要求所有的枚举值必须在第一行
    UP("向上"), DOWN("向下"), LEFT("向左"), RIGHT("向右");

    // 2.声明一个成员变量用于描述方向字符串
    // 为了达到必须初始化和不能更改的目的,所以用final修饰。
    private final String desc;

    // 3.私有化构造方法,此构造方法只能在本类使用
    private DirectionEnum(String desc) {
        this.desc = desc;
    }

    // get方法
    public String getDesc() {
        return desc;
    }
}

二十三、注解(陌生)

注解又叫标注,是从Java5开始增加的一种引用类型

注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载以及运行时执行指定的处理。

1. 注解的语法格式

访问修饰符 @interface 注解名称{
    注解成员;
}

通过@注解名称的方式可以修饰包、类、 成员方法、成员变量、构造方 法、参数、局部变量的声明等。

2. 注解的使用方式

  • 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
public @interface MyAnnotation {
	
	// 声明一个String类型的成员变量,变量名为value
	public String value() default "haha";		// 给默认值
	public String value2();
}
  • 如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型。

3. 元注解的概念

  • 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但 是它能够应用到其它的注解上面。

  • 元注解主要有 @Retention、@Documented、@Target、@Inherited、 @Repeatable。

3.1 元注解@Retention

@Retention 应用到一个注解上用于说明该注解的的生命周期,取值如下:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时 它将被丢弃忽视。
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加 载到 JVM 中,默认方式
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载 进入到 JVM 中,所以在程序运行时可以获取到它们。

3.2 元注解@Documented

定义为@Documented的注解必须设置Retention值为RUNTIME。

3.3 元注解@Target

  • @Target用于指定被修饰的注解能用于哪些元素的修饰,取值如下:

    ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
    ElementType.CONSTRUCTOR 可以给构造方法进行注解
    ElementType.FIELD 可以给属性进行注解
    ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
    ElementType.METHOD 可以给方法进行注解
    ElementType.PACKAGE 可以给一个包进行注解
    ElementType.PARAMETER 可以给一个方法内的参数进行注解
    ElementType.TYPE 可以给类型进行注解,比如类、接口、枚举

3.4 元注解@Inherited

@Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标 记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继 承超类的注解。

二十四、成员变量和局部变量的区别

  • 成员变量可以不手动赋初始值,但是它是有默认值的;而局部变量必须手动赋初始值,不然编译失败。
  • 成员变量是随对象进入堆内存的;而局部变量跟随自己的方法进入栈内存中。
  • 声明周期不同,堆中的内存是等待JVM清理之后才消失的;而栈中的内存随着方法的弹栈而消失。

推荐阅读