# java 基础

返回:Java 开发

API-DOC-8 | API-DOC-9 | API-DOC-12

🐉 java 虚拟机 JVm 🐉 java 执行过程
泛型有话说 Javadoc
异常 序列化
打包部署 java jdk

# 编译时运行时

  • 在开发和设计的时候,我们需要考虑编译时运行时以及构建时这三个概念
public class ConstantFolding {
    static final int number1 = 5;
    static final int number2 = 6;
    static int number3 = 5;
    static int number4= 6;
    public static void main(String[ ] args) {
        int product1 = number1 - number2; //line A
        int product2 = number3 - number4; //line B
    }
}
1
2
3
4
5
6
7
8
9
10

行 A 的代码中,product 的值是在编译期计算的,行 B 则是在运行时计算的

反编译此文件的 class 文件,可以看到:

public class ConstantFolding{
    static final int number1 = 5;
    static final int number2 = 6;
    static int number3 = 5;
    static int number4 = 6;
    public static void main(String[ ] args)
    {
        int product1 = 30;
        int product2 = number3 - number4;
    }
}
1
2
3
4
5
6
7
8
9
10
11

常量折叠是一种 Java 编译器使用的优化技术。由于final变量的值不会改变,因此就可以对它们优化。Java 反编译器和 javap 命令都是查看编译后的代码(例如,字节码)的利器。

# 方法重载

back

这个是发生在编译时的。方法重载也被称为编译时多态,因为编译器可以根据参数的类型来选择使用哪个方法。

# 方法覆盖

这个是在运行时发生的。方法重载被称为运行时多态,因为在编译期编译器不知道并且没法知道该去调用哪个方法。JVM 会在代码运行的时候做出决定。

public class A {
    public int compute(int input) { //method #3
        return 3 - input;
    }
}
public class B extends A {
    @Override
    public int compute(int input) { //method #4
        return 4 - input;
    }
}

