Java基础

java基础

java的基本程序设计结构

第一个程序

Main.java

public class Main {
    public static void main(String[] args) {
        System.out.println("hello,world");
    }
}

java的源代码的文件名必须和公共类的名字相同,必须以.java结尾。

  • 编译:javac Main.java 得到二进制文件Main.class
  • 执行:java Main打印hello world

或者直接java Main.java执行。

数据类型

基本和C++相同,不同的之处: java中的布尔类型boolean是不支持数值转换的,即x为数值,if(x)这样的代码是不能通过编译的。

变量和常量

变量

java中并不区分变量的声明和定义,且变量声明后必须显式赋值,使用未赋值的语句是错误的,如:

 double x;
 System.out.println(x);

java 10开始,可根据变量初始化值推断它的类型,只需借助关键字var: var x = 1.2;

常量

final关键字指示常量,表示变量只能被赋值一次,之后不能被更改,常量习惯上大写。

 public static final double PI = 3.14;

枚举类型

enum Color{RED,BLUE,GREEN};
Color cr = Color.BLUE;

运算符

数学函数

Math类包含很多数学函数,使用方法Math.funcion(),其中常用的函数有:

  • sqrt(): 开方
  • pow():幂运算
  • 三角函数:sin, cos, tan, atan...
  • 指数函数:exp, log,log10

非常接近\(\pi\)\(e\)的常量,分别为Math.PIMath.E

位运算

算术移位:用符号位填充高位,<<,>>

逻辑移位:用0填充高位,<<<, >>>

字符串

基础

  • 子串:​ str.substring(i,j), str索引[i, j)的字符组成的子串
  • 拼接:+适用于短小且频率不高的字符串拼接,因为效率低, String.join(str1,str2,str3)效率很高,或者转为StringBuilder,用对应的append方法
String s1 = "abcedfg";
String substr = s1.substring(1,3);   //bc
String s2 = "a".repeat(3);     // aaa java11 支持repeat
String s3 = "a" + "b" + "c";     // aaa
String s4 = String.join("aa","bc","cd");     // aaa

java中字符串的一个重要特点:不可变性,即不能修改字符串中的某个字符,如果非要修改呢?只能让其指向一个新的字符串。不可变性使得字符串间可以共享。JAVA(1)【基础类型】String类型的字符串池 - 小拙 - 博客园 (cnblogs.com)

  • 判断是否相等: s1.equals(s2), java中字符串如果用==判断,实际比较的是地址

  • 空串与null串: 长度为0的串为空串,null串表示没有任何对象和自己关联,判断一个字符串既不是空串也不是null串:

     if(s != null && s.length() != 0)
    

常用api

除了上面的,其他常用的api:

  • char charAt(int index): 返回给定位置的代码单元
  • empty():是否为空
  • startsWith(String prefix):是否以prefix作为前缀
  • endsWith(String suffix):是否以suffix作为后缀
  • length():长度
  • toLowerCase()/toUpperCase:转小/大写

StringBuilder类

拼接字符串append的效率高,对应的一些api:

  • length():长度
  • append():可添加单个字符或者字符串
  • setCharAt(int i, char c):将第i个代码单元设置为字符c
  • insert(int offset, String str/ char c):在索引offset后插入字符串str或者单个字符c
  • delete(index1, index2):删除索引[index1,index2)之间的字符串,并返回残余的字符串
  • toString():返回String类型的对象

输入输出

读取输入

 Scanner in = new Scanner(System.in);
 String s1 = in.nextLine();
 String s2 = in.next();  //以空格为分界
 int num = in.nextInt();  //in.nextDouble

检查是否还有输入:boolean hasNext() / hasNextInt()/hasNextDouble()

格式化输出

double a = 1 / 3.0;
System.out.printf("%8.2f",a); //总字符长度为8,精确至小数点后2位,即前面留了5个空格

文件输入

String path = "D:/Files/桌面文件/1.txt";
Scanner in = new Scanner(Path.of(path), StandardCharsets.UTF_8);
String s = in.nextLine();
System.out.println(s);

循环

和C++的break功能更强的是,java中break语句可以带标签,如多重循环中,break加上标签可直接跳转到最外层。标签放在循环最外层的前面,并加上冒号。

 public static void main(String[] args) {
            label:
            for(int i=0;i<5;i++) {
                for(int j=0;j<5;j++) {
                    if(j==3) {
                        break label;
                    }
                    System.out.print("i= "+i +" j= " +j + "\n");
                }
            }
            System.out.println("This is end");
    }
//输出
i= 0 j= 0
i= 0 j= 1
i= 0 j= 2
This is end

大数

BigDecimal a = BigDecimal.valueOf(100);
BigInteger b = new BigInteger("12466456663875366527162366515632664357");
BigInteger c = new BigInteger("12466456663875366527162366515632664357");
System.out.println(b.add(c));
System.out.println(b.subtract(c));  //减法
System.out.println(b.multiply(c));
System.out.println(b.divide(c));
System.out.println(b.mod(c));

BigDecimal 中除法除不尽会报错,所以除不尽的情况要指定舍去方式:

public class Main {
    public static void main(String[] args) {
        BigDecimal a = BigDecimal.valueOf(1);
        BigDecimal b = BigDecimal.valueOf(3);
        System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));   //保留两位小数,并四舍五入,0.33
        System.out.println(a.divide(b, 2, RoundingMode.CEILING));  //天花板除法 0.34
        System.out.println(a.divide(b, 2, RoundingMode.FLOOR));   //地板除   0.33
    }
}

数组

声明数组

public class Main {
    public static void main(String[] args) {
        int[] a = new int[10];
        int [] b = new int[]{1,2,3};
        int [] c = {1,4,6};
    }
}

数组拷贝

java的数组声明:int[] a = new int[100]等价于C++里面的int* a = new int[100],因此=赋值实际上只是引用同一个数组,如果想拷贝一份,则应该用Arrays.copyOf()

