java反序列化

java se 基础

前言:补一些java基础知识

前置知识

在写java的基础知识前,我们先来学习一下用vscode运行java项目的一些基础内容,首先,

想要在vscode中运行java在下载插件后需要创建java项目,一个不用其余工具的java项目中有

两个文件夹,一个是lib,用于存储引用的包,一个是src,用于存储源代码,在vscode中运行

java进行文件操作时,默认的当前路径并不是代码文件的当前路径,需要在setting.json中对

对应的类添加cwd属性进行配置,如果是${workspaceFolder}则是那个项目的根目录,如

果是${fileDirname}则是代码文件的目录

1.0 输入

import java.util.*;

public class hello {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        System.out.println(a.nestLine());
    }
}

nextLine()方法用于读取一行的字符,以换行符作为输入的结尾

如果我们想要以空格为结尾,则为

next()

想要读取一个整数,可以调用nextInt()方法

想要读取下一个浮点数,可以受用nextDouble方法

如果我们想知道文本中输入是否结束,可以使用hasNext()方法,判断文本中下一个是否还

有单词输入

我们通过Scanner读取的数据在输入过程中都是直接可见的,不是很安全,如果我们想要更

安全一点,可以使用Console

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

public class hello {
    public static void main(String[] args) {
        Console a = System.console();
        System.out.println(a.readPassword("Enter password: "));
    }
}

需要包含 java.io包的内容,当然也可以单独把Console给包含进去

1.1 读取文件

读取文件同样需要我们使用到Scanner对象,

import java.util.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        String filePath = "a.txt";
        try (Scanner scanner = new Scanner(Path.of(filePath), StandardCharsets.UTF_8)) {
            while (scanner.hasNextLine()) { // 如果存在下一行内容,则执行下述内容
                String line = scanner.nextLine();
                // 在这里处理读取到的每一行内容
                System.out.println(line); // 打印到控制台
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

其余的方法使用和上面的相同,需要主义的是,如果我们的路径中使用了反斜杠\,我们就

需要输入两个反斜杠对其进行转义,从而被识别为一个反斜杠。

StandardCharsets.UTF_8 表示文件使用UTF-8编码,如果没在此参数进行说明,则会采用

运行程序的机器的默认编码,为了正常读取,还是指定一下编码好一点。

需要注意的是,在我们利用该方法读取文件时,首先要引入java.nio.file.Path,其次必须

做处理异常,即使用trycatch否则会报错

如果我们直接Scanner(一个字符串)则会构造一个从定字符串读取数据的Scanner

1.2 写入文件

想要写入文件,需要用到PrintWrite 对象,该对象位于io包中,具体的使用如下

PrintWriter out =new PrintWriter("a.txt",StandardCharsets.UTF_8);

如果文件不存在,则会新建该文件,

具体使用时,该对象的函数和System.out对象基本相同,也可以使用println 等方法

import java.util.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        String filePath = "a.txt";
        try (PrintWriter out = new PrintWriter("a.txt", StandardCharsets.UTF_8);) {
            out.println("adasdsadsa");
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

同样,使用该对象也需要增加异常处理,对IOException进行抛出

对于上面的情况,如果我们不像进行异常处理,可以告知编译器我们已经知道可能出入输入或输出异常,这样就不

会因为未进行异常处理而出现错误了

import java.util.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) throws IOException {
        String filePath = "a.txt";
        PrintWriter out = new PrintWriter("a.txt", StandardCharsets.UTF_8);
        out.println("adasdsadsa");

    }
}

1.3带标签的循环

与c++不同,java中没有goto,而是增加了标签

具体的使用如下:

import java.util.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        label: for (int i = 1; i <= 2; i++) {
            System.out.println(i);
            if (i == 1) {
                break label;
            }
        }
    }
}

label是我们选中的标签名,利用标签我们可以实现一次break跳出多重循环,break将跳转

到冒号后的循环的外层

1.4 大数

如果整数和浮点数的精度不能符合要求,我们可以选取java.math包中包含的两个很有用的

类,BigIntegerBigDecimal这两个类可以处理包含任意长度的数字序列的数值,前者

是任意精度的整数,后者是任意精度的浮点数。

使用静态的valueOf方法可以将整形变为大数:

BigInteger a= BigInteger.valueOf(100);  

如果需要的数过大,纯数字字面量无法存储,可以使用带字符参数的构造器。

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        BigInteger a= new BigInteger("1111111111111111111111111111111111111111111111")

    }
}

我们在大数运算时不能使用+或者*直接去处理,应该去使用大数类的add或者multiply

方法,(感觉或许底层实现是高精度算法?)如果在c++中,我们可以通过重载运算符去实现直

接使用+ 或者***** 但在java中无法实现。

以下是一些常用的大整数类的方法:

BigInteger c= a.subtract(b);  

a,b,c都是大数类型,该方法为减法操作,返回的值是c=a-b

divide()方法用于做触发,用法和上面相同

mod()从名字也能看出,该方法是做模运算的

sqrt()做开方

