概述
Java 当中的六种结构
- 包 =>
package
- 类 =>
class
-
静态[成员]变量 =>
static int i
=> 存放在 JVM 中的一个全局的存储单元 => 在当前类中使用不需要 + 限定符,在其他类中访问需要 + 限定符
=>Main.<staticVariable>
=> 不和任何对象相绑定 -
静态[成员]方法 =>
public static void fn() {}
=> 不和任何对象相绑定 => 在其他类中使用分为两种- 添加限定符 =>
Main.fn()
- 导入静态成员方法 =>
import static com.github.hcsp.Main.*
=> 静态方法就会去Main
中查找
- 添加限定符 =>
-
实例变量 =>
String name
=> 和实例(对象)相绑定 => 调用时必须通过实例调用 -
实例方法 =>
public void fn() {}
=> 和实例(对象)相绑定 => 调用时必须通过实例调用
访问控制符
类的访问控制符
-
public
=> 任何类都可以访问 -
package private
=> 同一个包的类可以访问 => 默认值 -
private inner class
=> 只能在同一个类中访问
属性 | 方法的访问控制符
-
default
=> 缺省,不使用任何关键字 -
public
=> 任何人都可以访问 -
protected
=> 只有子类和同一个包的类可以访问 -
package private
=> 只有同一个包的类可以访问 ->「包级私有」 => 默认值 -
private
=> 只有自己可以访问 => 不能被继承 =>private
的访问限定是在当前的编译单元 => compilation unit (文件)
非访问修饰符
-
static
=> 用来修饰类方法和类变量 -
final
=> 用于修饰类、方法和变量- 修饰类 => 类不能继承
- 修饰方法 => 该方法不能被继承类重写
- 修饰变量 => 常量,不可修改
-
abstract
=> 用于创建抽象类和抽象方法 -
synchronized/volatile
=> 用于线程 -
transient
=> 表明属性不需要序列化
包
- 包的名字由包目录结构所确定
- 包的根目录 => default package 默认包 -> /src/main/java
- 包可以避免命名冲突 => 可用于区分同名但不同的类,可以放在不同的包中
- 访问控制 => 包可以提供一种封装的边界,把实现的细节和对外暴露的接口分离开
- 包之间没有嵌套包含关系,没有任何关系
类
类是最小的语言单位。每个类都处在一个包中。类中包含结构
-
static int i
=> 静态[成员]变量 -
static void main() {}
=> 静态[成员]方法 -
String name
=> 实例变量(成员变量) => 和对象相绑定 -
public Cat() {}
=> 实例方法(成员方法) => 和对象相绑定
全限定类名 FQCN
FQCN => Full Qualified [Class] Name => 在 JVM 中只存在 FQCN,防止命名冲突 => org.apache.commons.lang3.ArrayUtils
抽象类 abstract
- 使用
abstract
声明的类 - 当把一些功能的骨架抽离出来放在一个类中,可以把这个类命名为
Abstract
=> 例:AbstractList
|AbstractMap
|AbstractQueue
- 使用一个骨架实现,提供一个抽象实现,具体的类可以
@Override
细枝末节的方法,提供不同的行为
特点:
- 不可实例化 => 可以实例化的东西一定要补全所有的方法体
- 可以包含实例变量
- 可以声明抽象方法
- 抽象方法不能是
private
|static
=> 抽象方法是要去继承的 => 继承 | 多态只发生在实例方法上 =>private
只有自己能访问,不能被继承
// 模板方法
abstract class BookWriter {
public void writeBook() {
writeTitle();
writeIntroduction();
writeContent();
writeEnding();
}
public void writeTitle() {
System.out.println("标题");
}
public void writeIntroduction() {
System.out.println("引言");
}
// 内容根据每本书的不同而不同
public void writeContent() {
}
public void writeEnding() {
System.out.println("谢谢大家");
}
}
// 实现
class MyBookWriter extends BookWriter {
public static void main(String[] args) {
new MyBookWriter().writeBook();
}
@Override
public void writeTitle() {
// 如果想在模板的 writeTitle 方法中添加自己的方法
super.writeTitle();
System.out.println("My Title!");
}
// 覆盖模板的部分 => writeEnding
@Override
public void writeEnding() {
// 如果想在模板的 writeEnding 方法中添加自己的方法
System.out.println("Thank everyone!");
}
}
在模板模式中禁止默认实现
在上述例子中,内容是必须进行覆盖的,但是没有默认实现
- 抛出异常 =>
throw new UnsupportedOperationException();
=> 运行异常 - 抽象化 => 抽象类 => 方法 + 类都是抽象的 =>
public abstract void writeContent();
- 接口
内部类
用途:提供更加精细的封装
- 内部类
- 静态内部类
- 匿名内部类
静态内部类 vs 内部类
最佳实践:永远使用静态内部类,除非编译报错 => 内部类会自动创建一个变量,如果没有使用到,那么将会浪费内存 | 空间
public class Home { // 外围类
private void log() {
}
private class A { // 内部类
// 编辑器注册的实例 | 对象
// private Home this$0;
{
log(); // 实例方法 => 隐式的对 this 调用 => this.log();
}
}
private static class B { // 静态内部类
}
}
- 内部类和外围类的实例相绑定,使得内部类可以毫无障碍的调用外围类的实例方法 => 编译器偷偷摸摸注册了一个外围类的实例 | 对象 =>
private Home this$0
=> 为何可以访问外围类的原因 - 静态内部类不和任何外围类的实例相绑定,因此静态内部类不能调用外围类的实例方法
匿名内部类
- 直接通过
new
的方式创建的无名类 - 在字节码中的是有名字的 => 外围类的名字 +
$
+ 数字 =>Home$1.class
|Home$2.class
- 相近的两块逻辑组合到一起 + 可访问上下文中的所有变量
优点:
- 可以访问外围类的变量
- 短小
- 逻辑与外部逻辑紧密联系,方便阅读
- 可以变成 lamada 表达式
对象 & 构造器
public class Cat { // 类
public String name; // 实例变量
// constructor
public Cat() {
}
// constructor
public Cat(String name) {
this.name = name;
}
}
public class Main {
public static void Main() {
Cat cat = new Cat(); // cat => 对象 => Cat 类实例化 => 实例
cat.name = 'George';
Cat cat2 = new Cat('Lili'); // cat2 => 对象 => Cat 类实例化 => 实例
}
}
方法
重载 overload
重载:方法的参数表可以完全不一样,参数名可以一样。方法名相同,参数不同。根据类型来区分同名的不同重载方法。
如果一个方法调用可以匹配多个方法声明(int <=> Integer
)时,类型最匹配的优先,如果此时还能匹配多个,那么指定类型即可,即强制类型转换
能匹配多个且不构成父子类就会报错
在 JVM 「字节码」中是可以仅仅重载返回值的,在源代码中是非法的 => 写的是源代码,在 JVM 中运行的是字节码
public class Cat {
public static void main(String[] args) {
Cat cat = new Cat();
cat.fn(1); // 优先匹配 -> int i
// 以下匹配顺序为 int > Integer > Number > Object
cat.fn((Integer) null); // 此处强制为 null 指定类型即可 -> 强制类型转换
}
void fn(int i) {}
void fn(Integer i) {}
void fn(Number i) {}
void fn(Object i) {}
void fn(Object[] i){}
}
默认值
Java 中不可以设置默认值。以下示例使用了构造函数 Cat
的重载 => 侧面设置了默认值
public class Cat {
int age;
String name;
// 创建一只默认的🐱,1岁,喵喵
Cat() {
this("喵喵"); // this == Cat(String name) {}
}
// 创建一只默认的🐱,1岁,名为 name
Cat(String name) {
this(1, name); // this == Cat(int age, String name) {}
}
// 创建一只名为 name 年龄为 age 的🐱
Cat(int age, String name) {
this.age = age;
this.name = name;
}
}
知识点
- 类的初始化顺序 => 类的初始化顺序是自顶向下的,父类先于子类进行初始化 => 例:
A extends B => B extends C => C extends D
=> D 先创建,D 完成之后,C 创建,C
完成之后,B 创建,B 完成之后,A 创建 - 类在继承时,必须拥有匹配的
constructor
=>super();
- 静态与否在于是否与实例(对象)绑定 =>
static
关键字 - 放在
java.lang
下面的类无需使用import
- 一个 Java 的类文件必须只能包含一个和它同名的
public
成员,非public
成员可以很多个 - 修饰符
@Deprecated
=> 已弃用 -
com.github.xxx.JavaClass$InnerClass
=>$
区分内部类的分隔符