public class Main {
    public static void main(String[] args) {
        int [] c = {1,4,6};
        int [] d = c;
        c[1] = 9;
        System.out.println(c);
        System.out.println(d);
        for(int x:d){
            System.out.print(x +"\t");
        }
    }
}
//
[I@5fd0d5ae
[I@5fd0d5ae
1	9	6
public class Main {
    public static void main(String[] args) {
        int [] a = {1,4,6};
        int [] b = Arrays.copyOf(a,5);  //从数组a的起始复制,第二个参数为新数组的总长度,不够补0
        a[1] = 9;
        System.out.println(a);
        System.out.println(b);
        for(int x:b){
            System.out.print(x +"\t");
        }
    }
}
//
[I@5fd0d5ae
[I@2d98a335
1	4	6	0	0

用于数组的api

用法:Arrays.加上下面的api:

  • sort(arr);采用的是快速排序

  • static String toString(arr):[1, 7, 5, 3, 6, 2, 0]

  • copyOfRange(arr, from, to): 复制arr的区间[from, to)的数据

  • binarySearch(arr, value):在有序数组中寻找value,并返回对应的下标r,如果找不到,则-r-1为应该插入的位置

  • fill(arr,value):将数组全部用value填充

  • equals(arr1,arr2):判断两个数组是否相等(长度,对应的元素都是相等的才返回true)

多维数组

java多维数组可以是不规则的,

public class Main {
    public static void main(String[] args) {
        int[][] a = {{1,2,3},{2,3}};
        int[][] b = new int[2][3];
        System.out.println(a[1][1]);
        System.out.println(Arrays.deepToString(a));  //toString无法处理多维数组
    }
}

对象和类

本章只记录和C++的差别,类似的不记录。

构造器

默认字段初始化

如果没有显式地为字段设置处置,那么会被赋为默认值,数值为0,布尔为false, 对象引用为null

无参构造器

如果类没有构造器,则会提供一个无参构造器,但如果提供了有参构造器,而没有提供无参构造器,则构造对象时不提供参数是错误的。

类的导入

自定义类的导入

和使用完全限定名相比,简单的方式是采用import 导入

图片名称

静态导入

import static java.lang.Math.sqrt;

public class Main {
    public static void main(String[] args) {
        int a = 9;
        int b = (int) sqrt(a);
        System.out.println(b);
    }
}

包访问权限

  • public:可以被任意类使用
  • private:只能由定义它们的类使用
  • 没有指定private或者public: 可以被同包的所有方法使用
  • protected: 对本包和所有子类可见

和C++相比,protected的保护的安全性变差了

继承

类,父类,子类

定义子类

java中用关键字extends表示继承,且java中只有公有继承​,同样可以重写父类的方法,如下面的run方法,但如果还是想调用父类的,子类的run方法中用关键字super实现:super.run();

public class Student extends Person {
    @Override
    public void run() {
        System.out.println("a student is running!");
    }
    public void eat(){
       System.out.println("eating...");
   }
}

子类的构造器

相同的字段,可以借助父类的构造器,依旧是利用super调用构造器,这一句必须是子类构造器中的第一句。

int score;
public Student(String name,int age,int score){
     super(name,age);
     this.score = score;
}

java中动态绑定是默认的,在运行时自动选择恰当的方法,若不想一个方法是虚拟的,则用final修饰它。

阻止继承:final类和方法

Person类加上了final关键字,则Student无法继承它。

public final class Person

同样给Person类中的方法run加上final修饰,在子类中同样不能重写覆盖

public final void run(){
        System.out.println("running!");
}

强制类型转换

java中子类强制转为父类是合法的,但反过来一般会报错,因此转换前用instanceof检查。

if(p instanceof Student)

抽象类

抽象类不能实例化,将一个类声明为abstract,这个类就成为了抽象类。用abstract修饰的方法不用实现,类似C++的虚基类。

public abstract class Person {
    String name;
    int age;
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public abstract void run();
}

Object:所有类的父类

Object类型的变量

Object类是所有类的超类(父类),但定义的时候不需要加上extends。可用Object类引用任意类型的对象,当然要进行具体操作,还需要墙砖为对应的类型。对象数组和基本的数组都拓展了Object

Object obj = new Student("Alice", 23,89);  //ok
Student st = (Student)obj; 
Animal a = new Aninal[5];
obj = a;    //0k
obj = new int[10]; //ok

equals方法

用于检测两个对象是否相等。

  • 若引用同一个对象,则想等
  • 另一个对象为null或者不是同一个类型,不相等
  • 接下来判断各种字段,由于自己的name字段可能也是null,故name.equals(o.name)是有问题的,而应该用Objects.equals方法
  • 比较。
 public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

访问权限

图片名称

接口,lambda表达式与内部类

接口

使用

概念:不是类,而是对符合这个接口的类的一组规范(听了想打人🤣),可以将接口看做没有实例字段的抽象类。接口中的方法都自动是public.下面展示的是Arrays.sort承诺对对象数组进行排序,但需要对象所属的类实现下面的接口:

public interface Comparable{
	in compareTo(Object other);
}

接口中可以有常量,或者抽象方法,由于接口规范默认是公开的,所以接口,接口内的方法的public可以省略。

  • 常量:public,static,final可以省略
  • 方法:public,abstract可以省略
public interface InterfaceDemo {
    String SITE_NAME = "shanghai";
    void eat();
}

为何引入接口,而不是用抽象类呢?因为java中类只能单继承,即只能拓展一个类,而接口提供了多重继承。

class Student extends Person, Comparable{...} //error
class Student extends Person implements Comparable{...} // ok

接口属性

接口不是类,故不能用new初始化一个接口,但可以声明接口的变量

package Test;

public interface Person {
    default String getName(){return "person";}
}

package Test;

public interface Staff {
    default String getName(){return getClass().getName()+"_"+hashCode();}
}

package Test;

public class Student implements Person, Staff{
    @Override
    public String getName() {
        return Staff.super.getName();
    }
}

package Test;

public class Main {
    public static void main(String[] args) {
        Person x;
        x = new Student();
        System.out.println(st.getName());   

    }
}
//
Test.Student_1854731462

接口默认方法冲突

两个接口中具有一个同名方法,且一个类继承了这两个接口,因此必须在Student类中重新定义一个getName方法覆盖两个接口的类,另外无论定义的接口变量是啥,引用了接口的类对象,调用的同名函数实际上还是类重新定义的函数。

package Test;


public class Main {
    public static void main(String[] args) {
        Student st = new Student();
        System.out.println(st.getName());
    }
}

Comparator接口

String类实现了Comparable<String>,且String.compareTo方法默认按字典序排序,如果想把String按长度排序又当如何呢?因为Arrays.sort提供了第二个版本,即数组+比较器,这个比较器可以自定义,为实现了Comparator接口的类的实例

package Test;

import java.util.Comparator;

public class MyCompare implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
}
package Test;


import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] s = {"abcd","xyz"};

        Arrays.sort(s);
        for(String str:s){
            System.out.print(str +" ");
        }
        System.out.println();
        Arrays.sort(s,new MyCompare());
        for(String str:s){
            System.out.print(str + " ");
        }
    }
}
//
abcd xyz 
xyz abcd 

