Skip to content

Latest commit

 

History

History
648 lines (501 loc) · 20.4 KB

File metadata and controls

648 lines (501 loc) · 20.4 KB

第一部分 入门

开发环境

Java虚拟机

  • JVM:Java 虚拟机。Java 代码是运行在虚拟机上的。
  • 跨平台:代码运行在虚拟机上,不同版的 OS(linux,windows,mac)对应不同的虚拟机。虚拟机本身不具备跨平台功能,每个 OS 下都有不同版本的虚拟机。『可理解为,各个 OS 下的虚拟机都是采用一套编码指令集,JVM 是面向操作系统的,它负责把 Class 字节码解释成系统所能识别的指令并执行,同时也负责程序运行时内存的管理』

JRE&JDK

  • JRE (Java Runtime Environment)Java 程序的运行时环境,包含 JVM 和运行时所需要的核心类库
  • JDK(Java Development Kit)Java 程序开发的工具包,包含 JRE 和开发人员使用的工具。
  • 运行 Java 程序有 jre 就行,开发 Java 程序需要 JDK。
  • Windows 会把 %CC% 中的 CC 当作变量进行翻译

入门程序

程序开发步骤说明

  • 编写、编译、运行
  • Java 源程序-->Java 字节码文件-->JVM 运行
  • Javac.exe 编译器,将 Java 文件变成字节码文件
  • Java.exe 解释器,解释字节码的内容

第四章 常量

  • 常量;在程序运行期间固定不变的量。
  • 常量的分类
    • 字符串常量:凡是用双引号引起来的部分叫字符串常量。“asdfas”,可以是空串
    • 整数常量:直接写上数字的,没有小数点。
    • 浮点数常量:直接写上数字的,有小数点。
    • 字符常量:用单引号引起来的 ‘A’,不能是空字符‘’。
    • 布尔常量:只有两种取值。true,false
    • 空常量:null,代表没有任何数据。不能直接用来打印。syso(null) 是错的。

变量&数据类型

数据类型

基本数据类型

  • 整数
    • byte 1 个字节
    • short 2 个字节
    • int 4 个字节
    • long 8 个字节
  • 浮点数
    • float 4 个字节
    • double 8 个字节
  • 字符型
    • char 2 个字节
  • 布尔型
    • boolean 1 个字节

Java 中默认类型:整型是 int,浮点类型是 double 想要精确的数字不推荐用 double,用 BigDemical。

引用数据类型

字符串,数组,类,接口,Lambda

注意事项:

  • 字符串不是基本数据类型
  • 浮点数可能是一个近似值
  • 数据范围与字节数不一定相关。如 float 数据范围比 long 更大,但 float 是 4 字节,long 是 8 字节
  • 浮点数默认是 double,如果一定要用 float,需要加上一个后缀 F(推荐大写)
  • 如果是整数,默认为 int,如果一定要用 long,需要加上一个后缀 L(推荐大写)

变量

  • 变量:程序运行期间,内容可以发生改变的量
  • 编译原理,左值与右值

强制数据类型转换

  • 强制类型转换一般不推荐使用,因为可能发生精度损失
  • byte,short,char 这三种数据类型可发生数学运算。
  • byte,short,char 在运算时都会被首先提升为 int 类型,然后再计算。
byte num1 = 40;
byte num2 = 50;
//byte + byte --> int + int
int result = num1 + num2;
//如果用 byte接收 需要强转
byte result = (byte)(num1 + num2);
// short 同理

ASCII码表

0 -- 48
A -- 65
a -- 97

数字和字符的对照关系表(编码表)

ASCII 码表:American Standard Code for Information Interchange

Unicode 码表:万国码。也是数字和符号对照关系,开头 0-127 部分和 ASCII 完全一样,但是从 128 开始包含更多字符。

易错点

byte short char 这些在计算的时候,会有类型提升,提升为 int 进行计算。

public static void main(String[] args) {
    byte a = 8;
    byte b = 127;
    b = a+b; 
    // 会报错,提示你要进行强制类型转换。因为计算的时候,a和b会被提升为int类型,然后再进行计算,得到的结果也是int的,要把int类型的赋值给byte类型的变量需要进行强制类型转换。
}

包装类型的比较

public class Demo {
    public static void main(String[] args) {
        Integer b = new Integer(47);
        Integer a = new Integer(47);
        System.out.println(a == b);  // false 因为 a b 是不同的对象。
        Integer c = Integer.valueOf(47);
        Integer d = Integer.valueOf(47);
        System.out.println(c == d); // true 因为 valueOf 创建对象是会先从 IntegerCache 缓存中找,有就返回缓存中的对象。
        							// IntegerCache 是静态内部类。静态内部类在你使用的时候才会进行加载。注意:是说的静态内部类。
    }
}

