java的入门级笔记

看来我大概是要搞后端了,语言掌握大概是java(能恰饭)+python(水)+js(只能看懂)+cpp(acm鬼畜画风)

笔记来自于java核心技术/学堂在线/网上不留名的神仙大佬们

基本程序设计

java认为所有函数都属于某个类的方法,因此main函数也不例外,它需要一个与文件名相同的外壳类

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello the cruel world");
        // 没有return 0;
    }
}

注释不允许/*中嵌套*/,另外这种写法可用于文档

整型有byte,short,int,long,注意没有unsigned类型,boolean不算整型

浮点有日常的floatdouble,正整数除0为Double.POSITIVE_INFINITY,零除或负数开根为Double.NaN

long可用后缀Ll表示,double可用后缀D/d表示

前缀0表示八进制,0b/0B表示二进制

注意NaN无论何时都不相等,判断需要用Double.isNaN(val)

final大概就是c++的const,stataic fianl表示一个类常量(可在一个类的多个方法使用)

变参方法Xjb... xjb

字符串

字符串不可修改单独元素(不可变)

简单拼接可用+.按定界符分割可用String.join(flag,[])

判等可用.equals(str).equalsIgnoreCase(str),还有和strcmp相似的.compareTo(str)

空串和Null串不同,是否为null直接用==判断,是否为空用.length()判断,注意null串不可调用方法

.length()返回UTF-16编码所需的代码单元数量

.codePointCount(0,str.length())返回更为底层的码点数量

.charAt(idx)返回第idx个代码单

.codePoint(.offsetByCodePoint(0,idx))返回第idx个码点(int)

public class HelloWorld {
    public static void main(String[] args) {
        String str = new String("Hello the cruel world");
        System.out.println(str);
        int a = str.codePointAt(str.offsetByCodePoints(0,2));
        System.out.println(a);
        System.out.println(str.charAt(2));
    }
}

StringBuilder类可用于更好的构造字符串,.append(ch/str)连接,.toString()得到String对象

同时可更容易的修改元素

.setCharAt(idx,ch)将第idx个代码单元改为ch

.insert(pos,str/ch) 在pos位置插入str/ch

.delete(st,ed)删除[st,ed)的代码单元并返回this

静态的String.format()方法也可用于构造String

Integer.parseInt(str)返回一个转换好的字符串,其余包装类同理

这里贴一个.split(rex)要注意的点go

IO

输出可用System.out.print()

对于格式化输出,java提供友好的System.out.printf(),几乎与C一致

标准输入需要构造Scanner对象,并且与System.in关联

Scanner类定义在java.util包中

.nextLine() 读入一整行

.next() 读入下一个字符串

.nextInt() 读入下一个整型

.nextDouble() 不解释

.hasNext() 判断EOF的好方法

更为细分的有.hasNextInt().hasNextDouble()

Console定义在java.lang.System,其对象可用于控制terminal

对于格式化的一些补充说明,%b用于boolean,

%s可用于任意实现了Formattable接口的对象(否则调用toString)

文件读取需要File对象来构造Scanner对象(Scanner可接收FileString)

Scanner in = new Scanner(Paths.get("tmp.txt"),"UTF-8");

文件写入则需要构造PrintWriter对象(java.io.PrintWriter)

PrintWriter out = new PrintWriter("tmp.txt","UTF-8");

以下贴个fastIO板子

class FastScannerF {
    private InputStream stream;
    private byte[] buf = new byte[1024];
    private int curChar;
    private int numChars;
    private SpaceCharFilter filter;

    public FastScannerF(InputStream is) {
        stream = is;
    }

    public int read() {
        if (numChars == -1) throw new InputMismatchException();
        if (curChar >= numChars) {
            curChar = 0;
            try{
                numChars = stream.read(buf);
            }
            catch (IOException e) {
                throw new InputMismatchException();
            }
            if (numChars <= 0) return -1;
        }
        return buf[curChar++];
    }

    public int nextInt(){
        int c = read();
        while (isSpaceChar(c)) c = read();
        int sgn = 1;
        if (c == '-') {
            sgn = -1;
            c = read();
        }
        int res = 0;
        do {
            if (c < '0' || c > '9') throw new InputMismatchException();
            res *= 10;
            res += c - '0';
            c = read();
        } while (!isSpaceChar(c));
        return res * sgn;
    }

