Java关键字
类加载
类被加载的三种情况:
- 创建实例对象时
- 创建子类对象时,父类也会被加载(父类会先被加载)
- 使用类的静态成员时
Static:
表示静态,可作用在:
- 变量
- 方法
- 类
- 匿名方法块
变量
静态变量为类中所有方法共享,独立存在类中,可被直接调用(不用创建对象,使用 类名.变量名 直接调用 )。是在类加载的时候生成的。
static修饰的变量在内存中只会有一份拷贝。
JDK 8/7 以前,静态变量是类加载的时候放在方法区中。在之后的新版本中,是在堆内存中。
注意,静态变量仍受访问权限修饰符的控制,如果使用 private 修饰,将不能通过类名和对象名进行引用。
静态变量随着类加载就生成,随着类销毁而消失。
public class Potato {
static int price = 6;
String content = "";
public Potato(int price, String content) {
this.price = price;
this.content = content;
}
public static void main(String[] args) {
System.out.println(Potato.price);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
Potato obj1 = new Potato(10, "酸辣土豆丝");
System.out.println(Potato.price);
System.out.println(obj1.price);
Potato obj2 = new Potato(20, "甜辣土豆丝");
System.out.println(Potato.price);
System.out.println(obj2.price);
}
}
输出结果为:
6
>>>>>>>>>>>>>>>>>>>>>>>
10
10
20
20
可以看到,当新建一个对象更改类中的static变量price后,将会直接修改类的price的值。
事实上,在上述例子中的Potato类new的所有的对象会指向同一个static int price。 它是所有对象的共享成员。
方法
静态方法无需通过对象来引用,而通过类名可以直接引用。
工具类的方法一般都为静态方法。方便我们在使用方法时,不用创建实例就可直接使用。
- 静态方法内只能调用静态内容
- 静态方法中也不能使用 this 和 super 指针
- 静态方法不能被重写,可以被重载
- 非静态的方法可以访问静态内容。不过 非静态对象 访问 静态方法 会报警告
- static 不可以修饰构造函数
例子:
package courseTest;
public class StaticTest {
int a = 111;
static int b = 22222;
public static void hello() {
System.out.println("0000000");
System.out.println(b);
// System.out.println(a); // error, 非静态变量在此不可调用
// hi(); // error, 非静态方法在此不可调用
}
public void hi() { // 非静态方法可以调用静态方法和静态变量
System.out.println("3333333333");
hello();
System.out.println(a);
System.out.println(b);
}
public static void main(String[] args) {
StaticTest.hello();
// StaticTest.hi(); // error, 非静态方法不可用类名访问
StaticTest foo = new StaticTest();
foo.hello();
foo.hi();
}
}
类和块
static还可用来修饰类(内部类),但是使用场景比较少。不做介绍。
static块:
- 只在类第一次被加载(而非实际执行,比如main函数在一个类中,而这个类包含static块,进入main函数时需要加载类,即会执行static块)时调用,即在程序运行期间只会执行一次
- 执行顺序: static成员 > 匿名(普通)成员 > 构造函数,如果有多个 static/匿名(普通) 内容,按照定义顺序执行
class StaticBlock {
static { // static块
System.out.println("222222");
}
{ // 匿名块
System.out.println("111111");
}
public StaticBlock() { // 构造函数
System.out.println("333333");
}
{ // 匿名块
System.out.println("444444");
}
}
public class StaticBlockTest {
public static void main(String[] args) {
System.out.println("000000");
StaticBlock obj1 = new StaticBlock();
StaticBlock obj2 = new StaticBlock();
}
}
运行结果:
000000
222222
111111
444444
333333
111111
444444
333333
可以看到,第一次new一个StaticBlock对象时,会先执行static块,再执行匿名块,最后执行构造函数。而第二次new时,是先执行匿名块,最后执行构造函数,不再执行static块。之后无论再new多少次,结果都是一样的。
块代码不建议使用。
代码块
就是没有修饰符,也没有返回值和参数以及方法名的方法体。被一堆大括号包起来。
普通代码块会新建一个类的对象的时候调用。
- 相当于另一种形式的构造器,可以做初始化的操作
- 比如:当多个构造器有重复语句时,可以抽取到代码块中,提高代码重用性
- 执行顺序: static成员 > 匿名(普通)成员 > 构造函数,如果有多个 static/匿名(普通) 内容,按照定义顺序执行
构造器的最前面其实隐含了 super() 和 普通代码块
例子:
public class blockTest01 {
public static void main(String[] args) {
new BlockB();
}
}
class BlockA {
static {
System.out.println("A的 static 块");
}
{
System.out.println("A的 普通块");
}
public BlockA() {
// 隐含 super(),但是 super 是 Object,无内容
// 隐含 普通块
System.out.println("A的构造器");
}
}
class BlockB extends BlockA{
static {
System.out.println("B的 static 块");
}
{
System.out.println("B的 普通块");
}
public BlockB() {
// 隐含 super()
// 隐含 普通块
System.out.println("B的构造器");
}
}
执行结果:
A的 static 块
B的 static 块
A的 普通块
A的构造器
B的 普通块
B的构造器
在上述例子中可以看到,新建一个实例对象的过程是:
- 加载对象类的父类
- 加载对象类
- 调用构造函数(构造函数中先调用了super(),再调用普通块,最后才是构造函数本身的内容)
新建一个子类对象的实现过程
创建一个子类对象时,其与父类的静态代码块、静态属性初始化,普通代码块、普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法
main 方法
main 方法是我们程序执行时的入口,通过调用 main 方法来执行我们需要的功能。
Java 的 main 方法的方法头部为 public static void main(String[] args)
,原因:
- JVM 编译执行时,并不会和 main 方法在一个包里或者有继承关系,因而需要该方法的访问修饰符是 public
- 同样,JVM 执行时,并不会创建一个 main 方法所在类的实例对象再去调用 main,而是直接通过类名调用 main 方法,就像调用工具类的方法一样,所以需要加 static 才可以通过类名调用
- void 表示main方法没有返回值,直接调用就可以
- 最后 main 方法的形参是一个字符串数组,它会接受在执行时给定的命令行输入内容
如:
// 编译 java 文件
javac Hello.java
// 一般执行情况
java Hello
// 加参数的情况
java Hello Jerry Is A DOG
/**
* 在上面的情况中,main方法执行时形参args 就会有四个参数了
**/
使用 main 方法时还应该注意:
- main 方法中可以调用所在类的静态属性和静态方法
- 不可以调用非静态成员,只能通过创建当前类的实例对象来访问非静态成员
单例模式
又名单态模式。
限定某一个类在整个程序运行过程中,在内存空间只能保留一个实例对象。
保证一个类有且只有一个对象。
- 采用static共享对象实例
- 采用private构造函数
饿汉式
饿汉式是指无论当前是否使用单例对象,都先创建单例对象(类加载即创建)。可能会造成资源浪费。
实现方式:
- 将构造器私有化
- 在类的内部直接 new 一个对象,需要声明访问权限 private,以及 static 类型(因为获取对象的方法是 static 类型)
- 提供一个公共的 static 类型方法(这样才可以不创建对象就通过类名访问该方法),返回单例对象
public class Singleton {
private static Singleton obj = new Singleton();
private String content;
private Singleton() {
this.content = "aba";
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static Singleton getInstance() {
// 静态方法使用静态变量
// 可以再方法内使用临时变量,不可使用非静态变量
return obj;
}
public static void main(String[] args) {
Singleton obj1 = Singleton.getInstance();
System.out.println(obj1.getContent());
Singleton obj2 = Singleton.getInstance();
System.out.println(obj2.getContent());
obj2.setContent("def");
System.out.println(obj1.getContent());
System.out.println(obj2.getContent());
System.out.println(obj1 == obj2);
}
}
运行结果:
aba
aba
def
def
true
在刚刚的例子中,可以看到,外界想使用Singleton类的对象,只能通过getInstance()方法拿到对象obj的指针,而且拿到的对象实例都为obj。
懒汉式
单例对象被调用时才真正创建对象。
实现过程:
- 构造器私有化
- 定义一个 static 类型对象,但是不创建(new)
- 提供一个 public 的 static 类型方法,返回该对象 (当单例对象未创建时,先创建)
代码:
public class Singleton {
// 定义但不创建
private static Singleton obj;
private String content;
private Singleton() {
this.content = "aba";
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static Singleton getInstance() {
// 当对象未创建时才创建,其他情况直接返回
if (obj == null)
obj = new Singleton();
return obj;
}
public static void main(String[] args) {
Singleton obj1 = Singleton.getInstance();
System.out.println(obj1.getContent());
Singleton obj2 = Singleton.getInstance();
System.out.println(obj2.getContent());
obj2.setContent("def");
System.out.println(obj1.getContent());
System.out.println(obj2.getContent());
System.out.println(obj1 == obj2);
}
}
final:
final可以用来修饰:
- 类
- 方法
- 变量
类
final的类不可被继承。
如:
final public class FinalFather {
}
class Son1 extends FinalFather {
// 这里会报错。因为Son1继承了final类型的类
}
包装类 Integer/Double/Float/Boolean 等都是 final 类,String 也是
方法
父类中若有final方法,子类不能改写该方法。
若类已经是 final 修饰的,那么方法一般不会再修饰成 final。
final 不能修饰构造函数
如:
public class FinalMethodFather {
public final void fi() {
}
}
public class FinalMethodSon extends FinalMethodFather {
public void fi() {
// 这里会报错,因为子类尝试改写父类的final类型的方法
}
}
变量
final的变量,不能再次赋值。
- 如果是基本类型,则值不能修改
- 如果是对象实例,那么不能修改其指针(可以修改对象内部值)
final 修饰的属性,在定义的时候必须赋初值,可以在如下的位置赋初值:
- 定义时直接赋值
- 在构造器中
- 在代码块中
例子
public class FinalPrimitive {
public static void main(String[] args) {
final int a = 5;
a = 10; // 这里会报错,因为尝试修改final类型的值
}
}
class FinalObject {
int a = 10;
}
public class FinalObjectTest {
public static void main(String[] args) {
final FinalObject obj1= new FinalObject();
System.out.println(obj1.a);
obj1.a = 20;
System.out.println(obj1.a);
obj1 = new FinalObject(); // 这里会报错,因为尝试修改final类型对象的指针
}
}
final 与 static 搭配使用
编译器做了底层优化,比如
public FinaStatic() {
public final static int num = 6666;
staitc {
System.out.println("静态块执行了");
}
}
在上面的类中,若 使用类名调用属性 num,将不会完整的加载类,也就是说 static 块不会执行,节省内存。
内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的称为内部类,嵌套它的称为外部类。对于和外部类在同一个文件中,但是并未嵌套的类,称为内部类的外部其他类,如
class Outer { // 外部类
class Inner { // 内部类
}
}
class Other { // 外部其他类
}
类的五大成员:属性、方法、构造器、代码块、内部类
内部类的最大特点是可以直接访问私有属性,可以体现类与类之间的包含关系
内部类的分类
定义在外部类局部位置,如方法体内:
- 局部内部类(有类名)
- 匿名内部类(无类名)
定义在外部类的成员位置:
- 成员内部类(没用 static 修饰)
- 静态内部类(使用 static 修饰)
局部内部类
局部内部类是定义在内部类的局部位置的有类名的类,比如在方法中
注意:
- 可以直接访问外部类的的所有成员,包括私有的
- 不能添加访问修饰符,因为它的地位就类似一个局部变量,局部变量不可以使用访问修饰符,但是可以用 final,也就是说局部内部类可以使用 final 修饰(加 final 也就是说该内部类不能被继承)
- 作用域仅仅在定义它的方法体或者代码块中
- 局部内部类可以直接访问外部类的成员,就像一个类的方法内本来就可以使用它自身的其它成员
- 外部其他类不能访问局部内部类,因为局部内部类就类似一个局部变量
- 如果外部类成员和局部内部类成员重名,遵循就近原则,如果想访问外部类成员,则可以使用:外部类名.this.成员 访问,其中 外部类名.this 本质是 外部类的一个对象(即外部类的一个实例对象,该对象调用了包含局部内部类的方法/代码块)
匿名内部类
匿名内部类是定义在内部类的局部位置的没有类名的类,比如在方法中
特点:
- 本质是类
- 内部类
- 该类没有名字(表面没有,系统底层是有的)
- 同时还是一个对象
基本语法:
new 类或接口(参数列表) {
类体
};
场景:
使用某个接口,对应的类只想使用一次,后面不再使用(如使用容器自带的 sort 函数时,重写的 Compartor 比较器,就是一个匿名内部类)
基于接口
// 假设有接口
interface IA {
public void eat();
}
// 使用时可以创建一个匿名内部类
public static void main(String[] args) {
// 基于接口的匿名内部类
IA animal = new IA() {
@Override
public void eat() {
System.out.println("animal is eating");
}
};
// animal 的编译类型是接口 IA
// animal 的运行类型是匿名内部类
// 在底层该匿名内部类其实有名字
animal.eat();
System.out.println("运行类型是:" + animal.getClass());
// 输出结果:
// 运行类型是:class main.java.innerClass.Outer04$1
}
在上述例子中,JDK底层在创建匿名内部类 Outer04$1 时,立即创建了 Outer04$1 实例,并且把地址返回给对象 animal
注意,匿名内部类使用一次就不能再使用,但是生成的对象 animal 是可以多次使用的
基于类
就是在创建对象的时候,在语句末尾加上一个大括号,那么就相当于创建了一个基于某个类的匿名内部类。
如:
// 有一个类
class Tiger{
public void eat() {
System.out.println("tiger is eating");
}
}
// main 方法
public static void main(String[] args) {
// 基于类的匿名内部类,注意加了大括号
Tiger tiger = new Tiger(){
// 在该匿名内部类底层,它其实继承了原来的类 Tiger
@Override
public void eat() {
System.out.println("重写了eat方法");
}
};
System.out.println("运行类型:" + tiger.getClass());
// 运行类型:class main.java.innerClass.AnonymousTest$1
// 变成了一个新的类
tiger.eat(); // tiger 编译类型是 Tiger,运行类型是 匿名内部类AnonymousTest$1
// 下面的方法效果也是一样的
// new Tiger(){
// @Override
// public void eat() {
// System.out.println("重写了eat方法");
// }
// }.eat();
}
注意:
- 在上述的例子中,如果 Tiger 是一个抽象类,就必须要实现抽象类的抽象方法,也就是说,如果基于的对象是抽象类,那么匿名内部类就必须实现所有的抽象方法
- 基于类的匿名内部类在底层其实继承了它所基于的那个类,比如上述例子,匿名内部类继承了 Tiger
使用细节
- 匿名内部类既是一个类的定义,同时它也是一个对象
- 匿名内部类可以直接使用外部类的所有成员
- 不能添加访问修饰符
- 它的作用域仅仅在定义它的方法或者代码块中
- 外部其他类不能访问匿名内部类(因为它相当于一个局部变量)
- 如果外部类和匿名内部类重名,采取就近原则,若想使用外部类成员。可通过:外部类名.this.成员名,同样 外部类名.this 指的就是调用了创建该匿名内部类的方法/代码块的对象
实践场景
当作实参直接使用
public class AnonymousTest2 {
public static void main(String[] args) {
// 匿名内部类做实参
func(new IB() {
@Override
public void show() {
System.out.println("直接当实参,简洁高效");
}
});
// 传统方法
func(new Picture());
}
public static void func(IB ib) {
ib.show();
}
}
interface IB {
public void show();
}
class Picture implements IB {
@Override
public void show() {
System.out.println("这是一幅画");
}
}
成员内部类
定义在外部类的成员位置,并且没有 static 修饰
- 可以直接访问外部类的所有成员
- 可以添加任意访问修饰符
- 可以被其它成员调用,外部类的方法若想使用,需要先创建成员内部类对象
- 外部其他类也可以访问成员内部类
- 如果外部类和匿名内部类重名,采取就近原则,若想使用外部类成员。可通过:外部类名.this.成员名,同样 外部类名.this 指的就是调用了创建该匿名内部类的方法/代码块的对象
外部其他类使用成员内部类的两种方式:
public class AnonymousTest3 {
public static void main(String[] args) {
Outer03 outer03 = new Outer03();
outer03.hi();
// 第一种形式
// outer03.new Inner01() 相当于把 new Inner01() 当作是 outer03 的成员
Outer03.Inner01 inner01 = outer03.new Inner01();
inner01.hello();
// 第二种形式
// 在外部类中编写方法返回成员内部类实例
Outer03.Inner01 inner01Instance = outer03.gerInner03Instance();
inner01Instance.hello();
}
}
class Outer03 {
private int n1 = 18;
public String name = "jerry";
public void hi() {
System.out.println("hi~~~~~~~~~~~~~~");
}
class Inner01 {
public void hello() {
System.out.println("hello~~~~~~~~~~~");
}
}
public Inner01 gerInner03Instance() {
return new Inner01();
}
}
静态内部类
定义在外部类的成员位置,并且有 static 修饰
- 可以访问外部类的所有静态成员,但不能访问非静态成员
- 可以添加任意访问修饰符
- 作用域为整个类体,和正常的静态成员一样
- 如果外部类和匿名内部类重名,采取就近原则,若想使用外部类成员。可通过:外部类名.成员名(因为静态内部类只能访问静态成员,静态成员可以通过类名调用)
外部其他类访问静态内部类的方法:
public class AnonymousTest4 {
public static void main(String[] args) {
// 外部其他类访问静态内部类
// 方法一,通过类名直接访问(满足访问权限)
Outer05.Inner02 inner02 = new Outer05.Inner02();
inner02.eat();
// 方法二,编写方法返回静态内部类对象实例
Outer05 outer05 = new Outer05();
// 调用方法返回实例
Outer05.Inner02 inner02Instance = outer05.getInner5Instance();
inner02Instance.eat();
// 也可以直接调用静态方法
Outer05.Inner02 inner02Static = Outer05.gerInner02Static();
inner02Static.eat();
}
}
class Outer05 {
private int n1 = 10;
private static String name = "jerry";
private static void sleep() {
System.out.println("sleeeeeeep");
}
static class Inner02 {
public void eat() {
System.out.println("静态内部类的方法~~~~~");
sleep();
}
}
public Inner02 getInner5Instance() {
return new Inner02();
}
public static Inner02 gerInner02Static() {
return new Inner02();
}
}