cheatsheet-java syntax

292 阅读12分钟

计算机基础复习

ref:www.liaoxuefeng.com/wiki/125259…

内存的基本结构

内存的最小存储单元是字节(byte),一个字节就是一个8位二进制数,即8个bit。它的二进制表示范围从0000000011111111,换算成十进制是0255,换算成十六进制是00~ff

内存单元从0开始编号,称为内存地址。每个内存单元可以看作一间房间,内存地址就是门牌号。

  0   1   2   3   4   5   6  ...
┌───┬───┬───┬───┬───┬───┬───┐
│   │   │   │   │   │   │   │...
└───┴───┴───┴───┴───┴───┴───┘

一个字节是1byte,1024字节是1K,1024K是1M,1024M是1G,1024G是1T。一个拥有4T内存的计算机的字节数量就是:

4T = 4 x 1024G
   = 4 x 1024 x 1024M
   = 4 x 1024 x 1024 x 1024K
   = 4 x 1024 x 1024 x 1024 x 1024
   = 4398046511104

不同的数据类型占用的字节数不一样。我们看一下Java基本数据类型占用的字节数:

       ┌───┐
  byte │   │ -128 ~ 127
       └───┘
       ┌───┬───┐
 short │   │   │
       └───┴───┘
       ┌───┬───┬───┬───┐
   int │   │   │   │   │
       └───┴───┴───┴───┘
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
  long │   │   │   │   │   │   │   │   │
       └───┴───┴───┴───┴───┴───┴───┴───┘
       ┌───┬───┬───┬───┐
 float │   │   │   │   │
       └───┴───┴───┴───┘
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
double │   │   │   │   │   │   │   │   │
       └───┴───┴───┴───┴───┴───┴───┴───┘
       ┌───┬───┐
  char │   │   │
       └───┴───┘

byte恰好就是一个字节,而longdouble需要8个字节。 byte,shortintlong 都是整形,java 只有有符号整形,它们能表示的范围如下:

  • byte:-128 ~ 127
  • short: -32768 ~ 32767
  • int: -2147483648 ~ 2147483647。对于各种 id 来说,不太够。所以代码里的 memberId、xxId 一般都用long。
  • long: -9223372036854775808 ~ 9223372036854775807

常用基础语法

ref:wizardforcel.gitbooks.io/modern-java…

基本类型、引用类型

Java的数据类型分两种:

  • 基本类型:byteshortintlongbooleanfloatdoublechar
  • 引用类型:所有classinterface类型

传递区别

  • 基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。

  • 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。要注意不可变类型(比如String)修改后是产生了新的对象,会出现「一方修改后,不影响另一方」的幻觉。

类型自动提升与强制转型

short + int = int

int i = 12345;
short s = (short)i;

浮点数计算误差

todo

格式化输出

JDK文档java.util.Formatter

==equals

不可变类型:

  • String 引用类型: 相当于指针,可指向null

等待一会儿

TimeUnit.SECONDS.sleep(1); 
// 或
Thread.sleep(1000)

定义变量,可用 var 省略变量类型,编译器会根据赋值语句自动推断类型

var sb = new StringBuilder();
// 编译时会处理为:
StringBuilder sb = new StringBuilder();

List

长度不可变list

// 长度不可变的数组:T[],比如 int[]、float[]:
int[] ns = new int[5];
ns[1] = 2;

// 或定义时初始化:
int[] ns = new int[] { 68, 79, 91, 85, 62 }; 
// 或
int[] ns = { 68, 79, 91, 85, 62 };
// 求长度
System.out.println(ns.length);

如果执行 ns = new int[] { 1, 2, 3 }; ,ns 实际指向了新的内存地址,并不是修改了原数组的长度。

长度可变

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

遍历

int[] ns = { 1, 4, 9, 16, 25 };
        for (int i=0; i<ns.length; i++) {
            int n = ns[i];
            System.out.println(n);
            }
        // 或
        for (int n : ns) {
            System.out.println(n);
        }
        // 或
        import java.util.Arrays;
        System.out.println(Arrays.toString(ns));
        