    public String next() {
        int c = read();
        while (isSpaceChar(c))
            c = read();
        StringBuilder res = new StringBuilder();
        do {
            res.appendCodePoint(c);
            c = read();
        } while (!isSpaceChar(c));
        return res.toString();
    }  

    public double nextDouble() {
        int c = read();
        while (isSpaceChar(c)) c = read();
        int sgn = 1;
        if (c == '-') {
            sgn = -1;
            c = read();
        }
        double res = 0;
        while (!isSpaceChar(c) && c != '.') {
            if (c == 'e' || c == 'E') return res * Math.pow(10, nextInt());
            if (c < '0' || c > '9') throw new InputMismatchException();
            res *= 10;
            res += c - '0';
            c = read();
        }
        if (c == '.') {
            c = read();
            double m = 1;
            while (!isSpaceChar(c)) {
                if (c == 'e' || c == 'E') return res * Math.pow(10, nextInt());
                if (c < '0' || c > '9') throw new InputMismatchException();
                m /= 10;
                res += (c - '0') * m;
                c = read();
            }
        }
        return res * sgn;
    }
    
    public long nextLong() {
        int c = read();
        while (isSpaceChar(c)) c = read();
        int sgn = 1;
        if (c == '-') {
            sgn = -1;
            c = read();
        }
        long res = 0;
        do {
            if (c < '0' || c > '9') throw new InputMismatchException();
            res *= 10;
            res += c - '0';
            c = read();
        } while (!isSpaceChar(c));
        return res * sgn;
    }
    
    public boolean isSpaceChar(int c) {
        if (filter != null) return filter.isSpaceChar(c);
        return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
    }

    public interface SpaceCharFilter {
        public boolean isSpaceChar(int ch);
    }
}

控制循环

大多与C相同,这里仅列出要注意的地方

switch

对于switch,在javac时加上-Xlint:fallthrough可提醒break缺少的情况,如果特别的需要fallthrough,则在外围方法加上@SuppressWarnings("fallthrough")

break

java中存在带标签break,只直接跳出更外层的循环,可视为一个更安全的goto

public class HelloWorld {
    public static void main(String[] args) {
        int times = 0;
        flag:
        for (int i = 0; i < 5; i++) {
            times++;
            for (int j = 0; j < 6; j++) {
                times++;
                break flag;
            }
        }
        // 跳出到这里
        System.out.println(times);
    }
}

甚至对普通的块语句也支持

public class HelloWorld {
    public static void main(String[] args) {
        int times = 0;
        flag: {
            times++;
            if(~times != -1) break flag;
            times++; // 没有条件直接break的话编译器是不会放过你的
        }
        times++;
        System.out.println(times);
    }
}

for each

C++11也有的东西,但没auto

方法/函数

对象传入函数也是值传递(不太严谨,详见书),对于变量本身一般是对象的引用,传入参数相当于对该引用进行一个拷贝(也就是又多了引用指向了同一个对象),而不是简单的理解为起别名

方法传入参数时因此要修改形如Integer类可选用IntHolder,方法略

高精度

我好兴奋啊!

java.Math中封装有BigIntegerBigDecimal

.valueOf(int)可返回一个等值的大数(静态方法)

常用运算有.add(Big),.subtract(Big),.multiply(Big),.divide(Big).mod(Big)

比较可用.compareTo(Big)

对于BigDecimal特有的方法.value(long x,int scale),返回一个$x/10^{scale}$大实数

数组

数组的声明与定义惯例写法如下

int[] arr = new int[233];

new出来的默认值为0,如果是对象则是null

还有其它的写法

int[] arr = {1,2,3,5,8,13};

或者匿名数组(较繁琐)

int[] arr = new int[] {1,2,5,14,42};

甚至创造一个长度为0的数组(再次注意与null不同)

int[] arr = new int[0];

一个遍历打印数组的方法是Arrays.toString(arr)(包放在java.util.Arrays)

数组赋值不得用=,这是简单的引用同一数组,应用Arrays.copyOf(arr,len)或者Arrays.copyOfRange(arr,st,ed)

判等则是Arrays.equals(a,b)