和之前的String类一样,大整数之间要是想要去比较,也是不能使用==的,这样比较的是两

个大数对象存储的位置是否相同,想要比较两个大整数的值的大小,需要使用

compareTo()方法,如果调用该方法的大整数与另一个大整数相等,则返回0,如果这个

大整数小于另一个大整数,则返回负数,否则返回正数。

大实数的方法与大整数相同,就不加赘述了

1.5 数组

在java中数组的定义与c++存在区别,定义的方式如下:

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = new int[10];
        var b = new int[10];
    }
}

a和b都是一个长度为10的数组。

也可以在定义过程中直接初始化,甚至不用指定长度和使用new

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 1, 2, 3, 4, 5 };
    }
}

在java中允许空数组的出现,但需要注意,空数组不等同于null

创建一个数字数组时,所有元素的初始值都为0,boolean数组的初始值为false,对象数组

的元素正则初始化为null,比如字符串数组,大数数组。

获取数组长度的方式是length()方法

数组的遍历除了常规的循环还可以使用for each循环,这和js,php,python相同,都是用

来处理元素集合的循环,使用方式如下:

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 1, 2, 3, 4, 5 };
        for (int i : a) {
            System.out.print(i);
        }
    }
}

本质就是将元素集合a的每一个值赋予i并输出,需要注意的是i的类型需要和后面的元素集合

中的数据类型相同。

还存在一种方式打印数组,就是使用Arrays类的ToString()方法

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 1, 2, 3, 4, 5 };
        System.out.print(Arrays.toString(a));

    }
}

对于数组这种引用对象,我们用=去赋值本质上是地址的传递,传递后对新变量的改变也会

影响旧的变量就比如这样:

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 1, 2, 3, 4 };
        int[] b = a;
        b[0] = -1;
        System.out.println(a[0]);

    }
}

我们虽然改变的是b的值,但实际上去输出a的值也是改变后的结果。

除了数组,其他java中自带的其他数据结构也是这种情况,字符串和大数虽然是引用对象,

但实际和基础类型差不多,并不会出现这种特性

那我们如果单纯想获得一个和其他数组内容相同的数组,并不想获得那个数组的地址呢?

这时候就需要用到数组的拷贝了,我们具体使用到的是类方法copyOf()

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 1, 2, 3, 4 };
        int[] b = Arrays.copyOf(a, a.length);
        a[0] = 4;
        System.out.println(Arrays.toString(b));
    }
}

copyOf()的第二个参数是新数组的长度,一般用来增加数组大小,如果数组元素是数值

型,那么额外的元素将被赋值为0,如果数组元素是boolean型,则多出的位置将被赋值为

flase

1.6 命令行数组

我们在编写java程序中,每个程序中都包含

public static void main(String[] args) 一个main方法,我们的程序也在其中写着,

它接受了一个String数组,那这个数组是干什么的呢?

这个数组是命令行数组,用于接受我们在命令行中输入的命令

比如,我们在命令行窗口中java java文件 -g cruel world

在命令行数组中,args[0]="-g" args[1]="curel" args[2]="world"

命令行数组用于接受我们在命令行窗口输入的指令,便于进行进一步操作。

1.7 数组排序

数组排序和c++差不多,直接可以用sort方法

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 };
        Arrays.sort(a);
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

输出结果是按升序排序的结果。排序方法使用了快速排序。

1.8 数组的一些类方法

copyOfRange()

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 };
        int[] b = Arrays.copyOfRange(a, 1, 5);
        for (int i = 0; i < b.length; i++) {
            System.out.print(b[i] + " ");
        }
    }
}

选择区间的截取数组,是左闭右开的。

binarySearch()

二分查找函数

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[] a = { 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 };
        int b = Arrays.binarySearch(a, 3);
        System.out.println(b);
    }
}

二分查找一般用于对有序数组中的某个数进行查询,返回按二分查找第一个找到的符合的数的

索引

当然也可也指定区间的进行查找

fill()该方法用于将数组的所有元素设置初始值

equals()该函数用于比较两个数组,如果两个数组大小相同,并且下标相同的元素都相等,

则返回true

1.9 多维数组

多维数组的定义和之前一维数组类似,我们就以二维数组为例子

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

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

有这两种方式去定义,访问方式和c++相同

for each方式访问多维数组也和之前类似,不过要循环了。

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int[][] a = new int[10][10];
        int[][] b = { { 1, 2, 3 }, { 4, 5, 6 } };
        for (int[] i : b) {
            for (int j : i) {
                System.out.print(j + " ");
            }
        }
    }
}

2.0 不规则数组

多维数组的本质是一维数组里嵌套一维数组多重嵌套得到,第一层的一维数组的大小是必须得初始给定的

我们还可以开出不规则数组出来,比如像下面这样的一个金字塔型数组