二维数组

int[][] ns = {
            { 1, 2, 3, 4 },
            { 5, 6, 7, 8 },
            { 9, 10, 11, 12 }
        };

三维数组

int[][][] ns = {
    {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    },
    {
        {10, 11},
        {12, 13}
    },
    {
        {14, 15, 16},
        {17, 18}
    }
};

定义list排序

// 注:b.compareTo(a) 是 String 内置的比较器,实际可以自己另外定义。

//旧:
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});
// java8 可简化为:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});


// 继续简化:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

// 继续简化:
Collections.sort(names, (a, b) -> b.compareTo(a));

可变参数 vs list 参数

public void setNames(String... names) {
        this.names = names;
    }
    
public void setNames(String[] names) {
        this.names = names;
    }

可变参数有些优势:

  • 可以保证无法传入null,因为传入0个参数时,接收到的实际值是一个空数组而不是null
  • 调用方不用构造 list,使用方便

面向对象

class

一个Java源文件(xx.java)可以包含多个类的定义,但只能定义一个public类,且public类名必须与文件名一致。如果要定义多个public类,必须拆到多个Java源文件中。

定义class中不变的属性

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    //...
    
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    
    //...
}

field

类的数据成员,即字段

method

类的方法成员

setter 易于校验参数

overload-重载

函数名相同,参数不同

override-覆写

函数签名(出入参、函数名)相同,可覆盖父类方案,可以添加 @Override,让编译器帮助检查是否进行了正确的覆写。

继承

子类自动获得了父类的所有字段,严禁定义与父类重名的字段。

术语

  • 超类(super class),父类(parent class),基类(base class)
  • 子类(subclass),扩展类(extended class)

只有 Object类没有父类,其他类都有且只有一个父类。对 Object 类的继承一般是隐式的。

private vs protected

子类无法访问父类的private字段或者private方法,这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问。 因此,protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问

super

编译器会默认给子类的构造方法添加 super(),但如果父类没有无参数的构造方法,就需要显式调用:

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age); // 调用父类的构造方法Person(String, int)
        this.score = score;
    }
}

在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用

如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override

final 组织继承

final 的 class 不能被继承

向上转型

Person p = new Student(); 是可以的,因为 new Student()这个实例拥有p这个引用变量(类型为Person)的所有功能。这叫向上转型upcasting。可以继续对p执行 Object o = p

向下转型不一定可以。

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

需要先instance of

Person p = new Student();
if (p instanceof Student) {
    // 只有判断成功才会向下转型:
    Student s = (Student) p; // 一定会成功
}

组合

一般用于 has a。


class Student extends Person {
    protected Book book;
    protected int score;
}

多态

多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。例如:

Person p = new Student();
p.run(); // 无法确定运行时究竟调用哪个run()方法

有童鞋会问,从上面的代码一看就明白,肯定调用的是Studentrun()方法啊。

但是,假设我们编写这样一个方法:

public void runTwice(Person p) {
    p.run();
    p.run();
}

它传入的参数类型是Person,我们是无法知道传入的参数实际类型究竟是Person,还是Student,还是Person的其他子类,因此,也无法确定调用的是不是Person类定义的run()方法。

多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

抽象类 vs 接口

抽象类

如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。

因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。

使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:

abstract class Person {
    public abstract void run();
}

Person p = new Person(); // 编译错误

无法实例化的抽象类有什么用?

因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现(箱单雨一种覆写,可以用override)其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。

比如

Person s = new Student();
Person t = new Teacher();
// 同样不关心新的子类是如何实现run()方法的:
Person e = new Employee();
e.run();

这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。

面向抽象编程的本质就是:

  • 上层代码只定义规范(例如:abstract class Person);
  • 不需要子类就可以实现业务逻辑(正常编译);
  • 具体的业务逻辑由不同的子类实现,调用者并不关心。

接口

在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。

如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口。

