1 内容介绍
1. static关键字(掌握场景)
2. 内部类(掌握)
3. final修饰符(掌握场景)
4. 代码块(了解)
5. 枚举(掌握)
2.static修饰符
2.1 static概念: 关键字、修饰符,表示静态的
static是修饰符:可以修饰的内容:
目前java代码中存在成员
1.可以修饰普通方法
2.可以修饰字段[ 成员变量 ]
3.可以修饰内部类[暂时不了解]
4.不可以修饰外部类
5.不可以修饰局部变量;
6.不可以修饰构造方法
2.2 修饰成员变量和方法
访问方式: 类名.类中成员(变量、方法)
2.2.1 static修饰后特点
static类级别的修饰符(被修饰的成员属于类)
当用到一个类的时候(new 类名(…)),会将该类的字节码文件加载到元空间,在加载的时候,会将有static修饰的成员,优先加载到静态区。类加载的过程只会执行一次,只有一份。执行完毕后该static修饰的成员会被所有以该字节码文件为模板创建的对象所共享。
举例说明:
设计一个学生类,其中有name-姓名 字段,是否应该加static 呢?
效果:加了static修饰的字段:该字段被该类所有对象共享:当一个对象修改了该字段,其他对象使用该字段,都是修改之后的值
/**
* 学生类
*/
public class Student {
/**用static修饰name,会被所有用Student模板创建的对象共享*/
static String name;
public Student(String n) {
name = n;//将传入的n赋值给成员变量name
}
public static void testStatic() {
System.out.println("static修饰方法");
}
/**
* 内部类:今天只需要了解
*/
static class Inner{
}
}
/**
* static测试类
*/
public class StudentTest {
public static void main(String[] args) {
//调用有参构造,创建对象并且直接赋值
Student stu1 = new Student("隔壁老王");
//打印stu1的名字
System.out.println(stu1.name);//隔壁老王
//调用有参构造,创建对象并且直接赋值
Student stu2 = new Student("隔壁老孙");
//打印stu2的名字
System.out.println(stu2.name);//隔壁老孙
//重新打印stu1的名字
System.out.println(stu1.name);//隔壁老孙(居然不是隔壁老王)
}
}
加static修饰字段前后对比的堆栈费分析
加static之前
加static之后
2.3 static作用
- 可以避免对象被多次创建。例如,后面学习的:单例模式
- 需要几个对象有共享的数据。
- 一般情况下,修饰成员变量的时候,是配合 public static final 一起使用,被修饰的叫做:全局常量一般 用全大写+下划线的命名方式,例如: MAX_VALUE
- 修饰方法,只是为了方便调用。 类名.方法名(…)。例如: Arrays中全部都是工具方法(static)Arrays.toString(…);
- 最近面向对象语法,我们一般写的方法都写成非静态,除非非得使用static
2.4 static 小结
最近都不要使用static修饰变量
2.5 变量重新分类
成员变量(也叫字段) : 静态的(类变量)和非静态的(实例变量instance)
static int age; 类变量, 有static修饰的成员变量(字段);
int age; 实例变量,没有static修饰的成员变量(字段);
分类详解:
位置 是否有static 生命周期(开始) 生命周期(结束)
类变量 类中 √ 类被加载的时候 类被卸载的时候
实例变量 类中 创建对象的时候 对象失去外部引用
局部变量 方法内部(形参,代码块) 方法被调用的时候 方法调用完毕
3 内部类和匿名内部类
3.1 内部类
什么是内部类,把一个类定义在另一个类的内部,把里面的类称之为内部类,把外面的类称之为外部
类。(能认识内部类即可)
内部类可以看作和字段、方法一样,是外部类的成员,而成员可以有 static 修饰。
- 静态内部类:使用 static 修饰的内部类,那么访问内部类直接使用外部类名来访问
- 实例内部类:没有使用 static 修饰的内部类,访问内部类使用外部类的对象来访问
- 局部内部类:定义在方法中的内部类,一般不用
- 匿名内部类:特殊的局部内部类,适合于仅使用一次使用的类
对于每个内部类来说,Java 编译器会生成独立.class 文件。
- 静态和实例内部类:外部类名$内部类名字
- 局部内部类:外部类名$数字内部类名称
- 匿名内部类:外部类名$数字
3.2 匿名内部类(掌握)
在多态 USB 的案例中,当新增一种 USB 规范的设备,此时需要单独使用一个文件来定义一个新的类。
比如,新增一个 USB 规范的打印机设备。
public class Print implements IUSB{
public void swapData() {
System.out.println("打印....");
}
}
把打印机安装在主板上。
public class USBDemo {
public static void main(String[] args) {
// 创建主板对象
MotherBoard board = new MotherBoard();
// 创建打印机对象
Print p = new Print();
//把打印机安装在主板上
board.plugin(p);
}
}
如果这一个 Print 类只需要使用一次的话,就完全没有必要单独定义一个 Java 文件,直接使用匿名内
部类来完成。
匿名内部类,可以使用父类构造器和接口名来完成。
针对类,定义匿名内部类来继承父类(使用较少):
new 父类构造器([实参列表]){
//匿名内部类的类体部分
}
针对接口,定义匿名内部类来实现接口(使用较多):
new 接口名称(){
//匿名内部类的类体部分
}
注意:这里不是根据接口创建对象,而是一种语法而已。
board.plugin(new IUSB() {
public void swapData() {
System.out.println("打印...打印...");
}
});
其实匿名内部类,底层依然还是创建了一份字节码文件 USBDemo$1,其反编译代码为:
4 final修饰符
4.1 final概念
关键字,修饰符,表示最终的。就是一旦修饰一个成员,该成员就不能再被修改了。
4.2 final作用:
可以修饰:
外部类:太监类,不能被继承
实例变量:必须在声明的时候赋值或者在构造方法中赋值
类变量: 必须在声明的时候赋值
实例方法:不能被子类重写
类方法:不能被子类重写
内部类:
局部变量:
不能修饰:
构造方法
代码演示:
public class FinalTest {
/**实例变量 在堆中*/
private final int A;
private final int B = 1;//声明的时候直接赋值
/**类变量 在静态区*/
static final int C = 1;//声明的时候直接赋值
/**
* 构造方法,final不能修饰
*/
public FinalTest() {
A = 1;//在构造方法中赋值
}
/**
* 实例方法,不能被重写
*/
public final void test() {
}
/**
* 类方法,不能被重写
*/
public static final void testStatic() {
}
/**
* 内部类,不能被继承
*/
final class Inner{
}
public static void main(String[] args) {
final int a;//在栈帧中
a = 2;//一旦被final修饰就不能再修改
System.out.println(a);
// a = 3;//一旦被final修饰就不能再修改
}
}
5 代码块
3.1 什么是代码块
简单的认为就是一对 { };
看到{} 应该想到作用域问题;
3.2 代码块分类
静态代码块
语法:直接声明在类中,前面有static修饰
static {
//一般用来初始化数据,在类加载的时候就初始化完成
}
① {}直接写在类中的,有static修饰;
② 在类加载的时候执行和创建对象没有关系,且只会执行一次
③ 优先于主方法的执行的(加载完之后,JVM才找主方法执行)
④ 可以在类加载的时候做一些初始化的操作(例如JDBC的驱动的加载)
局部代码块(普通代码块)
基本没用,在方法中的一对{},一般不会单独使用,一般是配合if或者循环等语句使用
构造代码块
语法:直接声明在类中,前面没有static修饰
{
//一般没用
该代码块中代码,会生成在构造方法内部,super()下面,其他代码上面
}
② 直接写在类中的代码块(没有static修饰);
③ 编译完毕之后会在每一个构造方法里面复制一份;
④ 创建一个对象(调用一次构造方法)就会执行一次
⑤ 可以给对象的实例字段初始化值(基本不用)
3.3 掌握继承关系的类中各个成员(包括类变量)的执行顺序
有继承关系的执行流程:
从最高父类的静态代码块开始加载,然后逐级向下加载,直到加载到当前类的静态代码块结束。
再从最高父类的构造代码块开始执行,然后是构造方法,然后逐级向下执行,直到执行到当前类的构造代 码块、构造方法结束。
public class CodeBlock {
/**
* 静态代码块:只会执行一次
*/
static {
//一般用来初始化数据,在类加载的时候就初始化完成
System.out.println("静态代码块");
}
/**
* 构造代码块:(基本没用)每次创建对象都会执行
*/
{
//该代码块中代码,会在生成在构造方法内部,super()下面,其他代码上面
System.out.println("构造代码块");
}
/**
* 构造方法
*/
public CodeBlock() {
super();
//System.out.println("构造代码块");
System.out.println("构造方法!");
}
public static void main(String[] args) {
new CodeBlock();
new CodeBlock();
{//普通代码块
int a = 1;
}
// System.out.println(a);//出了作用域范围了
}
}
6 枚举
6.1枚举的引入
需求: 设计一个类专门用了表示性别(性别的取值比较固定:男、女、未知)
/**
* 设计一个类,表示性别
* 3种:男MAN、女WOMEN、未知UNKNOWN
*
* 侵入性问题:一个类中包含其他的数据类型都是有侵入性。尽可能避免。
*/
public class Gender {//表示性别类
// public static final String MAN = "男";//MAN是String的一个对象
//在Gender类中使用String类型,有侵入性问题。那么,能不能用Gender自己类型的对象表示一个男? 可以
public static final Gender MAN = new Gender();//MAN是Gender的一个对象
// public static final String WOMEN = "女";//WOMEN是String的一个对象
public static final Gender WOMEN = new Gender();//WOMEN是Gender的一个对象
// public static final String UNKNOWN = "未知";//UNKNOWN是String的一个对象
public static final Gender UNKNOWN = new Gender();//UNKNOWN是Gender的一个对象
@Override
public String toString() {
/*
* 希望打印格式如下:
* 是MAN打印男
* 是WOMEN打印女
* 是UNKNOWN打印未知
*/
if (this == MAN) {
return "男";
}else if (this == WOMEN) {
return "女";
}else{
return "未知";
}
}
}
测试类:
/**
* 测试性别,枚举引入
*/
public class GenderTest {
public static void main(String[] args) {
//调用全局常量方式,类名.常量名
System.out.println(Gender.MAN);//男
System.out.println(Gender.WOMEN);//女
System.out.println(Gender.UNKNOWN);//未知
}
}
是不是感觉上面的代码方式很麻烦 ?那么,有没有更简单的方法呢? 用枚举,那么是什么是枚举?
6.2 枚举概述
6.2.1 什么是枚举
枚举是JDK1.5引入的一种和类非常类似的新结构;
枚举的出现解决了哪些问题?
枚举类解决了一些取值比较固定的场景,简化了类中的常量字段。
6.2.2 枚举的作用和使用场景
作用:简化类中的常量声明这种代码,是代码变得更加优雅
使用场景:vip、生肖、段位、QQ状态、血型、性别、星座、月份、礼拜…
6.2.3 枚举的基本语法
① 声明语法:
public enum 枚举类名字{
字段
实例变量
实例方法
类方法
构造方法 - 枚举中的构造方法默认都是private修饰,不能够是public,protected修饰
构造方法作用:只能在当前枚举类中使用,就是给当前枚举类对象初始化实例变量的
}
语法:
public enum Gender {//表示性别类
// MAN,//相当于一个常量public static final Gender = new Gender();
MAN(),//MAN这里也可以这样写,这里就是在调用当前枚举类的无参构造
WOMEN("女"),//这里就是在调用当前枚举类的String类型有参构造
UNKNOWN,
其他;//也可以写中文,但是不建议,建议写法:WOMEN("");
private String name;//实例变量,属于每一个枚举对象,如:MAN,WOMEN..
/**
* 无参构造
*/
Gender() {//默认有一个隐式的private修饰
}
/**
* 有参构造
*/
Gender(String name) {//默认有一个隐式的private修饰
this.name = name;
}
@Override//实例方法
public String toString() {
if (this == MAN) {
return "男";
}else if (this == WOMEN) {
return "女";
}else{
return "未知";
}
}
/**
* 类方法
*/
public static void test() {
}
}
② 枚举类编译完毕也同样生成字节码文件
③ 每一个自定义的枚举类型都(隐式的)继承于 Enum抽象类,因此我们的枚举对象可以调用到Enum中的方法的(看API )。但是不能显示的写出继承关系。
//上面性别代码用枚举简化
public enum Gender {//表示性别类
MAN,//MAN字段等同于原来的public static final Gender MAN = new Gender();这一句
WOMEN,
UNKNOWN;
@Override
public String toString() {//该方法是从Enum中间接继承于Object
if (this == MAN) {
return "男";
}else if (this == WOMEN) {
return "女";
}else{
return "未知";
}
}
}
测试代码:
/**
* 测试枚举
*/
public class GenderTest {
public static void main(String[] args) {
//调用全局常量方式,类名.常量名
System.out.println(Gender.MAN);//男
System.out.println(Gender.WOMEN);//女
System.out.println(Gender.UNKNOWN);//未知
}
}
6.4 枚举注意事项
- 字段之间用逗号,最后一个字段用分号结束
- 可以写中文字段,但是不建议
- 枚举类都会隐式继承了Enum类(所有枚举类的基类),不能显示写出来
- 构造方法必须是private的
6.5 枚举常用的两种写法
方式1代码:
public enum Gender {
MAN,
WOMEN,
UNKNOWN;
}
方式2代码:
public enum Gender {
MAN("男"),
WOMEN("女");
private String name;
private Gender(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
配合switch语句练习:
/**
* 测试枚举
*/
public class GenderTest {
public static void main(String[] args) {
// 调用全局常量方式,类名.常量名
Gender gender = Gender.MAN;
switch (gender) {
case MAN:
System.out.println("男左");
break;
case WOMEN:
System.out.println("女优");
break;
default:
System.out.println("未知");
break;
}
}
}
7 课程总结
7.1.重点
枚举,抽象类,接口
7.2.难点
接口和抽象类的使用场景、匿名内部类