Java语言的学习---学习网站:廖雪峰,自行百度

不忘初心,方得始终
此篇文章主要参考廖雪峰的网站写的学习笔记,记录学习中出现的一些问题。
这篇文章用来激励自己吧,进阶之路,时间之长,凡是万物都需要积累,文章中作者说,跟着前辈或者跟着项目一步一个脚印,以点到面。方可有所成就。
不得不说,学习基础知识,有点乏味,听歌,写日志来调节学习的无聊。有时候生产任务都比这些有意思,而且,生产任务可以运用到许多知识。但是在实现生产任务的时候,才会回过神来感受到,自己学过的知识点是原来如此。正是为了这个原来如此,才苦巴巴的看这些吧。
https://blog.csdn.net/weixin_42683814/article/details/116569126?spm=1001.2014.3001.5501

java基础开凿

1.布尔类型

布尔类型(boolean)只有truefalse两个值,布尔类型总是关系运算的计算结果:
文章取名或者例子都结合了实际例子,比如下面,更大吗?是成年人吗?

boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
int age = 12;
boolean isAdult = age >= 18; // 计算结果为false

2.char

注意char类型使用 单引号 '且仅有一个字符,要和双引号字符串类型区分开。

public class Main {
    public static void main(String[] args) {
        char a = 'A';
        char zh = '中';
        System.out.println(a);
        System.out.println(zh);
    }
}

3.引用类型

除了上述基本类型的变量,剩下的都是引用类型。
例如,引用类型最常用的就是String字符串

String s = "hello";

引用类型的变量类似于C语言的指针,它内部存储一个“地址”,指向某个对象在内存的位置。

4.常量

定义变量的时候,如果加上final修饰符,这个变量就变成了常量:

final double PI = 3.14; // PI是一个常量
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!

常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。

5.字符类型

字符类型char是基本数据类型,它是character的缩写。一个char保存一个Unicode字符

因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节。要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可:

int n1 = 'A'; // 字母“A”的Unicodde编码是65
int n2 = '中'; // 汉字“中”的Unicode编码是20013

6.字符串类型

和char类型不同,字符串类型String是引用类型,我们用双引号"…"表示字符串。一个字符串可以存储0个到任意个字符:

String s = ""; // 空字符串,包含0个字符
String s1 = "A"; // 包含一个字符
String s2 = "ABC"; // 包含3个字符
String s3 = "中文 ABC"; // 包含6个字符,其中有一个空格

(1)要判断引用类型的变量内容是否相等,必须使用equals()方法:

public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "HELLO".toLowerCase();
        System.out.println(s1);
        System.out.println(s2);
        if (s1.equals(s2)) {
            System.out.println("s1 equals s2");
        } else {
            System.out.println("s1 not equals s2");
        }
    }
}

(2)创建实例的时候,我们经常需要同时初始化这个实例的字段

Person ming = new Person();
ming.setName("小明");
ming.setAge(12);

初始化对象实例需要3行代码,而且,如果忘了调用setName()或者setAge(),这个实例内部的状态就是不正确的。

创建实例的时候,实际上是通过构造方法来初始化实例的。我们先来定义一个构造方法,能在创建Person实例的时候,一次性传入name和age,完成初始化
Person p = new Person(“Xiao Ming”, 15);

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Xiao Ming", 15);
        System.out.println(p.getName());
        System.out.println(p.getAge());
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

7.默认构造方法

如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句
class Person {
public Person() { }
}

如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法:

public class Main {
    public static void main(String[] args) {
        Person p = new Person(); // 编译错误:找不到这个构造方法!!(Person p = new Person(name,age);)才正确
    }
}
class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
      public String getName() {
        return this.name;
    }
    public int getAge() {
        return this.age;
   }
}

8.继承父时,需要在子类里调用父类的构造方法

class Student extends Person {
    protected int score;

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

9.多态

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

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

这个其实是在覆写@override时候,你写的父亲变量,调用的是子类,问你到底用的是父还是子,其实是子;

一个实际类型为Student,引用类型为Person的变量,调用其run()方法,调用的是Person还是Student的run()方法?

运行一下上面的代码就可以知道,实际上调用的方法是Student的run()方法。因此可得出结论:

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

10.覆写(Override)

子类如果定义了一个与父类方法签名完全相同的方法

(1)overload重载-当方法相同参数不同时

String类提供了多个重载方法indexOf(),可以查找子串:

int indexOf(int ch):根据字符的Unicode码查找;

int indexOf(String str):根据字符串查找;

int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;

int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。

11.内部类

Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。

12.classpath是jvm的环境变量

java是编译型语言,文件后缀为.java,编译后为.class(jvm可识别的字节码)

比如:加载abc.xyz.hello类,找hello.class文件

classpath是 .;C:\work\project1\bin;C:\shared
点为当前路径

*ClassNotFoundException错误

出现则说明漏写了jar包

11.模块

从Java 9开始引入的模块,主要是为了解决“依赖”这个问题。如果a.jar必须依赖另一个b.jar才能运行,那我们应该给a.jar加点说明啥的,让程序在编译和运行的时候能自动定位到b.jar,这种自带“依赖关系”的class容器就是模块。

12.string

String还提供了isEmpty()和isBlank()来判断字符串是否为空和空白字符串:

"".isEmpty(); // true,因为字符串长度为0
"  ".isEmpty(); // false,因为字符串长度不为0
"  \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符

分割字符串
要分割字符串,使用split()方法,并且传入的也是正则表达式:

String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

拼接字符串
拼接字符串使用静态方法join(),它用指定的字符串连接字符串数组:

String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

13.格式化字符串

格式化字符串
字符串提供了formatted()方法和format()静态方法,可以传入其他参数,替换占位符,然后生成新的字符串:

public class Main {
    public static void main(String[] args) {
        String s = "Hi %s, your score is %d!";
        System.out.println(s.formatted("Alice", 80));
        System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
    }
}

有几个占位符,后面就传入几个参数。参数类型要和占位符一致。我们经常用这个方法来格式化信息。常用的占位符有:

%s:显示字符串;
%d:显示整数;
%x:显示十六进制整数;
%f:显示浮点数。
占位符还可以带格式,例如%.2f表示显示两位小数。如果你不确定用啥占位符,那就始终用%s,因为%s可以显示任何数据类型

14.类型转换

要把任意基本类型或引用类型转换为字符串,可以使用静态方法valueOf()。这是一个重载方法,编译器会根据参数自动选择合适的方法:

String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c

要把字符串转换为其他类型,就需要根据情况。例如,把字符串转换为int类型:

int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255

把字符串转换为boolean类型:

boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false

要特别注意,Integer有个getInteger(String)方法,它不是将字符串转换为int,而是把该字符串对应的系统变量转换为Integer:

Integer.getInteger(“java.version”); // 版本号,11
转换为char[]
String和char[]类型可以互相转换,方法是:

char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String

如果修改了char[]数组,String并不会改变:

public class Main {
    public static void main(String[] args) {
        char[] cs = "Hello".toCharArray();
        String s = new String(cs);
        System.out.println(s);
        cs[0] = 'X';
        System.out.println(s);
    }
}

String做了特殊处理,使得我们可以直接用+拼接字符串。

考察下面的循环代码:

String s = "";
for (int i = 0; i < 1000; i++) {
    s = s + "," + i;
}

*版本问题出现的找不到类

var sb = new StringBuilder(1024);
var在java10后才出现,
StringBuilder sb = new StringBuilder(1024);

15.使用方法来修改字段值–setname()方法

由于liao的网站上写的:这种方法会报错

Person ming = new Person();
ming.name = “Xiao Ming”; // 对字段name赋值
ming.age = 12; // 对字段age赋值

//观察下面的代码可以看出,可以对传入的参数进行判断
专业说法叫做在类当中暴露接口,来使得外部对内部的privte字段进行修改
public void setName(String name) {
    if (name == null || name.isBlank()) {
        throw new IllegalArgumentException("invalid name");
    }
    this.name = name.strip(); // 去掉首尾空格
}
//搞过来这段代码就是比较前面的set为啥没了