1
2 3
4 5 6
7 8 9 10

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        int maxn = 10;
        int[][] a = new int[maxn][];
        int n = 0;
        int m = 0;
        for (int i = 0; i < maxn; i++) {
            n++;
            a[i] = new int[n];
            for (int j = 0; j < n; j++) {
                a[i][j] = ++m;
                System.err.print(a[i][j] + " ");
            }
            System.err.println();
        }

    }
}

2.1 对象与类基础使用

默认读者会一门其他面向对象语言,这里会省略很多东西

对象的构造和其他语言相同,我们以java内置类Date为例子

Date date=new Date();

我们使用构造器构造了一个新的对象,我们也可也直接输出

System.out.println(new Date());像这样,在输出过程中隐式调用了ToString()方法,将Date对象转换成一个

字符串去输出

data类是按照UTC来计算时间的,java中表示时间的类还有LocalDate

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.Scanner;
import javax.xml.crypto.Data;

public class Hello {
    public static void main(String[] args) {
        LocalDate a = LocalDate.now();
        System.out.println(a);
    }
}

使用方式和Date有区别,定义时不使用构造器,而是静态工厂方法

定义时选择now方法就是返回当前时间,也可以输入需要的时间

LocalDate a = LocalDate.of(1999,12,31);

如果想要分别把年月日输出,可以使用其方法getYear() getMonthValue() getDayOfMonth()

用此方法可以方便的输出计算后的时间,比如:

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.Scanner;
import javax.xml.crypto.Data;

public class Hello {
    public static void main(String[] args) {
        LocalDate a = LocalDate.now();
        LocalDate b = a.plusDays(1000000);
        System.out.println(b.getYear());
    }
}

比如这样

2.2 更改器方法与访问器方法

更改器方法:调用此方法获得新的值是通过更改原有的值实现的

访问器方法:调用此方法获得的值是新构建的,并不会改变原来的值

我们上面使用的plusDays()方法就是访问器方法

2.3 用户自定义类

在java中,在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类,我们自定义的其他类在没有说明

类型时都是默认为私有类的,许多程序员习惯于将每一个类存放在一个单独的源文件中,需要的时候进行调用

但这时候我们就会产生疑问,我们在编译时的指令是java name.java似乎并没有编译我们调用的源文件,但事实

上,当java编译器发现我们当前的源文件中调用了其他的类时,就会去查找该类的class文件,如果没找到,就会

对调用的源代码进行编译成class文件。

在Java中,成员变量如果没有指定访问修饰符(如public、protected、private),它的访问权限不是“默认私

有”的概念,而是“包访问权限”(package-private)。这意味着该成员变量可以在同一个包内的其他类中访问,但

在不同包的类中则不可见。这并不涉及安全性或者说是“私有性”的概念,而是Java对访问控制级别的规定。

Java中成员方法如果没有指定访问修饰符,默认的访问权限同样是包访问权限(package-private),与成员变量

相同。这意味着没有显式指定访问修饰符的方法仅能在同一包内的其他类中被访问,而在不同包的类中无法直接访

问。对于方法的“私有”访问权限,需要明确使用private关键字来修饰。

package-private 与public不同,public是可以在不同的包里访问的,而package-private不行

2.4 构造器

java中的构造器有以下几条特性

1 构造器与类同名

2 每个类可以有一个以上构造器

3 构造器可以有0到多个参数

4 构造器没有返回值

5 构造器总是伴随着new操作符一起调用

2.5 使用null值引用

如果一个对象变量的未赋值,那它的值为null,我们如果对其使用一个方法,就会产生一个

NillPointerException

异常,如果程序没有捕获该异常,那运行就会终止,但如果我们想要如果遇到个别值为空,却不影响整个程序的运

行,有两种解决方式,第一种:

if(n==null) name="unknown"; else name=n;

第二种:

使用Objcts.requireNonNullElse()方法,我们通过传入参数,如果传入值为空,就返回预设值

或者使用:Objecs.requireNonNull()方法,遇见异常直接抛出。转到异常处理

2.6 final实例字段

final实例字段必须要在声明或者在构造函数中进行初始化,初始化后就不能对其的值进行更改,而且final修饰的

类不能被继承

class name {
    private final int n=1;
}

这样使得n的值不能更改,我们也可也在构造函数中设置

class name {
    private final int n;
    public name(int x){
        this.n=x;
    }
}

2.7 静态字段

和c++中的static没什么区别,也可以被叫做类字段,在所有该类的所有对象中都拥有,对应的也是同一个变量

static属于类级别,存储在方法区中,所有类的实例共享这一份数据,不会因实例的不同而拥有

多个副本,通过类名可以直接访问

class name {
    private static int sid = 0;
    private int id;

    public name() {
        id = ++sid;
        System.out.println(id);
    }
}

public class Hello {
    public static void main(String[] args) {
        name a = new name();
        name b = new name();
    }
}

我们通过static字段可以实现给每一个new的对象设置一个id,在本例中中,对象a的id是1,对象b的id是2;

对于静态字段,比如上面的字段,直接可以这样访问name.sid利用类名达到访问的效果

2.8 静态常量

字面意思,静态且不能被更改的常量

使用方式public static final int n=1;