public int evaluate(A reference, int arg2) {
    int result = reference.compute(arg2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

编译器是没法知道传入的参数 reference 的类型是 A 还是 B。因此,只能够在运行时,根据赋给输入变量“reference”的对象的类型(例如,A 或者 B 的实例)来决定调用方法#3 还是方法#4.

# 泛型

这个是发生在编译期的。编译器负责检查程序中类型的正确性,然后把使用了泛型的代码翻译或者重写成可以执行在当前 JVM 上的非泛型代码。这个技术被称为“类型擦除“。换句话来说,编译器会擦除所有在尖括号里的类型信息,来保证和版本 1.4.0 或者更早版本的 JRE 的兼容性。

List<String> myList = new ArrayList<String>(10);编译后List myList = new ArrayList(10);

# 注解(Annotation)

你可以使用运行时或者编译时的注解。

# 异常(Exception)

back

你可以使用运行时异常或者编译时异常

# 面向切面的编程(Aspect Oriented Programming-AOP

切面可以在编译时运行时或,加载时或者运行时织入。

# POJO、PO、DTO、DAO、BO、VO

back

  • POJO

全称为:Plain Ordinary Java Object,即简单普通的 java 对象。一般用在数据层映射到数据库表的类,类的属性与表字段一一对应。

  • PO

全称为:Persistant Object,即持久化对象。可以理解为数据库中的一条数据即一个 BO 对象,也可以理解为 POJO 经过持久化后的对象。

  • DTO

全称为:Data Transfer Object,即数据传输对象。一般用于向数据层外围提供仅需的数据,如查询一个表有 50 个字段,界面或服务只需要用到其中的某些字段,DTO 就包装出去的对象。可用于隐藏数据层字段定义,也可以提高系统性能,减少不必要字段的传输损耗。

  • DAO

全称为:Data Access Object,即数据访问对象。就是一般所说的 DAO 层,用于连接数据库与外层之间的桥梁,并且持久化数据层对象。

  • BO

全称为:Business Object,即业务对象。一般用在业务层,当业务比较复杂,用到比较多的业务对象时,可用 BO 类组合封装所有的对象一并传递。

  • VO

全称为:Value Object,有的也称为 View Object,即值对象或页面对象。一般用于 web 层向 view 层封装并提供需要展现的数据。

# instance_of

back

User:用户基类
PrivateUser:私人用户子类,继承UserPrivateUser priUser = new PrivateUser();

System.out.println(priUser instanceof User);// true
System.out.println(User.class.isInstance(priUser));// true

System.out.println(User.class.isAssignableFrom(PrivateUser.class));// true
System.out.println(PrivateUser.class.isAssignableFrom(User.class));// false
1
2
3
4
5
6
7
8
9
10

A.class.isAssignableFrom(B)
两个 class 的类型关系判断,判断 B 是不是 A 的子类或子接口

# 内部类

back

# 成员内部类

成员内部类是最普通的内部类

虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

Outer outer = new Outer();
// 第一种方式:
Outer.Inner inner = outer.new Inner();
// 第二种方式:
Outer.Inner inner = outer.getInnerClass();
System.out.println("outName:" + outName);//#outName:外部类
1
2
3
4
5
6

TIPS: 特殊情况:当外部类和内部类的成员变量同名的情况
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法

System.out.println("sameName:" + sameName);//#sameName:同名内部
System.out.println("sameName:" + this.sameName);//#sameName:同名内部,this指向Inner
System.out.println("sameName:" + Outer.this.sameName);//#sameName:同名外部
1
2
3

# 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

class People {
String peopleName = "people";
String sameName = "外部同名变量";
public People getWoman(final String methodName){
 final String localName = "局部变量";
 class Woman extends People{
 String womanName = "woman";
 String sameName = "局部内部类同名变量";
 public Woman(){
 //methodName = "";//编译错误:Cannot assign a value to final variable 'methodName'
 //localName = "";//编译错误:Variable 'localName' is accessed from within inner class, needs to be final or effectively final
 System.out.println(methodName);//#形参变量
 System.out.println(localName);//#局部变量
 System.out.println(peopleName);//#people
 System.out.println(womanName);//#woman
 System.out.println(sameName);//#局部内部类同名变量
 System.out.println(this.sameName);//#局部内部类同名变量
 System.out.println(People.this.sameName);//#外部同名变量
 }
 }
 return new Woman();
 }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

值得注意的是,局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。

# 匿名内部类

# 静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字 static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非 static 成员变量或者方法,

# 为什么局部内部类和匿名内部类只能访问局部 final 变量

public class Test {
 public static void main(String[] args) {
 Test test = new Test();
 test.test(1);
 }
 public void test(final int b) {
 final int a = 10;
 new Thread(){
 public void run() {
 // a = 2;//编译错误:Cannot assign a value to final variable 'b'
 // b = 3;//编译错误:Cannot assign a value to final variable 'b'
 System.out.println(a);
 System.out.println(b);
 };
 }.start();
 }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

当 test 方法执行完毕之后,变量 a 的生命周期就结束了,而此时 Thread 对象的生命周期很可能还没有结束,那么在 Thread 的 run 方法中继续访问变量 a 就变成不可能了,但是又要实现这样的效果,怎么办呢?Java 采用了 复制 的手段来解决这个问题。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。
也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

# 模板设计模式

back

顾名思义,模板设计模式就是将许多公用的常用的代码封装成一个模板,我们只需要实现不同的业务需求的代码,然后和模板组合在一起,那么就得到完整的逻辑

public void doSomething(){
    // 固定代码片段
    // 业务相关代码
    // 固定代码片段
}
1
2
3
4
5

# 用继承的方式实现

back

创建一个模板类,将模板代码封装

public abstract class Templet {

  public void doTemplet(){
    System.out.println("固定代码片段");
    //业务逻辑代码
    doSomething();
    System.out.println("固定代码片段");
  }
  public abstract void doSomething();
}
1
2
3
4
5
6
7
8
9
10

我们需要使用模板的类只需要继承这个模板类,并实现那个抽象方法,那么在调用 doTemplet 的时候,调用的业务逻辑代码自然就是我们那个子类中的实现了,这样就能实现不同的逻辑使用同一段代码了。

# 用回调的方式实现

back

public interface Callback<V,T> {
  public V doSomething(T t);
}
1
2
3
public class Test {
  public static void main(String[] args) {
    useTemplet("业务逻辑",new Callback<String, String>() {
      @Override
      public String doSomething(String t) {
        return t;
      }
    });
  }
  public static void useTemplet(String str,Callback<String,String> callback){
    System.out.println("固定代码");
    String result = callback.doSomething(str);
        System.out.println(result );
    System.out.println("固定代码");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# static 关键字

静态的内容是属于类的,动态的内容属于对象

# static 修饰变量

static 是一种修饰符,最常用的就是修饰变量,static 修饰的变量含有全局的意味,这个变量会变得脱颖而出,不再受对象的桎梏,它在类加载的时候就会分配内存来存储,存储在运行时数据区中的方法区中,属于类级别的存在,可以使用类名点用。

这种变量只会存在一份,但却可以被任意使用,可以被所有的对象所拥有,它的值的可变的,但却是共享的,一方改变,多方共享。