 p.setAge(n); //main函数里面

public void setAge(int age) { //方法里面
        this.age = age;
    }
    //来来来,比较一下
Person p = new Person("Xiao Ming", 15);//main里面
public Person(String name, int age) { //方法里面
        this.name = name;
        this.age = age;
    }
    //好像省略了两部,方便一点

16.税收练习

for-each中的格式是for(int i:array),i是数组下标

for-each遍历数组的格式是for (数组类型 数组元素:数组),如for (int num : nums),其中nums是要遍历的数组,num实际上是在for-each循环头部定义出来的局部变量,是每次循环中在该次循环所指向的数组的元素,for-each循环没有数组下标,需要使用数组下标时不应使用for-each循环。

17.instanceof

instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例

18.调用super,这个好强啊,家人们

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

class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}

Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + “!”;
}
}

19.final修饰规则:

1.用final修饰的方法不能被Override
public final String hello() {
return "Hello, " + name;
}
2.一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。
class Person {
public final String name = “Unamed”;
}
3.final修饰的类不能被继承
final class Person {
protected String name;
}

20.抽象方法–面向抽象编程

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

父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:
class Person {
public abstract void run();
}

Person类本身也声明为abstract,才能正确编译它:
abstract class Person {
public abstract void run();
}

使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:
抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。

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

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

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

上层代码只定义规范(例如:abstract class Person);

不需要子类就可以实现业务逻辑(正常编译);

具体的业务逻辑由不同的子类实现,调用者并不关心。

21.接口

如果一个抽象类没有字段,所有方法全部都是抽象方法:

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

就可以把该抽象类改写为接口:interface。

在Java中,使用interface可以声明一个接口:

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

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

具体的class去实现一个interface时,需要使用implements关键字。
class Student implements Person {}

Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface,例如:

class Student implements Person, Hello { // 实现了两个interface

}
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法。例如:

interface Hello {
    void hello();
}

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

在这里插入图片描述

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

22.default方法

在接口中,可以定义default方法。例如,把Person接口的run()方法改为default方法:

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

23. 静态字段

静态字段是公用的,实力对象组好不要通过实力访问该字段,直接person.number访问
在这里插入图片描述

public class Main {
    public static void main(String[] args) {
        Person ming = new Person("Xiao Ming", 12);
        Person hong = new Person("Xiao Hong", 15);
        ming.number = 88;
        System.out.println(hong.number);
        hong.number = 99;
        System.out.println(ming.number);
    }
}
class Person {
    public String name;
    public int age;
    public static int number;
    public Person(String name, int age) {
        this.name = name;
       this.age = age;
    }
}

24.包

  • JVM运行时,看完整的类名;
  • 编写class的时候,编译器会自动帮我们做两个import动作:
  • 默认自动import当前package的其他class;
    默认自动import java.lang.*。
    注意:自动导入的是java.lang包,但类似java.lang.reflect这些包仍需要手动导入。

25.内部类–可以修改类的private字段

class Outer {
    class Inner {
        // 定义了一个Inner Class
    }
}
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested"); // 实例化一个Outer
        Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
        inner.hello();
    }
}