这样去定义

2.9 静态方法

该方法其实就是定义一个类方法

public static int getnextid(){
	//方法内容
}

调用方式也和调用静态变量一样,直接类名.方法名()

我们对于实例化后的对象,也可也直接用引用.方法名()去调用静态方法,静态方法和静态变量都是在加载类的时

候一起加载到的,所以在静态方法中可以使用静态方法,但不能使用动态方法,因为在加载类的时候动态方法还没

有被加载

3.0 方法参数

程序语言中中传参分为按值调用,按引用调用,按值调用传入的是生成的副本,按引用调用传入的是地址,java中

总是按值调用。如果我们传入基础数据类型, 在方法中对值进行改变实际上并不会影响

还记的我们之前说的引用类型吗,就是数组对象等,对于这种类型的数据,我们传入的是其引用,也就是在方法中

对其更改是会影响到其实际值的,原因就是引用数据类型的特性。传参的过程是生成副本传入的过程,但是对于应

用数据类型,生成的副本与原来的变量指向相同的地址,所以会对双方产生影响。

2.1 重载

对于一个方法,当我们需要根据传入的参数的数量不同实现不同的功能时,就需要使用到重载

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.Scanner;
import javax.xml.crypto.Data;

class name {
    private static int sid = 0;
    private int id;

    public name() {
        id = ++sid;
        System.out.println(id);
    }

    public name(int a) {
        System.out.println(a);
    }
}

public class Hello {
    public static void main(String[] args) {
        name a = new name(1);
        name b = new name();
    }
}

对于上述的两个构造其方法,我们对其进行了重载,当我们传入一个int 参数时调用第二个构造器方法,当我们不

传入参数时调用第一个构造器方法。

2.2 默认字段初始化

如果构造器没有显示地为某些字段赋值,那其就会自动的赋予默认值,数值为0 ,布尔值为fale,对象应用为

null。

2.3 调用另一个构造器

关键字this还有另外一个含义,可以通过this来调用顶一个构造器

调用方法如下this()

2.4 初始化块

在java中,想给属性赋值除了在构造器中赋值,定义时赋值,还有第三种方式,即初始化块

import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.Scanner;
import javax.xml.crypto.Data;

class Name {
    private int age;
    private int id;
    {
        age = 1;
        id = 1;
    }

    public void echo() {
        System.out.println(this.age);
    }
}

public class Hello {
    public static void main(String[] args) {
        var aName = new Name();
        aName.echo();
    }
}

在定义类我们便使用了初始化块

我们也可以使用静态代码块来进行类的初始化

static {
    初始化内容
}

对于这几个块的执行优先级:静态代码块在类加载的时候执行,初始化代码块在对象实例化时被执行,并且在构造

器的前面进行执行。

静态代码块只会加载一次,就是在类加载的时候,在后续对象实例化时并不会被加载。

2.5 包

java允许使用包将类组织在一个集合中,借助包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库

分开管理

2.6 包名

使用包名来区分同名的不同类,只要其在不同的包中即可,为了确保包名的绝对唯一性,需要用域名的逆序作为包

名的前缀,再根据具体的业务需求进行命名,报名应该全部小写,并且只能包含字母,数字和下划线,为了提高代

码的可读性,报名的长度应该在合理的范围内。

比如某公司的域名为horst.com 我们将其进行逆序,就得到了com.horst 后面还可以追加一个工程名,比如

com.horst.appjava 如果在将比如Hello 类放在这个包里面,那么这个类的完全限定名就为

com.horst.appjava.Hello

2.7 类的导入

一个类可以使用所属包中的所有类,以及其他包中的公共类,我们可以通过完全限定名来实现

com.horst.appjava man = new com.horst.appjava();

这种写法很繁琐,我们可以还可以和java的import一样,导入包,这种方法下就不要求写出完全限定名了。

直接这样导入

import java.utill 

在使用时直接使用类名就可以了,不过需要注意的时,如果我们导入的多个包存在名字相同的类时,在使用这个类

时如果直接用类名的话,编译器会抛出一个java.lang.Error: Duplicate class name "ClassName"或者The

type ClassName is ambiguous之类的错误信息,明确告诉你类名是模糊的或重复的。解决方式就是使用完全限

定名,或者使用as

import com.example.tools.Util as ToolsUtil;
import com.another.example.Util as AnotherUtil;
ToolsUtil toolInstance = new ToolsUtil();
AnotherUtil anotherInstance = new AnotherUtil();

如果我们需要导入一个包中所有的类,直接使用通配符*即可

import java.time.*;

需要注意的是,使用通配符只能做到导入一个包中的所有类,不能做到导入所有包

2.8 在包中增加类

想要将类放入包中,就必须将包的名字放在源文件的开头,即放在定义这个包中各个类的代码之前。

比如:

package com.example.app;

如果没有在源文件中放置package,这个源文件中的类就属于无名包,无名包没有包名,我们之前使用的就属于无

名包,在使用包名时,我们也得对源文件进行路径管理,比如在根目录下的无名包导入了一个包名为