其它Arrays.sort(),Arrays.binarySearch(arr,[st,ed,]val),Arrays.fill(arr,val)你懂的

另外提一下Collections也有上述算法,并且与Arrays有一定关联,详略

高维度的示例

public class Main {
    public static void main(String[] args) {
        java.io.PrintStream o = System.out;
        int[][] arr = { // 注意与C的数组的不同之处
            {1},
            {1,1},
            {1,2,1},
            {1,3,3,1}
        };
        for (int[] row : arr) { // 普通for的话就用arr.length
            for (int val : row) { // arr[row].length
                o.printf("%d ",val);
            }
            o.println();
        }
    }
}

在Java中,高维度的数组确实只是数组的数组

输出可用更牛逼的Arrays.deepToString(arr)

面向对象

杂项

由于以往打ACM从来不讲究这些,所以现在要记录一下该注意的点

内部封装时如果是返回要保护的内容,要注意可能是返回一个引用,这样会破坏封装,应用.clone()并显式转换类型

同类型的对象可互相访问对方的私有域(不是很严谨,意会一下)

final实例域在构造赋值完成后则不得修改(private final String name;),一般用于不可变类型或基本类型域

使用final修饰引用类型变量时,我们可以保证变量不能被再次赋值,但无法保证对象值的改变

比如定义一个final StringBuilder sb = new StringBuilder(),sb的引用不再发生改变,但sb内部的内容却依然可改变,比如.append()一下就变了

static静态域属于类而不属于对象(这点lrj的书也提过)

理解静态常量public static final,如果存在static,则不需要通过对象来访问,而final保证了引用的不变,System.out就是一个静态常量

静态方法是一种不能向对象实时操作的方法,可认为没有隐式参数this,但可访问自身类中的静态域(很显然嘛)

工厂方法属于静态方法,暂留

类可以在构造器构造前实现初始化实例域,这点与C++有所不同

在构造器中的第一个语句放this()会调用另一个重载的构造器,蛮不错的设计(C++连调用另一个构造都做不到)

初始化块{}只要是构造器都会调用到

类路径暂留

内部类暂留

代理/反射暂留

继承

继承使用extends关键字,java只能支持公有继承

重写父类方法但需要用到原来的方法时可用super.fun()调用,同时super()也用于父类构造器的调用

一个很挫的示例

import static java.lang.System.out;

public class HelloWorld {
    public static void main(String[] args) {
        A[] testObj = new A[3];
        testObj[0] = new B(); // 100+233+233
        testObj[1] = new A(); // 必须new,否则是个null
        testObj[2] = new A(1999); // 100+1999
//        testObj[3] = new B(-1000); // undefined
        for (A obj : testObj) { // 即使obj声明是A,实际上两种对象都可引用,称为多态
            out.println(obj.showVal()); // 动态绑定
        }
    }
}

class A {
    private int tmp;
    public A() {
        tmp = 100;
    }
    public A(int val) {
        this();
        tmp+=val;
    }
    public int showVal() {
        return tmp;
    }
}
class B extends A {
    public B() {
        super(233);
    }
    public int showVal() {
        return super.showVal()+233; // 考虑到不能访问this.tmp
    }
}

子类可以赋值给超类,但编辑器依然会认为左值是超类

同时,子类数组的引用也可转换为超类数组的引用(非强制),但调用经过重新赋值为超类的对象的子类方法会异常

重载解析,了解一下简单过程

静态绑定(不依赖于隐式参数)

阻止定义子类可用final修饰(方法全部变为final,域不变),方法阻止重写也是final

instanceof操作符判断

抽象类/方法用abstract表示,大致示例如下

abstract class C {
    private int jojo;
    public C(int dio) {
        jojo = dio;
    }
    public abstract int getJ();
}

在类的设计中其占位的作用,在应用中不得创建抽象类的对象,但可定义抽象类的变量引用非抽象子类的对象(这样子就不必担心调用了抽象的还没实现的方法)

protected可令子类访问超类的某些方法(所有子类可见)

Object类

Object变量可引用任意对象

.getClass()返回一个类所属的类型

equals

Objects.equals(a,b)可分辨两者是否null的情况,全部非null则调用a.equals(b)

一个完美的equals方法(不是我吹,书上说的)

