编码

ASCII:用八位二进制的低七位,一共规定了128个字符的编码,一个字节表示一个字符,
扩展ASCII:第八位为1,规定了以1开头的128个字符
Unicode:固定大小的编码,通常两个字节表示一个字符,字母和汉字统一用两个字节,浪费空间
UTF-8:是一种变长的编码方式。字母用一个字节,汉字用三个字节,是在互联网上使用最广的一中Unicode的实现方式
gbk:可以表示汉字,范围广,字母用一个字节,汉字用两个字节 ANSI编码 -- 不同地区采用的编码的统称
UTF-8编码规则:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
编码 大小 支持语言
ASCII 1个字节 英文
Unicode 2个字节(生僻字4个) 所有语言
UTF-8 1-6个字节,英文字母1个字节,汉字3个字节,生僻字4-6个字节 所有语言

基本语法

编写 Java 程序时,应注意以下几点:

标识符

Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

关于 Java 标识符,有以下几点需要注意:

内置数据类型

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte:

short:

int:

long:

float:

double:

boolean:

char:

注:字符型的本质,其实是字符对应的ASCII编码

自动类型转换

强制类型转换

当进行数据精度大小 由大到小的转换时,就需要用到强制类型转换

int n1 = (int)1.9;
System.out.println("n1="+n1);   //n1=1
int n2 = 2000;
byte b1 = (byte)n2;
System.out.println("b1="+b1);    //b1=-48        发生溢出

强制类型强转只针对最近的操作数有效,往往会用小括号提高优先级

int x = (int)10*3.5+2.4;      //错误,不能将double转换为int
int x = (int)(10*3.5+2.4);    //正确 37.4->37

char类型可以保存int的常量值,但不能保存int的变量值,需要强转

byte ,short 和 char 类型在进行运算时,当做int类型处理

基本类型与字符串的转换?

//基本数据类型转字符串
int n1 = 100;
String s1 = n1 + "";  //+"" 即可将任意基本数据类型转为字符串
//字符串转基本类型  使用基本类型对应的包装类的相应方法,得到基本数据类型
String s2 = "123";
int n2 = Integer.parseInt(s2);  //使用基本数据类型对应的包装类,将String转为int,利用Integer.parseInt
boolean b1 = Boolean.parseBoolean("true"); //将String转为Boolean类型
//注意,如果s2="hello",将其转换为int类型,编译时不会出错,但是执行时会抛出异常Exception导致程序终止
//字符串加一个整形数据
String s1 = "abc";
System.out.println(s1 + 1);   //  结果为abc1

字符串的内容比较使用字符串的equal方法

==判断是两个字符串是否在同一块内存空间,而equal判断的是内容是否相等

String name = scan.next();
System.out.println("SVicen",equals(name));   //比较传入的字符串name是否等于SVicen
string[] names = {"金毛狮王",“白眉鹰王","紫衫龙王“};
    for (int i = 0; i <names.length; i++) {
    if(name.equals(names[i])) {
          System.out.println("找到了");
    }
}

引用类型

常量

常量在程序运行时是不能被修改的。

在 Java 中使用 final 关键字来修饰常量,声明方式和变量类似:

final double PI = 3.1415927;

虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。

字面量可以赋给任何内置类型的变量。例如:

byte a = 68;
char a = 'A'

byte、int、long、和short都可以用十进制、16进制以及8进制的方式来表示。

当使用字面量的时候,前缀 0 表示 8 进制,而前缀 0x 代表 16 进制, 例如:

int decimal = 100;
int octal = 0144;
int hexa =  0x64;

和其他语言一样,Java的字符串常量也是包含在两个引号之间的字符序列。下面是字符串型字面量的例子:

"Hello World"
"two\nlines"
"\"This is in quotes\""    // \"将"转义为",若不加",会将字符串内容认为前两个双引号的内容,后面的内容报错

字符串常量和字符变量都可以包含任何 Unicode 字符。例如:

char a = '\u0001';
String a = "\u0001";

转义字符

符号 字符含义
\n 换行 (0x0a)
\r 回车 (0x0d)
\f 换页符(0x0c)
\b 退格 (0x08)
\0 空字符 (0x20)
\s 字符串
\t 制表符
" 双引号
' 单引号
\ 反斜杠
\ddd 八进制字符 (ddd)
\uxxxx 16进制Unicode字符 (xx

注:\r 回车符的意思并不是换行,而是将光标移动到当前行的开始位置 详见下列

System.out.println("helloworld\r北京");
//输出结果为北京oworld    因为一个汉字占两个字节

变量类型

Java语言支持的变量类型有:

public class Variable{
    static int allClicks=0;    // 类变量
    String str="hello world";  // 实例变量
    public void method(){
        int i =0;  // 局部变量
    }
}

接收用户输入

import java.util.Scanner;
//1.引入Scanner类(简单文本扫描器)所在的包
//2.创建Scanner对象
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入名字");
String name = myScanner.next();  //接收用户输入
System.out.println("请输入年龄");
int age = myScanner.nextInt();
System.out.println("名字:" + name + "\n年龄:" + age);
myScanner.close();   //使用完后应该close掉
import java.util.Scanner;
public class ScannerDemo {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        // 从键盘接收数据
        // next方式接收字符串
        System.out.println("next方式接收:");
        // 判断是否还有输入
        if (scan.hasNext()) {
            String str1 = scan.next();  
            System.out.println("输入的数据为:" + str1);
        }
        scan.close();
    }
}

读入char型数据char ch = scan.next().charAt(0); --返回指定索引处的 char 值,这里的索引就是0

char ch = scan.next().charAt(0); --如果输入的是"你好",输出结果为''你''

next() 与 nextLine() 区别

next():

nextLine():

如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取:

流程控制

分支控制

if...else if...else 语句

if 语句后面可以跟 else if…else 语句,这种语句可以检测到多种可能的情况。

使用 if,else if,else 语句的时候,需要注意下面几点:

boolean b = true;
if (b = false) {         //注意这里将b赋值为false  此时第一个if的判断语句为false,不会执行
    System.out.println("a");
} else if(b) {
    System.out.println("b");
} else if(!b) {            //这里判断为true,会执行,最终输出结果为 c
    System.out.println("c");
} else {
    System.out.println("d");
}

switch case 语句

switch(expression){
    case value :
       //语句
       break; //可选,一般加上
    case value :
       //语句
       break; 
    default : //可选
       //语句
}

switch case 语句有如下规则:

switch case 执行时,一定会先进行匹配,匹配成功返回当前 case 的值,再根据是否有 break,判断是否继续输出,或是跳出判断。

char c = 'a';
switch(c){
    case 'a' :
       System.out.println("ok1");
       break;
    case 65 :    //这样是可以的,字符型实质上就是int
       System.out.println("ok2");
       break; 
    default :
       System.out.println("ok3");
}

循环结构

for循环

关于 for 循环有以下几点说明:

增强 for 循环--Java5中引入

for(声明语句 : 表达式) {
   //代码句子
}

声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。

表达式:表达式是要访问的数组名,或者是返回值为数组的方法。

public class Test {
   public static void main(String args[]){
      int [] numbers = {10, 20, 30, 40, 50};
      for(int x : numbers ){
         System.out.print( x );
         System.out.print(",");
      }
      System.out.print("\n");
      String [] names ={"James", "Larry", "Tom", "Lacy"};
      for( String name : names ) {
         System.out.print( name );
         System.out.print(",");
      }
   }
}
//运行结果为
//10,20,30,40,50,
//James,Larry,Tom,Lacy,

break 关键字

break 主要用在循环语句或者 switch 语句中,用来跳出整个语句块。

break 跳出最里层的循环,并且继续执行该循环下面的语句。

可以配合label使用, break label1; //跳出label层循环

数组

声明数组变量

dataType[] arrayRefVar;   // 首选的方法
dataType arrayRefVar[];  // 效果相同,但不是首选方法

创建数组

Java语言使用new操作符来创建数组,语法如下:

arrayRefVar = new dataType[arraySize];

上面的语法语句做了两件事:

  1. 使用 dataType[arraySize] 创建了一个数组。
  2. 把新创建的数组的引用赋值给变量 arrayRefVar。

数组变量的声明,和创建数组可以用一条语句完成,如下所示:

dataType[] arrayRefVar = new dataType[arraySize];

还可以使用如下的方式创建数组(静态初始化):

dataType[] arrayRefVar = {value0, value1, ..., valuek};

数组扩容

int[] arr1 = {1,2,3};
//要在arr1后加一个元素,注意不可以直接arr[3] = 4;  --发生数组下标越界
int[] newArr = new int[arr1.length + 1];
for (int i = 0; i < arr1.length; i++){
    newArr[i] = arr1[i];
}
newArr[newArr.length - 1] = 4;
arr1 = newArr;   //将扩容后的数组赋给原来的数组

For-Each 循环

又叫加强型循环,能在不使用下标的情况下遍历数组。

public class TestArray {
   public static void main(String[] args) {
      double[] myList = {1.9, 2.9, 3.4, 3.5};
      // 打印所有数组元素
      for (double element: myList) {
         System.out.println(element);
      }
   }
}

多维数组

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组,例如:

String str[][] = new String[3][4];
int[][] y 或者 int y[][] 或者 int []y[];
int[][] a= {{1, 2, 3}, {4, 5, 6},{7, 8, 9}};
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        System.out.print(a[i][j] + " ");
    }
    System.out.println("");
}
System.out.println(a[0][2]);   //注意java语言 这里不可以输出 a[0][3] 而c++可以,输出的为第二行第一列的4
//由此可见c++的多维数组内存存储空间是连续的,而java的多维数组内存空间是不连续的

Java的多维数组的每个数组的数据个数可以不同,因为每个一维的数组可以单独new一定的空间

多维数组的动态初始化(以二维数组为例)

1.直接为每一维分配空间,格式如下:

type[][] typeName = new type[typeLength1][typeLength2];
例如
int a[][] = new int[2][3];
int[][] arr = {{1},{1,2},{1,2,3}};

2.从最高维开始,分别为每一维分配空间,例如:

String[] strs = new String[] { "a", "b", "c" };  //是正确的  strs其实是一维的,只不过直接静态赋值了
//注意这里String[] strs = new String[3] { "a", "b", "c" }  就是错误的
String s[][] = new String[2][];
s[0] = new String[2];   //每一个一维的数组都需要 使用new运算符,否则其为空-无法存放数据
s[1] = new String[3];
//注意这里 s[0]中两个元素,而s[1]中有三个元素
s[0][0] = new String("Good");
s[0][1] = new String("Luck");
s[1][0] = new String("to");
s[1][1] = new String("you");
s[1][2] = new String("!");

s[0]=new String[2]s[1]=new String[3] 是为最高维分配引用空间,也就是为最高维限制其能保存数据的最长的长度,然后再为其每个数组元素单独分配空间 s0=new String("Good") 等操作。

Arrays 类

ava.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。

具有以下功能:

序号 方法和说明
1 public static int binarySearch(Object[] a, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
2 public static boolean equals(long[] a, long[] a2) 如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
3 public static void fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
4 public static void sort(Object[] a) 对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。

JAVA内存结构

类与对象

一个类可以包含以下类型变量:

局部变量

实例变量?

类变量(静态变量)

?属性和局部变量可以重名,访问时遵循就近原则

?属性随着对象的创建而创建,随着对象的销毁而销毁,其实非静态属性就是实例变量

?全局变量/属性:可以被本类使用,也可以被其他类使用,创建对象的时候实例化

?全局变量/属性可以加修饰符,但局部变量不可以加修饰符

构造方法/构造器

构造器是对对象进行初始化的而不是创建对象的

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法进行对象的初始化。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,且没有返回值。也不写void。

一旦定义了自己的构造器,系统默认的构造器就被覆盖了,不能使用,除非显示的再定义一下默认无参构造器

下面是一个构造方法示例:

public class Puppy{
    public Puppy(){
    }
    public Puppy(String name){
        // 这个构造器仅有一个参数:name
    }
}

反汇编指令javap 类名(.class后缀) 可以加 -c -v 选项

image-20220412213329202

this

-- 与对象关联,实际上每个对象在堆区分配空间的时候就隐式分配了一个this指向它自己

使用this输出的值一定是属性值,但是如果不加this,如果有局部变量与属性同名,则会就近输出局部变量

public static void main(String[] args) {
    T t1 = new T();
}
class T {
    public T() {
        //对this的调用必须在构造器的第一条语句
        //访问构造器语法:this(参数列表) 注意只能在一个构造器中访问另一个构造器  且this语句必须放在第一条
        this("SVicen", 20);   //在一个构造器内调用其他的构造器
        System.out.println("T()构造器调用");
    }
    public T(String name, int age) {
        System.out.println("T(String name, int age)构造器调用");
    }
}
//输出结果如下
T(String name, int age)构造器调用
T()构造器调用

IDEA自定义模板 settings-> editor -> live templates

创建对象

对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

public class Puppy{
   int age = 10;
   String name;
   public Puppy(String n,int a){  //构造函数
      name = n;
      age = a;
      //这个构造器仅有一个参数:name
      System.out.println("小狗的名字是 : " + name ); 
   }
   public static void main(String[] args){ 
      // 下面的语句将创建一个Puppy对象
      Puppy myPuppy = new Puppy( "tommy",18 );
   }
}
//结果为:小狗的名字是 : tommy

匿名对象

public class Circle {
    double radius; //半径
    public Circle(double r) {
        radius = r;
    }
    public double area() {
        return Math.PI * radius * radius;
    }
    public double len() {
        return 2 * Math.PI * radius;
    }
    public double test() {
        double a = 1.0;
        System.out.println(2 * a);
    }
    public static void main(String[] args) {
        new Circle.test();
    }
}

对象创建流程详解(以上代码为例)?

对象的赋值

public class Object {
    public static void main(String[] args) {
        //创建Person对象
        Person p1 = new Person(20,'男',"SVicen");
        System.out.println("姓名为:" + p1.name + "   性别为:" + p1.gender + "   年龄为:" + p1.age);
        Person p2 = p1; //注意这里传值,传的其实是引用,修改p1,p2任意一个的值都会导致两个都发生变化
        p2.age = 18;             //但是如果令p2=null,只是将p2指向的地址改为了null,并不影响p1的值
        p1.gender = '女';   
        System.out.println("=====第一个人的属性如下=====");
        System.out.println("姓名为:" + p1.name + "   性别为:" + p1.gender + "   年龄为:" + p1.age);
        System.out.println("=====第二个人的属性如下=====");
        System.out.println("姓名为:" + p2.name + "   性别为:" + p2.gender + "   年龄为:" + p2.age);
    }
}
class Person {
    int age;
    char gender;
    String name;
    Person(int a, char g, String s) {
        this.age = a;
        this.gender = g;
        this.name = s;
    }
}
//输出结果如下
姓名为:SVicen   性别为:男   年龄为:20
=====第一个人的属性如下=====
姓名为:SVicen   性别为:女   年龄为:18
=====第二个人的属性如下=====
姓名为:SVicen   性别为:女   年龄为:18

源文件声明规则

在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有import语句和package语句时,要特别注意这些规则。

类有若干种访问级别,并且类也分不同的类型:抽象类和final类等。这些将在访问控制章节介绍。

除了上面提到的几种类型,Java还有一些特殊的类,如:内部类、匿名类。

Java包

包主要用来对类和接口进行分类。当开发Java程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

IDEA,新建packet,名字为com.xiaoming -- 会创建二级目录xiaoming,以及目录com

包的命名一般为:com.公司名.项目名.业务模块名

包的作用

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

//例如 它的路径应该是 net/java/util/Something.java 这样保存的
package net.java.util;   // 一个类中只能有一个packeg
public class Something{
   ...
}

一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。

开发者可以自己把一组类和接口等打包,并定义自己的包。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

由于包创建了新的命名空间(namespace),所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。引用时只可以引入一个包,否则编译器无法区分。

以下是一些 Java 中的包:

创建包

创建包的时候,你需要为这个包取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。

包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。

如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

在 animals 包中加入一个接口(interface):

/* 文件名: Animal.java */
package animals;
interface Animal {
   public void eat();
   public void travel();
}

接下来,在同一个包中加入该接口的实现:

package animals;
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
   public void eat(){
      System.out.println("Mammal eats");
   }
   public void travel(){
      System.out.println("Mammal travels");
   } 
   public int noOfLegs(){
      return 0;
   }
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

可变参数?

注:当有多个参数时,可变参数需在最后一个参数的位置,否则无法确定个数,同时一个函数无法有多个类型的可变参数

class Method{
    public int sum(int... nums) {
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            res += nums[i];
        }
        return res;
    }
    public void f1(double... nums,double d1) {
        //这样写是错误的
    }
    //使用可变参数时,可以当做数组使用,即nums当做数组
    Method m = new Method();
    System.out.println(m.sum(5,10,100));
    System.out.println(m.sum(5,10,20,50,100));  //可以输入任意参数个数
}

Java 修饰符

Java语言提供了很多修饰符,主要分为以下两类:

修饰符用来定义类、方法或者变量,通常放在语句的最前端。

访问控制修饰符?

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

修饰符 当前类 同一包内 子孙类(同一包) 子孙类(不同包) 其他包
public Y Y Y Y Y
protected Y Y Y Y/N N
default Y Y Y N N
private Y N N N N

封装

在面向对象程式设计方法中,封装(Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

封装的优点
封装实现步骤

继承

继承的概念

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

类的继承格式

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

class 父类 {
}
class 子类 extends 父类 {
}
继承的构造器调用?

当创建子类对象时,不管是用子类的哪个构造器,默认情况下都会去调用父类的无参构造器(通过一个super()函数 ),如果父类没有提供无参构造器(定义了有参构造器而没有对无参构造器进行显示的声明),则必须**在子类的构造器中显示用super(构造器形参列表) 来指明调用父类的有参构造器来完成父类的初始化。 **

super()与this()类似,都是只能应用在构造器里,且必须放在构造器的第一行。但可以在普通非静态方法中通过super.eat()调用父类的非静态方法。

继承类型

Java 不支持多继承,但支持多重继承。

Java学习笔记

继承的特性
继承关键字

继承可以使用 extendsimplements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。

extends关键字

在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

public class Animal {
    private String name;   
    private int id; 
    public Animal(String myName, String myid) { 
        //初始化属性值
    } 
    public void eat() {  //吃东西方法的具体实现  } 
    public void sleep() { //睡觉方法的具体实现  } 
} 
public class Penguin  extends  Animal{ 
}
implements关键字

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

public interface A {
    public void eat();
    public void sleep();
}
public interface B {
    public void show();
}
public class C implements A,B {
}
super关键字?

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super取访问爷爷类的成员(前提是直接父类中没有同名成员),如果多个上级类中都有同名的成员,使用super访问遵循就近原则,

class Animal {
  void eat() {
    System.out.println("animal : eat");
  }
}
class Dog extends Animal {
  void eat() {
    System.out.println("dog : eat");
  }
  void eatTest() {
    this.eat();   // this 调用自己的方法
    super.eat();  // super 调用父类方法
  }
}
final关键字

final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:

继承的内存布局?

创建son对象时,father和grandpa对象都会进行构造且放在一块内存,对于father或grandpa的private属性son是不可以访问的

son s = new son();
//通过son访问名字时,如果子类有该属性,则访问子类的,否则访问父类的,知道Object类(最终父类)
s.name;     //大头儿子
s.age;        //爸爸的年龄 39
s.hobby;    //爷爷的爱好 旅游

image-20220413184449013

public class ExtendExercise {
    public static void main(String[] args) {
        B b = new B();  //创建子类对象
    }
}
class A{
    A(){
        System.out.println("a");
    }
    A(String name) {
        System.out.println("a name");
    }
}
class B extends A{
    B(){
        //调用自己的有参构造R
        this("abc");
        System.out.println("b");
    }
    B(String name) {
        //默认有一个 super
        System.out.println("b name");
    }
}
//输出结果为
a
b name
b

重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
class Dog extends Animal{
   public void move(){
      System.out.println("狗可以跑和走");
   }
}
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 对象
      Animal b = new Dog(); // Dog 对象
      a.move();// 执行 Animal 类的方法        动物可以移动
      b.move();//执行 Dog 类的方法            狗可以跑和走
   }
}

在上面的例子中可以看到,尽管b属于Animal类型,但是它运行的是Dog类的move方法。

这是由于在编译阶段,只是检查参数的引用类型(最前面声明的类型)。

然而在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。

因此在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象的方法。

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
class Dog extends Animal{
   public void move(){
      System.out.println("狗可以跑和走");
   }
   public void bark(){
      System.out.println("狗可以吠叫");
   }
}
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 对象
      Animal b = new Dog(); // Dog 对象
      a.move();// 执行 Animal 类的方法
      b.move();//执行 Dog 类的方法
      b.bark();
   }
}

这里,该程序将抛出一个编译错误,因为b的引用类型Animal(最左边的声明类型) 没有bark方法,编译不成功。

方法的重写规则?
重写与重载之间的区别
区别点 重载方法 重写方法
参数列表 必须修改 一定不能修改
返回类型 可以修改 一定不能修改
发生范围 本类 父子类
异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问修饰符 可以修改 一定不能做更严格的限制(可以降低限制)

多态?

多态:方法或对象具有多种形态,是OOP的第三大特征,建立在封装和继承基础之上。

多态的优点
多态存在的三个必要条件
对象的多态
多态的向上转型?

父类的引用指向了子类对象(子类的对象向上转型)

?当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

写代码时看的是编译类型,可以调用编译类型的所有可访问的成员,不能调用子类特有的成员

?写代码时能调用的方法和属性都是编译类型的,但运行时调用的方法时从子类开始找的

调用方法时,从子类(运行类型)向上查找方法调用,(有可能父类定义的函数子类未定义 --但要注意这就不是多态了)

?调用方法时,体现多态--其实就是子类对父类的方法做了具体的实现,所以应该从子类开始找方法。

修改后的父类可以调用父类的所有成员(访问权限满足),但是不能调用子类特有的成员(父类中必须有对应的接口)?

Animal ani = new Animal();//             编译类型为Animal  运行类型为Animal
ani = new Dog();    //父类引用指向Dog子类    编译类型为Animal  运行类型为 Dog
ani = new Cat();    //父类引用指向Cat子类   编译类型为Animal  运行类型为 Cat
ani.eat();    //注意这里先看子类的方法,运行时关注运行类型,输出的是猫吃鱼,如果子类没有eat方法再去调用父类的
public class Test {
    public static void main(String[] args) {
      show(new Cat());  // 以 Cat 对象调用 show 方法
      show(new Dog());  // 以 Dog 对象调用 show 方法
      Animal a = new Cat();  // 向上转型  
      a.eat();               // 调用的是 Cat 的 eat
      Cat c = (Cat)a;        // 向下转型  
      c.work();        // 调用的是 Cat 的 work
  }  
    public static void show(Animal a)  {
      a.eat();  
        // 类型判断
        if (a instanceof Cat)  {  // 猫做的事情  instanceof用于判断对象的运行类型是否为Cat类型或Cat类型的子类型
            Cat c = (Cat)a;  
            c.work();  
        } else if (a instanceof Dog) { //狗做的事情 instanceof用于判断对象的运行类型是否为Dog类型或Dog类型的子类型
            Dog c = (Dog)a;  
            c.work();  
        }  
    }  
}
abstract class Animal {  
    abstract void eat();    //abstract  抽象类
}  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void work() {  
        System.out.println("抓老鼠");  
    }  
}  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void work() {  
        System.out.println("看家");  
    }  
}
//输出结果
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
多态的向下转型

其实就是把执行子类对象的父类引用,转为执行子类对象的子类引用

属性没有重写之说, --看编译类型

public class Test {
    public static void main(String[] args) {
        Base base = new Sub(); //向上转型
        System.out.println(base.count);  //看编译类型,为Base,结果为10
        Sub sub = new Sub();
        System.out.println(sub.count);    //编译类型为Sub,  结果为20
    }
}
class Base{
    int count = 10;
}
class Sub{
    int count = 20;
}

instanceOf 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型。

动态绑定机制DynamicBinding

?详情见com.poly.dynamic 例子以及多态数组的实现

多态数组内会用到 instanceof 操作符

多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型 详见com\poly\polyexercise\TestPolyParameter.java

Object类(顶级父类)详解?

是类层次结构的根类,在java.lang.Object包里,每个类都是要它作为超类,所有对象(包括数组)都实现这个类的方法

断点调试

F7 --跳入方法内 F8 --逐行执行代码 F9 --resume执行到下一个断点 shift + F8 --跳出方法

设置填入源代码 settings->Build,Execution->Debugger->Data Views->Stepping->将java.*和javax.*前的对号取消掉

类与对象进阶

类变量(静态变量)

Static修饰的成员变量,被同一个类的所有对象共享,在类加载的时候就生成了

类方法(静态方法)

main方法

代码块?

又称为初始化块,属于类中的成员,类似于方法,用{}包起来但与方法不同,没有方法名、参数、返回值,不用通过对象显示调用。而是加载类时或创建对象时隐式调用。 修饰符可选,要写的话也只能写static {}; --分号可以写也可以不写。

单例设计模式

设计模式:

单例设计模式:

Final关键字

final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:

不希望类的某个属性被修改,也可以用final修饰该属性 public final double TAX_RATE = 0.1;

不希望某个局部变量被修改,也可以用final修饰,final int NUM = 1.0; (这时NUM可以成为局部常量)

抽象类

如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

接口

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口与类相似点:
接口与类的区别:
接口特性
抽象类和接口的区别?

:JDK 1.8 以后,接口里可以有静态方法和方法体了。?