com.corejava.test的包,那这个包中类文件的目录必须位于

com/corejava/test下,这样我们在根目录导包时才能正常导入。

如果我们想直接对包目录下的类进行编译,在使用编译器时要这样用:

javac com/corejava/test/Hello.java

我们使用解释器时:java com.corejava.test.Hello

需要注意的是,如果在项目文件中包名和包的位置不符合,当我们单个的运行包中的class文件时并不会报错,但

如果其他源文件中导入了这个类,那运行那个文件时就会报错。

2.9 包访问

和其他OOP语言一样,java中也存在访问修饰符,如果我们不加访问修饰符,那属性,方法,类的权限就是包访

问权限,只有在同一个包中的其他类可以访问,对于类来说,这种默认访问权限是比较合理的,在同一个包下也不

需要导入,直接就可以使用,但对于变量来说就不是很合适了,变量必须显示的标记为private,不然默认包访问

权限,破坏了变量的封装性

3.0 类路径

为了方便的导入自定义的类,我们可以设置类路径,在unix环境中,类路径各项用冒号:分割,在windows环境中,

则用;分割,即路径1:.:路径2或者路径1;.路径2, 在上述两个例子中,中间的点表示当前目录,所以查找类时会

在路径1,当前路径,路径2中查找

路径1和路径2也可也直接是jar的路径地址

javac编译器总是在当前目录中查找文件,但java虚拟机仅仅在类路径中包含.时材查看当前目录,虽然默认是当前

目录,但如果配置了类路径却忘记加.,那可能出现编译通过无法执行的情况。

从java6开始,可以在jar文件目录中使用通配符,比如:·

/home/user/archives/'*'

这样就可以把该目录下的所有jar文件都设置为查询的类目录了,需要注意的是,在unix中*必须进行转义

其次java api中的类默认会包含在类路径中

设置类路径的指令:java -classpath 路径 MyProg

在windows下也可也:set CLASSPATH= 路径

3.1 jar文件

jar文件既可以包含类文件,也可也包含诸如图像和声音等其他类型的文件,另外,jar文件是压缩的,使用的是zip

压缩格式

创建一个jar所需要使用到的命令:jar cvf 打包后的jar文件名 文件1 文件2

比如:jar cvf jarFileName file1 file2

3.2 清单文件

Jar包中除了资源文件外,每个jar文件还包含一个清单文件,用于描述归档文件的特殊性,清单文件,用于描述归

档文件的特殊性

清单文件总包含很多条目,这些条目被分为多杰,第一节被叫做主节,作用与整个jar文件,随后的条目用来指定

命名实体的属性,如单个文件,包或者url,它们都必须以以name条目开始,后面加上对该文件的描述节与节之间

用空行分开

3.3 封装

程序设计要追求,高内聚,低耦合,即类的内部数据操作细节自己完成,不允许外部干涉,,尽量暴露少量的方法

给外部使用

封装就是数据的隐藏,即数据需要用private访问修饰符来进行修饰

那我们外部如果想要获得或更改这些数据,应该怎么操作呢?

应该在类的内部写一些public的get或者set方法,通过调用这些参数来进行值的获取而改变,从而避免数据直接呗

随意改变,封装可以让我们进行一些安全性检查,从而过滤掉一些不合法的值,从而使程序更加安全。

还可以起到隐藏代码的实现细节,统一接口,提高代码的可维护性的作用

3.4 继承

和c++一样,用extands进行继承,子类是父类的扩展,java中只有单继承,没有多继承,使用继承的类可以叫做子类

也可也叫做派生类,如果我们不适用继承还想使用另一个类,还可以使用组合。

父类的父类也可以被继承。

和c++一样,子类可以使用父类public和protected的方法和属性

所有的类,都直接或者简介的继承Object类,object类提供了java自带的的一些本地方法。

3.5 super

super的作用和this相似,只不过super是指向父类的属性,为了避免不同类同名属性导致的指向不明,所以还是

使用super来调用父类的属性比较保险,不光是属性,方法也是这样。

我们在创建子类时会在子类的构造函数中隐式调用父类构造器,如果我们在子类中显式的使用super()也就是父

类的构造器,那只能将其放在第一行,如果父类的构造器是无参构造,那么可以不在子类构造函数中调用父类构造

函数,因为会隐式默认调用,如果父类的构造器是有参构造器,则必须主动调用父类构造函数并传入参数。

3.6 方法重载

重载就是将父类的某个方法重新更改,要求是访问修饰符,参数数量和类型,返回数据类型,是否有static,函数

名都得一致,此时在子类中写的方法可以对父类的方法进行覆盖,这种技术就叫做重载

package com.corejava.test;

public class Test {
    public static void test() {
        System.out.println("test");
    }
}
package com.corejava.test;

public class Hello extends Test {
    public static void test() {
        System.out.println(1);
    }

    public void aaa() {
        System.out.println(2);
    }
}

比如这个例子,就对test方法进行了重载。