class Outer {
    private String name;

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

    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

26.模块

jar文件就是class文件的容器
漏写了某个运行时需要用到的jar,那么在运行期极有可能抛出ClassNotFoundException。
Java标准库已经由一个单一巨大的rt.jar分拆成了几十个模块,这些模块以.jmod扩展名标识
在这里插入图片描述
bin目录下的所有class文件先打包成jar,在打包的时候,注意传入–main-class参数,让这个jar包能自己定位main方法所在的类:

$ jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .

当前目录下得到了hello.jar这个jar包,它和普通jar包并无区别,可以直接使用命令java -jar hello.jar来运行它。
但是我们的目标是创建模块,所以,继续使用JDK自带的jmod命令把一个jar包转换成模块:

$ jmod create --class-path hello.jar hello.jmod

当前目录下我们又得到了hello.jmod这个模块文件

运行j模块

$ java --module-path hello.jmod --module hello.world

hello.jmod,会出错,–module-path只能是jar,所以以下才对

$ java --module-path hello.jar --module hello.world

而打包出来的jmod为了打包jre使用。
将自身模块和需要的三个模块打包为jre发送给别人,个头小;

$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/

方便分发和部署
当前目录下,我们可以找到jre目录,这是一个完整的并且带有我们自己hello.jmod模块的JRE。试试直接运行这个JRE:
其实就是把需要的包打包成以一个jre包,然后自己打开也可以,发给对面 打开也是一样的,不会少东西,也很小。

$ jre/bin/java --module hello.world
Hello, xml!

声明的导出的包,外部代码才被允许访问

module java.xml {
    exports java.xml;
    exports javax.xml.catalog;
    exports javax.xml.datatype;
    ...
}

27.字符串

比较字符串的内容是否相同。必须使用equals()方法而不能用==。
忽略大小写比较,使用equalsIgnoreCase()方法。

public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "HELLO".toLowerCase();
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

String中和子串

// 搜索包含子串:
"Hello".contains("ll"); // true
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3 字符串最后出现的位置
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
//提取子串
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"
//移除字符串首尾空白字符。空白字符包括空格,\t,\r,\n:
"  \tHello\r\n ".trim(); // "Hello"
//strip()方法也可以移除字符串首尾空白字符
"\u3000Hello\u3000".strip(); // "Hello"
" Hello ".stripLeading(); // "Hello "
" Hello ".stripTrailing(); // " Hello"
//String还提供了isEmpty()和isBlank()来判断字符串是否为空和空白字符串
"".isEmpty(); // true,因为字符串长度为0
"  ".isEmpty(); // false,因为字符串长度不为0
"  \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符
//替换子串
String s = "hello";
s.replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
s.replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~"
//正则表达式替换:
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
//分割字符串
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
//拼接字符串
拼接字符串使用静态方法join(),它用指定的字符串连接字符串数组:
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
//类型转换
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c
//字符串转换为int类型
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255
//字符串转换为boolean类型
boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false
//系统变量转换为Integer
//Integer有个getInteger(String)方法
Integer.getInteger("java.version"); // 版本号,11

修改了char[]数组,String并不会改变:
new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组。

//String和char[]类型可以互相转换,方法是
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String

public class Main {
    public static void main(String[] args) {
        char[] cs = "Hello".toCharArray();
        String s = new String(cs);
        System.out.println(s);
        cs[0] = 'X';
        System.out.println(s);
    }
}

Score类保存一组学生的成绩

public class Main {
    public static void main(String[] args) {
        int[] scores = new int[] { 88, 77, 51, 66 };
        Score s = new Score(scores);
        s.printScores();
        scores[2] = 99;
        s.printScores();
    }
}
class Score {
    private int[] scores;
    public Score(int[] scores) {
        this.scores = scores;
    }
    public void printScores() {
        System.out.println(Arrays.toString(scores));
    }
}
//格式化字符串
public class Main {
    public static void main(String[] args) {
        String s = "Hi %s, your score is %d!";
        System.out.println(s.formatted("Alice", 80));
        System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
    }
}
%s:显示字符串;
%d:显示整数;
%x:显示十六进制整数;
%f:显示浮点数。

28.高效拼接字符串–StringBuilder

因为var是java9版本后,所以需要改变为

StringBuilder sb = new StringBuilder(1024);
//var sb = new StringBuilder(1024);
sb.append("Mr ")
  .append("Bob")
  .append("!")
  .insert(0, "Hello, ");
System.out.println(sb.toString());

StringJoiner的结果少了**前面的"Hello “和结尾的”!"!**遇到这种情况,需要给StringJoiner指定“开头”和“结尾”:

29.包装类型–把int类包装为integer–变为引用类型

引用类型可以赋值为null,表示空,但基本类型不能赋值为null:

基本类型:byte,short,int,long,boolean,float,double,char
引用类型:所有class和interface类型
int i = 100;
        // 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
        Integer n1 = new Integer(i);
        // 通过静态方法valueOf(int)创建Integer实例:
        Integer n2 = Integer.valueOf(i);
        // 通过静态方法valueOf(String)创建Integer实例:
        Integer n3 = Integer.valueOf("100");
        System.out.println(n3.intValue());

自动装箱
因为int和Integer可以互相转换:

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

所以,Java编译器可以帮助我们自动在int和Integer之间转型:

Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()

class代码是严格区分基本类型和引用类型的。自动拆箱执行时可能会报NullPointerException:

必须用equals()方法比较两个Integer

Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true

parseInt()可以把字符串解析成一个整数

int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析

System.out.println(n);是依靠核心库自动把整数格式化为10进制输出
使用Integer.toHexString(n)则通过核心库自动把整数格式化为16进制。

转换类型 证书和浮点数包装类型继承自number
// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

30.处理无符号整型

负的byte按无符号整型转换为int

byte x = -1;
        byte y = 127;
        System.out.println(Byte.toUnsignedInt(x)); // 255
        System.out.println(Byte.toUnsignedInt(y)); // 127

31.JavaBean

读写方法符合以下这种命名规范:

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

那么这种class被称为JavaBean
JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。
输入以下代码:

public class Person {
    private String name;
    private int age;
}

右键,在弹出的菜单中选择“Source”,“Generate Getters and Setters”,在弹出的对话框中选中需要生成getter和setter方法的字段

boolean字段比较特殊,它的读方法一般命名为isXyz():

// 读方法:
public boolean isChild()
// 写方法:
public void setChild(boolean value)

属性property:读getter和写setter这一组
例子:name属性 string getname() string settername()

只有getter的属性称为只读属性(read-only)
只有setter的属性称为只写属性(write-only)

?枚举JavaBean属性例子不会

32.枚举类

  • 定义常量:static final
    例子:定义周一到周日这7个常量.
public class Weekday {
public static final int SUN=0;
}

使用常量:

if (day == Weekday.SAT || day == Weekday.SUN) {
    // TODO: work at home
}
  • 定义字符串类型的常量:
public class Color {
publec static final string RED="r";
}

使用常量:使用equals比较

String color = ...
if (Color.RED.equals(color)) {
    // TODO:
}

问题:编译器无法检查不在枚举中的int值
以下的weekday值为0-6,但编译不会报错

if (weekday == 6 || weekday == 7) {
    if (tasks == Weekday.MON) {
        // TODO:
    }
}

解决方案:enum 定义枚举。enum是引用类型,但可以使用==来比较。

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day == Weekday.SAT || day == Weekday.SUN) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}
enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}
MON(1, "星期一")
 @Override
    public String toString() {
        return this.chinese;
    }
覆写toString()的目的是在输出时更有可读性。

33.纪录类

String、Integer等类型的时候,这些类型都是不变类
不变类具有以下特点:
1.定义class时使用final,无法派生子类;
2.每个字段使用final,保证创建实例后无法修改任何字段。

Java 14开始,引入了新的Record类
使用record关键字,可以一行写出一个不变类。
以下被叫做纪录类

public record Point(int x, int y) {}

检查参数:例子:没有负数

public record Point(int x, int y) {
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException();
        }
    }
}

静态方法为类所有,可以通过对象来使用,也可以通过类来使用。
静态方法和实例方法的区别主要体现在两个方面:

1、在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。

2、静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
基于同样的道理,静态方法中也不能使用关键字this。

静态方法只能访问静态成员,实例方法可以访问静态和实例成员。

作为record的Point仍然可以添加静态方法
一种常用的静态方法是of()方法