接口使用
public interface AInterface {
    //写属性
    public int n1 = 10;
    //写方法(抽象方法,默认实现方法,静态方法)
    //在接口中,抽象方法可以省略abstract关键字
    public void hi();
    //jdk8及之后版本,可以有默认实现方法,但需要用default关键字修饰
    default public void ok(){
        System.out.println("ok");
    }
    //jdk8之后,可以有静态方法,不需要使用default
    public static void run(){
        System.out.println("run");
    }
}
public class InterFaceDetail {
}
interface IA{
    void say();   //默认public
    void hi();
}
class Cat implements IA{   //  Ctrl + i 快速实现接口的方法
    @Override
    public void say() {
    }
    @Override
    public void hi() {
    }
}
接口多态特性

如下例,只要是实现了USBInterface接口方法的类都可以作为参数传递给USBinterface

public class InterfacePoly {
    public static void main(String[] args) {
        Camera camera = new Camera();
        Phone phone = new Phone();
        Computer computer = new Computer();
        computer.work(phone);    //这里由于work的参数类型为USBInterface,所以参数可以传进它的子类
        computer.work(camera);
    }
}
public class Computer {
    public void work(USBInterface usbInterface) {
        //通过接口,调用方法  体现多态性      固定接口的方法格式
        usbInterface.start();
        usbInterface.stop();
    }
}
public class Phone implements USBInterface{
    @Override
    public void start() {
        System.out.println("手机开始工作");     //对接口做具体的实现
    }
    @Override
    public void stop() {
        System.out.println("手机停止工作");
    }
}

再比如,接口类型可以指向所有实现了接口内方法的类的对象

interface IF{}
class Monster implements IF{}
class Car implements IF{}
public static void main(String[] args) {
    IF if01 = new Monster();  //创建接口类型的变量,指向Monster类型  相当于向上转型
    if01 = new Car();         //接口类型可以 指向实现了接口的类的对象实例
}

可以创建接口类型的数组,对于不同位置的元素放不同的实现接口的类

USBInterface usb[] = new USBInterface[2];
//多态数组
usb[0] = new Phone();
usb[1] = new Camera();
for (int i = 0; i < usb.length; i++) {
    usb[i].start();
    usb[i].stop();
    if (usb[i] instanceof Phone) {
        ((Phone) usb[i]).call();
    }
}
接口多态的传递?

如果IG继承了IH接口,而Cat类实现了IG接口,那么实际上相当于Cat类也实现了IH接口

内部类

局部内部类
匿名内部类?

匿名内部类存在的前提是要有继承或者实现关系的,但是并没有看到extends和implements关键字,由底层JVM实现?

匿名内部类其实还是一个对象,系统实现类的定义和对接口的实现后,new了一个对象并把它的地址返回给了我们要接收的变量

如上基于接口的匿名内部类

new IA(){
    @Override
    public void cry() {
        System.out.println("老虎叫唤");
    }
}.cry();      //当做对象直接调用方法
class Outer02{
    private int num = 10;
    public void method(){  
        new Father("jack") {
            //private int num = 20;
            @Override
            public void test() {
                System.out.println("匿名内部类调用的num=" + num);
//首先看内部类有没有重新定义num,没有则取继承的父类即Father找是否有num且可以访问,还没有则找外部类的num
                //System.out.println("Father类的 num=" + Outer02.this.num); 这是调用外部类的num值
                //   Outer02.this 就是调用了method方法的对象
            }
        }.test();  //当成对象直接调用方法 
    }
}
class Father{
    protected int num = 30;
    public Father(String name) {  //构造器
    }
    public void test() {
    }
}
public static void main(String[] args) {
        //匿名内部类可以当做实参直接传递
        f1(new IG() {
            @Override
            public void show() {
                System.out.println("这是一幅名画");
            }
        });
    }
    //静态方法  为了直接在main里调用
    public static void f1(IG ig){
        ig.show();
    }
}
interface IG{
    void show();
}

实例二

public class InnerClassExercise02 {
    public static void main(String[] args) {
        //测试手机的闹钟功能,通过匿名内部类(对象)作为参数,打印懒猪起床了 和 小伙伴上课了
        CellPhone cellPhone = new CellPhone();
        //这里的函数参数是一个实现了Bell接口的匿名内部类  重写了ring方法
        cellPhone.alarmclock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");  //运行类型为 InnerClassExercise02$1
            }
        });
        cellPhone.alarmclock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了"); //运行类型为 InnerClassExercise02$2
            }
        });
    }
}
//铃声接口
interface Bell{
    void ring();
}
class CellPhone{
    //闹钟功能
    public void alarmclock(Bell bell) {
        //这里传入的bell 编译类型为Bell   运行类型为调用时创建的匿名内部类(是个对象)
        bell.ring();  //调用ring方法,首先去自己的运行类型里找,运行类型里重写了ring方法,所以调用自己重写后的ring
        System.out.println("运行类型为" + bell.getClass());  
    }
}
成员内部类

没有定义在外部类的方法中,而是定义在外部类的成员的位置上

public class MemberInnerClass {
    public static void main(String[] args) {
        Outer03 outer03 = new Outer03();
        outer03.T();
    }
}
class Outer03{
    private int n1 =10;
    public String name = "张三";
    //成员内部类,没有定义在外部类的方法中
    class Inner03{
        public void say(){
            //可以直接访问外部类的所有成员,包括私有成员
            System.out.println("name=" + name + "  n1=" + n1);
        }
    }
    //使用成员内部类
    public void T(){
        Inner03 inner03 = new Inner03();
        inner03.say();
    }
}
静态内部类
public class StaticInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.m();
    }
}
class Outer04 {
    private int n1 = 10;
    public static String name = "张三";
    //同成员内部类,静态内部类的地位也是外部类的一个成员
    static class Inner04 {
        public void say(){
            System.out.println(name);
        }
    }
    //定义方法去创建内部类对象并调用内部类的方法
    public void m(){
        Inner04 inner04 = new Inner04();
        inner04.say();
    }
}

枚举类

自定义枚举类

使用enum关键字实现枚举类

