Jtoss Jtoss
首页
  • 数据结构与算法

    • 数据结构与算法 - 概述
    • 数据结构与算法 - 复杂度分析
    • 数据结构 - 线性表
    • 算法 - 常见排序算法
  • 代码规范

    • 代码简洁之道
    • 阿里巴巴开发手册
    • 谷歌Java编程风格指南
  • 设计模式

    • 编写高质量代码概述
    • 面向对象
    • 设计原则
    • 设计模式-创建型
    • 设计模式-结构型
    • 设计模式-行为型(上)
    • 设计模式-行为型(下)
    • 浅析框架源码中的设计模式
    • 业务框架实战案例
  • MySQL 基础

    • MySQL - 数据库设计规范
    • MySQL - 必知必会
  • MySQL 进阶

    • MySQL - 基础架构
    • MySQL - InnoDB存储引擎
    • MySQL - InnoDB缓冲池
    • MySQL - 事务与锁
    • MySQL - 索引
    • MySQL - 查询执行计划
    • MySQL - 性能优化
  • Redis 系列

    • Redis入门 - 基础相关
    • Redis进阶 - 数据结构
    • Redis进阶 - 持久化RDB和AOF
    • Redis进阶 - 事件机制
    • Redis进阶 - 事务
    • Redis进阶 - 高可用高可扩展
    • Redis进阶 - 缓存问题
    • Redis进阶 - 性能调优
  • Java 基础

    • Java 基础 - 知识点
    • Java 基础 - 面向对象
    • Java 基础 - Q/A
  • Java 进阶 - 集合框架

    • Java 集合框架详解
  • Java 进阶 - 多线程与并发

    • Java 并发 - 理论基础
    • Java 并发 - 线程基础
    • Java 并发 - 各种锁
    • Java 并发 - 关键字 volatile
    • Java 并发 - 关键字 synchronized
    • JUC - CAS与原子操作
    • JUC - 锁核心类AQS
    • JUC - 锁接口和类简介
    • JUC - 并发容器简介
    • JUC - 通信工具类
    • JUC - Fork-Join框架
    • JUC - 线程池
  • Java 进阶 - JVM

    • JVM - 概述
    • JVM - 类加载机制
    • JVM - 内存结构
    • JVM - 垃圾回收机制
    • JVM - 性能调优
  • Maven系列

    • Maven基础知识
    • Maven项目构建
    • Maven多模块配置
  • Spring 框架

    • Spring 框架 - 框架介绍
    • Spring 框架 - IOC详解
    • Spring 框架 - AOP详解
    • Spring 框架 - SpringMVC详解
  • Spring Boot 系列

    • Spring Boot - 开发入门
    • Spring Boot - 接口相关
  • Spring Cloud 系列
  • Mybatis 系列

    • Mybatis - 总体框架设计
    • Mybatis - 初始化基本过程
    • Mybatis - sqlSession执行过程
    • Mybatis - 插件机制
    • Mybatis - 事务管理机制
    • Mybatis - 缓存机制
  • 业务常见问题

    • Java 业务开发常见错误(一)
    • Java 业务开发常见错误(二)
    • Java 业务开发常见错误(三)
    • Java 业务开发常见错误(四)
    • Java 业务开发常见错误(五)
    • Java 业务开发常见错误(六)
  • IDEA系列

    • IDEA 2021开发环境配置
    • IDEA 快捷键
  • Git系列

    • git status中文乱码
  • 其他

    • Typora+Picgo 自动上传图片
    • hsdis 和 jitwatch
  • 实用技巧
  • 收藏
  • 摄影
  • 学习
  • 标签
  • 归档

Jason Huang