我们要先明白,父类的引用可以指向子类,对于重载的方法是否为静态方法,我们运行程序产生的后果也不同,如

果我们将静态方法重载,然后让父类的引用指向子类,此时运行重载方法时只能运行父类的方法,因为静态方法不

能被重写如果重载方法没有被static修饰,此时运行重载方法时运行的时子类的方法。

重载方法时修饰符可以扩大,不能缩小

public > protected > default > private

重写可能会抛出异常,范围可以被缩小,但不能被扩大。

3.7 多态

一个对象的实际类型是确定的,但可以指向的引用类型不确定了,具体的细节在方法重载中也已经提到了。

即,父类型可以执行子类,但是不能调用子类独有的方法。,只有在具体执行时才能明确引用的类型,我们可以通

过强制类型转化来实现,这些可以体现多态。

多态的特征 :

1 多态是方法的多态,属性没有多态。

2 只有像父类和子类这种有联系的才能实现类型转化,类型转换异常: ClassCastExceotion

3 存在条件:继承关系,方法需要重写 ,父类引用指向子类对象

但是注意: static方法属于类,不能重写,final修饰的是常量,也不能重写,

多态即同一方法可以根据发送对象的不同而采用多种不同的行为方式

3.8 instanceof 的使用

instanceof关键字在Java中用于测试对象是否是特定类的实例,或是特定类的子类的实例。它是一个二元操作

符,返回一个布尔值 (truefalse)。

import com.corejava.test.*;

public class App {
    public static void main(String[] args) throws Exception {
        Test per = new Hello();
        Hello hel = new Hello();
        Object object = new Object();
        System.out.println(per instanceof Object);
        System.out.println(per instanceof Test);
    }
}

对象 instanceof 想要判断的类

如果我们利用java的多态特性,使用父类的引用指向子类,此时还是按照子类对应的关系进行判断的。

所以对于上面的per和hel对象,虽然是不同的引用,但指向的都是Hello,在使用instanceof关键字是也是根据此

进行判断的,返回结果为true true

3.9 强制类型转换

由于java具有的多态的特性,我们可以让父类的引用指向子类,但此时如果方法没有重载,我们只能去调用父类的

方法和属性,如果此时我们想要调用子类的方法和属性,应该怎么办呢?这首就需要用到类型转换了,即将父类

的引用,转换为子类的引用,注意,强制类型转化转化的是引用,不是对象。

在进行强制类型转换前,使用instanceof关键字进行检测可以避免抛出ClassCastException异常。

具体的使用方式和c++中相同

import com.corejava.test.*;

public class App {
    public static void main(String[] args) throws Exception {
        Test per = new Hello();
        Hello hel = new Hello();
        Object object = new Object();
        System.out.println(per instanceof Test);
        Hello newhello = (Hello) per;
    }
}

转化后就可以调用子类的方法,注意,虽然经过了强制类型转化,但转化后两个引用的地址还是相同的。

当然我们也可以不选择用一个新的引用变量名去存储转化后的结果,而是直接去使用对应的方法

import com.corejava.test.*;

public class App {
    public static void main(String[] args) throws Exception {
        Test per = new Hello();
        Hello hel = new Hello();
        Object object = new Object();
        System.out.println(per instanceof Test);
        ((Hello) per).test();
    }
}

这样也是可行的。这是由父类向子类的转化,需要注意的是,只有父类引用指向子类实例时才能对父类的引用进行

强制类型转化,对于父类引用指向父类实例的情况,是无法将其转化为子类引用的,此时会报错。

如果我们想要把子类转化为父类,此时就不是必须得强制类型转化了,而是可以使用隐式转化了。

import com.corejava.test.*;

public class App {
    public static void main(String[] args) throws Exception {
        Hello hel = new Hello();
        Test test = hel;
        test.test();
    }
}

比如这个例子

在这个例子中,Hello 是Test的子类,我们先创建了一个Hello类的实例,然后将其赋值给Test类的实例,此时调用

子类的方法test,很明显,此时是无法调用的,因为我们已经将hel转化为父类的引用了,如果能调用,说明test方

法是对父类的test方法的重载。

4.0 抽象类

在java中,抽线类是一种无法实例化的类,用于作为其他类的基类,它们可以包含抽象方法和非抽象方法

定义抽象类:

package com.corejava.test;

public abstract class Hello {
    public abstract void sayHello();
    
}

abstract修饰类即可创建抽象类

同样,用abstract修饰方法可以创建抽象方法,抽象方法仅声明,不去实现,子类在继承抽象类时如果想要调用

抽象方法必须重写方法

抽象类不能直接实例化,也就是说,对于上面这个抽象类,像我们下面这样实例化是错误的

Hello hel =new Hello();

对于抽象类的实例化,我们需要通过实例化继承抽象类的子类来实现

我们使用抽象类的目的:

代码重用:将通用功能放在抽象类中,具体实现由子类提供

多态性: 通过抽象类引用子类对象,支持方法调用的动态绑定。