public record Point(int x, int y) {
    public static Point of() {
        return new Point(0, 0);
    }
    public static Point of(int x, int y) {
        return new Point(x, y);
    }
}
var z = Point.of();
var p = Point.of(123, 456);

34.biginteger–不变类,并且继承自Number

使用的整数范围超过了long型—>java.math.BigInteger
longValueExact()方法时,如果超出了long型的范围,会抛出ArithmeticException。
将BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确。

BigInteger i = new BigInteger("123456789000");
System.out.println(i.longValue()); // 123456789000
System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range

35.bigdecimal–任意大小且精度完全准确的浮点数

BigDecimal bd = new BigDecimal("123.4500");
System.out.println(bd.multiply(bd)); // 15241.55677489

BigDecimal用scale()表示小数位数
System.out.println(bd.scale()); // 4,位小数

stripTrailingZeros()方法

  • BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal
  • BigDecimal d4 = bd.stripTrailingZeros();
  • System.out.println(bd.scale()); // 2,两位小数

设置精度–除法除不尽会报错

BigDecimal d1 = new BigDecimal("123.456789");
BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
        
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽

BigDecimal做除法的同时求余数

  BigDecimal n = new BigDecimal("12.345");
  BigDecimal m = new BigDecimal("0.12");
  BigDecimal[] dr = n.divideAndRemainder(m);
  System.out.println(dr[0]); // 102
  System.out.println(dr[1]); // 0.105

比较:

  1. equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等。
  2. 必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。

36.常用工具类

求绝对值:
随机数x,x的范围是0 <= x < 1
Math.random(); // 0.53907… 每次都不一样
区间在[MIN, MAX)的随机数

 double x = Math.random(); // x的范围是[0,1)
        double min = 10;
        double max = 50;
        double y = x * (max - min) + min; // y的范围是[10,50)
        long n = (long) y; // n的范围是[10,50)的整数
        System.out.println(y);
        System.out.println(n);

Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int
r.nextLong(); // 8811649292570369305,每次都不一样
r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
r.nextDouble(); // 0.3716...生成一个[0,1)之间的double

如果使用不安全的伪随机数,所有加密体系都将被攻破。因此,时刻牢记必须使用SecureRandom来产生安全的随机数。

java异常

37.java异常处理

方法一:约定返回错误码。
处理一个文件,如果返回0,表示成功,返回其他整数,表示约定的错误码:

int code = processFile("C:\\test.txt");
if (code == 0) {
    // ok:
} else {
    // error:
    switch (code) {
    case 1:
        // file not found:
    case 2:
        // no read permission:
    default:
        // unknown error:
    }
}

方法二:在语言层面上提供一个异常处理机制。

try {
    String s = processFile(“C:\\test.txt”);
    // ok:
} catch (FileNotFoundException e) {
    // file not found:
} catch (SecurityException e) {
    // no read permission:
} catch (IOException e) {
    // io error:
} catch (Exception e) {
    // other error:
}

Java使用异常来表示错误,并通过try … catch捕获异常;

Java的异常是class,并且从Throwable继承;

Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误;

RuntimeException无需强制捕获,非RuntimeException(Checked Exception)需强制捕获,或者用throws声明;

不推荐捕获了异常但不进行任何处理。

先把异常记录下来:

static byte[] toGBK(String s) {
    try {
        return s.getBytes("GBK");
    } catch (UnsupportedEncodingException e) {
        // 先记下来再说:
        e.printStackTrace();
    }
    return null;

所有异常都可以调用printStackTrace()方法打印异常栈。

38.捕获异常

存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。

//UnsupportedEncodingException异常是永远捕获不到的,因为它是IOException的子类。
public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException e) {
        System.out.println("IO error");
    } catch (UnsupportedEncodingException e) { // 永远捕获不到
        System.out.println("Bad encoding");
    }
    //finally语句块保证有无错误都会执行
    finally {
        System.out.println("END");
    }
}

39.抛出异常

printStackTrace()可以打印出方法的调用栈

try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }

绝大部分抛出异常的代码都会合并写成一行:

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)

通常不要在finally中抛出异常,如果发生,origin变量保存原始异常,然后调用Throwable.addSuppressed(),
把原始异常添加进来,最后在finally抛出

public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
    }
}

Throwable.getSuppressed()可以获取所有的Suppressed Exception

40.自定义异常

当我们在代码中需要抛出异常时,尽量使用JDK已定义的异常类型。例如,参数检查不合法,应该抛出IllegalArgumentException:

static void process1(int age) {
    if (age <= 0) {
        throw new IllegalArgumentException();
    }
}
一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。
BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:

public class BaseException extends RuntimeException {
}

其他业务类型的异常就可以从BaseException派生:
public class UserNotFoundException extends BaseException {
}
public class LoginFailedException extends BaseException {
}

自定义的BaseException应该提供多个构造方法:

public class BaseException extends RuntimeException {
    public BaseException() {
        super();
    }
    public BaseException(String message, Throwable cause) {
        super(message, cause);
    }
    public BaseException(String message) {
        super(message);
    }
    public BaseException(Throwable cause) {
        super(cause);
    }
}

41.NullPointerException–空指针异常–NPE-代码逻辑错误

如果一个对象为null,调用其方法或访问其字段就会产生NullPointerException,这个异常通常是由JVM抛出的,例如:

public class Main {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.toLowerCase());
    }
}

好的编码习惯可以极大地降低NullPointerException的产生,例如:
成员变量在定义时初始化:
使用空字符串"“而不是默认的null可避免很多NullPointerException
返回空字符串”"、空数组而不是null

public class Person {
    private String name = "";
}
public String[] readLinesFromFile(String file) {
    if (getFileSize(file) == 0) {
        // 返回空数组而不是null:
        return new String[0];
    }
    ...
}

42.使用断言

assert关键字来实现断言

public static void main(String[] args) {
    double x = Math.abs(-123.45);
    assert x >= 0: "x must >= 0";添加一个可选的断言消息
    System.out.println(x);
    
}

语句assert x >= 0;即为断言,断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError。
执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)参数启用断言。所以,上述程序必须在命令行下运行才有效果:-ea:com.itranswarp.sample.Main,表示只对com.itranswarp.sample.Main这个类启用断言。

43.使用JDK Logging–日志

// logging
import java.util.logging.Level;
import java.util.logging.Logger;
public static void main(String[] args) {
        Logger logger = Logger.getGlobal();
        logger.info("start process...");
        logger.warning("memory is running out...");
        logger.fine("ignored.");
        logger.severe("process will be terminated...");
    }
}