后端程序猿
首页
  • 数据结构与算法

    • 数据结构与算法 - 概述
    • 数据结构与算法 - 复杂度分析
    • 数据结构 - 线性表
    • 算法 - 常见排序算法
  • 代码规范

    • 代码简洁之道
    • 阿里巴巴开发手册
    • 谷歌Java编程风格指南
  • 设计模式

    • 编写高质量代码概述
    • 面向对象
    • 设计原则
    • 设计模式-创建型
    • 设计模式-结构型
    • 设计模式-行为型(上)
    • 设计模式-行为型(下)
    • 浅析框架源码中的设计模式
    • 业务框架实战案例
  • MySQL 基础

    • MySQL - 数据库设计规范
    • MySQL - 必知必会
  • MySQL 进阶

    • MySQL - 基础架构
    • MySQL - InnoDB存储引擎
    • MySQL - InnoDB缓冲池
    • MySQL - 事务与锁
    • MySQL - 索引
    • MySQL - 查询执行计划
    • MySQL - 性能优化
  • Redis 系列

    • Redis入门 - 基础相关
    • Redis进阶 - 数据结构
    • Redis进阶 - 持久化RDB和AOF
    • Redis进阶 - 事件机制
    • Redis进阶 - 事务
    • Redis进阶 - 高可用高可扩展
    • Redis进阶 - 缓存问题
    • Redis进阶 - 性能调优
  • Java 基础

    • Java 基础 - 知识点
    • Java 基础 - 面向对象
    • Java 基础 - Q/A
  • Java 进阶 - 集合框架

    • Java 集合框架详解
  • Java 进阶 - 多线程与并发

    • Java 并发 - 理论基础
    • Java 并发 - 线程基础
    • Java 并发 - 各种锁
    • Java 并发 - 关键字 volatile
    • Java 并发 - 关键字 synchronized
    • JUC - CAS与原子操作
    • JUC - 锁核心类AQS
    • JUC - 锁接口和类简介
    • JUC - 并发容器简介
    • JUC - 通信工具类
    • JUC - Fork-Join框架
    • JUC - 线程池
  • Java 进阶 - JVM

    • JVM - 概述
    • JVM - 类加载机制
    • JVM - 内存结构
    • JVM - 垃圾回收机制
    • JVM - 性能调优
  • Maven系列

    • Maven基础知识
    • Maven项目构建
    • Maven多模块配置
  • Spring 框架

    • Spring 框架 - 框架介绍
    • Spring 框架 - IOC详解
    • Spring 框架 - AOP详解
    • Spring 框架 - SpringMVC详解
  • Spring Boot 系列

    • Spring Boot - 开发入门
    • Spring Boot - 接口相关
  • Spring Cloud 系列
  • Mybatis 系列

    • Mybatis - 总体框架设计
    • Mybatis - 初始化基本过程
    • Mybatis - sqlSession执行过程
    • Mybatis - 插件机制
    • Mybatis - 事务管理机制
    • Mybatis - 缓存机制
  • 业务常见问题

    • Java 业务开发常见错误(一)
    • Java 业务开发常见错误(二)
    • Java 业务开发常见错误(三)
    • Java 业务开发常见错误(四)
    • Java 业务开发常见错误(五)
    • Java 业务开发常见错误(六)
  • IDEA系列

    • IDEA 2021开发环境配置
    • IDEA 快捷键
  • Git系列

    • git status中文乱码
  • 其他

    • Typora+Picgo 自动上传图片
    • hsdis 和 jitwatch
  • 实用技巧
  • 收藏
  • 摄影
  • 学习
  • 标签
  • 归档
  • Java 基础

    • Java 基础 - 谈谈 Java 平台
    • Java 基础 - 知识点
      • 1 数据类型
        • 1.1 基本数据类型
        • 1.2 包装类
        • 1.2.1 缓存
        • 1.2.2 自动拆箱和装箱
        • 1.3 扩展
      • 2 String 字符串
        • 2.1 不可变
        • 2.2 String 设计为不可变的原因
        • 2.3 扩展
      • 3 操作符
        • 3.1 优先级
        • 3.2 赋值
        • 3.3 参数传递
        • 3.4 算术操作符
        • 3.5 自动递增和递减
        • 3.6 关系操作符
        • 3.7 逻辑操作符
        • 3.8 三元操作符
        • 3.9 字符串操作符 + 和 +=
        • 3.10 类型转换
        • 3.11 扩展
      • 4 控制执行流程
        • 4.1 if-else
        • 4.2 迭代
        • 4.2.1 while 与 do-while
        • 4.2.2 for
        • 4.2.3 Foreach
        • 4.3 return
        • 4.4 break 和 continue
        • 4.5 switch
      • 5 数组
        • 5.1 数组定义
        • 5.2 数组初始化及匿名数组
        • 5.3 数组遍历
        • 5.4 数组拷贝
        • 5.5 数组的比较
        • 5.6 数组排序
        • 5.7 数组与泛型
      • 6 枚举
      • 7 关键字
        • static
        • 静态变量
        • 静态方法
        • 静态代码块
        • 静态嵌套类
      • 8 异常
      • 9 反射
    • Java 基础 - 面向对象
    • Java 基础 - Q/A
    • 扩展 - int 和 Integer
    • 扩展 - final、finally、finalize
    • 扩展 - String、StringBuilder、StringBuffer
    • 扩展 - Exception 和 Error
    • 扩展 - 引用
    • 扩展 - 数值计算问题
    • 扩展 - 反射和动态代理
  • Java 进阶 - 集合框架

  • Java 进阶 - 多线程与并发

  • Java 进阶 - JVM

  • Java 进阶 - 版本特性

  • Java
  • Java 基础