class A {
    private int tmp;
    // XjbClass xjb;
    // ...省略
    public boolean equals(Object otherObject) {
        if (this == otherObject) return true;
        if (otherObject == null) return false;
        if (getClass() != otherObject.getClass()) return false;
        A other = (A) otherObject;
        return tmp == other.tmp; // == 判断基本类型域
//            && Objects.equals(xjb,other.xjb); // Objects.equals判断对象域,这里没有
//          必要时调用super.equals
    }
}

hashCode

.hashCode()定义在Object类中,因此每个对象都有默认的散列码(值为对象的存储地址)

为了方便STL的使用需要重新定义.hashCode()

一些细节

Objects.hashCode()对于null返回0

静态方法如Double.hashCode(db)可避免创建Double对象(不要用new Double(db).hashCode())

Objects.hashCode()可组合多个散列值

当两个对象equals()truehashCode()必须一致,否则说不定(大部分hash总得有碰撞)

由上面的规则可知定义hashCode()时应用到equals()方法内的域,以保持一致

Arrays.hashCode(arr)可计算一个数组的哈希

java.lang.(Integer|Double|..).hashCode() 返回给定值的哈希,因此Double.hashCode(12) == Float.hashCode(12)的判断为false

toString

内置的toString()一般遵循类名方括号域的写法

类名通过getClass().getName()得到对继承有很大帮助

举个以前的栗子

import static java.lang.System.out;

public class HelloWorld {
    public static void main(String[] args) {
        A[] testObj = new A[3];
        testObj[0] = new B();
        testObj[1] = new A();
        testObj[2] = new A(1999);
        for (A xjb : testObj) {
            out.println(xjb);
        }
    }
}

class A {
    private int tmp;
    public A() {
        tmp = 100;
    }
    public A(int val) {
        this();
        tmp+=val;
    }
    public int showVal() {
        return tmp;
    }
    public String toString() {
        return getClass().getName()
            + "[tmp=" + tmp
            + "]";
    }
}
class B extends A {
    public B() {
        super(233);
    }
    public int showVal() {
        return super.showVal()+233;
    }
}

// B[tmp=333]
// A[tmp=100]
// A[tmp=2099]

哪怕B类有新增的域,继承的时候只需super一下再重写一小部分即可

另外一个小trick是""+x可方便的调用xtoString(可以轻易对付基本类型)

STL

只是沿用了竞赛上的习惯称呼,本章从直接应用说起,最后再提较为底层的Collection

ArrayList

差不多是C++的vector,尽管java中也有Vector,但不鼓励使用(被ArrayList吊着打)

<>的特性就泛型(虽然没啥卵用但还是记下吧)

.add(obj) 往尾部添加,.add(pos,obj)是插入行为

.remove(pos) 你懂的

.ensureCapacity(cp) capacity为cp

ArrayList<Xjb> list = new ArrayList<>(100) 构造100个cp(new的时候方括号内部可以不填)

.size() 你懂的

.trimToSize() 确保数组列表不再添加时可使用,调整为当前元素个数大小并回收多余空间

.set(i,v) 相当于arr[i]=v,但java没有重载不给你这么干

.get(i) 你懂的

和C++一样,需要注意capacity和size的区别,访问边界不得超过size

.toArray(arr) 拷贝到arr数组中,注意初始化时xjb[] arr = new xjb[list.size()]

ClassName.function(list)传递列表到方法中(可用于其它类型的传递,警告-Xlint:unchecked)

@SuppressWarning("unchecked") ArrayList<Xjb> res = (ArrayList<Xjb>) Xbb.find(list);

<>不接受类似int的基本类型,应用封装好的Integer(对象包装器类,不可变,final,不允许定义子类)

此时.add(3)等价于.add(Integer.valueOf(3)),这种特性称为自动装箱

int val = list.get(i)经过自动拆箱,实际上是int val = list.get(i).intValue()

Queue

LinkedList实现了Queue接口

ArrayDeque实现了循环数组队列

构造示例Queue<Xjb> que = new ArrayDeque<>(100)

方法的命名感觉有点奇怪,多写多记

Queue接口的方法有add()/offer(),remove()/poll(),element()/peek()(前者需要考虑异常)