对象克隆

通过=赋值对象变量,实际上引用的是同一个对象,任何一个变量改变都会影响另一个。

var original = new Student();
Student cy = original;
cy.setName("Alice");   //also change the original

如果想复制得到一个新对象,初始状态和original相同,但对各自的操作是独立的,那么就该用clone方法

Student cy = original.clone();
cy.setName("Mike");  // original unchanged

但问题没那么简单,clone方法是Object的一个protected方法,回顾以下,protected方法只能在本包或者子类中可见,这个子类值得的在子类的方法中调用,而不能通过子类的实例调用​。此外object类并不知道子类有哪些字段,只能逐字段拷贝,如果子类有其他对象的引用,拷贝的字段是另一个对象的引用,依旧会和其他对象共享信息,即为浅拷贝​。解决的方法就是自定义一个clone方法。对于一个类,需确定:

  • 默认的clone方法是否满足要求
  • 可否在可变的子对象上调用clone来修改默认的clone方法
  • 是否不该用clone

若需要用clone,应该实现Cloneable接口,重新定义clone方法,并用public修饰。若默认的clone方法满足,还需重新实现Cloneable接口,看下面的例子,看起来没做什么工作,实际上只是把它变为public,对外部可见,但若深拷贝需要做更多的工作。

public class Student  implements Cloneable {
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }
}

lambda表达式

表达式语法

lambda表达式为匿名函数,先看第一个表达式:(String s1, String s2)->s2.length() - s1.length(),按字符串的长度逆序排序

 package Test;

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

public class Main {
    public static void main(String[] args) {
        String[] s = {"abcd","xyz","world"};

        Arrays.sort(s,(String s1, String s2)->s2.length() - s1.length());
        for(String str:s){
            System.out.print(str + " ");
        }

    }
}
//
world abcd xyz 

另外需要注意,lambda表达式不允许满足特定条件的分支参有返回值,所有分支必须都有返回值。

函数式接口

只有一个抽象方法的接口,需要该接口的对象时,那么就lambda表达式替代它

方法引用

若lambda表达式涉及一个方法,可以调用:

package Test;


import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class Main {
    public static void main(String[] args) {
        JFrame win = new JFrame("登录界面");
        JButton btn = new JButton("点击");
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("有人点了一下");
            }
        });
        win.add(btn);
        win.setVisible(true);
        win.setSize(400,300);
    }
}

将其修改为:

  btn.addActionListener((ActionEvent e)-> {
            System.out.println("有人点了一下");
  });

简化规则

如上面的Arrays.sort(s,(String s1, String s2)->s2.length() - s1.length());可进一步简化为:

 Arrays.sort(s,(s1, s2)->s2.length() - s1.length());
图片名称

处理lambda表达式

该表达式的重点是延迟执行,原因:

  • 在单独线程执行代码
  • 多次执行某段逻辑
  • 在合适位置执行代码
  • 某种情况下才执行
  • 必要时才执行

若想某个动作执行n次,可将这个动作和重复次数传递给repeat方法:

package Test;

public class Main {
    public static void repeat(int n, Runnable action){
        while(n-- > 0){
            action.run();
        }
    }
    public static void main(String[] args) {
        repeat(5, ()->System.out.println("hello,world"));
    }
}

其他用法见Java核心技术 卷I p253

再谈Comparator

Comparator接口提供了很多方便的静态方法创建比较器,如将字符串按长度升序排序:

 Arrays.sort(s, Comparator.comparingInt(String::length));

再如Person对象数组,按照名字对对象排序:

 Arrays.sort(s, Comparator.comparing(Person::getName));

人名若相同,可以按年龄排序:

 Arrays.sort(s, Comparator.comparing(Person::getName), (p1,p2)->Integer.compare(p1.getAge(),p2.getAge()));

内部类

使用场景、作用

  • 事务内部需要一个完整的结构进行描述,如People类中有一个Heart
  • 内部类可更方便访问外部类的数据,包括私有成员
  • 提供更好的封装性,内部类可用privateprotected修饰。

静态内部类

加了static修饰,和普通类没啥区别,使用:

Outer.Inner in = new Outer.Inner();  //先创建内部类

静态内部类可直接访问外部类的静态成员,但不可直接访问其他类型的成员。

package InnerClass_static;

public class Outer {
    private static int score;
    public static class Inner{
        private String name;
        private int age;
        public Inner(String name,int age){
            this.name = name;
            this.age = age;
        }
        public void show(){
            System.out.println(name + age + score);
        }
    }
}

成员内部类

static修饰,属于外部类的成员,JDK16开始,成员内部类支持静态成员,外部类成员访问没有限制范文。

Outer.Inner in = new Outer().new Inner();

访问所在外部类成员

class People{
    private int heartBeat = 150;
    public class Heart{
        private int heartBeat = 110;
        public void show(){
            int heartBeat = 78;
            System.out.println(heartBeat);              //  78
            System.out.println(this.heartBeat);         //  110
            System.out.println(People.this.heartBeat); // 150
        }
    }
}

匿名内部类

作用:​方便创建子类对象,简化代码

定义:new 类名/抽象类名/接口名 { 方法 }

Heart h = new Heart(){
	public void run(){
	   //do something
      }
}

使用形式

package InnerClass;