静态内部类加载时机的测试。遇到 new、getstatic、putstatic 或 invokestatic 这四条字节码指令执行的时候,如果类没有进行过初始化,则需要先触发其初始化。

public class Demo {
    // VM参数 -XX:+TraceClassLoading
    public static void main(String[] args) {
        int a = 0x2f;
        // 调用静态内部类,控制台输出,它被加载了。不用静态内部类,它就不加载。
        System.out.printf("", TestClassLoading.d); 
    }

    static class TestClassLoading {
        static int d = 10;
        { System.out.println("d"); }
    }
}

常用运算

  • 一元运算符:只需要一个数据就可以进行操作的运算符。

    • 取反
    • 自增
    • etc
  • 二元运算符:需要两个数据才可以进行操作的运算符。

    • 加法
    • 减法
    • 赋值
  • 三元运算符:需要三个数据才可以进行的运算符。

    • 变量名称 = 条件判断?表达式 A : 表达式 B;
    • int max = a > b ? a : b;
  • 拓展

    • 对于 byte/short/char 三种类型来说,如果右侧赋值的数值没有超过范围,那么 java 编译器会自动隐含地为我们补上一个 (byte)(short)(char).
    short = 5 + 8;(都要是常量才行)
    等同于
    short = 13; // 编译优化
    先计算出的结果在进行赋值的
    称为编译器的常量优化。
    

基本语法

switch & 循环

switch

基本语法

public class Demo {
    public static void main(String[] args) {
        int a = 5;
        switch (a) {
            case 1:
                System.out.println(1);
            case 2:
                System.out.println(2);
            default:
                System.out.println("over!");
        }
    }
}
  • 多个 case 后面的数值不可以重复
  • switch 后面小括号中只能是下列数据类型
    • 基本数据类型 byte/short/char/int
    • 引用数据类型 String 字符串、enum 枚举

Java 7 之前想用 String 判断的话

package tij.chapter5;

public class StringSwitch {
    public static void main(String[] args) {
        String color = "red";
        // 老的方式: 使用 if-then 判断
        if ("red".equals(color)) {
            System.out.println("RED");
        } else if ("green".equals(color)) {
            System.out.println("GREEN");
        } else if ("blue".equals(color)) {
            System.out.println("BLUE");
        } else if ("yellow".equals(color)) {
            System.out.println("YELLOW");
        } else {
            System.out.println("Unknown");
        }
        // 新的方法: 字符串搭配 switch
        switch (color) {
            case "red":
                System.out.println("RED");
                break;
            case "green":
                System.out.println("GREEN");
                break;
            case "blue":
                System.out.println("BLUE");
                break;
            case "yellow":
                System.out.println("YELLOW");
                break;
            default:
                System.out.println("Unknown");
                break;
        }
    }
}

switch 中可以用 String 的原理

调用字符串的 hashCode 方法,switch 中先比较 hashCode 的值,hashCode 的值一致再比较字符串的值。

public class TestSwitch {
    public static void main(String[] args) {
        String str = "dd";
        switch (str) {
            case "dd":
                System.out.println("odk");
                break;
            case "cc":
            default:
                System.out.println("over!");
        }
    }
}
// 反编译后的代码
public class TestSwitch {
    public TestSwitch() {}

    public static void main(String[] args) {
        String str = "dd";
        byte var3 = -1;
        switch(str.hashCode()) {
            case 3168:
                if (str.equals("cc")) { var3 = 1; }
                break;
            case 3200:
                if (str.equals("dd")) { var3 = 0; }
        }

        switch(var3) {
            case 0:
                System.out.println("odk");
                break;
            case 1:
            default:
                System.out.println("over!");
        }
    }
}

循环

for 循环,最常用的迭代形式

for(  ;  ;  ){
    
}

for(初始化表达1 ; 布尔表达式2 ; 步进表达式4){
    循环体3
}
流程 1 2 3 4 --> 2 3 4 --> 2 3 4 -->直到2不满足为止初始化语句只会执行一次

逗号操作符:在 for 循环的初始化和步进控制中定义多个变量。

public class CommaOperator {
    public static void main(String[] args) {
        for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {
        	System.out.println("i = " + i + " j = " + j);
        }
    }
}

增强 for 循环 foreach:操纵数组和集合

for(float x : f){
    System.out.println(x);
}
// 将每一个f的元素赋值给x

do-while

do {
    // doing something
} while (condtion);

break & continue

break 跳出一层循环,continue 开启下一次循环。IDEA 点击关键字可以看到下一步会执行到那里。

goto

public class GoToDemo {
    public static void main(String[] args) {
        outer:
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                if (j == 5) {
                    System.out.println(j);
                    break outer;
                }
            }
        }
    }
}

重载与重写