44.使用Commons Logging–Apache创建的日志模块

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
    public static void main(String[] args) {
        Log log = LogFactory.getLog(Main.class);
        log.info("start...");
        log.warn("end.");
    }
}

反射

为了解决在运行期,对某个实例一无所知的情况下

45.class类

通过Class实例获取class信息的方法称为反射(Reflection)。
如何获取一个class的Class实例?有三个方法:

方法一:直接通过一个class的静态变量class获取:
静态变量:与程序有着相同生命周期的变量

Class cls = String.class;
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
实例变量:
String s = "Hello";
Class cls = s.getClass();
方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");
静态方法与实例方法唯一不同的,就是静态方法在返回类型前加static关键字。静态方法的调用有两种途径:
(1)通过类的实例对象去调用
调用格式为: 对象名.方法名
(2) 通过类名直接调用
调用格式为: 类名.方法名
方法规则
我们在使用时要注意:
静态方法只能访问类的静态成员,不能访问类的非静态成员;
非静态方法可以访问类的静态成员,也可以访问类的非静态成员;

instanceof测试它左边的对象是否是它右边的类的实例

46.访问字段

通过Class实例获取字段信息

  • 通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();

  • 通过Field实例可以获取字段信息:getName(),getType(),getModifiers();

  • Field getField(name):根据字段名获取某个public的field(包括父类)

  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)

  • Field[] getFields():获取所有public的field(包括父类)

  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

 public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}

class Student extends Person {
    public int score;
    private int grade;
}

class Person {
    public String name;
}

?需要再理解 f.get§

public class Main {

    public static void main(String[] args) throws Exception {
        Object p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
}

Main类无法访问Person类的private字段
加一句f.setAccessible(true);--别管这个字段是不是public,一律允许访问
通过反射可以直接修改字段的值
Person p = new Person("Xiao Ming");
        System.out.println(p.getName()); // "Xiao Ming"
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);
        f.set(p, "Xiao Hong");
        System.out.println(p.getName()); // "Xiao Hong"

47.调用方法

通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:

Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);//为了调用非public方法,通过Method.setAccessible(true)允许其调用
        m.invoke(p, "Bob");
        System.out.println(p.name);
    }
}

class Person {
    String name;
    private void setName(String name) {
        this.name = name;
    }
}

48.调用构造方法

new操作符创建新的实例:
Person p = new Person();
反射来创建新的实例
Person p = Person.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

Class实例获取Constructor的方法如下:

getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

49.获取继承关系

粘贴这个关键目的是,看懂了这个例子的for循环,哈哈哈哈哈
Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口

import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

Class getSuperclass():获取父类类型;
Class[] getInterfaces():获取当前类实现的所有接口。
现在我有了个大胆的想法,反射是不是为了获取import值,哈哈哈哈,猜测而已,因为得到的结果全部都是调用的class ,java核心类。接着往下看吧,嘿嘿嘿

向上转型是否成立:isAssignableFrom():

Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

50.动态代理.没仔细看

Java的class和interface的区别

  • 可以实例化class(非abstract);
  • 不能实例化interface。

通常写代码的方式:
1. 定义接口:

public interface Hello {
    void morning(String name);
}

2. 编写实现类:

public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}

3. 创建实例,转型为接口并调用:

Hello hello = new HelloWorld();
hello.morning("Bob");

另一种方式:动态代码
仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

51.注解

public class Hello {
    @Check(min=0, max=100, value=55)
    public int n;

    @Check(value=99)
    public int p;

    @Check(99) // @Check(value=99)
    public int x;

    @Check
    public int y;
}

如果参数名称是value,且只有一个参数,那么可以省略参数名称。

52.定义注解–没看完

53.泛型

Java标准库提供的ArrayList–>“可变长度”数组,其内部是object[]数组。

Object数组(可以存储不同类型数据)

正常来说,相比于泛型,数组类型是一种协变类型,即如果类S是类F的子类,那么,S[]类型的数组是F[]类型数组的子类,也即从类型S[]转换到F[]是隐式转换的,那么由于Object是所有类型的父类,因此任何数组转换到Object数组都是一定成功的。

原文链接:https://blog.csdn.net/ph545450214/article/details/100096499

编写一次模版,可以创建任意类型的ArrayList,泛型就是定义一种模板

public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}
// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
编译器针对类型作检查:
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。

定义泛型类型<Number>后,List<T>的泛型接口变为强类型List<Number>:

List<Number> list = new ArrayList<Number>();
list.add(new Integer(123));
list.add(new Double(12.34));
Number first = list.get(0);
Number second = list.get(1);

54.Arrays.sort(Object[])

可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口:

写这个例子的时侯出现一些小问题:class test {}和class person{}两个应该分开,而不是在一个{}里面。

public class test {
    public static void main(String[] args) {

        Person[] ps = new Person[]{
                new Person("Bob", 61),

        };
        Arrays.sort(ps);
        System.out.println(Arrays.toString(ps));
    }
}
    class Person implements Comparable<Person> {
        String name;
        int score;

        Person(String name, int score) {
            this.name = name;
            this.score = score;
        }

        public int compareTo(Person other) {
            return this.name.compareTo(other.name);
        }

        public String toString() {
            return this.name + "," + this.score;

        }
    }

55.编写泛型

泛型类型不能用于静态方法
如需编写其他静态方法则使用区别
比如public staticPair create<k first,k last>{}

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);
    }
}

多个泛型类型

public class Pair<T, K> {
    private T first;
    private K last;
    public Pair(T first, K last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public K getLast() { ... }
}

使用的时候,需要指出两种类型:

Pair<String, Integer> p = new Pair<>("test", 123);

Java标准库的Map<K, V>就是使用两种泛型类型的例子

56.擦拭法

Java使用擦拭法实现泛型,导致了:

  • 编译器把类型视为Object;
  • 编译器根据实现安全的强制转型。

Java泛型的局限

局限一:<T>不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型:

Pair<int> p = new Pair<>(1, 2); // compile error!
局限二:无法取得带泛型的Class。观察以下代码
ir<String> p1 = new Pair<>("Hello", "world");
        Pair<Integer> p2 = new Pair<>(123, 456);
        Class c1 = p1.getClass();
        Class c2 = p2.getClass();
        System.out.println(c1==c2); // true
        System.out.println(c1==Pair.class); // true
class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>。
局限三:无法判断带泛型的类型
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>) {
}
局限四:不能实例化T类型:
public class Pair<T> {
    private T first;
    private T last;
    public Pair() {
        // Compile error:
        first = new T();
        last = new T();
    }
}
擦拭后实际上变成了
first = new Object();
last = new Object();
要实例化T类型,我们必须借助额外的Class<T>参数:使用的时候,也必须传入Class<T>。
public class Pair<T> {
    private T first;
    private T last;
    public Pair(Class<T> clazz) {
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}

Pair<String> pair = new Pair<>(String.class);

不恰当的覆写方法
public boolean equals(T t) {
return this == t;
}
定义的equals(T t)方法实际上会被擦拭成equals(Object t)
避开与Object.equals(Object)的冲突就可以成功编译
public class Pair {
public boolean same(T t) {
return this == t;
}
}

57.泛型继承 --------么的看懂

再看一遍理解大概是:泛型某些是不可以继承的。

58.extends通配符

Pair不是Pair的子类