Deque接口比上面的多区分FirstLast,还多了个getFirst()/getLast()替代element()

PriorityQueue

Set

TreeSet可用Comparator构造空树集,CollectionSortedSet构造树集

SortedSetfirst()last()不解释,还需实现comparator()

NavigableSethiger()/lower(),ceiling()/floor()不解释,pollFirst()/pollLast()删除并返回元素或null,还需实现descendingIterator()

HashSet没啥好说的

Map

Map接口已有两种通用实现HashMapTreeMap

注意这里没有[]重载,要用.get(key)put(key,val)

再强调一下赋值注意要new,要规范Xjb xjb = new Xjb("ooO0ooO")

小的trick是put是带返回的,返回的是上一个该键的元素(重复插入的情况下)

Map接口要求实现get(),getOrDefault,put,putAll,containsKey(),containsVal()forEach()

TreeMapTreeSet的构造方法相似

SortedMap接口要求实现比较器comparator(),firstKey()lastKey()

更新方法示例,注意对null的规避counts.put(word,counts.getOrDefault(word,0)+1)

或者先使用.putIfAbsent()提供默认值再直接putput

一个高端的方法是counts.merge(word,1,Integer::sum)挺好意会的不解释了

Map的接口要求比较晦涩,仅列出名字,用到的时候随缘处理

.merge(),.compute(),computeIfPresent(),computeIfAbsent(),replaceAll()

视图以后部分暂留

Collection

Collection接口要求实现add()iterator()方法,前者返回布尔值,后者返回Iterator<E>

Iterator接口包含4个方法next(),hasNext(),remove(),forEachRemaining()

注意在remove前该元素必须要经过next调用(算是规范吧)

Collection要求实现以下常见方法(并非全部)

size(),isEmpty(),contains(),equals,addAll,removeAll,clear(),retainAll(),toAray()

为了更方便为每个数据结构实现以上方法,Java类库提供AbstractCollection

接口List,Set,Queue的父均是Collection,而CollectionIterable的子类

除此以外另一大门派就是Map

具体的类关系可看核心技术卷1的图9-5

ListIterator接口是默认用于有序的,add()方法总是会改变,所以是void,除此以外还要求实现hasPrevious()previous()

要想连续删除两个元素,可以先remove接着previous再remove,因为remove依赖于迭代器的状态(刚越过哪个元素)

it.set()也是遵循相同的规则

另外,列表迭代器也提供.nextIndex().previousIndex()返回下标

获取迭代器也可用list.listIterator(idx),这会返回一个idx下标前的迭代器,只是效率感人..

关于遗留类,似乎并不鼓励使用,知道就好(似乎BitSet跑的贼快?)

接口

Comparable示例

import static java.lang.System.out;

import java.util.ArrayList;
import java.util.Arrays;

public class HelloWorld {
    public static void main(String[] args) {
        A[] testObj = new A[3];
        testObj[0] = new B();
        testObj[1] = new A();
        testObj[2] = new A(1999);
        Arrays.parallelSort(testObj);
        for (A xjb : testObj) {
            out.println(xjb);
        }
    }
}

class A implements Comparable<A> {
    private int tmp;
    public A() {
        tmp = 100;
    }
    public A(int val) {
        this();
        tmp+=val;
    }
    public String toString() {
        return getClass().getName()
            + "[tmp=" + tmp
            + "]";
    }
    public int compareTo(A rhs) {
        return -Integer.compare(tmp, rhs.tmp);
    }
}
class B extends A {
    public B() {
        super(233);
    }
}

// A[tmp=2099]
// B[tmp=333]
// A[tmp=100]

可用instanceof检查对象是否实现了某个接口

也可用接口直接声明一个变量,并引用实现该接口的对象

自己搞一个接口(一般还需<T>)

interface Xjb {
    int size();
    default boolean isEmpty() { // default 默认方法
        return size() == 0; // 无需关注不同类的size()的内部实现
    }
}

接口与抽象类很相似,比如size()就是一个必须实现的抽象方法

但多了default方法,以及最重要的,每个类可有多个接口,这是抽象类做不到的

并且对于接口演化,当添加新的方法到接口时,提供默认方法可以更好的兼容旧的类