方法调用的三种格式

1.单独调用:方法名称(参数) 2.打印调用:System.out.println(方法名称(参数)) 3.赋值调用:数据类型 变量名称 = 方法名称(参数)

方法重载 Overload

  • 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
  • 参数列表:个数不同,数据类型不同,顺序不同。
  • 重载方法调用:JVM 通过方法的参数列表,调用不同的方法。
// 以下参数顺序不一样也是重载!
public static void test(int a, short b){}
public static void test(short b,int a){}
  • 实际上,println 就是一个被重载的函数

  • 方法重写 Overrider

    • 子类中出现和父类中一模一样的方法(包括返回值类型,方法名,参数列表)
    • 1.重写的方法必须要和父类一模一样(包括返回值类型,方法名,参数列表)
    • 2.重写的方法可以使用 @Override 注解来标识

重载的注意事项

public static void f1(short i){
    System.out.println("f1(short)");
}
public static void f1(byte i){
    System.out.println("f1(byte)");
}
public static void f1(int i){
    System.out.println("f1(int)");
}
public static void main(String[] args) {
    short i = 5;
    byte ii = 6;
    int iii = 7;
    f1(1);  // f1(int)
    f1(1);	// f1(int)
    f1(1);	// f1(int)
    System.out.println("==========华丽的分割线==========");
    f1(i);	// f1(short)
    f1(ii);	// f1(byte)
    f1(iii);// f1(int)
}

数组

数组的初始化

  • 动态初始化 -- 指定数组长度
int [] array = new int[300];
  • 静态初始化 -- 指定数组内容
int [] array = new int[]{1,2,3,4,5,6}; // 标准格式
int [] array = {1,2,3,4,5,6}; // 省略格式
// 静态初始化不能拆分成
int [] array;
array = {1,2,34};
// 这样是错误得
  • 总结

动态初始化有默认值的过程, 整型 默认为 0 浮点 默认为 0.0 字符 默认为 '\u0000' 布尔 默认为 false 引用 默认为 null

静态初始化也有,不过系统自动马上将默认值替换为了大括号当中的具体数值。

数组作为参数,返回值

public static void cals(int[] arr){
    xxxx
}

public static int[] calculate(int a,int b){
    int [] array = {a,b};
    return array;
}

数组作为参数,作为返回值其实都是数组的地址值

Java 内存划分

  • 1.栈(stack):存放的都是方法中的局部变量。方法的运行一定要在栈中运行
    • 局部变量:方法的参数,或者方法{}内部的变量
    • 作用域:一旦超出作用域,立刻从栈内存当中消失
  • 2.堆(heap):凡是 new 出来的东西都在堆中
    • 堆里面的数据都有默认值。默认值同上
  • 3.方法区(method area):存储 .class 相关信息,包含方法的信息。
  • 4.本地方法栈(native method stack):与操作系统相关
  • 5.寄存器(register):与 CPU 相关

