Loading...
Navigation
Table of Contents

Core Java NotesCode

Notes of Core Java Volume I--Fundamentals.
Java SE 7 (2011.07.28 ~ 2014.03.18)
Start at 2017-06-07 16:36:59

真正喜欢Java的人, 都会强调Java能给人'内心宁静的感觉'. 可是什么是'内心宁静的感觉'呢? 基本上我是一个写程序很吹毛求疵的人, 如果只是为了完成某个功能, 能很简单地写出程序. 但我希望程序当中每一行代码, 每一个符号都能让我安心舒适. 安心舒适来自于程序整体结构的合理, 来自于对象划分的正确, 来自于处理流程的顺畅, 甚至来自于变量名字的满意. 在一个软件投入运行一段时间以后, 再组织力量, 重新进行整体设计和编程, 都无非是为了追求那种'内心宁静的感觉'. - 翁恺

Shining

  • p69的for循环的流程图要理解清楚

  • 除了while(){}, 还有do{} while();这种好用的写法

  • ++ii++效能更高, 因为没有缓存变量; i = i++;没有变化

  • instanceof运算符是一个双目运算符, 其左边的表达式是一个对象, 右边的表达式是一个类, 如果左边的对象是右边的类或者子类创建的对象, 则运算结果为true, 否则为false

  • Object.getClass()方法则是返回对象的class类型, 如果隐式参数为null, 会报错NullPointerException, 而instanceof则只是返回false

3. 基本程序设计结构

驼峰命名 所有类(文件)命名应该首字母大写: FirstExample

类由类域和类方法组成, Java中所有的函数都属于某个类的方法, 如main方法

函数调用 就是指调用一个对象的方法, 如System.out对象的println()方法; 通用语法为object.method(parameters)

数据类型 一共8种基本类型(primitive type), 4种整型byte short int(4字节 -2147483648~2147483647) long, 2种浮点型, 字符类型char(2字节, UTF-16), 布尔类型; 以下是一些特殊表示法

int a = 0b1001; //二进制表示法, 值为9
long a = 2_147_483_648L; //可以加下划线
float a = 3.14F;
char a = '\u2122'; //Unicode编码

final double MAX = 3.1; //final常量, 大写

位运算符 位运算符和关系运算符原理不同, 它不是短路的

  • a / b, 只有a和b都是整型的时候, 结果才是整型; a % b, a和b都必须是整型

  • a >> b, 将a右移b位, 高位用符号填充; a << b, 将a左移b位, 低位用0填充; a >>> b, 高位用0填充; 没有a <<< b;

  • a & b, a和b每一位做与操作, 还有或操作|, 异或操作^, 非操作~

  • a >>= b等价于a = a >> b, 也有a &= b

数学函数

double y = Math.sqrt(x);
import static java.lang.Math.*; //导入就不用Math.前缀了, 注意static
int z = (int) round(y); //最接近的整数, 但round()返回的是long
double r = ramdom(); //随机数0<=r<1

这里, println()方法存在于System.out对象中, 而Math.sqrt()是一个静态方法, 存在于Math类中.

String字符串

  • String类中, 不提供修改字符串的方法, 指向相同字符串的变量用的是同一块内存区域, 所以也无法用关系运算符判断两个字符串的关系; 但是py可以

  • Java字符串由char序列组成, 每个char是采用Unicode代码点(大多数字符用1个, 辅助字符用2个)表示的, 有些函数可以查看特定位置的代码点

String a = "hel" + "lo";
String a = "word" + 666; //可以将字符串和非字符串拼接
String b = a.substring(0,3); //substring()方法. 左闭由开区间, 和py一样
boolean t = "hel".equals(a); //检测字符串是否相等, 不要用 == 来检测
if (a != null && a.length() != 0) //先看a是否指向了一个对象, 再看是否为空串

控制台IO

import java.util.*
System.out.printf("%8.2f", x);//沿用了C中的格式化输出

Scanner a = new Scanner(System.in);//构造一个输入流对象
String s = a.nextLine();

输入需要构造一个Scanner对象, 想要读取字符串, 调用a.next()(空白符为分隔符)或者a.nextLine(); 想读取数字, 调用a.nextInt()a.nextDouble()

文件IO

import java.io.*;
import java.nio.file.Paths;
import java.util.*;
public static void main(String[] args)
throws IOException{ //必须输出异常!
PrintWriter a = new PrintWriter("f"); //a是一个类似于System.out的对象
a.print("hello\n"); //以LF结尾
a.close(); //必须关闭才能保存

Scanner b = null; //可以先这么声明
b = new Scanner(Paths.get("f")); //加上Paths来标识路径, 类似System.in
String s = a.nextLine();
int num = a.nextInt();
System.out.println(s); }

大数值 java.math中的BigIntegerBigDecimal类实现了任意精度的整数和浮点数的运算