enum Season2{
    //使用enum关键字
    SPRINT("春天","温暖"),
    SUMMER("夏天","炎热");      //这里的两个参数对应构造器的两个参数
    private String name;
    private String desc; //描述
    private Season2(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    @Override
    public String toString() {
        return "Season2{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

javap反编译

//命令行界面,使用javap 命令对生成的.class文件进行反编译
D:\JetBrains\IDEA\enum_annotation\out\production\enum_annotation\com\svicen\enum_>javap Season2.class
Compiled from "EnumExercise02.java"
final class com.svicen.enum_.Season2 extends java.lang.Enum<com.svicen.enum_.Season2> {
  public static final com.svicen.enum_.Season2 SPRINT;
  public static final com.svicen.enum_.Season2 SUMMER;
  public static com.svicen.enum_.Season2[] values();
  public static com.svicen.enum_.Season2 valueOf(java.lang.String);
  public java.lang.String getName();
  public java.lang.String getDesc();
  public java.lang.String toString();
  static {};
}
方法名称 描述
values() 以数组形式返回枚举类型的所有成员 调用时用枚举类名.values()
valueOf() 将普通字符串转换为枚举实例,要求字符串为已有的常量
compareTo() 比较两个枚举成员在定义时的顺序(索引),大于返回1,等于返回0,小于返回-1
ordinal() 获取枚举对象的索引位置(从0开始)
name() 获取枚举对象的名字
toString() Enum类已经重写过了,返回的是当前对象的对象名,子类可以重写该方法
System.out.println("name=" + Season2.SPRINT.name());
System.out.println("ordinal=" + Season2.AUTYMN.ordinal());
Season2[] values = Season2.values();
for (Season2 season:values) {
    System.out.println(season);
}
Season2 summer = Season2.valueOf("SUMMER");
System.out.println("summer.ordinal=" + summer.ordinal());
System.out.println(summer.compareTo(Season2.SUMMER));  //结果为0  比较的就是ordinal

注解

Java 注解(Annotation)又称 Java 标注,Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

内置的注解

Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

作用在代码的注解是

作用在其他注解的注解(或者说元注解)是:

从 Java 7 开始,额外添加了 3 个注解:

异常

执行过程发生的异常有两大类

进入Throwable后查看源码,右键Diagrams -> show Diagrams

image-20220421104141869

异常处理方式

try-catch细节

try {
    String str = "异常";
    int a = Integer.parseInt(str);
    System.out.println("转换后:" + a);  //不会执行
} catch(NumberFormatException e) {
    System.out.println("异常信息:" + e.getMessage());
} finally{
    System.out.println("finally代码块执行");
}
System.out.println("程序继续执行");  //会执行

注意:下面的catch语句块中会将++i后的值作为临时变量先保存起来再去执行finally语句,最后返回这个临时变量

image-20220421112848951

throws细节

public class Throws01 {
    public static void main(String[] args) {
    }
    public void f1() throws FileNotFoundException,NullPointerException,ArithmeticException  {
        //创建了一个文件流对象,
        //这里的异常是一个FileNotFoundException,编译时异常
        //可以使用try-catch-finally
        //使用throws,抛出异常  throws FileNotFoundException  也可以是 throws Exception
        FileInputStream fileInputStream = new FileInputStream("d//aa.txt"); //编译异常
    }
}

自定义异常

一般是继承RunTimeException

public class Throws01 {
    public static void main(String[] args) {
        int age = 200;
        if(!(age >=18 && age <=120)) {
            throw new AgeException("年龄需在18-120");  //抛出异常,输出的即为我们这里传的参数
        }
        System.out.println("你的年龄范围正确");
    }
}
class AgeException extends RuntimeException{
    public AgeException(String message) {  //构造器
        super(message);
    }
}
意义 位置 后面跟的东西
throw 手动生成异常对象的关键字,与new并用 方法体中 异常对象
throws 异常处理的一种方式 方法声明处 异常类型

包装类

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。

这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。

//手动装箱   jdk5之前手动装箱和拆箱
int n1 = 10;
Integer integer = new Integer(n1);   // jdk9以后 @Deprecated方法过时
//手动拆箱
int i = integer.intValue();
//自动装箱  jdk5之后可以
int n2 = 20;
Integer integer2 = n2;
System.out.println(n2);
//自动拆箱
int n3 = integer2;
System.out.println(n3);
//注意看以下代码  三元运算符是一个整体,虽然执行new Integer(1)但整体精度为double,输出1.0
Object obj1 = true ? new Integer(1):new Double(2.0);
System.out.println(obj1);  // 1.0

包装类方法

经典题目

Integer m = 1;
Integer n = 1;
System.out.println(m == n);   //true  这里没有new对象
Integer x = 128;
Integer y = 128;
System.out.println(x == y);      //false 这里返回的是new的对象
int n = 10;
Integer nn = 10;
System.out.println(n == nn);  //false 只要有基本数据类型判断的就是值是否相等
//Integer m = 1; 底层是调用了Integer的 valueOf方法,
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)  //low为-128   high为127 Integer的缓存机制
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

String类

image-20220422142032445

String的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节

String类有很多构造器的重载,常用的有

String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
String s5 = new String(byte[] b);

对象的创建

//1、直接在常量区找是否有"svicen"的数据空间,有则将s1指向该空间,没有则重新创建,然后指向。s1最终指向的常量池的空间地址
String s1 = "svicen";
//2、现在堆中创建空间,里面维护了value属性指向常量池的"svicen"的数据空间,如果常量池没有则创建后指向。s2指向的是堆的空间地址
String s2 = new String("svicen");

String的intern方法,如果池已经包含一个等于此String对象的字符串(用equals(Object)判断),则返回池中的字符串。否则,将此String对象添加到池中,并返回此对象的引用。 最终返回的是常量池的地址

经典题目

Person p1 = new Person();   //在堆区创建了两个Person对象,他的name属性时字符串,字符串的value指向常量区的"svicen"
p1.name = "svicen";
Person p2 = new Person();
p2.name = "svicen";
System.out.println(p1.name.equals(p2.name)); //true
System.out.println(p1.name == p2.name);     //true  只要常量区里有"svicen"就直接指向,所以p1 p2的value指向的地址相同
System.out.println(p1.name == "svicen");    //true  p1.name的value[]指向的就是常量区的"svicen"的地址
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);               //false  这里新创建了两个对象,对象的地址一定是不一样的
System.out.println(s1.intern() == s2.intern()); //true intern方法返回了两个对象的value指向的常量区的地址
//关于字符串对象创建的问题
String s1 = "hello";
s1 = "haha";              //创建了两个对象  因为字符串的值不能改变,修改后相当于新创建了一个对象让s1指向它
String a = "hello" + "abc";  //创建一个对象,编译器会做优化,判断创建的常量区对象是否有引用指向,
//等价于 String a = "helloabc"
String a = "hello";
String b = "abc";
String c = a + b;       //创建三个对象                             // d = "helloabc"
//1.先创建一个StringBuilder sb = StringBuilder()
//2.执行sb.append("hello");
//3.执行sb.append("abc");
//4.执行sb.toString()方法,return 上面的字符串  返回给c
//最后实际上是 c 指向堆中的对象(String) value[] value数组再指向 常量池的"helloabc"  所以 c==d 返回false
// 字符串常量相加结果直接指向常量池   字符串变量相加结果指向堆的内存

String常用方法

int length():返回字符串的长度:return value.length
char charAt(int index):返回某索引处的字符  return value[index]   不要使用str[0]取第一个字符
boolean isEmpty():判断是否是空字符串:return value.length == 07
String toLowerCase():使用默认语言环境,将String 中的所有字符转为小写
String toUpperCase():使用默认语言环境,将String 中的所有字符转为大写
char[] toCharArray():将String转换成一个char数组 
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String str) 功能与equals相似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾,等价于”+“
int compareTo(String str):比较两个字符串的大小,前者大返回正数,后者大返回负数,长度和内容都相同返回0
String a = "jacks";
String b = "jaaks";
System.out.println(a.compareTo(b));  //每一位依次比较,最终返回 'c' - 'a' = 2
a = "jaak";
System.out.println(a.compareTo(b)); //如果二者已比较的的都相等,但长度不等,最后返回的是二者的长度的差值
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex位置开始截取
String substring(int beginIndex,int endIndex):返回一个新的字符串,它是此字符串的从beginIndex位置开始截取到endIndex位置,
boolean endsWidth(String str):测试此字符串是否以指定的后缀结束
boolean startsWidth(String str):测试此字符串是否以指定的前缀开始
boolean startsWidth(String str, int toffset):测试此字符串是否在指定索引开始的位置以指定的前缀开始  
boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时返回true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现的索引,找不到返回-1
int indexOf(String str,int index):返回指定子字符串在此字符串中第一次出现的索引,从指定索引处开始查找
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现的索引,找不到返回-1
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现的索引,从指定索引处开始查找
String replace(char oldChar,char newChar):返回一个新的字符串,它是通过newChar替换此字符串中出现的所有oldChar得到的
s2 = s1.replace("a","b");  //对于s1本身没有影响
String replace(CharSequence target,CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
String replaceAll(String regex,String replacement):使用指定的replacement代替正则表达式查询出来的值
String replaceFirst(String regex,String replacement):使用指定的replacement代替正则表达式查询出来的第一个值
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串,s = s.split(",");以逗号为分割点分割
String[] split(String regex,int limit):根据匹配的正则表达式来拆分此字符串,最多不超过limit个,如果超过,剩下的全部放到最后一个元素中。
String format(String format, Object args):就是利用%S %d %x占位符   

JAVA的格式化字符串 format

String formatStr = "我的名字是%s,年龄是%d,成绩是%.2f,性别是%c.";
String info = String.format(formatStr,a,age,score,sex);
System.out.println(info);

由于String每次更新对象都需要重新开辟空间,效率较低,因此有StringBuilder和StringBuffer来增强String的功能

StringBuffer

StringBuffer方法

题目实例,对金额形式的转换,每三位添加一个,

Scanner scanner = new Scanner(System.in);
String str = scanner.next();
StringBuffer stringBuffer = new StringBuffer(str);
for (int i = stringBuffer.lastIndexOf(".") - 3; i > 0; i -= 3) {
    //123456.59
    //先找到小数点位置
    stringBuffer.insert(i,",");
}
System.out.println(stringBuffer);

StringBuilder

一个可变的字符序列,此类提供一个与StringBuffer兼容的API,但不保证同步(会有线程安全问题),此类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候,如果可能,建议优先采用该类,因为大多数实现中,它比StringBuffer更快

StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。

Math类

Arrays类

System类

大数处理

BigInteger

BigInteger bigInteger = new BigInteger("22222222222222222222222222222");
BigInteger bigInteger1 = new BigInteger("22222222222222222222222222222");
System.out.println(bigInteger);
//在对BigInteger进行加减乘除时需要使用对应的方法   将数字当成字符串,处理完后再转为数字
BigInteger sum = bigInteger.add(bigInteger1);
System.out.println(sum);
BigInteger sub = bigInteger.subtract(bigInteger1);
System.out.println(sub);
BigInteger multy = bigInteger.multiply(bigInteger1);
System.out.println(multy);
BigInteger div = bigInteger.divide(bigInteger1);
System.out.println(div);

BigDecimal

BigDecimal bigDecimal1 = new BigDecimal("1.11111111111111111111111111111111111111");
BigDecimal bigDecimal2 = new BigDecimal("1.11111111111111111111111111111111111111");
//也需要使用对应的方法对BigDecimal进行加减乘除
BigDecimal sum = bigDecimal1.add(bigDecimal2);
System.out.println(sum);
//对于除法  如果除不尽,有可能会抛出异常  -- 可以在divide方法后指定精度
BigDecimal div = bigDecimal1.divide(bigDecimal2);
BigDecimal div = bigDecimal1.divide(bigDecimal2,BigDecimal.ROUND_CEILING); 
//BigDecimal.ROUND_CEILING 在JDK9后已过时
System.out.println(div);

日期类

第一代日期类Date

第二代日期类Calendar

Calendar是抽象类,所以不可以实例化对象

缺点:可变性(日期和时间这样的类应该是不可变的),偏移性(月份从0开始),不能格式化,不是线程安全的,不能处理闰秒

Calendar c = Calendar.getInstance();
System.out.println("c=" + c);   //会将整个日历的字段全部打印出来
//如果要分别打印字段  需要自己组合,没有提供格式化字符串的方法
System.out.println("年:" + c.get(Calendar.YEAR));
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));  //月份按照0开始编号的,所以需要+1
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("时:" + c.get(Calendar.HOUR));
System.out.println("时:" + c.get(Calendar.HOUR_OF_DAY)); //以24小时的方式获取 时
System.out.println("分:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));

第三代日期类LocalDateTime

LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//2022-04-25T19:34:02.573627400
System.out.println("年:" + ldt.getYear());
System.out.println("月:" + ldt.getMonth());             //APRIL
System.out.println("月:" + ldt.getMonthValue());        //4
System.out.println("日:" + ldt.getDayOfMonth());
//使用DateTimeFormatter 对象对日期进行格式化
DateTimeFormatter date = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");//注意小时使用 HH 按24小时来算
String format = date.format(ldt);  
System.out.println("格式化日期:" + format);  //格式化日期:2022年04月25日 19:42:23
//通过静态方法,now 获取当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//通过 Date类的from 方法可以把Instant转为Date
Date date = Date.from(now);
System.out.println(date);
//通过 date对象的toInstant方法可以把一个Date转换为Instant
Instant instant = date.toInstant();
System.out.println(instant);
//最终输出结果为
2022-04-25T11:47:50.124618200Z
Mon Apr 25 19:47:50 CST 2022
2022-04-25T11:47:50.124Z      可知再转换为Instannt对象后时间戳的精度比now出来的小
//对日期的加减
LocalDateTime ldt = LocalDateTime.now();
LocalDateTime localDateTime = ldt.plusDays(23);
System.out.println("23天后:" + localDateTime);
//也可以格式化输出
DateTimeFormatter date = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
System.out.println("23天后:" + date.format(localDateTime)); //23天后:2022年05月18日 19:55:16
//对日期的减
LocalDateTime localDateTime1 = ldt.minusHours(24);
System.out.println(date.format(localDateTime1));     //    减去24小时后:2022年04月24日 19:55:16

集合

ctrl + alt +b 在类图中查看子接口 alt + insert 快速插入方法

image-20220427154620682
image-20220427155432910

集合主要分为两类,单列集合和双列集合List和Set都是单列集合,map是双列集合。

List

ArrayList

底层维护了一个Object类型的数组transient Object[] elementData,transient 表示瞬间的,短暂的,表示该属性不会被序列化

List list = new ArrayList();
//add方法插入
list.add("svicen");
list.add(10);
list.add(true);
System.out.println(list);
//remove方法删除元素
list.remove("svicen");
System.out.println(list);
//contains方法查看是否有指定元素
System.out.println(list.contains(true));
//isEmpty方法判断是否为空
System.out.println(list.isEmpty());
//clear方法清空
list.clear();
//addAll方法,添加一个集合
ArrayList list1 = new ArrayList();
list1.add("西游记");
list1.add("红楼梦");
list.addAll(list1);
System.out.println(list);
//containsAll方法查找集合是否存在
System.out.println(list.containsAll(list1));
//removeAll方法删除集合
System.out.println(list.removeAll(list1));
ArrayList col = new ArrayList();
col.add(new Book("三国演义","罗贯中",50));
col.add(new Book("西游记","吴承恩",40));
col.add(new Book("红楼梦","曹雪芹",60));
//使用迭代器遍历
//首先需要获得迭代器
Iterator iterator = col.iterator();
//快捷键 itit    查看所有快捷键 ctrl + j
while (iterator.hasNext()) {
    Object next =  iterator.next();
    System.out.println(next);
}
//退出循环后,iterator指向最后的元素
//如果需要再次遍历,需要重置迭代器
iterator = col.iterator();
//快捷键 I
for (Object book : col) {
    System.out.println(book);
}
Vector
LinkedList
//自己写的代码
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
//默认删除第一个结点
linkedList.remove();
//跟进去的源码
public boolean add(E e) {
    linkLast(e);
    return true;
}
//默认向链表的最后添加结点
void linkLast(E e) {
    final Node<E> l = last;
    //新结点的prev指向l,值item为e,next指向null
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    //如果l为空,说明原来链表里没有元素,直接让first也指向新添加的结点即可
    if (l == null)
        first = newNode;
    else//l不为空,则让l.next指向新添加的结点
        l.next = newNode;
    size++;
    modCount++;
}
public E remove() {
    return removeFirst();
}
public E removeFirst() {
    final Node<E> f = first;
    //如果删除的是空结点,则会报异常
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
//核心逻辑,将双向链表的第一个结点删除
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;  //取出内容
    final Node<E> next = f.next; //取出要删去的结点的下一个结点
    f.item = null;
    f.next = null; // help GC  GC垃圾处理机制处理
    first = next;  //把first指向后移
    if (next == null)  //如果删除后链表为空,则让last也为空
        last = null;
    else   //first指向的不为空,last不用修改,只需要则将next的prev指向设置为空
        next.prev = null;
    size--; //元素个数--
    modCount++; //修改次数++
    return element;
}
数据结构 底层结构 启用版本 线程安全 扩容倍数
ArrayList 可变数组 jdk1.2 不安全,增删效率低,改查效率高 有参,按照1.5倍扩容;无参,第一次扩容10,之后按照1.5倍扩容
Vector 可变数组 jdk1.0 安全,效率不如ArrayList 无参,默认10,然后按照2倍扩容,有参,按照2倍扩容
LinkedList 双向链表 不安全,增删效率高,改查效率低

Set?

Set接口对象 -- 即实现了set接口的类的对象

HashSet

作业例题?
public class Homework04 {
    public static void main(String[] args) {
        Person p1 = new Person(1001, "AA");
        Person p2 = new Person(1002, "BB");
        HashSet hashSet = new HashSet();
        hashSet.add(p1);
        hashSet.add(p2);
        System.out.println("删除前: " + hashSet);
        p1.setName("CC"); //这里修改了p1的属性
        //因为上面修改了p1的name,所以删除时根据Person的id和name查找的索引不再是原来的p1的位置,无法将p1删除
        hashSet.remove(p1);
        System.out.println("删除后: " + hashSet); // 2个对象,没有删除
        hashSet.add(new Person(1001,"CC"));
        System.out.println(hashSet);  // 3个对象
        hashSet.add(new Person(1001,"AA"));
        System.out.println(hashSet); //4个对象,因为上面p1的name已经修改,加入时比较时equals方法判断不相等,可以加入
    }
}
/*
删除前: [Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
删除后: [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
*/
class Person{
    private int id;
    private String name;
    //注意这里需要重写Person 的equals和hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}
LinkedHashSet

TreeSet

TreeSet 与 HashSet比较

二者去重比较:

HashSet LinkedHashSet TreeSet
按hash值定索引,取出与插入顺序往往不同 有pre和next属性,使得元素看起来是以插入顺序排序的 可以自己指定排序的方法
底层其实就是HashMap 继承了HashSet 底层其实就是TreeMap

Map

HashMap

Hashtable

?以上所有扩容机制都是先检查是否达到threshold然后再将要加入的元素加进table表,然后count++

由于每次判断的都是count >= threshold 所以在加入第八个元素时count还为7,不会扩容

Properties

TreeMap

Collection工具类

ArrayList arrayList = new ArrayList();
arrayList.add("tom");
arrayList.add("smith");
arrayList.add("king");
arrayList.add("milan");
System.out.println(arrayList);
Collections.reverse(arrayList);
System.out.println(arrayList);
Collections.sort(arrayList);
System.out.println(arrayList);
//自己指定排序方式
Collections.sort(arrayList, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        //校验代码
        if (o1 instanceof String && o2 instanceof String) {
            return ((String) o1).length() - ((String) o2).length();
        }
        return 0;
    }
});
System.out.println(arrayList);
// 交换指定位置元素
Collections.swap(arrayList,1,2);
//求指定排序方式的最大元素
System.out.println(Collections.max(arrayList));
System.out.println(Collections.max(arrayList, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        return ((String)o1).length() - ((String)o2).length();
    }
}));
//查找出现次数
System.out.println("tom的次数" + Collections.frequency(arrayList,"tom"));
//拷贝
ArrayList dest = new ArrayList();
for (int i = 0; i < arrayList.size(); i++) {
    dest.add(i);
}
Collections.copy(dest,arrayList);  //这里需要先把dest的数组大小设置为与原来的arrayList大小相同,不然会抛出数组越界异常
System.out.println(dest);
//替换
Collections.replaceAll(arrayList,"tom","汤姆");