public class Test {
    public static void main(String[] args) {
       /* Animal a = new Tiger();   
      */
        Animal a = new Animal() {
            @Override
            public void run() {
                System.out.println("running fast");
            }
        };
        a.run();
    }
}

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

/*
class Tiger extends Animal{

    @Override
    public void run() {
        System.out.println("I'm running");
    }
}*/

集合

概述

图片名称

Collection接口

方法名称 说明
boolean add(E e) 添加元素,若成功,返回true
boolean addAll(Collection c) 添加集合c内所有元素
void clear() 清空元素
boolean contains(Object o) 检测是否含有指定元素
boolean containsAll(Collection c) 检测是否含有集合c中所有元素
boolean isEmpty() 检测集合是否为空
Iterator iterator() 返回迭代器
boolean remove(Object o) 删除第一个符合的指定元素
boolean removeAll(Collection c) 删除所有在集合c中出现的元素,调用的集合改变了则返回true
int size() 返回集合元素个数
Object[] toArray() 集合转为数组

迭代器

包含的方法:

public interface Iterator<E> {
     boolean hasNext();
     E next();
     void remove();
     default void forEachRemaining();
}
  • hasNext:检测是否还含有元素
  • next:下一个元素的迭代器
  • remove:删除上次调用next方法时返回的元素。必须通过调用next才能调用remove
Iterator<String> it = list1.iterator();
it.next();
it.remove();

ArrayList

底层数据结构为数组,因此查询快,效率高,但线程不安全

构造方法及接口

ArrayList<E>() ;  //构造一个容量为10的空数组
ArrayList<E>(int initialCapacity);    //用指定容量构造空数组列表
public ArrayList(Collection<? extends E> c); //以集合c构造
  • boolean add(E obj):在数组尾部增加元素obj,返回true
  • int size(); 返回元素个数
  • void ensureCapacity(int capasity); 和C++的vector的reserve()效果类似,预备capasity个对象的容量
  • void trimToSize(): 将数组列表的存储容量缩减为当前大小
import java.util.ArrayList;


public class Main {
    public static void main(String[] args) {
        ArrayList<String > list1 = new ArrayList(); // 创建集合 list1
        list1.add("one"); // 向 list1 添加一个元素
        list1.add("two"); // 向 list1 添加一个元素
        for(String s:list1){
            System.out.println(s);
        }
    }
}

访问元素

ArrayList不能使用[ ]访问指定索引的元素,而使用getset方法。

  • get(int index):返回索引为index处的元素
  • set(int index, E obj): 将索引为index处的值设置为obj,并返回index位置的旧元素

链表LinkedList

java中链表都是双向的,每个节点都存放了前驱和后继的引用。构造:

LinkedList<E>();
LinkedList<Collection<? extends E> c);

常用API

  • void addFirst(E e): 在链表头部增加元素
  • void addLast(E e): 在尾部增加元素
  • E removeFist():删除并返回头节点
  • E removeLast():删除并返回尾节点
  • E getFirst():获取链表头节点
  • E getLast():获取链表尾节点

ListIterator接口

ListIterator继承自Iterator, 包含方法:

  • void add(E e):增加元素,它假定该操作必定会改变链表
  • E previous( ) :和next相反,迭代器往前一位,并返回该处的值
  • boolean hasPrevious(): 检测前面是否有元素
  • void set(E e):用新元素替换next或者previous访问的上一个元素

尽管java提供了访问某个特定位置元素的方法get(int index),但效率低(你懂的)。

package Test;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;


public class Main {
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(3);
        list.add(5);
        ListIterator<Integer> iter = list.listIterator();

        while (iter.hasNext()) {
            System.out.print(iter.next() + " ");
        }
        System.out.println();
        while(iter.hasPrevious()){
            System.out.print(iter.previous() + " ");
        }
        System.out.println();
        System.out.println(list.get(1));
    }
}
//
1 3 5 
5 3 1 
3

HashSet

构造

HashSet();
Hashset(Collection<?extends E> elements);
HashSert(int cap);
HashSet(int cap, float loadFactor); //构造一个指定容量,装填因子的空散列表(当大约这个因子,就再散列)

例子

package Test;

import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<Integer> s = new HashSet<>();
        s.add(1);
        s.add(3);
        s.add(1);
        System.out.println(s);
    }
}

TreeSet

元素会自动排序,采用红黑树实现。

构造

TreeSet();
TreeSet(Comparator<?super E>comparator;
TreeSet(collection<? extends E> elements);
TreeSet(SortSet<E>s);  //以已有的集合/有序集合构造

TreeSet继承了NavigableSet接口,对应的接口:

  • E higher/lower(E val):返回排序后第一个大于/小于val的元素

  • E ceiling/floor(E val): ​返回排序后第一个大于等于/小于等于val的元素

  • E pollFirst()/pollLast():删除并返回第一个/最后一个元素

队列和双端队列

Queue

boolean offer(E e); 
//添加元素,如果队列已经满了,就返回false
E poll(); //队列为空,返回null
E peek();  //返回队首元素不删除,若为空,返回null

Deque

boolean offerFirst(E e);  //队首增加元素e
boolean offerLast(E e); //队尾增加元素e
E pollFirst();
E pollLast();   //删除队首/队尾元素
E peekFirst();
E peekLast();   //返回队首/队尾元素

例子

package Test;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Deque<Integer>deque = new LinkedList<>();
        deque.offerLast(4);
        deque.offerFirst(1);
        System.out.println(deque);
    }
}

优先队列PriorityQueue

构造时可以提供容量,容量达到了再添加元素会扩容,默认是小根堆

public class Main {
    public static void main(String[] args) {
       PriorityQueue<Integer> pq = new PriorityQueue<>();
       pq.add(1);
       pq.add(8);
       pq.add(4);
       pq.add(2);
       while(!pq.isEmpty()){
           System.out.println(pq.remove());
       }
    }
}

大根堆的定义:

 PriorityQueue<Integer> pq = new PriorityQueue<>(11, new Comparator<Integer>() {
            //大顶堆,容量为11
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });

替换为lambda表达式:

 PriorityQueue<Integer> pq = new PriorityQueue<>(11, (o1, o2) -> o2-o1);

映射HashMap,TreeMap

