Java 学习笔记

ShiftWatchOut,java

Java是一门具有编译时和运行时的语言,介于编译型语言和解释型语言之间。javac 先将代码编译成一种“字节码”(.class 文件),再放到针对不同平台编写的虚拟机上(JVM)运行,实现了“一次编写,到处运行”的效果。

1 C-like 语言所共有基础

1.1 值声明

类型 标识符 = 右值;

类型包括基础类型和复合类型(引用类型):

  1. 基础类型
类型占据字节取值范围对应的引用类型
byte1-128 ~ 127java.lang.Byte
short2-32768 ~ 32767java.lang.Short
int4-2147483648 ~ 2147483647java.lang.Integer
long8-9223372036854775808 ~ 9223372036854775807java.lang.Long
float4-2^128 ~ +2^128, 6~7 位有效数字java.lang.Float
double8-2^1024 ~ +2^1024,15~16 位有效数字java.lang.Double
char20 ~ 65535,Unicode 集合java.lang.Character
boolean取决于 jvm 实现true,falsejava.lang.Boolean

有一个例外:void,值为 null

  1. 复合类型:除了基础类型全都是复合类型,可理解为标识符指向了复合变量的地址

值得一提的是,在“一切皆对象”的编程语言中,一个基础类型的值自身并不是对象,也就是说不能通过 . 来访问任何方法或属性,需要借助各自的包装类型,很多情况下编译器会自动装包(Auto Boxing)、解包(Auto Unboxing),也可以通过包装类型的静态工厂方法 对应包装类型.valueOf(基础类型值) 手动创建对象。

1.2 运算符

运算符类别包含
算术运算符+-*/%++--
关系运算符==!=>>=<<=
位运算符|^<<>>>>>
逻辑运算符&&||!
赋值运算符=+=-=*=/=%=<<=>>=&=^=|=
其他运算符?:instanceof

1.3 方法声明

修饰符 返回值类型 标识符(参数类型 标识符 ...) throws 异常... {
    ...
}

1.3 流程控制

流程名代码模板
条件if (条件表达式) {...} else if (条件表达式) {...} else {...}
多重选择switch(表达式) { case 1: case 2: ...;break; default: ...;break; } 比较整型、字符串或枚举类型
while 循环while (条件表达式) { ...}do { ... } while (条件表达式);
for 循环for (初始执行语句; 循环条件; 循环后执行语句) {...}for (类型 标识符: 可迭代对象) { ... }
跳出循环break 跳出当前循环,continue 提前结束本次循环,均可跳转至循环前的 label

1.4 异常处理

try {
    String s = processFile(“C:\\test.txt”);
    // 一切正常会继续执行 try 中的部分
    
    throw new Exception(); // 自行抛出异常
} catch (FileNotFoundException e) {
    // 文件未找到
 
    e.printStackTrace(); // 打印异常栈信息
} catch (SecurityException e) {
    // 没有读取权限
 
    throw new RuntimeException(e); // 包装后再次抛出
} catch (IOException | NumberFormatException e) {
    // io 错误
} catch (Exception e) {
    // 其他错误
    // 越基础的基类需要越靠后,不然作为基类被捕获后永远不会被作为子类捕获
} finally {
    // 无论前面的代码块正常还是错误,最终都会执行,即使有 return
}

2 面向对象

2.1 继承和多态

class Person {
    String name; // 引用类型默认初始化为 null
    public int age; // 基本类型有各自默认值,int 默认初始化为 0,boolean 初始化为 false
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 参数类型不同,方法重载,调用时会自动区分
    public Person(String name) {
        this.name = name;
        this.age = 12;
    }
    
    public String getName() {
        return this.name;
    }
 
    public int getAge() {
        return this.age;
    }
}
 
class Student extends Person {
    private int score;
    protected int grade;
 
    public int getScore() {
        return score;
    }
 
    // 覆盖父类相同签名方法,加 `@Override` 让编译器帮忙检查是否进行了正确的覆盖
    @Override
    public String getName() {
        return "Stu: " + super.getName();
    }
}

2.2 抽象类和接口

abstract class AbstractClass {
    String v;
    public void name() {
    };
 
    // 允许没有具体实现的方法,必须由非抽象子类完成实现
    abstract void fun();
 
    // 不允许静态抽象方法,可用的修饰符只有 public 和 protected
    // 以下语句会报错
    // static abstract void test();
}
 
interface ITest {
    // 接口只允许定义抽象实例方法,没有实例字段
    // 接口中定义的字段,即使不带 static 和 final 也是静态和引用不可变的。
    String StaticConstant = "";
 
    void run();
 
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}
 
class RealClass extends AbstractClass implements ITest {
    void fun() {    }
 
    public void run() {
        this.v.hashCode();
        RealClass.StaticConstant.hashCode();
    }
}

Java中的继承是单继承的,一个子类只能继承一个父类,但是可以实现多个接口。

有一种特殊的接口,有且只有一个抽象方法,被称之为函数式接口(@FunctionalInterface),在需要实现了这种接口的类的地方,可以用 lambda 表达式 (参数列表) -> {...} 或函数引用 System.out::println 来替代,简化书写。

2.3 修饰符

修饰符作用
public表示该成员可以被任何类访问
protected表示该成员只能被同一个包内的类或者该类的子类访问
private表示该成员只能被该类内部的其他方法访问,无法被其他类访问
default表示该成员只能被同一个包内的类访问(默认)
static表示该成员属于类本身,而不是属于类的实例。可以通过类名直接访问
final表示该成员的值不能被修改,或者该类不能被继承
abstract表示该成员只有声明,没有实现,必须在子类中实现
synchronized表示该方法在多线程环境下是线程安全的,即同一时间只有一个线程能够访问该方法
volatile表示该成员的值可能在多线程环境下被修改,需要立即同步
transient表示该成员在序列化过程中将被忽略
  1. synchronized 除了用作方法的修饰符之外,也可以用方法内,将代码块变为原子操作。