 public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

    static int add(Pair<Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
    //改写为以下的,利用extends方法
     static int add(Pair<? extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}
class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

59.集合

  • Java对象可以在内部持有若干其他Java对象,并对外提供访问接口,我们把这种Java对象称为集合
  • 数组初始化后大小不可变;数组只能按索引顺序存取。
String[] ss = new String[10]; // 可以持有10个String对象
ss[0] = "Hello"; // 可以放入String对象
String first = ss[0]; // 可以获取String对象

Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口。Java的java.util包主要提供了以下三种类型的集合:

List:一种有序列表的集合,例如,按索引排列的Student的List;
Set:一种保证没有重复元素的集合,例如,所有无重复名称的Student的Set;
Map:一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map。

  • Java访问集合总是通过统一的方式——迭代器(Iterator)来实现
  • 需要增删元素的有序列表,我们使用最多的是ArrayList

这种方法可以添加null

List<String> list = new ArrayList<>();
        list.add("apple"); // size=1
         list.add(null); // size=2
list的of()接口可以直接创建带数据的数组,但是不可以传null
List<Integer> list = List.of(1, 2, 5);

遍历List get(i)+for+List.size()

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (int i=0; i<list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
    }
}

迭代器进行遍历 hasNext()+next()

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s);
        }
    }
}

for each 使用迭代器遍历

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (String s : list) {
            System.out.println(s);
        }
    }
}
  • List和Array转换
    - Object[] array = list.toArray();–丢失信息
    - Integer[] array = list.toArray(new Integer[3]); --直接放个array进去
    - Integer[] array = list.toArray(new Integer[list.size()]);–传入大小恰好的数组
    - Integer[] array = list.toArray(Integer[]::new); --简化,程序员的爱

  • Array和List转换
    - List list = List.of(array); —只读!!
    -
    判断list是否包含某个元素,数组返回索引
    System.out.println(list.contains(“X”)); // false
    System.out.println(list.indexOf(“C”)); // 2

  • 正确使用List的contains()、indexOf(),实例正确覆写equals
    以下是一个例子,为了证明覆写equals的重要性,写了才可以使用list的一些方法。
    instanceof,测试它左边的对象是否是它右边的类的实例

  • 引用字段比较,我们使用equals(),对于基本类型字段的比较,我们使用==

  • this.name.equals(p.name) && this.age == p.age;

public class Main {
    public static void main(String[] args) {
        List<Person> list = List.of(
            new Person("Xiao Ming"),
            new Person("Xiao Hong"),
            new Person("Bob")
        );
        System.out.println(list.contains(new Person("Bob"))); // false
    }
}

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

60.使用Map

Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。

  • entrySet()集合的使用:
  • 以下的例子是可以看出有两个方法:包括getKey()和getValue()两个方法。
public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 123);
        map.put("pear", 456);
        map.put("banana", 789);
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + " = " + value);
        }
    }
}

这章节有个例子没有看:

public class Main {
    public static void main(String[] args) {
        List<Student> list = List.of(
            new Student("Bob", 78),
            new Student("Alice", 85),
            new Student("Brush", 66),
            new Student("Newton", 99));
        var holder = new Students(list);
        System.out.println(holder.getScore("Bob") == 78 ? "测试成功!" : "测试失败!");
        System.out.println(holder.getScore("Alice") == 85 ? "测试成功!" : "测试失败!");
        System.out.println(holder.getScore("Tom") == -1 ? "测试成功!" : "测试失败!");
    }
}

class Students {
    List<Student> list;
    Map<String, Integer> cache;

    Students(List<Student> list) {
        this.list = list;
        cache = new HashMap<>();
    }

    /**
     * 根据name查找score,找到返回score,未找到返回-1
     */
    int getScore(String name) {
        // 先在Map中查找:
        Integer score = this.cache.get(name);
        if (score == null) {
            // TODO:
        }
        return score == null ? -1 : score.intValue();
    }

    Integer findInList(String name) {
        for (var ss : this.list) {
            if (ss.name.equals(name)) {
                return ss.score;
            }
        }
        return null;
    }
}

class Student {
    String name;
    int score;

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

61.编写equals和hashcode

  • hashCode值相同并不一定是同一个对象,就好比字典上的第100页上有“书”,“数”,“熟”等,100就是hashCode值,“书”,“数”,“熟”的具体位置就是地址;也就是有了List<entry<string,value>>
  • 但是为了提高效率,上面的hashcode还是不要相同。
  • 这个hashmap就是神奇,啥都能连,我愿称其为大牛逼
  • 空间换时间的例子
Map<String, Person> map = new HashMap<>();
map.put("a", new Person("Xiao Ming"));
map.put("b", new Person("Xiao Hong"));
map.put("c", new Person("Xiao Jun"));

map.get("a"); // Person("Xiao Ming")
map.get("x"); // null

62.时间复杂度和空间复杂度,耗时和消耗内存

**时间复杂度:**就是说执行算法需要消耗的时间长短,越快越好。比如你在电脑上打开计算器,如果一个普通的运算要消耗1分钟时间,那谁还会用它呢,还不如自己口算呢。时间复杂度是非常重要算法考察指标,甚至比空间复杂度更重要。因为现在大多数条件下,计算机的内存和存储都是足够充裕的。但是短时间能够出结果,用户体验会更好。

**空间复杂度:**就是说执行当前算法需要消耗的存储空间大小,也是越少越好。本来计算机的存储资源就是有限的,如果你的算法总是需要耗费很大的存储空间,这样也会给机器带来很大的负担。尤其是在嵌入式开发领域,内存和存储空间是非常有限的,因此会非常重视算法的空间复杂度。
在这里插入图片描述
在这里插入图片描述
原文:https://cloud.tencent.com/developer/article/1539064 侵删

63.使用TreeMap

SortedMap为接口-->