Java 的垃圾回收,对于提高对象的创建速度,具有明显的效果。Java 从堆空间分配空间的速度,可以和其他语言从堆栈上分配空间的速度相媲美。在某些 Java VM 中,堆的实现截然不同,但是堆内存的分配可以看做:有一个堆指针,简单移动到尚未分配的区域,通过这种方式分配对象内存,其效率比得上 C++ 在栈上分配空间的效率。当然,在实际工作方面,还有少量额外的开销,但是比不上查找可用空间的开销。(Java GC 会清理出可用的空间,堆指针在空间中移动,这样就完成了内存的分配。而 C++ 需要遍历查找可用的内存,这个查找开销较大。这样一对比,会发现,Java 分配对象的速度并不比 C++ 慢

Java 的 GC 工作的时候,一面回收内存空间,一面使堆中的对象紧凑排列。

Java 的优化技术==>JIT(Just-In-Time):这种技术可以把程序的全部或部分代码翻译成本地机器码,提升程序速度。当要装载某个类时,编译器会先找到其 .class 文件,然后将该类的字节码转入内存。此时有两种方式可供选择

  • 一、让即时编译器编译所有代码,但是这种做法有两个缺陷:
    • ①这种加载动作散落在整个程序的生命周期内,累加起来要花很多时间
    • ②会增加可执行代码的长度(字节码要比 JIT 展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。
  • 二、惰性评估,只在必要的时候编译代码。

常见异常

ArrayIndexOfBoundsException

NullPointException

OutOfMemmory

OOP

  • 面向对象是指把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式【来自百度】
  • 当需要实现一个功能时,不关心具体的步骤,而是找一个已经具有该功能的人,来替我们做事。
  • 面向对象的基本特征:继承,封装,多态

类&对象

  • 类:一组相关属性和行为的集合。
  • 对象:具体存在的实例,是真实地。 实例==对象。
  • 代码层面,必须先有类,才能创建出对象。

定义类的格式

修饰符 class 类名{
	//1.成员变量(Field 描述类和对象的属性信息)

	//2.成员方法(Method:描述类或者对象的行为信息)

	//3.构造器(Constructor:初始化一个类的对象并返回引用)

	//4.代码块

	//5.内部类
}

构造器

  • 作用:初始化一个类的对象并返回。
  • 构造器初始化对象的格式:类名 对象名称 = new 构造器()

this 关键字的作用

  • this 代表当前对象的引用。
  • this 关键字可以用在实例方法和构造器中。
  • this 用在方法中,谁调用这个方法,this 就代表谁。
  • this 用在构造器,代表构造器正在初始化那个对象的引用。

封装

封装的作用

  • 可以提高安全性,合理暴露合理封装。
  • 可以实现代码组件化。

封装的规范

  • 建议成员变量都私有。
  • 提供成套的 getter+setter 方法暴露成员变量的取值和赋值。

小结:封装的核心思想==>合理隐藏,合理暴露。

static 关键字

Java 通过成员变量是否有 static 修饰来区分是属于类的还是属于对象的。

static ==> 静态 ==> 修饰的成员(方法和成员变量)属于类本身。

  • 有 static,静态成员变量:属于类本身。
  • 无 static,实例成员变量:属于每个实例对象,必须用类的对象来访问。

成员方法也类似:

  • 静态方法
  • 实例方法

static 修饰,属于类本身,随类的加载而加载,因为只有一份,所以可以被类和类的对象共享。

类与类之间的关系

  • 依赖(user-a):一个类使用了另一个类的方法,A 调用了 B 的方法,B 出 bug 了,A 也可能出 bug,软件工程中称之为耦合。
  • 聚合(has-a):一个对象将一个或者多个其它对象作为自己的成员
  • 继承(is-a):一个子类继承父类,那么子类会拥有父类的非 private 修饰的方法、变量。

对象的内存图

  • 方法区中存放 class 信息。class 中的成员方法一直在方法区中。
    • 方法区的实现有两种:
      • jdk 1.8 以前(不包括 1.8)用永久代实现
      • jdk 1.8 及 1.8 以后用元空间实现
    • 1.8 及其以后,方法区中的常量池和静态变量都移动到了 JVM 堆中。具体看 openJDK 的描述 JEP 122: Remove the Permanent Generation (java.net)
  • 堆中拿到成员方法的地址,通过地址对方法进行调用【回忆组成原理】。
  • 堆将方法区中的成员变量拿到堆中(相当于 copy 一份),对其进行初始化值的操作【不同对象的成员变量是独立的(非静态成员变量)】
  • main 方法中的变量指向堆中的对象,并对对象进行赋值操作。
  • stack--栈,FIFO

成员变量和局部变量

  • 定义的位置不一样【重点】
    • 局部变量:在方法的内部
    • 成员变量:在方法的外部,直接写在类当中
  • 作用范围不一样【重点】
    • 局部变量:只有方法当中才可以使用,出了方法就不能再用
    • 成员变量:整个类全都可以通用。
  • 默认值不一样【重点】
    • 局部变量:没有默认值,如果要想使用,必须手动进行赋值
    • 成员变量:如果没有赋值,会有默认值,规则和数组一样
  • 内存的位置不一样(了解)
    • 局部变量:位于栈内存
    • 成员变量:位于堆内存
  • 生命周期不一样(了解)[ 通常是这样,但是不绝对 ]
public class Demo01VariableDifference {
 
    String name; // 成员变量

    public void methodA() {
        int num = 20; // 局部变量
        System.out.println(num);
        System.out.println(name);
    }

    public void methodB(int param) { // 方法的参数就是局部变量
        // 参数在方法调用的时候,必然会被赋值的。
        System.out.println(param);

        int age; // 局部变量
        System.out.println(age); // 没赋值不能用

        System.out.println(num); // 错误写法!
        System.out.println(name);
    }
}

变量的初始化顺序

public Class Counter{
    int i;
    public Counter(){
        i=7;
        //在调用构造方法为 i 赋值为 7之前,i 就已经被初始化为 0 了。
        // 即,自动初始化发生在构造器被调用之前。
    }
}
/**
PS:回顾下类加载过程
- 加载(将字节码文件加载进内存)
- 验证(验证 Class 文件是否符合 JVM 规范,并确保它们不会危害虚拟机安全)
- 准备(为静态变量分配内存并设置类变量的初始值)
- 解析(将常量池内的符号引用替换为直接引用)
- 初始化(执行 clinit 方法),初始化指的是代码中定义的初始化,而非编译器默认的初始化
类加载是类加载,创建实例化对象是创建对象。
*/