引入抽象类是对java多态的进一步延申。

继承抽象类:

package com.corejava.test;

public class Test extends Hello {
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

对父类的方法进行了一下重载

4.1 接口

在Java中,接口是一种抽象类型,定义了一组方法,但不提供其实现。接口用于规范类应具备的行为,并支持多重

继承。接口声明的关键字是interferce

无论是接口还是类,用public修饰的只能出现一个,也就是说只能有一个公共接口或者一个公共类,不能同时出现

两个,且文件名也得是公共的接口名或者公共的类名,

以下是接口的主要特点和用法:

import com.corejava.test.*;

interface Animal {
    void makeSound();
}

class Dog implements Animal {
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.makeSound();
    }
}

当然我们也可以单独把Animal接口和Dog类放在单独的文件中,在接口中定义的方法其实有神略,完全写全应该

是:public abstract void makeSound(); 把公有和抽象修饰符给省略了,但记住,实际上是有的。

接口有别于抽象类,一个类可以继承复数个接口

package com.corejava.test;

public class Dog implements Animal, Mautal {

    public void makeSound() {
        System.out.println("Woof!");
    }

    public void eat() {
        System.out.println("Eat meat!");
    }
}

不同接口名之间用逗号隔开

如果我们在接口中定义一个变量,比如int a=1;

其实也省略了一部分内容 完整的是:public static final int a=1;

本质上其实不是变量,而是常量。

4.2 内部类

内部类,就和它的名字一样,定义在类内部的类,分为成员内部类和静态内部类两种,我们先来看成员内部类

这是类文件:

package com.corejava.test;

public class Hello {
    public class Inner {
        public void print() {
            System.out.println("Hello");
        }
    }

}

这是执行文件:

import com.corejava.test.*;

public class Main {
    public static void main(String[] args) {
        Hello hel = new Hello();
        Hello.Inner inner = hel.new Inner();
        
    }
}

内部类可以调用外部类的私有方法和属性,方法有两种

如果不同名,直接调用即可

package com.corejava.test;

public class Hello {
    private void print() {
        System.out.println("Hello");
    }

    public class Inner {
        public void pri() {
            // System.out.println("Hello");
            print();
        }
    }

}

如果内部类方法或属性与外部类重名,我们调用时要精准执向,有着两种方式。

package com.corejava.test;

public class Hello {
    private void print() {
        System.out.println("H");
    }

    public class Inner {
        public void print() {
            System.out.println("Hel");
            new Hello().print();
        }
    }
}

或者也可以这样:

package com.corejava.test;

public class Hello {
    private void print() {
        System.out.println("H");
    }

    public class Inner {
        public void print() {
            System.out.println("Hel");
            Hello.this.print();
        }
    }

}

用这两种方法调用外部类的属性或者方法。

如果我们在定义内部类时加上static,就形成了静态内部类,静态内部类就不能调用外部类的方法和属性了,因为

它是在类加载时一起加载的,

如果将类定义在方法内部,就被称作局部内部类。

如果不指定实例化的对象名而直接用,就被称作匿名类

4.3 认识异常

在我们的程序实际工作是,遇到的情况可能不是很完美,比如用户输入不一定符合要求,或者打开文件不存在或者

格式不对等问题,或者要读取的数据库为空。

软件在运行过程中,遇到的这些问题被统一称作异常(Exception) 异常发生在程序运行期间,它影响了正常的程序

执行流程。

异常分为两类:

检查性异常: 一般是用户错误或问题引起的异常,这是程序员无法遇见的,这些异常在编译时不能被简单的忽略

运行时异常: 运行时异常是肯能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时被忽略。

还有一种不是异常的存在,

错误(ERROR),错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略,例如,

当栈溢出时,一个错误就发生了, 在编译时也检查不到。

基于运行中存在的这么多问题,java中存在异常处理框架来针对这些异常进行处理。java把异常当错对象处理,并

定义一个基类作为所有异常的超类。

所有的异常都是java.langThrowable的子类,分为Error和Exception两种

Error类对象是由Java虚拟机生成并抛出的,大多是错误与代码编写者所执行的操作无关。

此时JVM将不再继续处理执行操作所需的内存资源,将出现OutOfMemoryError 此时虚拟就会选择线程终止

对于Exception 分支中存在一个重要的子类RuntimeEception 即运行时异常,下属很多具体异常,这些异常抛出

后可以选择捕获,也可也不选择捕获,这些异常一般都是程序逻辑错误引起的,应该从逻辑角度避免这类异常的发

4.4 处理异常

1.try

在try的代码块中,如果出现异常会被跳转到catch代码块进行捕获

2 catch

catch用于在try代码块中出现异常时进行捕获,可以通过设置异常类型来捕获不同的异常

举个例子:

import com.corejava.test.*;

public class Main {
    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

此时我们捕获的异常是Exception 这是所有异常的基类,如果我们在catch中将捕获的异常类

型设置为Exception那么所有的异常都会被捕捉

如果我们想要根据异常类型的不同来设置不同的处理方法,就需要在catch设置更细分的异常

类型,比如:

public class Demo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 这将抛出 ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException occurred: " + e.getMessage());
        }
    }
}