泛型

//泛型入门,指定泛型的具体数据类型时 只能用引用类型,不能用基本类型
Person<String, Integer> hs = new Person<>("hs", 123);   //建议这样写
//Person<String, Integer> hs = new Person<String, Integer>("hs", 123);  后边的<>里类型可以省略,且推荐省略
System.out.println(hs);
HashMap<String, Person> stringPersonHashMap = new HashMap<>();
//这样也可以 HashMap<String, Person<String,Integer>> stringPersonHashMap = new HashMap<>();
stringPersonHashMap.put("jack",new Person("jak",123));
System.out.println(stringPersonHashMap);
Person person = new Person("123", 123);  //创建Person实例化对象时也可以不显示给出变量类型声明,直接用特定变量填充E、T即可
class Person<E,T>{
    private E name;
    private T age;
}
@SuppressWarnings({"all"})
public class GenericExercise01 {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("jack",30000,new MyDate(2000,6,19)));
        employees.add(new Employee("tom",10000,new MyDate(2001,6,15)));
        employees.add(new Employee("tom",20000,new MyDate(2001,5,17)));
        System.out.println(employees);
        //下面是对birthday的比较,最好将其放在MyDate类完成
        employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee emp1, Employee emp2) {
                //先对传入的参数进行验证
                if (!(emp1 instanceof Employee && emp2 instanceof Employee)) {
                    System.out.println("类型不正确");
                    return 0;
                }
                //先按名字排序,如果名字相同就比较出生日期
                int i = emp1.getName().compareTo(emp2.getName());
                if (i != 0){
                    return i;
                }
                //为了提高程序的可读性和封装性,比较的具体操作再MyDate类中实现
                return emp1.getBirthday().compareTo(emp2.getBirthday());
            }
        });
        System.out.println("=====排序后======");
        System.out.println(employees);
    }
}
//继承Comparable接口,传入参数类型为MyDate
class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;
    @Override
    public int compareTo(MyDate o) {
        //如果 name 相同,比较birthday,依次比较年、月、日
        int yearMinus = year - o.getYear();
        if (yearMinus != 0){
            return yearMinus;
        }
        int monthMinus = month - o.getMonth();
        if (monthMinus != 0){
            return monthMinus;
        }
        return day - o.getDay();
    }
}
class Employee{
    private String name;
    private double sal;
    private MyDate birthday;
}

自定义泛型

自定义接口

interface IA extends IUsb<String,Double>{
}
//这里继承了IA,也就相当于指定了IUsb的泛型的类型,在实现IUsb方法时,使用String替换U,Double替换R
//如果直接继承了IUsb,则需要自己在继承的时候或者实现接口方法的时候指定类型
class AA implements IA{
    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {
    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {
    }
}
interface IUsb<U,R>{
    //U u;  错误,接口的变量隐式的指定为 public static final   静态成员不能使用泛型
    int n = 10;  //这是正确的
    //普通方法中,可以使用接口泛型
    R get(U u);
    void hi(R r);
    void run(R r1,R r2,U u1, U u2);
    //可以在接口中使用默认方法,默认方法也是可以使用泛型的
    default R method(U u) {
        return null;
    }
}

泛型方法

泛型继承与通配符

//可以接收任意泛型类型
public static void printCollection(List<?> c){
    for (Object o :c) {
        System.out.println(o);
    }
}

JUnit使用

public class Junit_ {
    public static void main(String[] args) {
    }
    @Test  //输入@Test后 alt + enter 添加 JUnit5.8  之后会引入包  之后就可以直接运行该方法了,也可以使用快捷键
    public void m1(){ 
        System.out.println("m1被调用");
    }
    @Test
    public void m2(){
        System.out.println("m2被调用");
    }
}
@SuppressWarnings({"all"})
public class Homework01 {
    public static void main(String[] args) {
    }
    @Test
    public void testList(){
        //这里给T的指定类型为User
        DAO<User> dao = new DAO<>();
        dao.save("001",new User(1,18,"jack"));
        dao.save("002",new User(2,28,"rom"));
        dao.save("003",new User(3,38,"smith"));
        //拿到返回的所有对象
        List<User> list = dao.list();
        System.out.println("list=" + list);
        //修改对象
        dao.update("003",new User(15,55,"milan"));
        //获取修改后的所有对象
        list = dao.list();
        System.out.println(list);
        System.out.println(dao.get("003"));
    }
}
@SuppressWarnings({"all"})
class DAO<T>{
    private Map<String,T> map = new HashMap<>();
    //存放元素
    public void save(String id, T entity) {
        map.put(id,entity);
    }
    public T get(String id){
        return map.get(id);
    }
    //更改对象value
    public void update(String id,T entity) {
        map.put(id,entity);
    }
    //返回map里的对象
    public List<T> list(){
        //遍历map的key,找到所有的value,然后封装到ArrayList返回即可
        List<T> list = new ArrayList<>();
        //遍历map
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            list.add(get(key));
            //list.add(map.get(key)); 这样也可以
        }
        return list;
    }
}
class User{
    private int id;
    private int age;
    private String name;
}

绘图

package com.svicen.draw_;
import javax.swing.*;
import java.awt.*;
//要想绘图,需要继承JFrame  在窗口内嵌入面板
public class DrawCircle extends JFrame{
    //定义一个面板
    private MyPanel mp = null;
    public static void main(String[] args) {
        new DrawCircle();
    }
    public DrawCircle(){
        //初始化面板
        mp = new MyPanel();
        //面板放入到窗口(就是画框)
        this.add(mp);
        this.setSize(400,300);  //400*300 像素
        //当关闭弹出的组件后 jvm并不会释放这个JFrame对象  在这里设置当点击组件的关闭x后程序完全退出
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);  //设置可以显示
    }
}
//先定义一个MyPanel,继承java的JPanel类  在画板上画图形
class MyPanel extends JPanel {
    //Graphics 可以认为是一个画笔  Panel是一个面板
    @Override
    /*   paint方法调用有四种情况
    1.当组件第一次在屏幕显示时,系统自动调用  
    2.窗口最小化后再最大化
    3.窗口的大小发生变化
    4.repaint函数被调用
    */
    public void paint(Graphics g) {
        super.paint(g); //调用父类的方法完成初始化
        //画一个圆形 四个参数 x,y,width,height
        g.drawOval(10,10,100,100); 
    }
}
//画一个圆形 四个参数 x,y,width,height  x y为圆的边界的坐标
g.drawOval(10,10,100,100);
//画直线  四个参数 x1 x2 y1 y2
g.drawLine(10,10,100,100);
//绘制矩形边框  四个参数 x y width height
g.drawRect(10,10,100,100);
//绘制填充矩形,先指定颜色
g.setColor(Color.blue);
g.fillRect(10,10,100,100);
//绘制填充圆形,可以使椭圆
g.fillOval(10,10,100,100);
//获取图片资源  这里getResource在jdk9之后做了修改
Image image = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/1.png"));
g.drawImage(image,10,10,28,28,this);
//绘制字符串,即写字
g.setColor(Color.red);
g.setFont(new Font("隶书",Font.BOLD,50)); //字体,是否加粗,字体大小
g.drawString("你好",100,100); //字体的左下角坐标
public class BallMove extends JFrame {
    MyPanel mp = null;
    public static void main(String[] args) {
        new BallMove();
    }
    public BallMove(){
        mp = new MyPanel();
        this.add(mp);
        this.setSize(400,300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //使窗口的的JFrame对象可以监听到面板上发生的键盘时间   这里必须要add进去
        this.addKeyListener(mp);
        this.setVisible(true);
    }
}
//implements KeyListener  实现监听键盘的接口
class MyPanel extends JPanel implements KeyListener {
    //为了让小球可以移动,我们需要把小球左上角的坐标设置为变量
    int x = 10;
    int y = 10;
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.fillOval(x,y,20,20);
    }
    //当某个键按下时该方法触发
    @Override
    public void keyPressed(KeyEvent e) {
        //System.out.println((char) e.getKeyCode() + "被按下");
        if(e.getKeyCode() == KeyEvent.VK_DOWN) {
            //向下的键被按下
            y++;
        } else if (e.getKeyCode() == KeyEvent.VK_UP) {
            y--;
        } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            x--;
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            x++;
        }
        //改变后需要让面板重绘
        this.repaint();
    }
}

详情见坦克大战代码

线程

image-20220505100344887

线程使用

继承Thread类
实现接口Runnable
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog.start();   由于实现接口的线程类没有start方法
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable{
    int count = 0;
    @Override
    public void run() {
        while(true){
            System.out.println("小狗汪汪叫" + (++count) + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(count == 10){
                break;
            }
        }
    }
}

通知线程退出

public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();
        //如果希望main线程去控制t1线程的终止,可以修改loop为false 让t1退出run方法,从而终止t1线程
        //主线程休眠5秒,退出t1线程
        Thread.sleep(5000);
        t1.setLoop(false);
    }
}
class T extends Thread{
    private int count = 0;
    private boolean loop = true;
    @Override
    public void run() {
        while(loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("T 运行中" + (++count));
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

线程方法

setName 设置线程名称,使之与参数name相同

getName 返回该线程的名称

start 使该线程开始执行;Java虚拟机底层调用该线程的start0方法

run 调用线程对象run方法;

setPriority 更改线程的优先级

getPriority 获取线程的优先级

sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

interrupt 中断线程,而不是终止线程,如果正在休眠则提前终止休眠

public static void static yield() 暂停当前正在执行的线程对象,并执行其他线程。礼让进程,但礼让的时间不确定,不一定礼让成功

public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。

public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。插入的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

public static Thread currentThread() 返回对当前正在执行的线程对象的引用。

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        //测试相关的方法
        T t = new T();
        t.setName("jack");
        t.setPriority(Thread.MIN_PRIORITY);//设置优先级为 1 最小优先级
        t.start();//启动子线程
        //主线程打印5次hi ,然后就中断子线程的休眠
        for(int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + i);
        }
        System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
        t.interrupt();//当执行到这里,就会中断 t线程的休眠.  提前结束休眠
    }
}
class T extends Thread { //自定义的线程类
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                //Thread.currentThread().getName() 获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + " 吃东西~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(20000);    //20秒
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以在这里加入自己的业务代码
                //InterruptedException 是捕获到一个中断异常.
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T3 t3 = new T3();
        t3.start();
        for(int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程(小弟) 吃了 " + i  + " 包子");
            if(i == 5) {
                System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
                //join, 线程插队
                t3.join();// 这里相当于让t2 线程先执行完毕  调用的是对方的join方法,join到自己的线程里
               //Thread.yield();//礼让,不一定成功 如果内核资源不紧张,可以满足两个线程  那就不会礼让
                System.out.println("子线程(老大) 吃完了 主线程(小弟) 接着吃..");
            }
        }
    }
}
class T3 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);//休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大) 吃了 " + i +  " 包子");
        }
    }
}

用户线程:也叫工作线程,当线程的任务执行完毕或以通知方式结束(即上面的退出方式)

守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,一般用于监视某线程

常见的守护线程:垃圾回收机制。只要还有线程在工作,垃圾回收机制就一直会守护。

线程七大状态

JDK 中用 Thread.State 枚举表示了线程的六种状态

Java学习笔记

线程状态转换图(RUNNABLE又可以细分为两种状态(Ready和Running))

Thread_state

使用程序查看线程状态
创建 T 线程,然后输出此时的状态,再启动线程,利用循环,查看线程状态,只要线程没终止,就会不停的输出状态

