浮点数运算

由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数:

  1. // 比较x和y是否相等,先计算其差的绝对值:
  2. double r = Math.abs(x - y);
  3. // 再判断绝对值是否足够小:
  4. if (r < 0.00001) {
  5. // 可以认为相等
  6. } else {
  7. // 不相等
  8. }

浮点数在内存的表示方法和整数比更加复杂。Java的浮点数完全遵循IEEE-754标准,这也是绝大多数计算机平台都支持的浮点数标准表示方法。

即使同为数值类型,仍然有等级之差,其中
double>float> long>int>short
运算中,同时出现两项时,结果会返回高级的数类型

溢出

整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:

  • NaN表示Not a Number
  • Infinity表示无穷大
  • -Infinity表示负无穷大

例如:

  1. double d1 = 0.0 / 0; // NaN
  2. double d2 = 1.0 / 0; // Infinity
  3. double d3 = -1.0 / 0; // -Infinity

这三种特殊值在实际运算中很少碰到,我们只需要了解即可。

强制转型

可以将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值。例如:

  1. int n1 = (int) 12.3; // 12
  2. int n2 = (int) 12.7; // 12
  3. int n2 = (int) -12.7; // -12
  4. int n3 = (int) (12.7 + 0.5); // 13
  5. int n4 = (int) 1.2e20; // 2147483647

如果要进行四舍五入,可以对浮点数加上0.5再强制转型

  1. public class Main {
  2. public static void main(String[] args) {
  3. double d = 2.6;
  4. int n = (int) (d + 0.5);
  5. System.out.println(n);
  6. }
  7. }

数组

  1. public class Main {
  2. public static void main(String[] args) {
  3. // 5位同学的成绩:
  4. int[] ns = new int[5];
  5. int[] ns2 = new int[] { 68, 79, 91, 85, 62 };
  6. int n = 5;
  7. System.out.println(ns[n]); // 索引n不能超出范围
  8. }
  9. }

数组大小一旦设置,不可改变

输出

  1. public class Main {
  2. public static void main(String[] args) {
  3. double d = 3.1415926;
  4. System.out.printf("%.2f\n", d); // 显示两位小数3.14
  5. System.out.printf("%.4f\n", d); // 显示4位小数3.1416
  6. }
  7. }

image.png

字符串相等判断

==对于引用是判断引用是否相同,因此不能用来判断字符串(返回了引用)
字符串相等判断:

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s1 = null;
  4. if (s1 != null && s1.equals("hello")) {
  5. System.out.println("hello");
  6. }
  7. }
  8. }

先判空,如果是空会报错

数组遍历

  1. public class Main {
  2. public static void main(String[] args) {
  3. int[] ns = { 1, 4, 9, 16, 25 };
  4. for (int n : ns) {
  5. System.out.println(n);
  6. }
  7. }
  8. }

方法

  1. public class Main {
  2. public static void main(String[] args) {
  3. Person p = new Person("Xiao Ming", 15);
  4. System.out.println(p.getName());
  5. System.out.println(p.getAge());
  6. }
  7. }
  8. class Person {
  9. private String name;
  10. private int age;
  11. public Person(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. public String getName() {
  16. return this.name;
  17. }
  18. public int getAge() {
  19. return this.age;
  20. }
  21. }

类的构造方法和类名相同
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false
可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分

super

super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName。例如:

  1. class Student extends Person {
  2. public String hello() {
  3. return "Hello, " + super.name;
  4. }
  5. }

如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

判断是否为某个类

  1. Person p = new Person();
  2. System.out.println(p instanceof Person); // true
  3. System.out.println(p instanceof Student); // false

关于多态

关于多态,和C++类似,实行动态编译,即当代码运行后会根据类的隶属决定其子类,进而确定使用哪个子类的方法(重载)。
类似于TypeScript的接口扩展,某种程度上为config的设置和类型的自动识别提供了便利性。

覆写Object方法

因为所有的class最终都继承自Object,而Object定义了几个重要的方法:

  • toString():把instance输出为String;
  • equals():判断两个instance是否逻辑相等;
  • hashCode():计算一个instance的哈希值。

    1. class Person {
    2. ...
    3. // 显示更有意义的字符串:
    4. @Override
    5. public String toString() {
    6. return "Person:name=" + name;
    7. }
    8. // 比较是否相等:
    9. @Override
    10. public boolean equals(Object o) {
    11. // 当且仅当o为Person类型:
    12. if (o instanceof Person) {
    13. Person p = (Person) o;
    14. // 并且name字段相同时,返回true:
    15. return this.name.equals(p.name);
    16. }
    17. return false;
    18. }
    19. // 计算hash:
    20. @Override
    21. public int hashCode() {
    22. return this.name.hashCode();
    23. }
    24. }

    final

    类中,希望不被改变的特性。
    继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override:
    class Person {
    protected String name;
    public final String hello() {
    return “Hello, “ + name;
    }
    }

Student extends Person {
// compile error: 不允许覆写
@Override
public String hello() {
}
}

如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承:
final class Person {
protected String name;
}

// compile error: 不允许继承自Person
Student extends Person {
}

对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。例如:
class Person {
public final String name = “Unamed”;
}

对final字段重新赋值会报错:
Person p = new Person();
p.name = “New Name”; // compile error!

可以在构造方法中初始化final字段:
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}

这种方法更为常用,因为可以保证实例一旦创建,其final字段就不可修改。