ArithmeticException 异常就是细分的异常,当我们在catch中使用该异常时,只会在产生

这种异常时进行捕捉,当出现以下状况时,会产生该异常:

  1. 除以零
    • 当你尝试执行一个整数除法,如 int result = 10 / 0; 或浮点数除法,如 double result = 10.0 / 0.0; 时,如果除数是零,Java 会抛出 ArithmeticException
  2. 模运算的除数为零
    • 类似地,当执行模运算 % 且除数是零时,也会抛出此异常,如 int result = 10 % 0;
  3. 其他非法数学运算
    • 虽然不常见,但任何导致数学上无效的操作,比如某些库方法在遇到无法处理的数学条件时,也可能会抛出 ArithmeticException

3 finally 最后进行处理,无论出现或不出现异常,最后的finally都会进行执行。finally不写也

可以,一般用于关闭一些流的情况。

当我们的代码产生异常时,如果不进行捕获,程序是一般不会正常进行的,当我们对异常进

行捕获处理后,程序便可以继续进行。

如果程序中可能出现错误,我们在捕捉时可以将catch捕捉的异常类型设置为Throwable或者

Error,前者包含所有的异常和错误,后者包含所有的错误。

当我们的代码中可能出现按多个异常时,我们可以写多个catch语句进行捕获,由于当产生异常

时,会立即跳转到catch语句中,try剩余的代码将不会执行,如果我们设置多个catch语句,

那么需要将范围更大的异常写在后面,便于准确的处理,而且如果不这么写也会出错。

主动抛出异常

我们可以用throw来主动抛出异常,

import com.corejava.test.*;

public class Main {
    static int c = 1;

    public static void main(String[] args) {
        int d = 0;
        try {
            if (d == 0) {
                throw new Exception("d is zero");
            }
            int a = 1 / d;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

我们主观判断,判断会产生异常时直接抛出,即使后面会产生异常的操作还没有执行,一般

主动抛出异常会使用在方法中,比如:

import com.corejava.test.*;

public class Main {
    public static void main(String[] args) {
        try {
            new Main().add(0, 1);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public void add(int a, int b) {
        int c = b / a;
        if(a==0){
            throw new Exception("a is zero");
        }
    }
}

我们在方法中抛出的异常一般是用于在方法中进行捕获处理的,如果在方法中没有捕获处

理,会向上查找引用,在上一次找对应的捕捉处理的catch,如果一直到最后也没有找到,那

么程序就会停止运行,但一般在方法中抛出就在方法中捕获了。如果在方法中我们处理不了

,我们就会选择使用throws在方法签名后声明异常交给上一层的调用者进行处理。

通过声明异常,代码的使用者可以清楚地看到哪些方法可能会抛出异常,有助于理解代码的

潜在问题和编写健壮的调用代码。

import com.corejava.test.*;

public class Main {
    public static void main(String[] args) {
        try {
            new Main().add(0, 1);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public void add(int a, int b) throws Exception {
        int c = b / a;
    }
}

如果方法中存在运行时异常,我们如果不进行异常处理程序也能运行,对于可能存在非运

行时异常的代码,即使代码中没有产生异常,我们如果不进行异常处理也会报错。

需要注意的是,如果我们在方法内抛出运行时异常,此时即使不在方法签名后声明异常也

是可行的,如果我们抛出的是非运行时异常,那我们必须在方法签名后声明该异常,如果不

想声明,那我们就必须在方法内部对其进行捕获并处理,我们即使在方法后签名声明了运行

时异常,也可也不对其进行处理,如果是非运行时异常就不能这样了。

4.5 自定义异常

一般使用java内置的异常类可以描述在编程中出现的大部分异常,初次之外,用户还可以进行

自定义异常,自定异常类继承java中已有的异常类就行,比如Exception,需要注意的是,只

有当自定义异常直接去继承RuntimeException时,该自定义异常才是运行时异常,不用使用

方法后签名对异常进行声明,否则都是要对异常进行方法签名后声明的。举个例子:

这是自定义异常类:

package com.corejava.test;

public class MyException extends Exception {
    private int detail;

    public MyException(int a) {
        this.detail=a;
    }
    @Override
    public String toString() {
        return "MyException{" +
                "detail=" + detail +
                '}';
    }
}

这是使用:

import com.corejava.test.*;

public class Main {
    public void judge(int a) throws MyException {
        if (a >= 10)
            throw new MyException(a);
        System.out.println("ok");
    }

    static void main(String[] args) {
        try {
            new Main().judge(11);
        } catch (MyException e) {
            System.out.println(e);
        }
    }
}

上面的toString方法再异常类实例化的对象在被当作字符串输出时进行了执行。

以上就是java se的基础内容

完结撒花!!!

posted @ 2024-04-30 00:50  折翼的小鸟先生  阅读(22)  评论(0编辑  收藏  举报