public class Thread_State {
    public static void main(String[] args) throws InterruptedException {
        T4 t4 = new T4();
        System.out.println(t4.getName() + " 状态 " + t4.getState()) ;
        t4.start();
        while(t4.getState() != Thread.State.TERMINATED) {
            System.out.println(t4.getName() + " 状态 " + t4.getState());
            Thread.sleep(1000);
        }
        System.out.println(t4.getName() + " 状态 " + t4.getState());
    }
}
class T4 extends Thread{
    @Override
    public void run() {
        while(true){
            for (int i = 0; i < 10; i++) {
                System.out.println("hi  " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            break;
        }
    }
}

线程同步

同步具体方法:Synchronized

public class SellTickets {
    public static void main(String[] args) {
        SellTicket02 sellTicket02 = new SellTicket02();
        //这里会出现票数超卖现象,需要进行线程互斥的限制
        //同一个对象开启三个线程
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();
    }
}
//使用同步方法Synchronized实现线程同步
class SellTicket02 implements Runnable{
    private static int ticketNum = 100;
    private boolean loop = true;
    //这里就是一个同步方法,锁的对象为  this对象 ,也可以用同步代码块实现
    public synchronized void sell(){
        if(ticketNum <= 0) {
            System.out.println("票已售空");
            loop = false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" +
                "  剩余票数" + (--ticketNum));
    }
    public /*synchronized*/ void sell(){
        synchronized (this) {
            if (ticketNum <= 0) {
                System.out.println("票已售空");
                loop = false;
                return;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" +
                    "  剩余票数" + (--ticketNum));
        }
    }
    //静态方法实现同步代码块,不能用于锁this,只能锁  类名.class
    public static void m(){
        synchronized (/*this*/SellTicket02.class){
            System.out.println("静态方法锁");
        }
    }
    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}
互斥锁

线程死锁

多个线程都占用了对方的锁资源,但不肯相认,导致了死锁

释放锁

以下情况会释放锁

以下情况不会释放锁

IO流

文件的创建

//方式一  直接写明文件路径 public File(String pathname)
@Test
public void create01(){
    String filePath = "D:\\JetBrains\\hello.txt";
    File file = new File(filePath); //只是创建了一个file对象但是还没有创建具体的文件
    try {
        file.createNewFile();
        System.out.println("hello1文件创建成功");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
@Test
//方式二  通过父目录文件 + 子路径 public File(File parent, String child)
public void create02(){
    File parentfile = new File("D:\\JetBrains\\");
    String fileName = "hello2.txt";
    File file = new File(parentfile, fileName);
    try {
        file.createNewFile();
        System.out.println("hello2创建成功");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
@Test
//方式三 通过父目录 + 子文件路径   public File(String parent, String child) 用于在一个父目录下创建多个子文件
public void create03(){
    String parentPath = "D:\\JetBrains\\";
    String fileName = "hello3.txt";
    File file = new File(parentPath, fileName);
    try {
        file.createNewFile();
        System.out.println("hello3创建成功");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

常用的文件操作

方法名称 说明
public String getName() 返回由此抽象路径名表示的文件或文件夹的名称
public booleean isFile() 测试此抽象路径名表示的File是否为文件
public boolean isDirectory() 测试此抽象路径名表示的File是否为文件夹
public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
public String getPath() 将此抽象路径名转换为路径名字符串
public boolean exists() 测试此抽象路径名表示的File是否存在
public long lastModified() 返回文件最后修改的时间毫秒值
public long length() 返回文件大小,utf-8中英文字母1个字节,汉字3个字节
public File[] listFiles()(常用) 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回
public boolean mkdir() 只能创建一级文件夹
public boolean mkdirs() 可以创建多级文件夹
public boolean delete() 删除由此抽象路径名表示的文件或空文件夹,默认不能删除非空文件夹
//判断文件是否存在,存在则删除
@Test
public void m(){
    String filePath = "D:\\JetBrains\\hello2.txt";
    File file = new File(filePath);
    if (file.exists()){
        if (file.delete()){  //删除成功返回true
            System.out.println(filePath + "删除成功");
        } else {
            System.out.println(filePath + "删除失败");
        }
    } else {
        System.out.println("该文件不存在");
    }
}
//判断目录是否存在,存在则删除
@Test
public void m2(){
    String filePath = "D:\\JetBrains\\test";
    File file = new File(filePath);
    if (file.exists()){
        if (file.delete()){
            System.out.println(filePath + "删除成功");
        } else {
            System.out.println(filePath + "删除失败");
        }
    } else {
        System.out.println("该目录不存在...");
    }
}
//判断目录是否存在,不存在就创建该目录
@Test
public void m3(){
    String dirPath = "D:\\JetBrains\\test";
    File file = new File(dirPath);
    if (file.exists()){
        System.out.println("该目录存在");
    } else {
        if (file.mkdirs()) {  //创建多级目录使用mkdirs,创建一级目录使用mkdir
            System.out.println("目录创建成功");
        } else {
            System.out.println("目录创建失败");
        }
    }
}

I/O流原理和分类

流与文件:文件通过输入流读入到程序(内存)中,程序输出的结果通过输出流写入到文件里?

Java学习笔记

image-20220531102006140

字节流

FileInputStream

image-20220530174632645

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。

可以使用字符串类型的文件名来创建一个输入流对象来读取文件:

InputStream f = new FileInputStream("C:/java/hello");

也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello");
InputStream out = new FileInputStream(f);

创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作。

序号 方法及描述
1 public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
2 protected void finalize()throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
3 public int read(int r)throws IOException{} 这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回-1。
4 public int read(byte[] r) throws IOException{} 这个方法从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。
5 public int available() throws IOException{} 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。
@Test
public void readFile02() {
    String filePath = "D:\\JetBrains\\hello.txt";
    int readData = 0;
    int readLen = 0;
    FileInputStream fileInputStream = null;
    //使用read(byte [] b)读取
    byte[] buffer = new byte[8];  //一次读取八个字节  循环读入,超过八个会从头覆盖之前的数据
    try {
        //创建FileInputStream对象,用于读取文件
        fileInputStream = new FileInputStream(filePath);
        //read()读取一个字节的数据,如果没有输入则不读,读到最后返回-1  效率较低
        /*while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char) readData);
            }*/
        //使用read(byte [] b)读取提高效率,一次读取八个字节  返回实际读取的字节数
        //底层维护一个byte型数组的缓冲区
        while ((readLen = fileInputStream.read(buffer)) != -1) {
            System.out.print(new String(buffer,0,readLen));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //这里要关闭流对象,防止其占用资源
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
FileOutputStream

该类用来创建一个文件并向文件中写数据。

如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。

有两个构造方法可以用来创建 FileOutputStream 对象。

使用字符串类型的文件名来创建一个输出流对象:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:

File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);

创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。

序号 方法及描述
1 public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
2 protected void finalize()throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
3 public void write(int w)throws IOException{} 这个方法把指定的字节写到输出流中。
4 public void write(byte[] w) 把指定数组中w.length长度的字节写到OutputStream中。
@Test
public void writeFile(){
    //创建FileOutputStream对象
    FileOutputStream fileOutputStream = null;
    String outputPath = "D:\\JetBrains\\output.txt";
    try {
        //public FileOutputStream(String name, boolean append) 如果append设为true则在上次写后追加而非覆盖
        fileOutputStream = new FileOutputStream(outputPath);
        //得到FileOutputStream对象后可以调用write方法  可以写入一个字节或多个字节
        try {
            String str = "hello123,world!";
            fileOutputStream.write(str.getBytes());
            //继续写的话会从上次写的光标后开始写
            fileOutputStream.write(str.getBytes(),5,3);  //参数依次为byte[]  off  len 
            //最终写入结果为 hello123,world!123  off为5 从索引为5的字符开始写入三个字符长度
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }finally {
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
//例题 拷贝文件(图片)
public class FileCopy {
    public static void main(String[] args) {
        //完成文件拷贝,将F://算法代码截图//1.png拷贝到D://JetBrains//1.png
        //思路分析
        //1.创建文件输入流,将文件读入到程序
        //2.创建文件输出流,将读取到的文件写入指定位置
        //注意读取部分数据就写入,利用循环写入全部
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        String SourcePath = "F://算法代码截图//1.png";
        String destPath = "D://JetBrains//1.png";
        try {
            fileInputStream = new FileInputStream(SourcePath);
            fileOutputStream = new FileOutputStream(destPath);
            //利用字符数组一次读入多个字符
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = fileInputStream.read(buf)) != -1) {
                //读取到后写入到目标文件
                fileOutputStream.write(buf,0,readLen);  //必须使用这个write方法  不能直接传入buf 防止最后写入的字符长度大于文件还剩的字符长度
            }
            System.out.println("copy finished");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

字符流

FileReader

FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。可以通过以下几种构造方法创建需要的对象。

在给定从中读取数据的 File 的情况下创建一个新 FileReader。

FileReader(File file)

在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。

FileReader(FileDescriptor fd)

在给定从中读取数据的文件名的情况下创建一个新 FileReader。

FileReader(String fileName)

创建FIleReader对象成功后,可以参照以下列表里的方法操作文件。

序号 文件描述
1 public int read() throws IOException 读取单个字符,返回一个int型变量代表读取到的字符
2 public int read(char [] c, int offset, int len) 读取字符到c数组,返回读取到字符的个数
public class fileReader {
    public static void main(String[] args) {
        String path = "D://JetBrains//hello.txt";
        FileReader fileReader = null;
        char[] buf = new char[50];
        int readLen = 0;
        int data = 0;
        try {
            //创建FileReader对象
            fileReader = new FileReader(path);
            //读取文件内容
            //1.单个字符读取
            while ((data = fileReader.read()) != -1) {  //把异常改为IOException
                System.out.print((char) data);
            }
            //2.字符数组读取
            while ((readLen = fileReader.read(buf)) != -1) {  //把异常改为IOException
                System.out.print(new String(buf,0,readLen));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            if(fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
FileWriter

写完后必须关闭流或者flush才能真正写入文件,否则只有文件里面没有内容,close相当于flush + 关闭文件

FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。可以通过以下几种构造方法创建需要的对象。

在给出 File 对象的情况下构造一个 FileWriter 对象。

FileWriter(File file)

在给出 File 对象的情况下构造一个 FileWriter 对象。

FileWriter(File file, boolean append)

参数:

FileWriter(FileDescriptor fd)

在给出文件名的情况下构造 FileWriter 对象,它具有指示是否挂起写入数据的 boolean 值。

FileWriter(String fileName, boolean append)

创建FileWriter对象成功后,可以参照以下列表里的方法操作文件。

序号 方法描述
1 public void write(int c) throws IOException 写入单个字符c。
2 public void write(char [] c, int offset, int len) 写入字符数组中开始为offset长度为len的某一部分。
3 public void write(String s, int offset, int len) 写入字符串中开始为offset长度为len的某一部分。
public class fileWriter {
    public static void main(String[] args) {
        String path = "D://JetBrains//fileWriter.txt";
        FileWriter fileWriter = null;
        char[] ch = {'a','b','c'};
        try {
            fileWriter = new FileWriter(path);
            //写入单个字符
            fileWriter.write('a');  //其实write(int b) 但是char和int是可以隐式转换的
            //写入字符数组 默认是覆盖写入
            fileWriter.write(ch);
            //写入指定数组的指定部分
            fileWriter.write("svicen".toCharArray(),0,6);
            //写入字符串
            fileWriter.write("心之所向");
            //写入字符串的指定部分
            fileWriter.write("北京上海",2,2);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                fileWriter.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("程序结束");
    }
}

节点流和处理流

image-20220531090741349

public class BufferedReader extends Reader {
    private Reader in;
    private char cb[];
    private int nChars, nextChar;
}
//BufferedReader封装了一个属性Reader,该属性可以是任何节点流,可以放FileReader或者CharArrayReader或者StringReader等,   只要是Reader的子类即可,使用更加灵活          ----对应设计模式 修饰器模式

?修饰器模式模拟见 package com.svicen.reader_

BufferedReader
public class BufferedReader_ {
    public static void main(String[] args) throws Exception{
        String path = "D://JetBrains//hello.txt";
        //创建bufferedReader
        BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
        //按行读取,性能较高   当返回null时读取完毕
        String line;
        while ((line = bufferedReader.readLine()) != null){
            System.out.println(line);
        }
        //关闭流,只需要关闭BufferedReader流即可,底层会自动关闭节点流
        bufferedReader.close();
    }
}
//close底层源码
public void close() throws IOException {
    synchronized (lock) {
        if (in == null)
            return;
        try {
            in.close(); //这里的in就是我们传入的FileReader
        } finally {
            in = null;
            cb = null;
        }
    }
}
BufferedWriter
public class BufferedWriter_ {
    public static void main(String[] args) throws IOException {
        String path = "D://JetBrains//bufWriter.txt";
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path,true));
        bufferedWriter.write("hello,svicen\n");
        bufferedWriter.write("123456",2,3); //写入456
        //关闭外部流(包装流)即可
        bufferedWriter.close();
    }
}
//拷贝文件示例
public class BufferedCopy_ {
    public static void main(String[] args) throws IOException {
        String srcPath = "D://JetBrains//hello.txt";
        String destPath = "D://JetBrains//hello2.txt";
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        String line;
        bufferedReader = new BufferedReader(new FileReader(srcPath));
        bufferedWriter = new BufferedWriter(new FileWriter(destPath));
        while((line = bufferedReader.readLine()) != null){
            //每读取一行就写入到目标文件 readline读取一行的内容,不读取换行符
            bufferedWriter.write(line);
            //插入换行符
            bufferedWriter.newLine();
        }
        if(bufferedReader != null){
            bufferedReader.close();
        }
        if(bufferedWriter != null){
            bufferedWriter.close();
        }
    }
}

?注意,BufferedReader和BufferedWriter用于处理字符流,不能操作二进制文件(声音,视频,docx,pdf)

BufferedInputStream

image-20220601130052160

BufferedOutputStream

image-20220601130651594

//拷贝图片/视频,字节流用于拷贝二进制文件,也可以用来拷贝字符流文件,但是字符处理流不能控制二进制文件
public class BufferedIOStream_ {
    public static void main(String[] args) throws IOException{
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        String srcPath = "D://JetBrains//1.png";
        String destPath = "D://JetBrains//1_bak.png";
        try {
            //这里是因为FileInputStream是InputStream的子类
            bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath));
            bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destPath));
            //循环读取文件 写入destPath
            byte[] buf = new byte[1024];
            int readLen = 0;
            //读到-1代表读取结束
            while((readLen = bufferedInputStream.read(buf)) != -1){
                bufferedOutputStream.write(buf,0,readLen);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            if (bufferedInputStream != null){
                bufferedInputStream.close();
            }
            if (bufferedOutputStream != null){
                bufferedOutputStream.close();
            }
        }
    }
}

对象处理流

ObjectIputStream

image-20220601182328442

//读取序列化数据
public class ObjectInputStream_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String srcPath = "D://JetBrains//Object.txt";
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(srcPath));
        //读取(反序列化)的顺序必须和保存数据(序列化)的顺序一致,否则会出现异常 先读Int再读Boolean
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());
        Object dog = ois.readObject();
        System.out.println("运行类型:" + dog.getClass());  //会抛出类型转换异常 运行类型为Dog 这里用Object接收
        System.out.println("dog信息" + dog);  //底层Object->Dog
//如果想调用Dog类的方法需要向下转型,而且需要将Dog类的定义放在此类可以引用的地方(单独定义Dog类导入或者放在同一个包内)
        Dog dog2 = (Dog) dog;
        System.out.println(dog2.getName());
        ois.close();
    }
}
ObjectOutputStream

image-20220601182524166

//保存序列化数据
public class ObjectOutputStream_ {
    public static void main(String[] args) throws Exception{
        String filePath = "D://JetBrains//Object.txt";  //保存到txt文件中为乱码
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
        //序列化数据到目标文件
        oos.writeInt(100); // int 底层 调用包装类Integer 而Integer是实现了Serializable接口的
        oos.writeBoolean(true);//boolean -> Boolead 实现了Serializable接口
        oos.writeChar('a'); //char -> Character 实现了Serializable接口
        //oos.writeChars("svicen");//String 实现了Serializable接口  没有与其对应的readChars方法
        oos.writeDouble(3.6); //double -> Double 实现了Serializable接口
        oos.writeUTF("苏文成"); //String 实现了Serializable接口
        oos.writeObject(new Dog("旺财",10));//由于Dog没有实现Serializable接口  运行会抛出异常
        oos.close();
        System.out.println("序列化数据保存完毕");
    }
}
class Dog implements Serializable {  //Dog必须实现Serializable接口  否则运行会抛出异常
    private String name;
    private int age;
   //序列化的类中最好加上serialVersionUID的属性,为了提高兼容性。比如 类中加入属性后会认为是增加而不是一个新的类
    private static final long serialVersionUID = 1L;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

注意事项

标准输入输出流

image-20220601215750615

public static final InputStream in = null; //System.in  可以看出其编译类型为InputStream  标准输入 --> 键盘
//而System.in运行类型为BufferedInputStream --字节流,包装流(处理流)
public static final PrintStream out = null;//System.out 其编译类型为PrintStream          标准输出 --> 显示器
//System.in运行类型也为PrintStream,是FilterOutStream的子类,也是字节流 BufferedOutputStream也是FilterOutStream的子类

image-20220601220330387

转换流

转换流:将字节流转换为字符流。(需要指定编码方式)transformation

InputStreamReader

image-20220602132307421

//利用InputStreamReader解决读取到乱码问题(其实要做的就是指定编码方式)
public class InputStreamReader_ {
    public static void main(String[] args) throws IOException {
        //将字节流 FileInPutStream 转为字符流  指定编码 gbk/utf-8
        String path = "D:\\JetBrains\\hello2.txt";
        //1.将 FileInPutStream 转为 InputStreamReader 并指定编码UTF-8  StandardCharsets是一个类,内有各种编码方式
        InputStreamReader isr = new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8);
        //2.把 InputStreamReader 传入字符处理流 BufferedReader
        BufferedReader bufferedReader = new BufferedReader(isr);
        //3.读取
        String data = bufferedReader.readLine();
        System.out.println("读取到内容" + data);
        //4.关闭外层流即可(处理流)
        isr.close();
    }
}
OutputStreamWriter

image-20220602132557184

public class OutputStreamWriter_ {
    public static void main(String[] args) throws IOException {
        String filePath = "D:\\JetBrains\\swc.txt";
        String charSet = "gbk";
        //利用OutputStreamWriter把字节流FileOutputStream转为了字符流
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath),charSet);
        osw.write("hello,苏文成");
        osw.close();
        System.out.println("按照" + charSet + "保存文件成功");
    }
}

打印流

PrintStream
public static final PrintStream out = null;//System.out 其编译类型为PrintStream
public class PrintStream_ {
    public static void main(String[] args) throws IOException {
        PrintStream out = System.out;
        out.println("1322");
        out.write("hello,svicen".getBytes());
        out.close();
        //修改打印流打印的位置
        /* System类中有一个设置out的方法
        public static void setOut(PrintStream out) {   是一个native方法,就是java调用非java实现的接口,用c++实现的
            checkIO();
            setOut0(out);
        }
        */
        System.setOut(new PrintStream("D:\\JetBrains\\printStream.txt"));
        System.out.println("修改setOut后   System.out.println打印到文件");
    }
}
//println追进去源码,调用print,print又使用了write方法,所以可以直接使用write方法打印
private void write(String s) {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.write(s);
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush && (s.indexOf('\n') >= 0))
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }
PrintWriter

Properties

网络编程

InetAddress

public static void main(String[] args) throws UnknownHostException {
    //1.获取本机的InetAddress对象
    InetAddress localHost = InetAddress.getLocalHost();
    System.out.println(localHost); //LAPTOP-V7FVCK3U/192.168.192.1
    InetAddress host1 = InetAddress.getByName("LAPTOP-V7FVCK3U");
    //2.根据指定主机名获取InetAddress对象
    System.out.println("host1 = " + host1);//host1 = LAPTOP-V7FVCK3U/192.168.192.1
    //3.根据域名返回InetAddress对象,比如www.baidu.com
    InetAddress host2 = InetAddress.getByName("www.baidu.com");
    System.out.println("host2 = " + host2); //host2 = www.baidu.com/36.152.44.95
    //4.通过InetAddress对象 获取对应的主机地址
    String hostAddress = host2.getHostAddress();
    System.out.println("host2地址为 " + hostAddress); //host2地址为 36.152.44.95
    //5.通过InetAddress对象 获取对应的主机名/域名
    String hostName = host2.getHostName();
    System.out.println("host2主机名为 " + hostName); //host2主机名为 www.baidu.com
}

Socket

image-20220607215017189

TCP字节流编程

服务器端:

1.监听本机的 某个 端口

2.当没有客户端连接该端口时,程序会阻塞,等待连接

3.通过socket.getInputStream()获取客户端写入到数据通道的数据,显示

客户端:

1.连接服务端(IP,端口)

2.连接后,通过socket.getOutputStream(),向数据通道写入数据

//服务器端
public class SockcetTCP01Server {
    public static void main(String[] args) throws IOException {
        //1.服务器监听本机9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        //2.当有客户端连接时,则返回一个socket对象,程序继续执行
        System.out.println("服务端监听9999端口,等待连接...");
        //注意:serverSocket可以通过accept方法,返回多个socket对象(多个客户机连接服务器的多并发)
        Socket socket = serverSocket.accept();
        System.out.println("成功获取socket对象,服务器socket = " + socket);
        //3.通过socket.getInputStream()读取客户端写入到数据通道的数据
        InputStream inputStream = socket.getInputStream();
        //4.IO读取 字节流
        byte[] buf = new byte[1024];
        int readlen = 0;
        while((readlen = inputStream.read(buf)) != -1){
            System.out.println(new String(buf,0,readlen)); //根据读取到的实际长度显示数据
            //public String(byte bytes[], int offset, int length)
        }
        //5.关闭流和socket
        inputStream.close();
        socket.close();
        System.out.println("服务器端退出");
    }
}
//客户端
public class SockcetTCP01Client {
    public static void main(String[] args) throws IOException {
        //1.连接服务器(ip + 端口)  由于服务器,客户机都是本机  连接成功的话返回socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(),9999);
        System.out.println("客户端  socket = " + socket);
        //2.连接后,通过socket.getOutputStream() 获取和socket对象相关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //3.通过输出流对象,向数据通道写入数据
        outputStream.write("hello,server".getBytes(StandardCharsets.UTF_8));
        //4.关闭socket和流对象,必须关闭,否则占用资源
        outputStream.close();
        socket.close();
        System.out.println("客户端断开连接");
    }
}
//例题:客户端向服务器端发送图片,客户端需要IO读磁盘,然后在写入数据通道,服务端需要读数据通道,然后IO写磁盘
//工具类
public class StreamUtils {
    //功能:把输入流转换成byte[]
    public static byte[] streamToByteArray(InputStream is) throws IOException {
        //创建输出流对象
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int len;
        while((len = is.read(bytes)) != -1) {
            baos.write(bytes,0,len);
        }
        byte[] arr = baos.toByteArray(); //将输出流读入的内容转为byte数组
        baos.close();
        return arr;
    }
    //功能:将InputStream转换成String
    public static String streamToString(InputStream is) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
        //字符串处理流
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while((line = bufferedReader.readLine()) != null) {
            stringBuilder.append(line + "\r\n");
        }
        return stringBuilder.toString();
    }
}
public class TCPFileUploadClient {
    public static void main(String[] args) throws IOException {
        //客户端向服务器端上传一张图片,并接收服务器端回复的 消息
        //客户端连接服务器端
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        //从磁盘上读取文件 ,利用字节流
        String path = "D:\\JetBrains\\1.png";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
        //bytes就是这个文件对应的字节数组   BufferedInputStream是InputStream的子类,所以
        byte[] bytes = StreamUtils.streamToByteArray(bis);
        //获取输出流,将bytes数据发送到数据通道
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(bytes);
        //设置结束标志
        socket.shutdownOutput();
        //获取输入流,读取服务器回送消息 可字节流,也可字符流,这里用了字节流
        InputStream inputStream = socket.getInputStream();
        //使用StreamUtils工具类的方法将输入流转为字符串
        String s = StreamUtils.streamToString(inputStream);
        System.out.println("收到服务器端回送消息: " + s);
        //关闭流
        bis.close();
        bos.close();
        socket.close();
    }
}
public class TCPFileUploadServer {
    public static void main(String[] args) throws IOException {
        //服务器端在本机监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器端监听8888端口 ");
        //等待客户端连接 ,连接成功后返回一个socket对象
        Socket socket = serverSocket.accept();
        //读取数据通道的内容 先创建输入流对象,然后再读取
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = StreamUtils.streamToByteArray(bis);
        //将得到的bytes数组写入指定路径
        String destPath = "src\\code.png";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath));
        bos.write(bytes);
        bos.close();
        //服务器端向客户端回送消息, 说明收到照片 ,这次用字符流(利用转换流将字节流转换为字符流)
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("收到图片");
        //需要刷新才可写入到数据通道
        bw.flush();
        socket.shutdownOutput();
        //关闭其它资源
        bis.close();
        socket.close();
        serverSocket.close();
    }
}

UDP字节流编程

类DatagramSocket和DatagramPacket(数据包/数据报)实现了基于UDP的协议网络程序

UDP数据报通过数据报套接字DatagramSocket 发送和接收,发送数据前先要建立数据报DatagramPacket对象,系统不保证UDP数据报一定可以安全送到目的地,也不确定什么时候送达

DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号

UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接。

UDP中没有客户端和服务器端的说法,只区分发送端和接收端

image-20220609095833873

//接收端和发送端是相对的,发送端也可以接收数据  两方进行udp通信,只需创建一个datagramSocket对象,可能创建多个packet对象
public class UDPSenderB {
    public static void main(String[] args) throws IOException {
        //1.创建 DatagramSocket 对象  端口最好不与接收端的一样,是发送端要接收数据的端口(不需要连接到接收端)
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        //2.创建一个字节数组, DatagramPacket 对象,需要知道目的主机IP,目的主机端口,用于在网络上传输数据
        byte[] bytes = "hello udp".getBytes(StandardCharsets.UTF_8);  //要发送的数据
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.192.1"),8888);
        //3.发送数据
        datagramSocket.send(datagramPacket);
        //4.接收数据
        byte[] data = new byte[64 * 1024];
        //发送和接收时datagramSocket是一样的,但是发送时datagramPacket需指明目标IP和端口
        DatagramPacket datagramPacket1 = new DatagramPacket(data, data.length);
        System.out.println("等待接受A回送的消息");
        datagramSocket.receive(datagramPacket1);
        int length = datagramPacket1.getLength();
        byte[] data1 = datagramPacket1.getData();
        String s = new String(data1, 0, length);
        System.out.println(s);
        //5.关闭资源
        datagramSocket.close();
        System.out.println("B端退出");
    }
}
public class UDPReceiverA {
    public static void main(String[] args) throws IOException {
        //1.创建一个 DatagramSocket 对象,端口8888,准备接收数据
        DatagramSocket datagramSocket = new DatagramSocket(8888);
        //2.创建一个 DatagramPacket 对象,传入一个byte数组,准备接收数据
        byte[] buf = new byte[64 * 1024];  //UDP数据包最大 64k
        DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
        //3.准备接收数据,调用 datagramSocket 的方法,将通过网络传输的 datagramPacket 填充到 datagramSocket中
        // 有数据包发送给本机的8888端口就会接收到数据,否则阻塞等待
        System.out.println("接收端A,等待接收数据");
        datagramSocket.receive(datagramPacket);
        //4.把 datagramPacket 进行拆包,取出数据并显示
        int length = datagramPacket.getLength();  //实际接收到的数据字节长度
        byte[] data = datagramPacket.getData();
        String s = new String(data, 0, length);
        //5.发送数据,这里实现接收端向发送端发送数据(发送端和接收端都是相对的)
        byte[] sendData = "hello,see you later".getBytes(StandardCharsets.UTF_8);
        //发送数据时 DatagramPacket 的构造器有四个参数,要发送的数据的字节数组,长度,目标IP和端口
        DatagramPacket datagramPacket1 = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("192.168.192.1"),9999);
        datagramSocket.send(datagramPacket1);
        System.out.println(s);
        //6.关闭资源
        datagramSocket.close();
        System.out.println("A端退出");
    }
}
//TCP编程作业
public class Homework01Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        //获取输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //使用字符流处理流写入数据通道
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        //从键盘读取用户输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你的问题");
        String question = scanner.next();
        //将问题写入数据通道
        bufferedWriter.write(question);
        //设置读入结束符  这里要求读取的时候必须使用 readLine()
        bufferedWriter.newLine();
        bufferedWriter.flush();  //字符流写入,需要刷新
        InputStream inputStream = socket.getInputStream();
        //直接利用字节流读入
        int readLen = 0;
        byte[] buf = new byte[1024];
        while((readLen = inputStream.read(buf)) != -1){
            //显示从服务器端读取的数据
            System.out.println(new String(buf,0,readLen));
        }
        /*BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        System.out.println(s);*/         //也可以这样读入  利用转换流将字节流转为字符流
        //bufferedReader.close();
        inputStream.close();
        outputStream.close();
        socket.close();
    }
}
public class Homework01Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端等待连接");
        Socket socket = serverSocket.accept();
        //连接后成功获取到socket对象
        //获取写入数据通道的输入流
        InputStream inputStream = socket.getInputStream();
        // 读取数据,使用字符流,利用转换流将字节流转换为字符流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        String answer;
        if("name".equals(s)) {
            answer = "svicen";
        } else if ("hobby".equals(s)) {
            answer = "programing";
        }else {
            answer = "what?";
        }
        //向数据通道写入数据  这次以字节流的形式
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(answer.getBytes(StandardCharsets.UTF_8));
        socket.shutdownOutput();  //设置结束标志
        /*BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write(answer);
        bufferedWriter.newLine();
        bufferedWriter.flush();*/  // 利用转换流将字节流转换为字符流再写入,注意字符流写入需要flush
        //关闭流
        outputStream.close();
        //bufferedWriter.close();
        bufferedReader.close();
        socket.close();
    }
}