对于超类和接口的同名方法冲突,超类的方法最为优先,否则如果有两个接口的同名方法冲突,则会被编译器指出,此时需要在当前类中明确声明如

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Claris cls = new Claris();
        System.out.println(cls.fun());
    }
}
interface Xjb0 {
    default boolean fun() {
        return true;
    }
}
interface Xjb1 {
    default boolean fun() {
        return false;
    }
}
class Claris implements Xjb0, Xjb1 {
    public boolean fun() {
        return Xjb0.super.fun();
    }
}

即使两个接口中一个有默认方法另一个有同名方法(无default),编译器也会指出需要解决二义性(只要有至少一个默认实现)

如果均没有默认方法,不实现的话,类本身是抽象类

Comparator示例

import java.util.Arrays;
import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        java.io.PrintStream o = System.out;
        Comparator<String> cmp = new myComparator();
        String[] testStr = {
            "awsl", "xswl", "tql"
        };
        Arrays.sort(testStr,cmp); // 比较长度
        o.println(Arrays.toString(testStr));
    }
}
class myComparator implements Comparator<String> {
    public int compare(String s0,String s1) {
        return s0.length()-s1.length();
    }
}

由于compare()不是静态方法,所以需要new个对象出来,并且传入类比仅传入方法更为灵活(设计者说的)

Cloneable接口可实现安全的.clone(),因为默认情况下的拷贝是浅拷贝,对非不可变的子对象依然会是引用(当你没重写clone()时,默认使用的方法是Objectclone(),因为它是protected,所有子类均可访问)

Cloneable属于标记接口,里面没有任何方法!仅是提醒使用instanceof

示例略

lambda表达式

lambda在数学含义上表达为参数,在编程意义上强调可(语)读(法)性(糖)

一个lambda表达式实现比较器的例子(适用于只有一个抽象方法的接口)

import java.util.Arrays;
import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        java.io.PrintStream o = System.out;
        Comparator<String> cmp = (String s0,String s1) -> {
            return s0.length()-s1.length();
        }; // 函数式接口
        String[] testStr = {
            "awsl", "xswl", "tql"
        };
        Arrays.sort(testStr,cmp); // 其实也可以在这里直接来个lambda
        o.println(Arrays.toString(testStr));
    }
}

甚至可以省略参数类型,在单一语句时还可省略花括号

Arrays.sort(testStr,(s0,s1)->s0.length()-s1.length());

当不需要带参数时,应用() -> {...}的写法

单一参时可使用var -> {...}的写法

Predicate的应用

list.removeIf(e -> e == null); //ArrayList

::方法引用示例

1,object::instanceMethod 比如e -> System.out.print(e)等价于System.out::print

2.Class::staticMethod 比如(x,y) -> Math.pow(x,y) 等价于Math::pow

3.Class::instanceMethod 比如(x,y) -> x.compareTo(y) 等价于String::compareTo

对于this的使用示例 this::equals等价于x -> this.equals(x)(this指创建lambda表达式的方法的this)

Class::new为构造器引用.int[]::new等价于x -> new int[x]

数组构造器引用可用于构造泛型类型的数组,如stream.toArray()返回Object[],而构造器引用可以做到stream.toArray(Person[]::new)使得返回Person[]

泛型

直接给个栗子吧

public class Main {
    public static void main(String[] args) {
        String a = "After putting your Chromebook device in developer mode "
                + "and logging in to your device, you’ll proceed to open "
                + "the command shell; it’s similar to the Terminal you "
                + "would use with Windows or Mac. ";
        String[] b = a.split(", |; |\\. | ");
        Pair<String> res = JustFun.minmax(b);
        System.out.printf("First:[%s] Second:[%s]\n",res.getFirst(),res.getSecond());
        System.out.println(JustFun.<String>getMiddle(b));
    }
}

class Pair<T> {
    private T first,second;
    public Pair() {
        first = null;
        second = null;
    }
    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }
    public T getFirst() {
        return first;
    }
    public T getSecond() {
        return second;
    }
}
class JustFun {
    public static <T> T getMiddle(T... a){
        return a[a.length/2];
    }
    public static Pair<String> minmax(String[] a) {
        if(a == null || a.length == 0) return null;
        String min = a[0], max = a[0];
        for(String e : a) {
            if(min.compareTo(e) > 0) min = e;
            if(max.compareTo(e) < 0) max = e;
        }
        return new Pair<>(min,max); //编译器可直接推断<>内类型
    }
}