二者都实现了Map<K,V>的接口,

  • V get(object K):获取键对应的值,没有就返回null
  • default V getOrDefault(object key, V val):获取值,若没有这个键,则返回设定的默认值,注意 这个接口不会将键,值插入
  • V put(K key, V value); ​存入键值对,若键已存在,则返回旧值,反正返回null
  • void putAll(Map<? extends K, extends V>entries)将entries的条目放入映射
  • boolean containsKey(Object key); ​是否含有键

LinkedHashSet/LinkHashMap

变化的是插入的各个元素之间组成了双向链表,记录了相对位置。此外LRU缓存有个最近最少使用,采用LinkHashMap也可以实现,最后一个参数设置为true即可。

Map<Integer,Integer> mp = new LinkedHashMap<>(12, 0.75F,true);
mp.put(5,2);
mp.put(10,5);
mp.put(4,5);
mp.put(1,5);
System.out.println(mp);
mp.get(5);
System.out.println(mp);
/*
{5=2, 10=5, 4=5, 1=5}
{10=5, 4=5, 1=5, 5=2}
*/

多线程

创建

创建有三种方式

继承自Thread

最后创建方法,Test t = new Test(); t.start();

package MyThread;

public class Test extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程开始:"+i);
        }
    }
}

缺点:继承的Thread类功能比较单一。注意:直接用run方法,会被当做普通方法,而不能实现多线程。

继承Runable接口

  • 定义线程任务类MyRunnable,重写run方法
  • 创建MyRunnable任务对象
  • MyRunnable对象交给Thead处理
  • 调用start()处理
package MyThread;

public class Test implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程开始**********:"+i);
        }
    }
}

Test th = new Test();
Thread t = new Thread(th);
t.start();

可以采用匿名的方法实现。优点:扩展性更强。缺点:多一层封装,无法处理有返回值的线程任务

实现Callable接口

  • 得到任务对象:1. 定义类实现Callable接口,重写call方法,封装要做的事; 2. 用FutureTask把Callable对象封装成线程对象。
  • 把FutureTask传递给Thread创建对象
package MyThread;

import java.util.concurrent.Callable;

public class Test implements Callable<Integer> {
    private int n;
    public Test(int n){
        this.n = n;
    }
    @Override
    public Integer call() throws Exception {
       int sum = 0;
       for(int i = 1; i <= n;++i){
           sum += i;
       }
       return sum;
    }
}

package MyThread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test th = new Test(100);
        FutureTask<Integer> ft = new FutureTask<>(th);
        Thread t1 = new Thread(ft);
        t1.start();
        System.out.println(ft.get()); //等待线程结束才提取结果
    }
}
//5050

优点:扩展性强,可获得线程执行的结果。缺点:编程相对复杂

Thread常用方法

  • String getName():获取线程名字

但也可以直接起名字,实际开发中并不自己起名字,因为默认就有名字

package MyThread;

public class Test extends Thread{
    Test(){

    }
    Test(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

package MyThread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Test("1号");
        //t1.setName("1号");
        t1.start();

        Thread t2 = new Test("2号");
       // t2.setName("2号");
        t2.start();

        Thread cur = Thread.currentThread();
        System.out.println(cur.getName()); //等待线程结束才提取结果
        for (int i = 0; i < 5; i++) {
            System.out.println("main线程:"+i);
        }
    }
}

  • public static void sleep(long time):线程睡眠time毫秒
package MyThread;

import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 5; i++) {
            if(i == 3){
                Thread.sleep(3000); //本线程这里休眠3000毫秒
            }
            System.out.println("i:"+i);
        }
    }
}

同步机制

以双方从一个共享账户取钱为例

账户类

package MyThread;

public class Account {
    private  String cardId;
    private  double money;

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public void withdraw(String name, int money) {
        Thread t  = Thread.currentThread();
        synchronized (this){
            if(this.money >= money){
                System.out.println(name + "取走" + money + "元");
                this.money -= money;
                System.out.println("账户余额:" + this.money);
            }
            else{
                System.out.println(name + "的余额为:" + this.money + ",取钱失败!");
            }
        }
    }
}

用户类

package MyThread;

public class UserThread extends Thread{
    private String name;
    private int money;
    private Account account;
    UserThread(String name, int money, Account account){
        this.name = name;
        this.money = money;
        this.account = account;
    }
    @Override
    public void run() {
        account.withdraw(name, money);
    }
}

main函数调用

package MyThread;

public class Main {
    public static void main(String[] args) {
       Account account = new Account("M201972608",100);
       UserThread user1 = new UserThread("张三",100,account);
       user1.start();
       UserThread user2 = new UserThread("李四",100,account);
       user2.start();
    }
}

如果没有synchronized设置同步,执行结果为最后账户余额:-100.0,,而采用synchronized后,逻辑正确。synchronized提倡用唯一的不变独享对象作为锁,但不可用全局唯一的,因此面向对象实现时,提倡用this。如果静态方法,同步默认用类名.class.此外也可以把方法设置为同步方法public synchronized void withdraw(String name, int money),这个效率虽然低一点,但是写法简单,根据方法名就知道要完成同步。

Lock锁

  • 定义锁: private final Lock lock = new ReentrantLock();
  • 上锁:​ lock.lock()
  • 解锁:lock.unlock()

此外由于开发中安全起见,在try-final中上锁解锁,即及时中间代码执行出异常,也能成功解锁。在之前的withdraw方法中上锁,在try里面故意设置一个异常,finally里面解锁

 public void withdraw(String name, int money) {
        Thread t  = Thread.currentThread();
        lock.lock();
        try {

            if(this.money >= money){
                System.out.println(name + "取走" + money + "元");
                this.money -= money;
                System.out.println("账户余额:" + this.money);
            }
            else{
                System.out.println(name + "的余额为:" + this.money + ",取钱失败!");
            }
            System.out.println(10 / 0);
        }
        finally {
            lock.unlock();
            System.out.println("解锁啦!");
        }
    }