所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。实现类可以不必覆写default方法。

default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。

interface Person {
    void run();
    String getName();
}

class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(this.name + " run");
    }

    @Override
    public String getName() {
        return this.name;
    }
}

如果定义好了接口,但希望给所有子类添加实现相同的方法,可以用default。

interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + " run");
    }
}

对比

注意,一个类可以实现多个interface,但只能继承一个类。

抽象类和接口的对比如下:

abstract classinterface
继承只能extends一个class可以implements多个interface
字段可以定义实例字段不能定义实例字段
抽象方法可以定义抽象方法可以定义抽象方法
非抽象方法可以定义非抽象方法可以定义default方法

类继承接口用implements,接口继承接口用extends,相当于扩展接口的方法。

继承的设计(用不利索)

合理设计interfaceabstract class的继承关系,可以充分复用代码。一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。可以参考Java的集合类定义的一组接口、抽象类以及具体子类的继承关系:

┌───────────────┐
│   Iterable    │
└───────────────┘
        ▲                ┌───────────────────┐
        │                │      Object       │
┌───────────────┐        └───────────────────┘
│  Collection   │                  ▲
└───────────────┘                  │
        ▲     ▲          ┌───────────────────┐
        │     └──────────│AbstractCollection │
┌───────────────┐        └───────────────────┘
│     List      │                  ▲
└───────────────┘                  │
              ▲          ┌───────────────────┐
              └──────────│   AbstractList    │
                         └───────────────────┘
                                ▲     ▲
                                │     │
                                │     │
                     ┌────────────┐ ┌────────────┐
                     │ ArrayList  │ │ LinkedList │
                     └────────────┘ └────────────┘

在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象:

List list = new ArrayList(); // 用List接口引用具体子类的实例
Collection coll = list; // 向上转型为Collection接口
Iterable it = coll; // 向上转型为Iterable接口

匿名类

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested");
        outer.asyncHello();
    }
}

class Outer {
    private String name;

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

    void asyncHello() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };
        new Thread(r).start();
    }
}

Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。在定义匿名类的时候就必须实例化它,定义匿名类的写法如下:

Runnable r = new Runnable() {
    // 实现必要的抽象方法...
};

匿名类和Inner Class一样,可以访问Outer Class的private字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义Inner Class可以少写很多代码。

static

静态字段

www.liaoxuefeng.com/wiki/125259…

对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例:

        ┌──────────────────┐
ming ──>│Person instance   │
        ├──────────────────┤
        │name = "Xiao Ming"│
        │age = 12          │
        │number ───────────┼──┐    ┌─────────────┐
        └──────────────────┘  │    │Person class │
                              │    ├─────────────┤
                              ├───>│number = 99  │
        ┌──────────────────┐  │    └─────────────┘
hong ──>│Person instance   │  │
        ├──────────────────┤  │
        │name = "Xiao Hong"│  │
        │age = 15          │  │
        │number ───────────┼──┘
        └──────────────────┘

因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型,且public static final 关键字可以省略

静态方法

调用静态方法则不需要实例变量,通过类名就可以调用。 因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。

静态方法经常用于工具类。例如:

  • Arrays.sort()
  • Math.random()

静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。

package

用于隔离命名空间、作用域等。

包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

作用域:不用publicprotectedprivate修饰的字段和方法就是包作用域.

命名注意不要和java.lang包的类重名,即自己的类不要使用StringSystemRuntime这些名字。因为默认自动import java.lang.*

JavaBean

只有field及其读写方法的class 就是javaBean,类似于贫血模式中的一种数据模型。

JavaBean一定要实现Serializable接口实现序列化吗?Serializable序列化到底是什么意思呢???

泛型

<T> TT 区别在于一个函数的入参类型是函数层面的自由如风;还是只是类实例化时限定入参类型,只在类这里自由。

www.cnblogs.com/jpfss/p/992…

<T><?> 没太看懂,遇到的时候再说。

www.cnblogs.com/jpfss/p/992…