Jason
目录

Java 基础 - 知识点

# Java 基础知识

# 1 数据类型

# 1.1 基本数据类型

8 种基本数据类型:

  • byte:1 字节(8位),默认值 0,-128 ~ 127
  • short:2 字节(16位),默认值 0,-32768 ~ 32767
  • int:4 字节(32位),默认值 0,-2147483648 ~ 2147483647
  • long:8字节(64位),默认值 0L,-9223372036854775808 ~ 9223372036854775807
  • float:4字节(32位),默认值 0.0f,单精度,大约 +-3.40282347E+38F(有效十进制位数为6~7位)
  • double:8字节(64位),默认值 0.0d,双精度,大约 +-1.79769313486231570E+-308(有效十进制位数为15位)
  • char:一个单一的 16 位 Unicode 字符,默认值'\u0000'(null), '\u0000' ~ '\uffff' 即 0 ~ 65535
  • boolean:默认值 false,true/false

当类的某个成员属性是基本类型,即使没有进行初始化,Java 也会确保它获得一个默认值。

# 1.2 包装类

包装类为基本类型 int 添加了属性和方法,丰富了基本类型的操作。

Java 为 8 种基本数据类型提供了 8 种对应的包装类, 一切都是对象:

  • Byte (byte)
  • Short (short)
  • Integer (int)
  • Long(long)
  • Float (float)
  • Double (double)
  • Character (char)
  • Boolean (boolean)

# 1.2.1 缓存

Java 为 6 种基本数据类型提供的缓存范围:

  • Byte 全部缓存,缓存所有, -128 到 127 数值
  • Short 缓存 -128 到 127 数值
  • Integer 默认缓存 -128 到 127 数值,可设置最大缓存值
  • Long 缓存 -128 到 127 数值
  • Character,缓存范围 ’\u0000’ 到 ‘\u007F’
  • Boolean 缓存 true/false 对应实例,确切说,只会返回两个常量实例 Boolean.TRUE/FALSE

以 Integer 包装类为例:

在 Java 5 进行了另一个改进: Integer 值缓存。通常构建 Integer 对象是调用构造函数 new Integer(1),直接 new 一个对象。但实际中发现大部分的数据操作都是集中在有限的、较小数值范围。因而 Java 5 中在 Integer 中新增了 valueOf() 静态方法,将某范围内的值缓存,之后直接从缓存中获取,无需创建新的对象。带来明显的性能改进。

由于存在缓存机制,所以在进行数值比较时可能出现:额

Integer a1 = 100;  // 会自动编译为:Integer.valueOf(100)
Integer a2 = Integer.valueOf(100);
a1 == a2; // 结果为 true;

Integer b1 = Integer.valueOf(200);
Integer b2 = Integer.valueOf(200);
b1 = b2; // 结果为 false

Integer c1 = new Integer(100);
Integer c2 = Integer.valueOf(100);
c1 == c2; // 结果为 false;c1 是 new 出来的对象是在堆中开辟了新地址,所以地址不相同,
1
2
3
4
5
6
7
8
9
10
11

# 1.2.2 自动拆箱和装箱

在 Java 5 中,引入自动装箱和自动拆箱语法糖(boxing / unboxing),Java 在编译阶段可以根据上下文,自动进行装拆箱,即生成相同的字节码,极大简化了相关编程。

以 Integer 包装类为例:int 到 Integer 和 Integer 到 int

Integer boxing = 1; // 实际会自动转换成 Integer.valueOf(1);  int 自动装箱为 Integer 包装类
int unboxing = integer++; // 实际会自动转换成 (integer ++).intValue(); Integer 自动拆箱为 int 基本类型
1
2

# 1.3 扩展

int 和 Integer 的区别

# 2 String 字符串

# 2.1 不可变

Java 中 String 是不可变的。实现方式:String 类使用关键字 final 修饰,且保存字符串内容的字符数组 value 也使用关键字 final 修饰,一旦初始化就不可变。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    ...
}
1
2
3
4
5
6

当需要改变字符串内容时,String 类的方法都会返回一个新的 String 对象;如果没有变化则只返回指向原对象的引用。