发现即使出现了异常,锁依旧被解开(防止开发过程中,某一处出现错误占用锁被释放,造成死锁。

图片名称

线程间通信

以生产者消费者线程为例:

共享资源类

package Produce_Consumer;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Goods {
    private int num_goods = 0;
    private final Lock lock = new ReentrantLock();
    private static  final int Capacity = 1000;
    public  synchronized void produce(int add){
        try{
            if(num_goods < Capacity ){
                System.out.println("生产者进入,当前产品数为:"+this.num_goods);
                num_goods += add;
                System.out.println(Thread.currentThread().getName() + "生产了1个产品,当前产品数为:"+this.num_goods);
            }
            this.notifyAll();
            this.wait();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public  synchronized void consume(int sub){
        try{
            if(num_goods >= sub){
                System.out.println("消费者进入,当前产品数为:"+this.num_goods);
                num_goods -= sub;
                System.out.println(Thread.currentThread().getName() + "消费了1个产品,当前产品数为:"+this.num_goods);
            }
            this.notifyAll();
            this.wait();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


生产者线程

package Produce_Consumer;

public class Producer extends Thread{
    private Goods goods;
    Producer(Goods goods){
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(1000);
                goods.produce(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

消费者线程

package Produce_Consumer;

public class Consumer extends Thread {
    private Goods goods;
    Consumer(Goods goods){
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(2000);
                goods.consume(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Main调用

package Produce_Consumer;

public class Main {
    public static void main(String[] args) {
        Goods goods = new Goods();
        new Producer(goods).start();
        new Producer(goods).start();
        new Producer(goods).start();


        new Consumer(goods).start();
        new Consumer(goods).start();
        new Consumer(goods).start();
        new Consumer(goods).start();
    }
}

线程池

优点:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

java的线程池的实现类ThreadPoolExecutor,参数最全的构造器:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
图片名称

核心线程始终存活,临时线程可能用完就销毁。新任务来了,发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,就会创建它。

创建任务

package ThreadPool;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i = 0;i < 5;++i){
            System.out.println(Thread.currentThread().getName() + " is printing " + i);
            try{
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

创建线程池,添加任务到线程池

package ThreadPool;

import java.util.concurrent.*;

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunnable();
        int n = 100;
        while(--n > 0){
            pool.execute(target);
        }
    }
}

图片名称

关闭线程池:​ pool.shutdown() 或者​ pool.shutdownNow(),前者将正在执行的任何执行完再关闭,后者立即关闭

利用线程池提交有返回值的任务

任务

package ThreadPool;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    private int n;
    MyCallable(int n){
        this.n = n;
    }
    @Override
    public String call() throws Exception {
        int sum = 0;
       for(int i = 1; i <= n;++i){
           sum += i;
       }
        return Thread.currentThread().getName() + "计算1+2+...+" + n +"的和,结果为:" + sum;
    }
}


执行任务

package ThreadPool;

import java.util.concurrent.*;

public class ThreadPoolTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(100));
        Future<String> f3 = pool.submit(new MyCallable(100));
        Future<String> f4 = pool.submit(new MyCallable(100));
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        pool.shutdown();   //不加这个主线程不会结束
    }
}

Executors得到线程池对象的常用方法

图片名称

定时器

控制任务延迟调用或者周期调用。如视频定时加载广告,闹钟,邮件定时发送。

Timer定时器

 Timer t = new Timer();
 t.schedule(new TimerTask() {
   @Override
	public void run() {
	try {
                    Thread.sleep(5000);
            } catch (InterruptedException e) {
                    throw new RuntimeException(e);
            }
                System.out.println(Thread.currentThread().getName() + "访问了AAA!");
           }
   },2000,1000);

在当前时间,延迟2000毫秒后,没间隔1000毫秒执行任务。Timer定时器虽然简单,但是缺点明显:收到该定时器其他任务的干扰,如下面的例子,第一个任务中延迟了5000毫秒,结果第二个任务是才7秒才执行,且某个任务挂了,其他任务也挂

package Main;

import java.util.Timer;
import java.util.TimerTask;

public class Main {
    public static void main(String[] args) {
        Timer t = new Timer();
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "访问了AAA!");
            }
        },2000,1000);
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "访问了BBB!");
            }
        },2000,1000);
    }
}

ScheduledExecutorService线程池制作定时器

package Main;

import java.sql.Time;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is printing AAA.");
            }
        },5,2, TimeUnit.SECONDS);
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is printing BBB.");
            }
        },0,2, TimeUnit.SECONDS);
    }
}

延迟\(x\)秒后,每次间隔\(y\)秒执行run内的任务。优点:采用了多线程技术,互不干扰(某一个挂了,其他不受影响)

图片名称

阻塞队列

ArrayBlockingQueue

图片名称

底层通过数组实现,必须指定容量。接口:

  • put: ​队尾存放元素,若队列已经满了再次存放会阻塞(锁实现)
  • take:队首取出元素,若队列为空再取元素会阻塞(锁实现)

用同一把锁同步存和取操作,是因为二者是会相互影响的过程:通过源码发现ArrayBlockingQueue维护了一个int变量count记录当前队列的元素个数,而存、取都会修改这个变量,如果不用同一把锁,会造成这个变量修改出现问题。而LinkBlockingQueue的元素存取各自采用了一把锁,但是用的是原子变量AtomicInteger记录元素个数。同时存放没有问题,多个同时修改原子变量,只有一个线程能成功。

网络编程

TCP通信

单线程多发多收

客户端

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        try{
            Socket socket = new Socket("127.0.0.1",7777);
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
            bw.write("你好,服务器");
            bw.newLine();
            bw.flush();
            String data  = br.readLine();
            System.out.println(socket.getRemoteSocketAddress()+ "发送了:" + data);
            Scanner sc = new Scanner(System.in);
            while(true) {
                String msg = sc.nextLine();
                bw.write(msg);
                bw.newLine();
                bw.flush();
            }
           // socket.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

服务器

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try{
            ServerSocket serversocket = new ServerSocket(7777);
            Socket socket = serversocket.accept();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));

            bw.write("你好,客户端!");
            bw.newLine();
            bw.flush();
            String msg;
            while((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "发送了:" + msg);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }

    }
}

需要注意的是,发送(写到缓存)消息时,每次发送写入后需要发送一个换行符,不然会卡主。然而,服务器无法接收多个客户端的消息。

服务器多线程