反射

应用场景:学习框架时用到的较多

通过外部配置文件,在不修改源码的情况下来控制程序,符合设计模式的ocp原则(开闭原则)

反射其实就是 new 的第二种和形态

package com.svicen.reflection;
import com.svicen.Cat;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
 * @author svicen
 * @version 1.0
 */
public class ReflectionQuestion {
    // 使用发射类机制 解决通过配置文件加载类
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 1.使用 Properties 类可以读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        System.out.println("classfullpath = " + classfullpath);
        System.out.println("method = " + methodName);
        // 但是此时的 classfullpath 是一个字符串类型 无法直接创建 Cat对象
        // 2.使用反射机制解决上述问题  可以不修改源码只修改配置文件实现功能的切换
        // (1) 加载类,返回一个 Class 对象,这个类名就叫 Class
        Class<?> cls = Class.forName(classfullpath);
        // (2) 通过 cls 可以得到加载的类的实例对象
        Object o = cls.newInstance();
        System.out.println(o.getClass()); // class com.svicen.Cat
        // (3) 通过 cls 得到加载的类 com.svicen.Cat 的 methodName 的方法对象 "hi"
        Method method1 = cls.getMethod(methodName);
        // (4) 通过 method1 调用方法:即通过方法对象实现调用方法  -- 万物皆对象
        System.out.println("=======反射======");
        method1.invoke(o);  // 反射机制: 方法.invoke(对象)
// 通过 Field 对象获取某个类的成员变量
        Field nameField = cls.getField("name");  // getField 不能得到私有的属性
        System.out.println(nameField.get(o));    //输出:招财猫   反射的 括号里的参数 一般都是对象
    }
}