import java.math.BigInteger;
String s = "12345678987654321";
BigInteger a = new BigInteger(s);
BigInteger b = BigInteger.valueOf(16); //valueOf()可接收long或者int
BigInteger c = a.add(b); //c=a+b

还有a.subtract(b), a.multiply(b), a.divide(b), a.mod(b), 等等

数组array[a're] 创建一个数字数组时, 所有元素初始化为0, boolean数组为false, 对象数组为null

  • 对于数组或者有interable接口的对象, 可以用for each循环 for (int i: a), 和py里面的用法相似

  • 想获得数组的长度, 可以用a.length(); 数组一旦创建, 就不可改变长度, 和String一样; 数组列表array list可以改变数组长度

int[] a = {1, 3, 2};
int[] a = new int[] {1, 3, 2};
import java.util.*;
Arrays.sort(a); //Arrays类里的快排函数, 升序
int l = a.length; //数组不是length(), 不同于String

String b = Arrays.toString(a); //数组变成字符串
int[] a = new int[0]; //可以初始化长度为0, 这和null不同
int [] b = a; //b和a引用的是同一个数组
int[][] a = new int[2][]
a[0] = new int[1];
a[1] = new int[2]; //不规则数组
.print(Arrays.deepToString(a)); //打印多维数组

命令行参数 注意到main的参数为args数组, 若命令行为java test -a lol, 则args[0]"-a", args[1]"lol"

4. 对象和类

Object类 所有的类都源于超类Object, 见第五章

类之间 类之间的关系有依赖(uses-a), 聚合(has-a), 继承(is-a)

对象 在Java中不用很担心找不到对象, 任何对象变量的值都是对一个对象的引用, new操作符也算返回的引用; 所有的对象都是在堆中构造的

类构造器

  • 构造器与类同名, 可重载, 无返回值, 伴随new一起调用

  • 重载:当且仅当类中没有构造器, 系统会提供一个无参数构造器, 使得所有的实例域为默认值

  • 调用构造器:如果某个构造器第一个语句形如this(...);, 那么编译器会根据参数类型调用这个类的另一个构造器

  • 调用构造器的步骤:

    • 所有数据初始化为0, false和null
    • 按照在类中出现的次序, 执行初始化语句和初始化块
    • 如果构造器第一句调用了另一个构造器, 那么执行另一个构造器
    • 执行这个构造器
import java.util.Date;
public class Employee{
String name;
static String major = "CS"; //可以在构造器外赋默认值
Date hireDay; //注意命名习惯

{//初始化块, 注意其位置
  name = "jiaxi";
  major = "EE";
  //依据顺序, 会覆盖CS
  .print(major);
}

static {//静态初始化块
major = "CS2"; //只能加入静态变量
System.out.print(major);
}

public Employee(String aName){
  name = aName; //不要构造和"实例域"重名的临时变量
  this.hireDay = ...; //this叫"隐式参数"
  .print("initialize");}

public Employee(String name, int age){ //如果非得实例域重名, 就会覆盖
  this(name); //调用构造器
  .print("age is useless");}

public Date getDate(){
  return hireDay.clone();} //返回对可变对象的引用, 应该先克隆

public static void main(String[] args){ //只有静态main方法会自动执行
  System.out.print("main");
  Employee e = new Employee("jiaxi");}}
//以上程序会依次输出 #0 #1 #2
//可以看出初始化块和构造器都能进行打印

点击编译后, 会先从上往下初始化静态域和块(不包括方法, 因为得调用才行), 所以打印CS2; 接着自动从main()方法进入, 打印main, 由于创建了对象e, 所以自上而下执行(非静态?!)初始化语句, 初始化块和构造器(详见类构造器), 打印EEinitialize

注:好像把初始化块, main函数放在最上面也没问题, 类里面块和方法顺序不无所谓?

实例域/方法 指类的实例的域, 方法

  • 是否开放:private/public, 公有, 所有类都能访问; 私有, 当前类能访问(子类也不行)

  • 是否共享:static, 静态域/方法, 不需要对象, 直接可以类名调用, 也推荐只使用类名访问; 所有类共享同一个静态域的值, 如Math.PI; 静态main方法在程序运行时会自动执行

  • 是否修改:final, 用来标识不可修改的域

按值调用 方法的参数有两种类型:基本数据类型, 对象的引用, 不管是哪种, 方法被调用时总是值传递(拷贝一份参数的值), 然后对拷贝进行操作

public static void swap(int x, int y){
  int tmp = x;
  x = y;
  y = tmp;}
swap(a, b);
//无效, 其实交换的是x和y

一个包指的范围就类似于对应文件夹的范围, 想要import一个包, 需要把包和测试文件放在一个文件夹, 或者把包/JAR文件在的目录加入系统变量中的CLASSPATH中

  • 要将类放入包中, 可在开头写package com.jiaxi.a;, 同时这个类文件要放在相应的目录下; 不加这个语句则会放在在"默认包"中

  • 单类型导入import java.io.File;; 按需类型导入import java.io.*;; 静态成员导入import java.lang.Math.*;, 注意lang(language包)其实默认是导入的, 不用写import, 其它包就必须写

5. 继承

5.1 超类和子类

超类子类 使用关键字extends来使用继承, 子类和超类是is-a的关系, 子类对象肯定是一个超类对象

super关键字

public class Manager extends Employee{
private double bonus; //新的域
public Manager(String name, int age){
  super(name, age);
//使用super调用构造器, 如果不显示调用, 编译器
//会自动调用超类的无参数构造器; 若没有, 则报错
  bonus = 0;}
public double getSalary(){
  //子类方法覆盖超类方法
  double tmp = super.getSalary();
  //使用super调用超类的public方法
  return tmp + bonus;}}
  //manager的薪水要加上奖金

覆盖/多态/动态绑定

  • 子类引用可以赋值给超类变量, 超类引用不能赋值给子类变量! 一个变量可以指向多种实际类型(一个类和其各种子类)的现象叫多态

  • 程序运行时, 虚拟机一定调用和变量引用的对象的实际类型最匹配的方法(名字+参数), 否则会递归地考虑其超类的方法. 能根据具体引用来选择调用方法的现象叫动态绑定

  • 覆盖方法时, 子类方法的可见性一定要高于等于超类方法的可见性, 超类是public, 子类也必须pubic

Manager a = new Manager(...);
//假设底薪1000
a.setBonus(500);
Employee b = a;
//子类引用赋值给超类变量
.println(a.getSalary());
//1_500元一个月
.println(b.getSalary());
//也是1_500元一个月, 多态
b.setBonus(100); //ERROR
//报错, 因为b仍然是Employee对象

强制类型转换 这里指引用而不是基本类型的转换, 转换能否进行, 取决于实际类型是否和目标类型相同; 转换的唯一目的是为了恢复实际类型的方法

Employee realEmployee = new Employee(...);
Employee realManager = new Manager(...);
//使用了多态, 暂时屏蔽了a的子类方法
Manager a = (Employee) realManager;
//强制类型转换, 恢复这个实际类型的全方法
Manager a = (Employee) realEmployee; //WRONG
//报错! 内存中的Employee对象是不可以被转换的

//正确写法如下
Manager b;
if (b instanceof Manager)
//利用instanof判断实际类型是否为Manager
  b = a;

阻止继承 final class表示不允许定义子类; final method()表示此方法不允许被覆盖. String类就是final类, 如果有一个String的引用, 它引用的一定是一个String对象, 而不是其他对象.

抽象类

  • 继承中, 上层的类更具有通用性, 因此可以做一个抽象; 抽象类中可以包含一些抽象或非抽象方法

  • 抽象类不能用new生成实例; 但是可以声明一个抽象类变量指向子类对象, 实际上抽象方法的作用就是如此, 因为如果不声明抽象方法, 就不能访问子类中同名/覆盖的方法了

public abstract Person(){
//用abstract关键字定义抽象类
public abstract String getDescription();}
//用abstract关键字定义抽象方法
public Employee extends Person(){
//非抽象子类继承了抽象类
public String getDescription(){
  return "an Employee"}}
//子类覆盖抽象类方法

访问修饰符

  • 仅对本类可见:private; 注意, 子类也不可访问

  • 对所有类可见:public

  • 对本包和所有子类可见:protected, 注意, 子类方法可以访问子类对象中包含的父类中的域, 不能访问其它某个父类对象的域

  • 对本包可见:无修饰符

5.2 Object类

equals方法 Object类中的equals()方法是判断两者是否有相同引用; 可以在子类中覆盖此方法;

//超类Employee类中的equals方法
public boolean equals(Object aObject){
if (this == aObject) return true;
if (aObject == null) return false;
//直接剔除空对象, 并防止下面的getClass报错
if (getClass() != aObject.getClass())
  return false;
//现在知道aObject是Employee非空对象了
Employee other = (Employee) aObject;
//强制类型转换
return name == other.name;}

//子类Manager类中的equals方法
public boolean equals(Object aObject){
if (!super.equals(aObject)) return false;
//先看用超类的判断一下
Manager other = (Manager) aObject;
//再增加一些判断
return bonus == other.bonus;}

注意, 在上述例子中, 是严格按照类, 所有域都完全一致做判断的. 实际上, 如果我们只根据姓名判断的话, 就不能用getclass了, 因为无法将Employee对象和Manager对象做比较, 但是可以用instanceof. (这里和书上讲的instanceof违反对称性不是同一件事)

//超类Employee类中的equals方法
public boolean equals(Object aObject){
if (this == aObject) return true;
if (aObject == null) return false;
//直接剔除空对象, 并防止下面的getClass报错
if (!aObject instanceof Employee)
  return false;
Employee other = (Employee) aObject;
//先转换, 才能调用public方法
return name.euqals(other.getName());}
//只能调用方法来匹配, 注意String也得用equals

Last updated on Jan 30, 2020.