为了使得T所属的类有确切的方法,可以使用extends关键字,比如<T extends Comparable>就限定了T必有compareTo方法,如果要绑定多个接口,可用&<T extends Comparable & Serializable>

因此,从代码的35行开始把String改为T extends Comparable后并把其余改为T也足够安全

目前只记录最简单的泛型用法,其余的暂留(太无聊了我实在看不下去啊)

多线程

以下笔记来自xuetangx

多线程的两种实现方法

一是使用继承自Thread的类,重写run()方法

另一种是实现Runnable接口,这种方法可以继承其他的类,实现上更为灵活

前者直接调用.start()即可,后者需要匿名的构造

public class Main {
    public static void main(String[] args) {
        Test test = new Test(10);
//        test.start();
        new Thread(test,"test").start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main fin");
    }
}
//class Test extends Thread {
//    private int num = 1, fac;
//    public Test(int fac) {
//        this.fac = fac;
//    }
//    public void run() {
//        for(int i = 1; i <= fac; i++) num *= i;
//        System.out.println("Test OK: Ans = "+num);
//    }
//}
class Test implements Runnable {
    private int num, fac;
    public Test(int fac) {
        super();
        num = 1;
        this.fac = fac;
        
    }
    public void run() {
        for(int i = 1; i <= fac; i++) num *= i;
        System.out.println("Test OK: Ans = "+num);
    }
}

上述代码的注释部分表示第一种实现方法

线程共享和同步

使用Runnable实现数据在线程间的共享的示例

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        SellTicket sell = new SellTicket();
        new Thread(sell,"s1").start();
        new Thread(sell,"s2").start();
        new Thread(sell,"s3").start();
    }
}
class SellTicket implements Runnable {
    private int ticket = 20;
    public synchronized void run() {
        while(ticket > 0) {
            System.out.println(Thread.currentThread().getName()+" is selling:"+(ticket--));
        }
    }
}

如果改为直接继承Thread的话也许使用这种匿名构造的方法,多个同一对象的start()会抛出异常

此时在run()上用synchronized修饰同步方法来实现线程互斥(某时刻只允许一个线程的操作称为互斥),否则会导致售票号的混乱(协作),也可以直接获得对象的锁来实现互斥synchronized(obj)

Object类中有用于线程协作的wait()方法和notify()/notifyAll()方法,当执行同步代码的线程在某对象调用wait(),该线程会暂停执行并进入该对象的等待池同时释放获得对象的锁,直到其它线程在该对象上调用notify()(随机唤醒)或者notifyAll()

线程调度

使用.setDaemo(true)使得进程进入后台,当没有前台进程运行时则结束生命周期

使用.setPriority()设置进程优先级,当优先级相同时可用yield()进行调整

使进程进入阻塞状态的情况: 1.IO等待 2.synchronized未获得锁(block池 / wait然后notify) 3.wait()(wait池) // sleep()(休眠)

线程运行时进行暂停的情况: 1.更高优先级就绪 2.IO/sleep/wait/yield(阻塞) 3.时间片的时间期满

线程安全

多个线程访问同一对象时,不考虑调度和交替执行以及额外的同步或者调用方的协调操作都能获得正确结果

相对线程安全指单独调用不需额外保障,但不保证特顺序的结果正确

线程兼容:本身不安全,但使用同步方法达到并发环境下安全

线程对立:怎么搞都不行

线程安全的实现方法:互斥同步/非阻塞同步/无同步方案

互斥同步除了原生的sychronized,还可使用ReentrantLock重入锁,一般性能比较牛皮

非阻塞同步是基于冲突检测的乐观并发策略,就是不断尝试直至成功而不把线程挂起(阻塞)的方法,比如AtomicInteger

无同步方案略

锁优化

有点玄学,暂留

JVM

java中数据类型有原始数据类型和引用数据类型(类类型/接口类型/数组类型),而JVM中还存在一种返回值类型

内存划分

运行时数据区包含线程共享的方法区和堆,以及线程私有的程序计数器,虚拟机栈和本地方法栈