Java学习笔记

反射的优点和缺点:

优点:可以动态的创建和使用对象(是框架的底层核心),使用灵活

缺点:使用反射机制基本是解释执行,会降低执行速度,可以关闭反射调用方法时的访问检测来优化反射,但效果有限

Class类

Java学习笔记

Java程序的执行阶段

Java学习笔记

类加载过程

Java学习笔记Java学习笔记

Java学习笔记

静态加载

在编译时进行类的加载,依赖性较强。找不到类编译时就会报错

import java.lang.reflect.Method;
import java.util.Scanner;
public class ClassLoad_ {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入key");
        String key = scanner.next();
        switch (key) {
            case "1":
                //Dog dog = new Dog();  // 静态加载类  依赖性较强
                //dog.cry();
                break;
            case "2":
                // 动态加载类  使用时再加载 用到的类
                Class cls = Class.forName("Person");
                Object o = cls.newInstance();
                Method m = cls.getMethod("hi");
                m.invoke(o);
                System.out.println("ok");
            default:
                System.out.println("nothing!");
        }
    }
}
class Dog {
    public void cry() {
        System.out.println("小狗汪汪叫");
    }
}
动态加载

执行到具体的代码时才会加载指定类

加载阶段
连接阶段
验证
准备
解析
初始化

获取类结构信息的API

package com.svicen.reflection;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
 * @author svicen
 * @version 1.0
 * 演示如何通过反射获取类的结构信息
 */
public class ReflectionUtils {
    public static void main(String[] args) {
    }
    // 第一组 API
    @Test
    public void api_01() throws ClassNotFoundException {
        // 获取 Class 对象
        Class<?> personCls = Class.forName("com.svicen.reflection.Person");
        // getName 获取全类名
        System.out.println(personCls.getName());  // com.svicen.reflection.Person
        // getSimpleName 获取简单类名
        System.out.println(personCls.getSimpleName()); //Person
        // getFields 获取所有public修饰的属性,包括本类以及父类
        Field[] fields = personCls.getFields();
        for(Field filed : fields){
            System.out.println("本类及父类的public属性:" + filed.getName()); // name  hobby
        }
        // getDeclaredFields 获取本类中所有属性R
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("本类中所有属性:" + declaredField.getName());
        }
        // getMethods 获取本类及父类(超类)中的public修饰的方法
        Method[] methods = personCls.getMethods();
        for (Method method : methods) {
            System.out.println("本类及父类的public方法:" + method.getName()); // 还有超类的方法
        }
        // getDeclaredMethods 获取本类所有方法
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类中所有方法:" + declaredMethod.getName());
        }
        // getConstructors 获取本类所有public修饰的构造器  没有父类
        Constructor<?>[] constructors = personCls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("本类所有public修饰的构造器:" + constructor.getName());
        }
        // getDeclaredConstructors 获取本类中所有构造器
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类中所有构造器" + declaredConstructor.getName());
        }
        // getPackage 以 Package 形式返回包信息
        Package aPackage = personCls.getPackage();
        System.out.println(aPackage);
        // getSuperclass 以Class 形式返回父类信息
        Class<?> superclass = personCls.getSuperclass();
        System.out.println("父类类名:" + superclass.getSimpleName()); // A
        // getInterfaces 以 Class[]形式返回接口信息
        Class<?>[] interfaces = personCls.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("接口信息:" + anInterface.getName());
        }
        // getAnnotations 以 Annotation[] 的形式返回注解信息
        Annotation[] annotations = personCls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解信息:" + annotation);
        }
    }
}
class  A{
    public String hobby;
    public A(){}
    public void hi(){}
    private void hello(){}
}
interface IA{
}
interface IB{
}
@Deprecated
class Person extends A implements IA, IB{
    // 属性
    public String name;
    protected int age;
    String job;
    private double sal;
    // 构造器
    public Person(){}
    public Person(String name){}
    private Person(String name, int age){}
    // 方法
    public void m1(){}
    protected void m2(){}
    void m3(){}
    private void m4(){}
}
// 第二组API
    @Test
    public void api_02() throws ClassNotFoundException {
        // 获取 Class 对象
        Class<?> personCls = Class.forName("com.svicen.reflection.Person");
        // getDeclaredFields 获取本类中所有属性
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredField  declaredFields) {
            System.out.println("本类中所有属性:" + declaredField.getName() +
                    "  该属性的修饰符值为:" + declaredField.getModifiers() +
                    "  该属性的类型为:" + declaredField.getType()); // 返回属性对应的类的 Class 对象
        // 修饰符对应的值: 0位默认,1为public,2为private,4为protected,8为static,16为final
        }
        // getDeclaredMethods 获取本类所有方法
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类中所有方法:" + declaredMethod.getName() +
                    "  该方法的访问修饰符的值:" + declaredMethod.getModifiers() +
                    "  该方法返回类型:" + declaredMethod.getReturnType());  // 返回返回类型对应的Class对象
            // 输出方法的形参类型
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该方法的形参类型:" + parameterType);
            }
        }
        // getDeclaredConstructors 获取本类中所有构造器
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类中所有构造器" + declaredConstructor.getName());
            // 获取构造器的形参类型
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该构造器的形参有:" + parameterType);
            }
            System.out.println("============================");
        }
    }    

反射爆破创建实例

public class ReflecCreateInstance {
    public static void main(String[] args) throws Exception {
        //1.先获取到User类的Class对象
        Class<?> userClass = Class.forName("com.svicen.reflection.User");
        //2.通过public的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);
        //3.通过public的有参构造器创建实例
        // 首先获取有参构造器
        Constructor<?> constructor = userClass.getConstructor(String.class);
        Object swc = constructor.newInstance("苏文成");//体现反射 方法(对象)
        System.out.println(swc);
        //4.通过非public的有参构造器创建实例
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
        // 爆破:使用反射可以利用私有的构造器构造实例
        declaredConstructor.setAccessible(true);  // 设置爆破,否则对于非public的构造器创建实例会报错
        Object swc1 = declaredConstructor.newInstance(21, "swc"); // 由于构造器是私有的  会报错
        System.out.println(swc1);
    }
}
class User{
    private int age = 20;
    private String name = "svicen";
    public User(){}
    public User(String name) {
        this.name = name;
    }
    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

反射爆破操作属性

public class ReflecAccessProperty {
    public static void main(String[] args) throws Exception{
        //1. 获取Student类对应的Class对象
        Class<?> stuClass = Class.forName("com.svicen.reflection.Student");
        //2. 利用无参构造器创建对象
        Object o = stuClass.newInstance();
        System.out.println(o);
        //3. 使用反射得到 age 属性对象
        Field age = stuClass.getField("age");
        age.set(o,22);  //通过反射对指定对象设置属性值
        System.out.println(o);
        System.out.println(age.get(o));  // age.get() 括号内传入对象
        //4. 使用反射操作 私有的、静态的 name对象
        Field name = stuClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(null, "svicen");
        System.out.println(o);
        System.out.println(name.get(null));
    }
}
class Student{
    public int age;
    private static String name;
    public Student(){}
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name=" + name + '}';
    }
}

反射爆破操作方法

public class ReflecAccessMethod {
    public static void main(String[] args) throws Exception {
        // 1.得到 Boss 类对应的Class对象
        Class<?> bossCls = Class.forName("com.svicen.reflection.Boss");
        // 2.创建一个 对象
        Object o = bossCls.newInstance();
        // 3.得到public的hi方法对象  注意需要指明参数的类型对应的class对象
        Method hi = bossCls.getMethod("hi", String.class);
        // 4.通过反射调用该方法
        hi.invoke(o, "svicen");
        // 5.private且static的say方法对象
        Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
        // 6.通过反射调用方法  注意invoke的第一个参数可以为null,因为是静态方法
        say.setAccessible(true);  // 因为方法时私有的,所以需要爆破
        System.out.println(say.invoke(null,22,"svicen",'男'));
        // 如果调用的方法有返回值,对于反射而言,返回的类型一定为 Object
        // 但是运行类型和方法中定义的方法类型一致
        Object returnVal = say.invoke(null, 33, "sv", '女');
        System.out.println(returnVal.getClass());
    }
}
class Boss{
    public int age;
    private static String name;
    public Boss(){}
    private static String say(int n, String s, char c) {
        return n + " " + s + " " + c;
    }
    public void hi(String s){
        System.out.println("hi " + s);
    }
}

发表回复