public class TestThread {
    Object lock = new Object();
    public void testMethod() {
        // 只能锁住引用类型
        synchronized (lock) {
            ...
        }
        ...
        // 作为修饰符的 synchronized 实际是下面代码的语法糖
        synchronized (this) {
            ...
        }
    }
    public synchronized void syncMethod() { ... }
}
  1. final 修饰基本类型时,值不可变,修饰引用类型时,仅代表引用不可变,下面字段仍是可变的,并不代表引用所指向的值变成了不可变对象。
class Point {
    int x;
    int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
 
final Point p = new Point(0, 0);
// p = new Point(1, 0);
p.x = 1;

2.4 泛型

泛型就是定义一种模板,可以适应任意类型,例如 ArrayList<T>,然后在代码中为用到的类创建对应的 ArrayList<类型>

public class ArrayList<T> implements List<T> {
    ...
}
 
// <> 外可以使用父类进行声明,尖括号内不行
List<String> list = new ArrayList<String>();
 
// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!
 
// 在接口中定义泛型
public interface Comparable<T> {
    int compareTo(T o);
}
 
public interface Iterable<T> {
    Iterator<T> iterator();
}
 
// 在类中定义泛型
public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }
 
    // 静态泛型方法应该使用其他类型区分:
    public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
    }
}

Java语言的泛型实现方式是擦拭法

// 虚拟机实际执行的代码
public class Pair {
    private Object first;
    private Object last;
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    public Object getFirst() {
        return first;
    }
    public Object getLast() {
        return last;
    }
}

局限:

  1. <T>不能是基本类型:Pair<int> p = new Pair<>(1, 2); // compile error!
  2. 无法取得带泛型的Class:Pair<String>.class 实际上还是 Pair.class
  3. 无法判断带泛型的类型:p instanceof Pair<String> // Compile error
  4. 不能实例化T类型:不允许 new T()

2.5 注解

注解是放在 Java 源码的类、方法、字段、参数前的一种特殊“注释”,可以被编译器读取而不像注释那样完全被编译器忽略。

// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
    String name() default "";
}

2.6 反射

Java的反射是指程序在运行期可以拿到一个对象的所有信息。在运行期,不直接导入某个实例的类,依然可以调用这个实例的方法和访问修改字段。

  1. 类 Class
// 获取类作为 Class 类型的实例,方法 1
Class cls = String.class;
// 方法 2
String s = "Hello";
Class cls = s.getClass();
// 方法 3
Class cls = Class.forName("java.lang.String");
 
// 直接比较 class 和使用 instanceof 的差别
Integer n = new Integer(123);
n instanceof Integer; // true,因为n是Integer类型
n instanceof Number; // true,因为n是Number类型的子类
 
n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
n.getClass() == Number.class; // false,因为Integer.class!=Number.class
 
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
 
Class s = Integer.class.getSuperclass();
Class[] is = s.getInterfaces();
  1. 字段 Field
Class 方法说明
Field getField(name)根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name)根据字段名获取当前类的某个field(不包括父类)
Field[] getFields()获取所有public的field(包括父类)
Field[] getDeclaredFields()获取当前类的所有field(不包括父类)
Field 方法说明
String getName()返回字段名称,例如,"name"
Class getType()返回字段类型,也是一个Class实例,例如,String.class
int getModifiers()返回字段的修饰符,它是一个int,不同的bit表示不同的含义
Object get(实例) 获取指定实例该字段的值
void set(实例, 新值)设定指定实例的字段为新值
void setAccessible(true)忽略可见性修饰符,一律允许访问,可能会失败
  1. 方法 Method
Class 方法说明
Method getMethod(name, Class...)获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class...)获取当前类的某个Method(不包括父类)
Method[] getMethods() 获取所有public的Method(包括父类)
Method[] getDeclaredMethods()获取当前类的所有Method(不包括父类)
Method 方法说明
String getName()返回方法名称,例如:"getScore"
Class getReturnType()返回方法返回值类型,也是一个Class实例,例如:String.class
Class[] getParameterTypes()返回方法的参数类型,是一个Class数组,例如:{String.class, Integer.class}
int getModifiers()返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
Object invoke(实例, 可变参数列表...)调用指定实例方法并获取结果
void setAccessible(true)忽略可见性修饰符,一律允许调用,可能会失败
  1. 构造方法 Constructor
Class 方法说明
getConstructor(Class...)获取某个public的Constructor
getDeclaredConstructor(Class...)获取某个Constructor
getConstructors()获取所有public的Constructor
getDeclaredConstructors()获取所有Constructor
Constructor 方法说明
newInstance(参数列表...)创建一个实例对象
setAccessible(true)访问非 public 构造方法

3 总结

Java 以面向对象的编程范式闻名于世,但它面向对象的问题在于它还不够面向对象:

  1. 基本类型还需要借助包装类型,才有方法可调用。像 JS、Rust 等更新的语言中基本类型无需再包装
  2. System.out::println 这种方法的引用不算对象。像 JS、Kotlin 中函数也是对象
  3. 运算符如+-操作作为一种行为却没有广泛可用的接口,仅额外支持 String 类型的相加

作为一门历史悠久的语言,其诞生有时代局限性,但也激发出后续更完善的语言,并仍在继续发展。

© ShiftWatchOut.RSS