  • new Comparator 内部比较器
  • 在自定义类使用sortmap()–>使用其实现类treemap时,需要覆写比较器。
    网站提供了两种方法
//方法一
public int compare(Student p1, Student p2) {
    if (p1.score == p2.score) {
        return 0;
    }
    return p1.score > p2.score ? -1 : 1;
}
//方法二
public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }

9/1

64.ahhh,学完了map啦 接下来是properties,这个也不想学了,明天吧

Properties读取配置文件,一共有三步:

  1. 创建Properties实例;
  2. 调用load()读取文件;
  3. 调用getProperty()获取配置。

JAVA里面对于类进行调用配置资源的文件数据,以this.getClass().getResourceAsStream()来读取比较合适。

原文连接:https://blog.csdn.net/funi16/article/details/8137708

package myspider;
import java.io.UnsupportedEncodingException;

public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException{
        Test t=new Test();
        //文件名前不加“/”,则表示从当前类所在的包下查找该资源。如下则表示的是从包myspider下查找22.properties文件资源。
        System.out.println("1:"+t.getClass().getResourceAsStream("22.properties"));//输出java.io.BufferedInputStream@61de33
 
        //文件名前加了“/”,则表示从类路径下也就是从classes文件夹下查找资源,如下表示从classes文件夹下查找22.properties文件资源。
        System.out.println("2:"+t.getClass().getResourceAsStream("/22.properties"));//输出null
 
        //文件名前加了“/”,则表示从类路径下也就是从classes文件夹下查找资源,如下表示从classes文件夹下查找11.properties文件资源。
        System.out.println("3:"+t.getClass().getResourceAsStream("/11.properties"));//输出java.io.BufferedInputStream@14318bb
        System.out.println();
 
        //当前包路径4:file:/E:/myobject/myspider/build/classes/myspider/
        System.out.println("4:"+t.getClass().getResource(""));
 
        //输出当前类路径5:file:/E:/myobject/myspider/build/classes/
        System.out.println("5:"+t.getClass().getResource("/"));
 
        /*
         * 如果类路径下的当前包有22.properties文件,则输出6:file:/E:/myobject/myspider/build/classes/myspider/22.properties
         * 否者输出源文件下的22.properties文件的路径,则输出:6:file:/E:/myobject/myspider/src/myspider/22.properties
         */
        System.out.println("6:"+t.getClass().getResource("22.properties"));
        /*
         * 如果类路径下有11.properties文件,则输出7:file:/E:/myobject/myspider/build/classes/11.properties
         * 否者输出源文件下的11.properties文件的路径,则输出:6:7:file:/E:/myobject/myspider/src/11.properties
         */
        System.out.println("7:"+t.getClass().getResource("/11.properties"));
 
    }
}

先创建一个properties,再根据以上的两种方法去读取资源流。也就是资源文件。

Properties props = new Properties();
props.load(getClass().getResourceAsStream("/common/setting.properties"));

65.接下来是set

如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set

public class Main {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        System.out.println(set.add("abc")); // true
        System.out.println(set.add("xyz")); // true
        System.out.println(set.add("xyz")); // false,添加失败,因为元素已存在
        System.out.println(set.contains("xyz")); // true,元素存在
        System.out.println(set.contains("XYZ")); // false,元素不存在
        System.out.println(set.remove("hello")); // false,删除失败,因为元素不存在
        System.out.println(set.size()); // 2,一共两个元素
    }
}

队列

queue

  • 通过add()/offer()方法将元素添加到队尾;
  • 通过remove()/poll()从队首获取元素并删除;
  • 通过element()/peek()从队首获取元素但不删除。

PriorityQueue

PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。

Deque

Deque实现了一个双端队列(Double Ended Queue),它可以:

将元素添加到队尾或队首:addLast()/offerLast()/addFirst()/offerFirst();
从队首/队尾获取元素并删除:removeFirst()/pollFirst()/removeLast()/pollLast();
从队首/队尾获取元素但不删除:getFirst()/peekFirst()/getLast()/peekLast();
总是调用xxxFirst()/xxxLast()以便与Queue的方法区分开;
避免把null添加到队列。

9/2 感觉不能急,慢慢来才看的进去

iterator

List<String> list = List.of("Apple", "Orange", "Pear");
for (String s : list) {
    System.out.println(s);
}
迭代器将其改写为以下的内容
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
     String s = it.next();
     System.out.println(s);
}

collections

相比较之下,list.of()方法用的更多。

创建空集合
List list1 = List.of();
List list2 = Collections.emptyList();

创建单元素集合
List list1 = List.of(“apple”);
List list2 = Collections.singletonList(“apple”);
排序 Collections.sort(list);
洗牌 Collections.shuffle(list);

不可变集合
有点意思

public class Main {
    public static void main(String[] args) {
        List<String> mutable = new ArrayList<>();
        mutable.add("apple");
        mutable.add("pear");
        // 变为不可变集合:
        List<String> immutable = Collections.unmodifiableList(mutable);
        immutable.add("orange"); // UnsupportedOperationException!
    }
}

对原始集合改变,不变集合也会改变
mutable = null;
直接扔掉可变集合,以免发生意外

public class Main {
    public static void main(String[] args) {
        List<String> mutable = new ArrayList<>();
        mutable.add("apple");
        mutable.add("pear");
        // 变为不可变集合:
        List<String> immutable = Collections.unmodifiableList(mutable);
        mutable.add("orange");
        System.out.println(immutable);
        //比如加到这里:mutable = null;
    }
}

正则表达式

正则表达式是用字符串描述的一个匹配规则,使用正则表达式可以快速判断给定的字符串是否符合匹配规则。Java标准库java.util.regex内建了正则表达式引擎。

  • 判断手机号,我们用正则表达式\d{11}:
boolean isValidMobileNumber(String s) {
    return s.matches("\\d{11}"); }
  • 判断年份
    String regex = “20\d\d”;
    System.out.println(“2019”.matches(regex)); // true
    System.out.println(“2100”.matches(regex)); // false

匹配规则

匹配任意字符

  • 正则表达式a.c中间的.可以匹配一个任意字符
    -“abc”,因为.可以匹配字符b;
    “a&c”,因为.可以匹配字符&;
    “acc”,因为.可以匹配字符c。

匹配数字

  • 只想匹配0~9这样的数字,可以用\d匹配
    “007”,因为\d可以匹配字符7;
    “008”,因为\d可以匹配字符8。

匹配常用字符

  • \w可以匹配一个字母、数字或下划线
    “javac”,因为\w可以匹配英文字符c;
    “java9”,因为\w可以匹配数字字符9;。
    “java_”,因为\w可以匹配下划线_。