客户端不变,修改服务器端。

  • 主线程:循环接收socket
  • 子线程:处理单个socket的消息

服务器

import java.net.ServerSocket;
import java.net.Socket;

public class MultiServer {
    public static void main(String[] args) {
        try{
            System.out.println("服务器启动!");
            ServerSocket server = new ServerSocket(7777);
            while(true){
                Socket socket = server.accept();
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

处理消息的子线程

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerReaderThread extends Thread {
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
            String msg;
            while((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() +" send:" + msg);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() +"下线了");
           // throw new RuntimeException(e);
        }
    }
}

以上实现看似解决了问题,但是高并发场景,大量客户端请求过来,那么会创建大量线程处理对应的请求,所以需要利用线程池优化

线程池优化

修改服务器,采用线程池优化。优点:复用线程处理多个客户端,适用于客户端连接时间较短的场景。

处理任务的接口

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerReaderRunnable implements Runnable {
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
            String msg;
            while((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() +" send:" + msg);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() +"下线了");
            // throw new RuntimeException(e);
        }
    }
}

服务器端

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class MultiServer {
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),
            Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) {
        try{
            System.out.println("服务器启动!");
            ServerSocket server = new ServerSocket(7777);
            while(true){
                Socket socket = server.accept();
                Runnable target = new  ServerReaderRunnable(socket);
                pool.execute(target);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

群聊模式

服务器群发从客户端接收的消息。所以客户端具备接收和发送消息的能力

package PortForward;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/*
    客户端同时处理接收和发送
 */
public class Client {
    public static void main(String[] args) {
        try{
            Socket socket = new Socket("127.0.0.1",7777);
            new ClientReadThread(socket).start();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
            WriteMsg(bw);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void WriteMsg(BufferedWriter bw) throws IOException {
        bw.write("你好,服务器");
        bw.newLine();
        bw.flush();
        Scanner sc = new Scanner(System.in);
        while(true) {
            String msg = sc.nextLine();
            bw.write(msg);
            bw.newLine();
            bw.flush();
        }
    }
}

package PortForward;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ClientReadThread extends Thread{
    private Socket socket;
    public ClientReadThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
            String msg;
            while((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() +" send:" + msg);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() +"把你踢出去了!");
        }
    }
}

服务器端要群发的话,需要用一个静态List记录连接的socket,然后将从某个客户端接收的消息发送给其他客户端。

package PortForward;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;


public class Server {
    public static List<Socket> allOnLineSocket = new ArrayList<>();

    public static void main(String[] args) {
        try {
            System.out.println("服务器启动!");
            ServerSocket server = new ServerSocket(7777);
            while (true) {
                Socket socket = server.accept();
                allOnLineSocket.add(socket);
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


package PortForward;

import java.io.*;
import java.net.Socket;


public class ServerReaderThread extends Thread {
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
            String msg;
            while((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() +" send:" + msg);
                sendMsgToAll(msg,socket);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() +"下线了");
            Server.allOnLineSocket.remove(socket);
            // throw new RuntimeException(e);
        }
    }

    private void sendMsgToAll(String msg,Socket mysocket) throws IOException {

        for(Socket socket: Server.allOnLineSocket){
            if(socket == mysocket) continue;
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
            bw.write(msg);
            bw.newLine();
            bw.flush();
        }
    }
}

BS模型服务器

客户端为浏览器,因此需要按照网络协议发送给浏览器信息

package BSserver;

import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try{
            ServerSocket server = new ServerSocket(8080);
            while (true) {
                Socket socket = server.accept();
                new ServerReaderThread(socket).start();
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }

    }
}

package BSserver;

import java.io.*;
import java.net.Socket;

public class ServerReaderThread extends Thread {
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
            StringBuffer str = new StringBuffer();
            str.append("HTTP/1.1 200 OK\r\n");
            str.append("Content-type:text/html;charset=utf-8\r\n");
            str.append("\r\n");
            str.append("<html><head><title>网页</title></head><body>开眼看时间<body></html>");

            bw.write(str.toString());
            bw.flush();
            bw.close();


           /* PrintStream ps = new PrintStream(socket.getOutputStream());
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=gbk");
            ps.println();

            ps.println("<span style='color:red; font-size=500px'> 开眼看世界! </span>");
            ps.close();*/
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
图片名称

异常

异常分类

异对对象都派生于Throwable类的实例。下一层分为两个分支:

  • Error:内存错误和资源耗尽的错误,用户几乎无能为例

  • Exception:再分解为两个分支:一个派生于RuntimeException,编程错误导致的异常,另一个分支包含其他异常,如程序本身没问题,但是I/O错误导致的异常。

派生于RuntimeException的异常包括

  1. 错误的类型转换

  2. 数组访问越界

  3. 访问null指针:

    String name = null;
    System.out.println(name.length());  //nullPointerException
    

其他异常

  1. 试图打开不存在的文件
  2. 试图越过文件尾继续读取数据

默认的异常处理

默认在异常的代码哪里创建一个异常对象,抛出给调用者,调用者最终抛出给JVM,最终程序直接挂了。缺点:异常导致系统全部瘫痪。

异常处理

三种方式:

  1. 层层上抛(依旧会导致程序死亡),规范上抛:
方法名 throw Exception
  1. 自己处理,通过try catch。出现了异常不会导致程序挂了。try包裹容易出现问题的程序,catch捕获异常处理
public class Main {
    public static void main(String[] args) {
        try {
            int a = 2;
            int b = 0;
            System.out.println(a / b);
        } catch (Exception e) {
            System.out.println("出现了除以0的异常");
        }
        System.out.println("我运行到这了哦!");
    }
}

//运行结果
出现了除以0的异常
我运行到这了哦!
  1. 抛给上层调用者,由上层调用者决定怎么处理。
public class Main {
    public static void main(String[] args) {
        try {
            test();
        } catch (Exception e) {
            System.out.println("下层抛给我的异常");
        }
        System.out.println("我运行到这了哦!");
    }

    public static void test() throws Exception{
        System.out.println(3 / 0);
    }
}
//
下层抛给我的异常
我运行到这了哦!

异常处理

编译时异常处理形式有三种:

  • 层层上抛(依旧抛给虚拟机,程序死亡),规范做法:
方法名 throw Exception
  • 自己捕获异常处理:
try{
   容易出问题的代码
}catch(异常变量 变量1){
}
catch(异常变量 变量2){
}
...
  • 抛出给调用者,调用者处理:这种处理是比较好的

日志

日志相对于直接打印的优点:

  • 存储于文件或者数据库
  • 随时以开关控制是否记录日志,无需改代码
  • 多线程场景性能好

Logback

是由log4j创始人设计一款性能更好开源日志

图片名称

使用logback

安装与配置

先安装以下三个文件:logback下载地址,slf4j下载地址

  • slf4j-api-1.27x.jar
  • logback-classic-1.2.x.jar
  • logback-core-1.2.x.jar

创建配置文件logback.xml到src目录下,文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <file>log/output.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>log/output.log.%i</fileNamePattern>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>1MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <root level="all">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

接着将上面在项目下创建文件夹lib,把上面三个jar文件复制到里面,鼠标右键点击:添加为库(项目库)

图片名称

测试

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static final Logger LOGGER = LoggerFactory.getLogger("Main.class");
    public static void main(String[] args) {
        LOGGER.debug("调试开始");
        LOGGER.info("我要开始做除法");
        try {
            int a = 1;
            int b = 0;

            LOGGER.trace("a = " + a);
            LOGGER.trace("b = " + b);
            System.out.println(a / b);
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("异常出现了" + e);
        }
    }
}

配置文件里的level=all表示所有级别都显示,控制台最终显示:

图片名称

此外所有历史记录都在该项目的log文件夹下的output.log里面(自动创建)

图片名称

日志级别

级别层次:error > warn > info > debug > trace。例如在logback中配置显示的日志信息为info,则只有级别不低于info的才显示。此外,设置all/off,表示显示/关闭所有日志信息

反射

图片名称

获取类

//静态方法forName
Class<?> c1 = Class.forName("JavaBean.Person");   // class JavaBean.Person
 //类名
Class c2 = Person.class;
    
 //对象.class
Person p = new Person();
Class<? extends Person> c3 = p.getClass();  // class JavaBean.Person

获取构造器

Constructor<?>[] constructors = c1.getConstructors();  //只能获取公开的构造器
Constructor<?>[] constructors = c1.getDeclaredConstructors();  //公开,私有都获取

for( Constructor<?> constructor:constructors){
       System.out.println(constructor);
}


文件

try-with-resource

概念

用try语句声明资源, 资源对象在执行完自动关闭. 任何实现了java.lang.AutoCloseable​包括java.io.Closeable​的对象,采用这种写法都能自动关闭资源, 即使出现异常.

public class MyAutoClosable implements AutoCloseable {
    public void doInit() {
	System.out.println(1 / 0);
        System.out.println("MyAutoClosable doing it!");
    }

    @Override
    public void close() throws Exception {
        System.out.println("MyAutoClosable closed!");
    }

    public static void main(String[] args) {
        try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
            myAutoClosable.doInit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
图片名称

结果发现: 即使出现异常, 自动调用close方法.

使用场景4

读取文件过程中, 如果出现异常, 我们往往希望将文件关闭, 防止损坏文件. 传统处理方法:

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
     	  Main obj = new Main();
       	  obj.readFile();
    }
    public void readFile()  {
        FileReader fr = null;
        BufferedReader br = null;
        try{
            fr = new FileReader("d:/1.txt");
            br = new BufferedReader(fr);
            System.out.println(1 / 0);    // 人为制造异常
            String s = "";
            while((s = br.readLine()) != null){
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                System.out.println("开始关闭");
                br.close();
                fr.close();
                System.out.println("关闭成功");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

图片名称

换用try-with-resouce写法十分简洁, 且正确性能得到保证. 其中FileReader 和BufferedReader 都实现了Closeable的接口

public void readFileWithTryResource() throws FileNotFoundException {
        try(
                FileReader fr = new FileReader("d:/input.txt");
                BufferedReader br = new BufferedReader(fr)
        ){
            String s = "";
            while((s = br.readLine()) != null){
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

泛型

何为泛型

泛型就是定义一种模板, 使用时实例化该类型. 我理解的是和C++的模板编程基本一样

ArrayList<String> strList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

向上转型

可以转为接口类型, 如上面的List​实际上为接口interface​, 虽然Number是Integer的父类, 但是不能将​ ArrayList<Integer>​​其转为ArrayList<Number>​​. 原因很简单, 转为ArrayList<Number>​​后可以添加double, float等类型的数据, 但它本身是ArrayList<Integer>​​,不能添加其他类型数据, 故编译器禁止了这种做法. 根本上: ArrayList<Number>​​ArrayList<Integer>​​没有继承关系.

T类型和?通配符

表示括号里要用到泛型参数,

List<T> al = new ArrayList<T>();指定集合元素只能是T类型  
List<?> al = new ArrayList<?>();集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法  
List<? extends E> al = new ArrayList<? extends E>(); E:接收E类型或者E的子类型
Integer[] arr = {1, 2, 3};
Double[] arr2 = {1.2d, 2.5d};
String[] arr3 = {"aa", "bb"};
ArrayList<Integer> arrayList1 = new ArrayList<>(Arrays.asList(arr));
ArrayList<Double> arrayList2 = new ArrayList<>(Arrays.asList(arr2));
ArrayList<String> arrayList3 = new ArrayList<>(Arrays.asList(arr3));

List<? extends Number> list = arrayList1;

System.out.println(x);
System.out.println(list);
list = arrayList2;
System.out.println(list);

// list = arrayList3;  // error
  • < ? extends E>: 限定了只能接受E类型或者E的子类型. 故上面list = arrayList3无法通过编译
  • ?super E: 限定接收E类型或者E的父类型.

ArrrayList中< ? extends E>的一种典型应用: 其中E为初始限定ArrayList的模板类型, 可以保证传入数据类型的正确性.

   //ArrayList集合的构造器,extends代表E范型或是E范型的子类,否则报错。
   public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
      
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  

posted @ 2022-11-28 00:14  shmilyt  阅读(21)  评论(0编辑  收藏  举报