public class Imutable {
    public static String upcase(String s) {
        return s.toUpperCase();
    }
    
    public static void main(String[] args) {
        String q = "howdy";
        System.out.println(q); // howdy
        String qq = upcase(q);
        System.out.println(q); // howdy
        System.out.println(qq); // HOWDY
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

如上代码中,当把 q 传递给 upcase 方法时,实际传递的是引用的一个拷贝,引用别名为 s ,当方法 upcase 执行时才存在参数 s,在执行完后被销毁。upcase 方法返回了一个新的 String 对象。

# 2.2 String 设计为不可变的原因

参考:Why String is Immutable in Java? (opens new window)

  1. String Pool (缓存池)

    字符串是使用最广泛的数据结构,如果减少重复创建相同内容的字符串对象,可以有效降低内存消耗和对象创建开销。从而设计了 String Pool ,即缓存 String 对象字面值,由于 String 在 Java 中是不可变的,JVM 通过在池中仅存储相同 String 字面值的一个副本即可。即相同字面值的字符串变量的引用是同一个对象。

    String s1 = "hello world";
    String s2 = "hello world";
    String s3 = new String("hello world");
    
    System.out.println(s1 == s2);  // true
    System.out.println(s1 == s3);  // false
    
    1
    2
    3
    4
    5
    6

    Why_String_Is_Immutable_In_Java

  2. Security(安全性)

    Java 中 String 广泛用于存储敏感信息,如用户名、密码、网络连接等,比如 String 常常作为方法参数传递,如下面的代码:

    从不可信的调用者接收字符串参数 userName,对 userName 进行安全性检查后用于数据库更新操作。如果字符串是可变的,不可信的调用者也引用该字符串对象,随时可以变更字符串的值,导致 SQL 注入的情况。

    void criticalMethod(String userName) {
        // perform security checks
        if (!isAlphaNumeric(userName)) {
            throw new SecurityException(); 
        }
    	
        // do some secondary tasks
        initializeDatabase();
    	
        // critical task
        connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
          " WHERE UserName = '" + userName + "'");
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  3. Synchronization(线程安全)

    由于不可变性,使得 String 是线程安全的,即在多线程访问时,无法被改变,可在同时运行的多个线程之间共享。

  4. Hashcode Caching

    由于 String 常常会被应用于哈希实现,如 HashMap、HashTable、HashSet 的 Key 值,而 hashCode() 方法经常被调用,由于 String 的不可变性,使得 hashCode() 也是相同的,这样在第一次 hashCode() 调用期间计算并缓存哈希,并且从那以后返回相同的值即可,即只需要计算一次哈希值,提高了性能。

# 2.3 扩展

String、StringBuilder、StringBuffer

Java String Pool (opens new window)

# 3 操作符

Java 操作符接受一个或多个参数,并生成一个新值,有些炒作可能会改变操作数自身的值,这被称为“副作用”。

# 3.1 优先级

最简单的规则就是先乘除后加减,建议使用括号明确规定计算顺序。

# 3.2 赋值

赋值使用操作符“=”。意思是“取右边的值,把它复制给左边”。

基本数据类型存储了实际的数值,而并非执行一个对象的引用,所以在为其赋值的时候,是直接将一个地方的内容复制到另一个地方。如:

int a,b;
a = 10;
b = a;
a = 20;
// 此时 a = 20; b=10;
1
2
3
4
5

对一个对象进行赋值操作的时候,真正操作的是对对象的引用。所以“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方。如:

class Tank {
	int level;
}
public class Assignment {
    Tank t1 = new Tank();
    Tank t2 = new Tank();
    t1.level = 9;
    t2.level = 47;
    print("t1 level:" + t1.level);  // t1 level:9
    print("t2 level:" + t2.level);	// t2 level:47
    t1 = t2;	// t1 原来的引用被覆盖,不再被引用的对象会由垃圾回收器自动清理
    print("t1 level:" + t1.level);	// t1 level:47
    print("t2 level:" + t2.level);	// t2 level:47
    t1.level = 27;
    print("t1 level:" + t1.level);	// t1 level:27
    print("t2 level:" + t2.level);	// t2 level:47
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.3 参数传递

Java 的参数是以值传递的形式传入方法中,而不是引用传递。

以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。

public class Dog {
    String name;

    Dog(String name) {
        this.name = name;
    }

    String getName() {
        return this.name;
    }

    void setName(String name) {
        this.name = name;
    }

    String getObjectAddress() {
        return super.toString();
    }
}
public class PassByValueExample {
    public static void main(String[] args) {
        Dog dog = new Dog("A");
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        func(dog);
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        System.out.println(dog.getName());          // A
    }

    private static void func(Dog dog) {
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        dog = new Dog("B");
        System.out.println(dog.getObjectAddress()); // Dog@74a14482
        System.out.println(dog.getName());          // B
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容:

class Letter {
    char c;
}
public class PassObject {
    static void f(Letter y) {
        y.c = 'z';
    }
    public static void main(String[] args) {
        Letter x = new Letter();
        x.c = 'a';
        print("x.c: " + x.c); // x.c: a
        f(x);
        print("x.c: " + x.c); // x.c: z
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

方法 f(x) 参数传递了一个引用,所以在方法内修改参数对象,实际改变的是方法之外的对象。

# 3.4 算术操作符

加号(+)、减号(-)、除号(/)、乘号(*)、取模(%)。整数除法会直接去掉结果的小数位,而不是四舍五入。

同时进行运算和赋值操作:

int x;
x += 4;	// 等同于 x = x + 4
1
2

# 3.5 自动递增和递减

对于前缀递增和前缀递减(如 ++a 或 --a),会先执行运算,再生产值;而对于后缀递增和后缀递减(如 a++ 或 a--),会先生成值,再执行运算。

public static void main(String[] args) {
    int i = 1;
    print("i: " + i);  // i: 1
    print("++i: " + ++i); // ++i: 2
    print("i++: " + i++); // ++i: 2
    print("i: " + i); // i: 3
    print("--i: " + --i); // --i: 2
    print("i--: " + i--); // i--: 2
    print("i: " + i);  // i: 1
}
1
2
3
4
5
6
7
8
9
10

# 3.6 关系操作符

关系操作符包括小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于适用于所有基本数据类型,而其他比较复杂不适用于 boolean 类型。

关系操作符 == 和 != 也适用于所有对象,但比较的是对象的引用。要想比较对象的实际内容是否相同,必须使用所有对象都适用的特殊方法 equals()

public static void main(String[] args) {
	Integer a = new Integer(47);
	Integer b = new Integer(47);
	System.out.println(a == b) // false
	System.out.println(a != b); // true
	System.out.println(a.equals(b); // true
}
1
2
3
4
5
6
7

如果使用 equals 方法比较自己的类时,需要根据实际情况重写 equals 方法。因为 equals 方法默认是比较引用。

# 3.7 逻辑操作符

逻辑操作符 与(&&)、或(||)、非(!)能根据参数的逻辑关系,生成一个布尔值 true 或 false。

当使用逻辑操作符是,会遇到一种”短路“现象。一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下部分。

// 如果 test(0) 返回 false;则 a = false,不会再执行 test(1) 和 test(2)
boolean a = test(0) && test(1) && test(2);
1
2

# 3.8 三元操作符

三元操作符也称为条件操作符,引入目的是提高编程效率,但若频繁使用容易产生可读性差问题。

boolean-exp ? value0 : value1;
1

# 3.9 字符串操作符 + 和 +=

如果表达式以一个字符串起头,那么后续所有操作数都必须是字符串型。

public static void main(String[] args) {
    int x = 0, y = 1, z = 2;
    String s= "x, y, z ";
    print(s + x + y + z); // x, y, z 012
    print(x + " " + s); // 0 x, y, z 
    s += "(summary) = "; 
    print(s + (x + y + z)); // x, y, z (summary) = 3
    print("" + x); // 0
}
1
2
3
4
5
6
7
8
9

# 3.10 类型转换

  • 如果两个操作数中有一个是 double 类型,另一个操作数就会转换为 double 类型
  • 否则,如果其中一个操作数是 float 类型,另一个操作数将会转换为 float 类型
  • 否则,如果其中一个操作数是 long 类型,另一个操作数将会转换为 long 类型
  • 否则,两个操作数都将被转换为 int 类型

image10

double 或 float 类型转换为 int 时,总是对数字执行截尾,如果想要得到舍入结果,需要使用 java.lang.Math 中的 round() 方法。

有时需要将 double 或 long 数字转换为 int 类型,由于存在面临信息丢失的情况,需要通过强制类型转换实现:

double x = 9.997;
int nx = (int) Math.round(x);  // 10。Math.round(x) 返回 long 类型;
1
2

# 3.11 扩展

数值计算问题

# 4 控制执行流程

# 4.1 if-else

If-else 语句是控制程序流程的最基本形式,其中的 else 是可选的。

if (Boolean-expression) {
    statement;
}

if (Boolean-expression) {
    statement;
} else {
    statement;
}
1
2
3
4
5
6
7
8
9

Boolean-expression 布尔表达式必须产生一个布尔结果,true 或 false。

# 4.2 迭代

# 4.2.1 while 与 do-while

statement 语句会重复执行,直到起控制作用的布尔表达式 Boolean-expression 得到 false 值为止。

while (Boolean-expression) {
    statement;
}


do {
  statement;  
} while (Boolean-expression);

1
2
3
4
5
6
7
8
9

while 和 do-while 唯一的不同之处在于 do-while 的 statement 语句一定会执行一次,即便布尔表达式第一次结果为 false;而 while 循环结构,如果布尔表达式第一次为 false,则 statement 语句根本不会被执行。

无穷循环:while(true)

# 4.2.2 for

for 循环在第一次迭代前要进行初始化,随后进行布尔表达式测试,如果布尔表达式结果为 true,则执行 statement 语句,在 statement 语句执行结束时,进行某种形式的 ”进步“ 操作。如果布尔表达式结果为 false,则结束整个循环。

for (initialzation; Boolean-expression; step) {
    statement;
}

// 变量 i 的作用域为 for 循环
for (int i = 1; i < 10; i++) {
    System.out.println(i);
}
1
2
3
4
5
6
7
8

逗号操作符:只有 for 循环中才能使用;使用逗号操作符定义多个变量,但变量类型必须相同

for (int i= 1, j = i + 10; i < 5; i++, j = i * 2) {
	System.out.println("i = " + i + " j = " + j);
}
1
2
3

无穷循环:for(;;)

# 4.2.3 Foreach

Java 5 引入了一种新的更加简洁的 for 语句,用于遍历数组和容器

List<String> stringList = new ArrayList();
stringList.add("a");
stringList.add("b");
stringList.add("c");

for (String item : stringList) {
    System.out.println(item);
}
1
2
3
4
5
6
7
8

# 4.3 return

两方面用途:

  • 指定一个方法返回什么值,假设它没有 void
  • 导致当前的方法退出,并返回那个值

在返回 void 的方法中没有 return 语句,那么该方法的结尾会有一个隐式的 return

# 4.4 break 和 continue

区别:

  • break 用于退出整个循环,不再执行循环的语句;
  • continue 则用于停止当前的迭代,然后退回到循环起始位置,然后执行下一个迭代

# 4.5 switch

switch 是实现多路选择的一种干净利落的方法。根据整数表达式的值,switch 语句可以从一系列代码中选出一段去执行。

switch (integral-selector) {
    case integral-value1: statement; break;
    case integral-value2: statement; break;
    case integral-value3: statement; break;
    default: statement;
}
1
2
3
4
5
6

integral-selector 整数表达因子(必须是 int 或 char 或 enum),是一个能够产生整数数值的表达式,将结果与每个 case 的 integral-value 进行比较,如果符合则执行相应的 statement;如果没有相符的,则执行 default 的 statement;

case 的 break 是可选的,如果没有则会继续执行后面的 case 语句。

# 5 数组

# 5.1 数组定义

数组是一种数据结构,用来存储统一数据类型值的集合。通过整型下标可以访问数组的每一个值。

无论使用哪种类型的数组,数值标识符其实只是一个引用,指向在堆中创建的一个真实的对象,这个(数组)对象用以保存指向其他对象的引用

声明数组:int[] a 或 int a[],通常使用第一种方式。

初始化数组:int[] a = new int[100],该语句创建了一个可以存储 100 个整数的数组。

注意:

  • 创建一个数值数组时,所有元素都初始化为 0;char 数组的元素初始化为 (char)0;boolean 数组的元素初始化为 false;对象数组的元素初始化为一个特素值 null,表示还未存放任何对象;
  • 数组 length 表示数组的大小,而不是实际保存元素的个数

一旦创建了数组,就不能再改变它的大小。如需在运行过程中扩展数组大小,则应使用 ArrayList 列表

# 5.2 数组初始化及匿名数组

// 创建对象,再初始化
int[] a = new int[2];
a[0] = 2;
a[1] = 3;
// 创建对象且同时初始化,数组大小就是初始值的个数
int[] smallPrimes = {2, 3};

// 匿名数组, 重新初始化一个数组
smallPrimes = new int[] {2, 3, 13};
1
2
3
4
5
6
7
8
9

# 5.3 数组遍历

使用 for 或 foreach 语句遍历数组:

for (int i= 0; i < a.length; i++) {
    System.out.println(a[i]);
}

for (int item : a) {
    System.out.println(item);
}
1
2
3
4
5
6
7

# 5.4 数组拷贝

int[] smallPrimes = {2, 3};
int[] luckNumbers = smallPrimes;
luckNumbers[1] = 13;
System.out.println(Arrays.toString(smallPrimes)); // [2, 13]
System.out.println(Arrays.toString(luckNumbers)); // [2, 13]
1
2
3
4
5

luckNumbers = smallPrimes 使得两个变量引用同一个数组,并没有创建新的数组。

如果想创建新的数组则使用 Arrays.copyOf() 方法,如果第二个参数值小于原始数组长度,则只拷贝最前面的数据元素:

int[] smallPrimes = {2, 3};
int[] luckNumbers = Arrays.copyOf(smallPrimes, smallPrimes.length);
System.out.println(smallPrimes == luckNumbers); // false
System.out.println(Arrays.toString(smallPrimes)); // [2, 3]
System.out.println(Arrays.toString(luckNumbers)); // [2, 3]
luckNumbers[0] = 12;
System.out.println(Arrays.toString(smallPrimes)); // [2, 3]
System.out.println(Arrays.toString(luckNumbers)); // [12, 3]
1
2
3
4
5
6
7
8

Arrays.copyOf() 方法使用了 System.arraycopy() 方法实现数组复制。System.arraycopy() 不会执行自动包装和自动拆包,所以两个数组必须具有相同的确切类型。

public static int[] copyOf(int[] original, int newLength) {
	int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
           Math.min(original.length, newLength));
    return copy;
}
1
2
3
4
5
6

注意:使用 System.arraycopy() 方法复制数组,当复制的是对象数组,只是复制了对象的引用,而不是对象本身的拷贝。称为 浅复制 或 浅拷贝

# 5.5 数组的比较

Arrays 类为不同数据类型提供了重载后的 equals() 方法,用来比较整个数组。

数组相等的条件:元素个数必须相等;并且对应位置的元素也相等;

比较数组元素是否相等,是使用元素的 equals() 方法。对于基本类型,需要使用基本类型的包装器类的 equals() 方法;如果是自建的类对象,则可以通过重写类 equals() 方法实现比较内容是否相等,而非父类 Object 默认的 equals(只比较引用是否相等)

# 5.6 数组排序

排序必须根据对象的实际类型执行比较操作。

程序设计的目标是:“将保持不变的事物与会发生变化的事物相分离”,而数组排序不变的是通用的排序算法,变化的是各种对象相互比较的方式。通过策略模式将“会发生变化的代码”封装在单独的类中(策略对象),可以将策略对象传递给总是相同的排序代码完成其排序算法。

Arrays 类为基本数组类型分别重载了 sort() 方法,使用 Dual-Pivot 快排(双轴快速排序算法) 或 并行排序算法 实现数组的排序功能;

Java 为对象数组排序提供两种方式来实现比较功能。

public class Arrays {
     private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
    
    // 第一种
    public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            // 兼容 1.6 之前的旧版本,采用冒泡排序和归并排序
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }
    
    // 第二种
    // 参数 a 没有实现 Comparable 接口;所以需要第二个参数接收 Comparator 策略
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
    
    ...
    
    // 双轴快速排序算法(单线程)O(n log(n))
    public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }
    
    ...
    // 如果小于 MIN_ARRAY_SORT_GRAN 2的13次方即8192, 使用单线程的双轴快速排序算法
    // 否则使用并行排序算法
    public static void parallelSort(byte[] a) {
        int n = a.length, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1);
        else
            new ArraysParallelSortHelpers.FJByte.Sorter
                (null, a, new byte[n], 0, n, 0,
                 ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                 MIN_ARRAY_SORT_GRAN : g).invoke();
    }
    
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  1. 实现 java.lang.Comparable 接口,该接口只用一个 compareTo() 方法,此方法接收一个 Object 类型的参数。如果当前对象小于它则返回 -1;如果相等则返回 0;如果大于返回 1;

    常见类都实现了 java.lang.Comparable 接口,如果基本类型的包装类:

    public final class Integer extends Number implements Comparable<Integer> {
        ...
        public int compareTo(Integer anotherInteger) {
            return compare(this.value, anotherInteger.value);
        }
        
        public static int compare(int x, int y) {
            return (x < y) ? -1 : ((x == y) ? 0 : 1);
        }
        ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    对象数组则调用 sort(Object[] a) 方法,ComparableTimSort(与 TimSort 算法一样)直接利用的是实现了Comparable 接口的对象来进行比较操作。即 Object[] a 必须实现 Comparable 接口,否则将抛出 ClassCastException

  2. 创建一个单独的类实现 java.util.Comparator 作为比较策略,调用 Arrays 的 sort(T[] a, Comparator<? super T> c) 并将传递比较策略。Comparator 有两个方法 compare() 和 equals()

# 5.7 数组与泛型

class ClassParameter<T> {
    public T[] f(T[] arg) { return arg; }
}

class MethodParameter {
    public static <T> T[] f(T[] arg) { return arg; }
}

public class ParameterizedArrayType {
    public static void main(String[] args) {
        Integer[] ints = {1, 2, 3, 4, 5};
        Double[] doubles = {1.1, 2.2, 3.3, 4.4, 5.5};
        Integer[] ints2 = new ClassParameter<Integer>().f(ints);
        Double[] doubles2 = new ClassParameter<Double>().f(doubles);
        ints2 = MethodParameter.f(ints);
        doubles2 = MethodParameter.f(doubles);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6 枚举

# 7 关键字

# static

  • 静态只能访问静态
  • 非静态既可以访问非静态,也可以访问静态

# 静态变量

  • 静态变量在类被初次加载时初始化
  • 静态变量在类的任何对象实例化前被初始化
  • 静态变量在类的任何静态方法执行之前被初始化

**被 static 修饰的类变量独立于该类的任何对象,而是被所有类的对象所共享。**即 static 变量值在类加载的时候分配空间,优先于类的实例对象,在内存中只有一个副本。而非 static 变量是在类实例化对象时初始化,每个对象都有一个副本。

class StaticTest {
	static int i = 47;
}
1
2
3

即使创建两个 StaticTest 对象,StaticTest.i 也只有一份存储空间,这两个对象共享一个 i

StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
st1.i == st2.i;  // true
StaticTest.i++;
st1.i == 48;  // true
st2.i == 48;  // true
1
2
3
4
5
6

使用类名是引用 static 变量的首选方式,不仅强调变量的 static 结构,而且在某些情况有利于编译器进行优化。

# 静态方法

  • 静态方法在类被初次加载时初始化
  • 静态方法属于类而不是类的对象,在不创建任何对象是调用
  • 静态方法中只能访问静态变量和静态方法
  • 静态方法中不能使用 this 或 super 关键字
  • 抽象方法不能是静态的
  • 静态方法不能被覆盖

static 方法的一个重要用法就是在不创建任何对象的前提下就可以调用它。在静态方法中不能访问非静态变量和非静态方法(它们都依赖于具体的对象才能够被调用)。

class Incrementable {
	static void increment() {
        StaticTeset.i++;
    }
}
1
2
3
4
5

在单例模式中常常使用静态方法来创建类的实例化对象。或一些工具类如:Arrays.sort()、Math.random()

# 静态代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static 块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块,并且只会执行一次。很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。

class Test {
	static Map<Integer, String> month;
	static {
		month = new HashMap<>();
    	month.put(1, "一月");
    	month.put(2, "二月");
    	month.put(3, "三月");
    	...
	}
}
1
2
3
4
5
6
7
8
9
10

# 静态嵌套类

  • 在 Java 中,外部类不能是静态的,嵌套类才能是静态类
  • 静态嵌套类不需要引用外部类的内容
  • 静态嵌套类不能访问外部类的非静态成员

将只在一个地方使用的类嵌套在一个类中,增加了封装性、可读性、可维护性。

Java 源码中 static 关键字经常被使用,如 Integer 源码:

public final class Integer extends Number implements Comparable<Integer> {
	@Native public static final int	MIN_VALUE = 0x80000000;
    @Native public static final int MAX_VALUE = 0x7fffffff;
    ...
    
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    ...
    
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        // 初始化缓存
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

由上面源码可见,static 关键字通常与 final 关键字同时使用。

# 8 异常

# 9 反射

#Java
上次更新: 2025-02-24
Java 基础 - 谈谈 Java 平台
Java 基础 - 面向对象

← Java 基础 - 谈谈 Java 平台 Java 基础 - 面向对象→

最近更新
01
开始
01-09
02
AI工具分享
01-09
03
AI 导读
01-07
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Jason Huang | 闽ICP备2025088096号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式