匹配空格字符

  • 用\s可以匹配一个空格字符,注意空格字符不但包括空格,还包括tab字符(在Java中用\t表示)。
    “a c”,因为\s可以匹配空格字符;
    “a c”,因为\s可以匹配tab字符\t。

匹配非数字

  • \D则匹配一个非数字
    “00A”,因为\D可以匹配非数字字符A;
    “00#”,因为\D可以匹配非数字字符#。
    类似的,\W可以匹配\w不能匹配的字符,\S可以匹配\s不能匹配的字符,这几个正好是反着来的。

重复匹配

  • 修饰符可以匹配任意个字符,包括0个字符
    A:因为\d
    可以匹配0个数字;
    A0:因为\d可以匹配1个数字0;
    A380:因为\d
    可以匹配多个数字380
  • 精确指定n个字符,A\d{3}可以精确匹配
  • A380:因为\d{3}可以匹配3个数字380。
  • 匹配n~m个字符,用修饰符{n,m}就可以。A\d{3,5}可以精确匹配
  • A380:因为\d{3,5}可以匹配3个数字380;
  • A3800:因为\d{3,5}可以匹配4个数字3800;
  • A38000:因为\d{3,5}可以匹配5个数字38000。

复杂匹配规则

匹配开头和结尾

  • 用^表示开头,KaTeX parse error: Undefined control sequence: \d at position 11: 表示结尾。例如,^A\̲d̲{3},可以匹配"A001"、“A380”。
    匹配指定范围
    规定一个78位数字的电话号码不能以0开头:**使用[…]可以匹配范围内的字符**,例如,[123456789]可以匹配19,这样就可以写出上述电话号码的规则:[123456789]\d{6,7}。
  • […]还有一种写法,直接写[1-9]就可以
  • […]还有一种排除法:匹配任意字符,但不包括数字,可以写[^1-9]{3}
    或规则匹配
    AB|CD表示可以匹配AB或CD:正则表达式java|php
    使用括号匹配字符串
    (…)把子规则括起来表示成learn\s(java|php|go)。

分组匹配

区号+电话号 分开提取
(…)先把要提取的规则分组,把上述正则表达式变为(\d{3,4})-(\d{6,8})

import java.util.regex.*;

public class Main {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
        Matcher m = p.matcher("010-12345678");
        if (m.matches()) {
            String g1 = m.group(1);
            String g2 = m.group(2);
            System.out.println(g1);
            System.out.println(g2);
        } else {
            System.out.println("匹配失败!");
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
        pattern.matcher("010-12345678").matches(); // true
        pattern.matcher("021-123456").matches(); // true
        pattern.matcher("022#1234567").matches(); // false
        // 获得Matcher对象:
        Matcher matcher = pattern.matcher("010-12345678");
        if (matcher.matches()) {
            String whole = matcher.group(0); // "010-12345678", 0表示匹配的整个字符串
            String area = matcher.group(1); // "010", 1表示匹配的第1个子串
            String tel = matcher.group(2); // "12345678", 2表示匹配的第2个子串
            System.out.println(area);
            System.out.println(tel);
        }
    }
}

非贪婪匹配

加个?????
Pattern pattern = Pattern.compile(“(\d+)(0*)”);
Pattern pattern = Pattern.compile(“(\d+?)(0*)”);

搜索与替换

单元测试

assertEquals(expected, actual)是最常用的测试方法,它在Assertion类中定义。Assertion还定义了其他断言方法,例如:

assertTrue(): 期待结果为true
assertFalse(): 期待结果为false
assertNotNull(): 期待结果为非null
assertArrayEquals(): 期待结果为数组并与期望数组每个元素的值均相等

package com.itranswarp.learnjava;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class FactorialTest {

    @Test
    void testFact() {
        assertEquals(1, Factorial.fact(1));
        assertEquals(2, Factorial.fact(2));
        assertEquals(6, Factorial.fact(3));
        assertEquals(3628800, Factorial.fact(10));
        assertEquals(2432902008176640000L, Factorial.fact(20));
    }

JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture。
我们来看一个具体的Calculator的例子:

public class Calculator {
    private long n = 0;

    public long add(long x) {
        n = n + x;
        return n;
    }

    public long sub(long x) {
        n = n - x;
        return n;
    }
}

这个类的功能很简单,但是测试的时候,我们要先初始化对象,我们不必在每个测试方法中都写上初始化代码,而是通过@BeforeEach来初始化,通过@AfterEach来清理资源:

public class CalculatorTest {

    Calculator calculator;

    @BeforeEach
    public void setUp() {
        this.calculator = new Calculator();
    }

    @AfterEach
    public void tearDown() {
        this.calculator = null;
    }

    @Test
    void testAdd() {
        assertEquals(100, this.calculator.add(100));
        assertEquals(150, this.calculator.add(50));
        assertEquals(130, this.calculator.add(-20));
    }

    @Test
    void testSub() {
        assertEquals(-100, this.calculator.sub(100));
        assertEquals(-150, this.calculator.sub(50));
        assertEquals(-130, this.calculator.sub(-20));
    }
}

异常测试

public class Factorial {
    public static long fact(long n) {
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long r = 1;
        for (long i = 1; i <= n; i++) {
            r = r * i;
        }
        return r;
    }
}

对i值进行检查

@Test
void testNegative() {
    assertThrows(IllegalArgumentException.class, new Executable() {
        @Override
        public void execute() throws Throwable {
            Factorial.fact(-1);  //异常代码测试
        }
    });
}
  • 从java8之后可以省略匿名类,new Executable()
  • 上述奇怪的**->**语法就是函数式接口的实现代码
@Test
void testNegative() {
    assertThrows(IllegalArgumentException.class, () -> {
        Factorial.fact(-1);
    });
}

条件测试

  • @Disabled 跳过测试
  • 以上这种跳过的方式为条件测试
  • @EnabledOnOs(OS.WINDOWS)
    @EnabledOnOs({ OS.LINUX, OS.MAC })条件测试判断
  • 不在Windows平台执行的测试,可以加上@DisabledOnOs(OS.WINDOWS):
  • @DisabledOnJre(JRE.JAVA_8):
  • 只能在64位操作系统上执行的测试,可以用@EnabledIfSystemProperty判断:@EnabledIfSystemProperty(named = “os.arch”, matches = “.64.”)

参数化测试

JUnit提供了一个@ParameterizedTest注解,用来进行参数化测试。

@ParameterizedTest
@ValueSource(ints = { 0, 1, 5, 100 })
void testAbs(int x) {
    assertEquals(x, Math.abs(x));
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值