VM Stack指所谓的栈内存,方法调用至完成的过程就是入栈出栈的过程

Native Method Stack执行本地方法(不同平台的内部实现)

Heap的唯一目的是存储对象实例,分为新生代和老生代

Method Area用于存储类信息/常量/静态变量/class文件

类加载机制

类的生命周期包括加载/验证/准备/解析/初始化/使用/卸载

加载:通过类的权限定名获得该类的二进制字节码,把字节码代表的静态存储结构转化为方法区的运行时数据结构,堆中生成一个代表该类的Class对象作为方法区中该数据的访问入口

验证: JVM规范/文件格式验证/元数据验证/字节码验证(比较玄学)/符号引用验证

太玄学了,暂留

判断存活算法和对象引用

当没有引用指向该内存时,JVM则负责回收内存资源

JVM并没有使用引用计数算法,因为无法处理相互循环引用问题(只能处理树形关系)

另一种可行算法时可达性分析算法,引用关系作为图,GC ROOT作为源点,如果某个节点与ROOT没有引用链,那显然应作为垃圾回收

可作为GC Root对象: JVM栈中引用的对象/方法区中的泪静态属性引用的对象/常量引用的对象/本地方法栈中JNI引用的对象

强引用只要引用非null就不会回收

软引用通过SoftReference来get然后把new的引用指null来实现,来达到类似缓存的功能,当内存不足时才从原来的引用获取,以提高性能/空间的利用率

弱引用通过WeakReference来实现在第二次垃圾回收时才回收的功能,.get()偶尔为null(内部实现暂留),一般用于监控对象isEnQueued()是否标记(也就是没啥卵用的意思)

虚引用PhantomReference,.get()永远返回null,用于检测对象是否已经从内存中删除(注意上一种方法只是标记)

分代垃圾回收

据说除了置null以外,调用System.gc()也可用于释放内存(先mark)

分代假设:1.绝大多数对象在短时间内变得不可达 2.只有少量年老对象引用年轻对象(个人认为这种是基于工程实践的设想,没有绝对依据但好像很有道理)

年轻代Young: 新创建的对象存放在这里(大多数对象的创建/消失都在这里),对象在这里消失时称为minor GC(小型的垃圾回收)

老年代Tenured: 存活下来的年轻代对象被复制到这里,内存区域更大,回收频率更低,回收时称为major GC / full GC

年轻代有1块eden区,2块survivor区,绝大多数创建的对象分配在eden区,eden区一次GC后存货的对象移到Survivor区,该区满了以后会把存活对象移动到另一个Survivor区并清空之前的Survivor区,反复存活于这种过程的对象会被移动到老年代

垃圾回收算法

Mark-Sweep(标记清除)算法分为两个阶段.标记阶段标记所有需要回收的对象,清除阶段你懂的,是一种说起来简单但是没人要的算法

缺陷在于内存碎片,后序的大对象分配空间会很困难,导提前再次垃圾收集

Copying算法将可用内存分为相等的两块,每次仅用一块.当某一块内存即将用完时,把存活对象复制到另一块上,清空已使用的内存空间(机智啊)

缺陷显然是内存只能用一半了,并且与存活对象数目有关(太多了再怎么搬也是白费力气)

Mark-Compact(标记整理)算法标记阶段与Mark-Sweep一致,清除阶段把存活对象往一端移动后再清除

Generational Collection(分代收集)是JVM主流的回收算法,Young Generation采用Copying(因为会回收大量对象,复制操作也少),每次使用Eden和一块Survivor,存活则全部复制到另一块Survivor,而Tenured Generation则使用Mark-Compact(回收少量对象).堆区外还有永久代(Permanent Generation),存储class类/常量/方法描述,回收常量和无用类(所以是干嘛的)

垃圾收集器

Serial/Serial Old是单线程的收集器(回收时暂停所有用户线程),就这一缺点可认为是垃圾货色

ParNew是Serial的多线程版本

Parallel Scavenge是一个新生代的多线程收集器,采用Copying,主要目的是达到可控的吞吐量

Parallel Old则用于老年代

CMS(Current Mark Sweep)是以获取最短回收停顿时间为目的的收集器(并发)

G1反正很牛逼就对了,用于服务器端

last update:2019/02/17

标签: none

添加新评论