类变量(静态变量)
特点:该变量会被该类所有的实例共享
语法:访问修饰符 static 数据类型 变量名
// 用static修饰
public class Main {
private String name;
public static int count = 0; // 静态变量
public static void main(String[] args) {
}
}
类变量使用细节
什么时候需要使用类变量
当各个实例都有共享/访问同一个变量的时候
类变量和实例变量的区别
类变量所有实例共享,实例变量(属性)当前实例共享
类方法
什么时候用到类方法
当我们的方法中不涉及到任何静态成员的时候,可以将实例方法设计成静态方法,提高开发效率,比如工具类中的方法。或者我们希望某个方法不用创建实例也能调用,也应该设计成静态方法。
自己的理解:基本上可以理解为工具方法,相当于js中的某一个对象下创建了很多工具方法。
类方法(静态方法)注意细节
- 类方法和实例方法,都是随着类的加载而加载。
- 类方法中没有this这个参数,不能访问this,普通方法中有this
- 类方法可以通过类名调用,也可以通过实例调用,实例方法只能通过实例调用
- 类方法中不能使用实例对象有关的关键字,比如this,super,实例方法可以
- 类方法只能访问类变量(静态变量)和类方法,不能访问实例变量和实例方法
- 访问类变量和类方法的时候,依然要遵守访问权限 小结:静态方法只能访问静态成员,实例方法可以访问静态成员和实例成员
mian方法解析
- mian方法谁在调用 1)mian方法是java虚拟机在调用,所以一定是public,不能是别的,因为java虚拟机不是和mian方法不是同一个类。 2)为什么main方法必须是static,因为java虚拟机调用main方法的时候不创建实例。 3)main方法参数必须是字符串数组,会在执行程序的命令行中写参数传进去,比如java HelloWorld params1 params2 4)在main方法中,可以使用当前类的静态方法和静态属性 5)要在静态main方法中访问非静态属性,则可以通过先创建实例在访问的方式访问 Main01 mian01 = new Main01()
代码块
代码块属于类中的成员,既类的一部分,类似于类方法,将逻辑语句封装在方法体中,通过{}包围起来。
基本语法
[修饰符] {
// todo
};
修饰符要么不写,要么只能写static。
对于代码块的理解: 代码块相当于另一种形式的构造器,是对构造器机制的补充,可以做初始化。
应用场景:如果多个构造器中,有重复的语句,则可以抽离到代码块中,提高代码的复用性。构造器在执行的时候,会优先执行代码块中的语句。
代码块细节
-
static代码块,作用就是对类进行初始化,它随着类的加载而执行(重点),并且只会执行一次,如果是普通代码块,则每创建一个实例,就会执行一次。
-
类在什么时候会被加载(重点,必背)
- 1)创建实例对象的时候
- 2)创建子类对象,父类也会被加载
- 3)使用类的静态成员时
-
普通代码块,在创建实例时候,会被隐式调用,创建一次实例,就会调用一次,如果只是使用了类的静态成员,则不调用。
-
(重点,必背)创建一个实例时候,在一个类中,调用顺序是:
- 1)调用静态代码块和静态属性初始化(这两个优先级一样,按照定义顺序去调用)
- 2)调用实例代码块和实例属性,实例代码块和实例属性调用优先级是一样的,如果有多个,依然是按照定义顺序从上到下
- 3)调用构造器
-
构造器的最前面,其实隐藏了super()和调用普通代码块(先调用super在调用普通代码块),而静态代码块以及属性的初始化在类加载的时候就已经执行完毕。super执行代表
-
静态代码块只能调用静态成员,普通代码块能调用静态成员和非静态成员
案例(重点面试题,必背):
public class Main {
public static void main(String[] args) {
new Child();
}
}
class Father {
String name2 = "name2";
static {
System.out.println("父类静态代码块执行");
}
{
System.out.println("父类代码块执行");
}
Father() {
System.out.println("父类构造函数执行");
}
}
class Child extends Father {
String name3 = "name3";
static {
System.out.println("子类静态代码块执行");
}
{
System.out.println("子类代码块执行");
}
Child() {
System.out.println("子类构造函数执行");
}
}
顺序:
- 1)父类静态代码块执行
- 2)子类静态代码块执行
- 3)父类代码块执行
- 4)父类构造函数执行
- 5)子类代码块执行
- 6)子类构造函数执行
解释:
- 要想创建子类,首先要加载子类,想要加载子类,首先又要加载父类
- 加载父类就要执行父类静态代码块,所以首先执行1)
- 加载完父类,接着加载子类,所以接着执行2)
- 加载完子类了,就要创建子实例,创建实例就要执行构造函数,而构造函数内,优先隐式调用super(),执行super的意思就是执行父类的构造函数,执行父类构造函数之前又优先执行父类普通代码块,所以接着执行3),然后执行4)
- super执行完后,接着执行子类的构造器,执行子类的构造器之前又优先调用子类普通代码块,所以执行5),然后执行6),完毕。
单例模式(静态方法的经典使用)
定义:在整个程序运行的过程中,从开始到结束,最多只能存在某个类的一个实例,且该类只提供一个获取对象实例的方法。
实现步骤
- 构造器私有化(防止直接new)
- 类的内部创建对象
- 向外暴露一个静态公共方法getInstance
- 代码实现
单例饿汉式
class GirlFriend {
private final String name;
private static final GirlFriend girlFriend = new GirlFriend("jane");
// 构造器私有化
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
return girlFriend;
}
}
GirlFriend gf = GirlFriend.getInstance()
为什么叫做饿汉式,因为在类加载的时候就已经创建了实例,此时还没有调用呢
单例懒汉式
class GirlFriend {
private final String name;
private static GirlFriend girlFriend;
// 构造器私有化
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
if (girlFriend == null) {
return new GirlFriend("jane");
} else {
return girlFriend;
}
}
}
饿汉式和懒汉式区别
- 懒汉式在调用的时候才创建,饿汉式在加载类的时候创建。
- 懒汉式存在线程安全的问题(多个线程同时执行getInstance时候,会在一瞬间执行new出多个实例)
- 饿汉式因为可能创建了却不用,有可能浪费资源
final关键字
final可以修饰类,属性,方法和局部变量。
通常以下情况会使用final:
- 当不希望类被继承时候,可以用final
- 当不希望父类的某个方法被子类覆盖,重写时候,用final
- 当不希望某个属性的值被修改,用final
- 当不希望某个局部变量被修改,用final
final使用注意事项
- final修饰的属性一般叫常量,常量名一般用大写XX_XX
- final在定义的时候必须赋值,并且不能修改,赋值可以在以下位置之中
- 定义的时候
- 在构造器中(非静态属性)
- 在代码块中
- final如果是修饰的是静态属性,则不能在构造器中赋值
- final类不能被继承
- final方法可以被继承,但不能被覆盖
- final不能修饰构造方法
- final和static搭配修饰属性,此时访问这个属性不会导致类加载(编译器做了优化)
class B {
public final static int num = 10;
}
B.num // 此时不会导致类加载
- 包装类,Boolean,String等都是final类
抽象类
抽象方法:当一个父类的某个方法需要定义,但还不知如何实现,于是只定义暂不实现,那么这个方法叫做抽象方法,真正实现等子类去实现。 抽象类:具有抽象方法的类,叫做抽象类。
代码示范
abstract class A {
public abstract test void();
}
抽象类的价值更多在于设计,设计者设计好以后让子类去实现。面试比较喜欢问(问设计,不是问定义)。
抽象类注意细节
- 抽象类不能被实例化
- 抽象类不一定要包含抽象方法
- abstract只能修饰类和方法,不能修饰属性
- 抽象类除了有抽象方法,还可以有其他任意成员
- 一个类继承了抽象类,那么它必须实现抽象方法,除非这个类也是抽象类
- 抽象方法不能使用private,final,static这三个关键字修饰,因为这三个关键字都是和重写违背的
接口
定义:给出一些没有实现的方法,封装到一起,再根据具体情况把这些方法实现出来。 jdk8以后,接口可以有具体实现的静态方法(static),也可以有默认方法(非静态,用default关键字定义),也就是jdk8以后,接口可以有具体方法的实现 语法
interface A {
public static final String name = "jane";
test1 void();
test2 void();
// 默认方法
defatult pulick void test3() {
// todo
}
// 静态方法
public static void test4() {
// todo
}
}
publick class B implements B {
@overwrite
test1 void() {
// todo
}
@overwrite
test2 void() {
// tot
}
}
接口的注意事项
- 接口不能被实例化
- 接口中的所有方法,都是public方法,没有别的修饰词,被实现的类中的对应实现的方法,也还同样的是public方法。
- 一个普通类实现接口,就必须实现该接口的所有方法
- 抽象类可以不实现接口的方法
- 一个类可以同时实现多个接口(但是一个类只能继承一个类)
- 接口中的属性,必须是public,static,final这三个修饰词,不能是别的修饰词,但可以简化不写这三个,必须初始化
- 接口属性的访问形式:接口名.属性名
- 接口不能继承类,但接口可以继承多个别的接口 interface A extends B,C {}
- 接口的修饰词,只能是public和默认,不能是别的,可以省略,和类的修饰符是一样的规则
接口和继承类比较
- 解决的问题不同:继承的价值主要解决代码复用性和维护性,接口的价值在于设计。
- 接口比继承更灵活,继承是is->a,接口是like->a。
- 接口在一定程度上实现代码解耦。
实现接口是对java的单继承机制的补充。 当子类继承了父类,就自动拥有了父类的功能,如果需要扩展这个功能,可以通过实现接口的方式进行扩展。
接口的多态
接口引用可以指向实现接口的类的对象。
案例
public class Main {
public static void main(String[] args) {
A b = new B();
b = new C();
}
}
interface A {}
class B implements A {}
class C implements A {}
内部类
一个类的内部完成了嵌套了另一个类,被嵌套的类叫做内部类,嵌套其他类的类叫做外部类。
内部类最大的特点:可以直接访问私有属性,并且可以体现类与类之间的包含关系。
语法
// 外部类
class A {
// 内部类
class B {
}
}
类的五大成员(面试题)
- 属性
- 方法
- 构造器
- 代码块
- 内部类
内部类的分类(4种)
定义在外部类的局部(比如方法内)
- 局部内部类(有类名)
- 匿名内部类(没有类名)(重点) 定义在外部类的成员位置上
- 成员内部类(没有static修饰)
- 静态内部类(有static修饰)
局部内部类
定义在外部类的局部位置,比如方法中,并且有类名。
- 可以直接访问外部类所有成员,包含私有的。
- 不能添加访问修饰符,因为它就是一个局部变量(final可以添加)。
- 作用域:仅仅在当前方法或者代码块中。
- 外部类要使用局部内部类,唯一方式是创建内部类对象,然后调用方法和属性。
- 外部其他类不能访问局部内部类。
- 如果外部类的属性和局部内部类的属性重名,访问遵循就近原则,要访问外部类属性,可以写成:外部类.this.xx。
匿名内部类(重点难点)
匿名内部类定义外外部类的局部(方法中,代码块中),没有名字,同时,匿名内部类也是一个对象。
语法
new 接口/类 () {}
interface B {
void test01();
}
// 外部类
class A {
public void test () {
// 匿名内部类
B b = new B() {
public void test01() {
//
}
};
b.test01();
}
}
匿名内部类使用细节
- 可以访问外部类的所有成员
- 只能调用一次就不能再调用了
- 不能添加访问修饰符(final也不行)
- 作用域仅仅在局部(代码块/方法体中)
- 外部类和其他类,不能访问匿名内部类
- 外部类属性和匿名内部类属性重名,访问同样遵循就近原则
成员内部类
定义在外部类的成员位置
语法
// 外部类
class A {
// 成员内部类
class B {
void test02() {}
}
}
- 可以直接访问外部类的所有成员
- 可以添加任意的访问修饰符
- 作用域和外部类的其他成员一样
- 外部类访问方式:先创建内部类对象,再访问
- 其他类访问:外部类.new 内部类()
静态内部类
- 放在外部类成员位置
- 用static修饰
- 可以直接访问外部类所有成员
- 可以添加任意修饰符
- 作用域:同其他成员,为整个类体