Java学习笔记

本笔记跟随该课程学习,持续更新中。

Java的作用

用户->界面->接收数据,调用数据->数据库

  1. 首先学习JAVA语言
    1. 基础语法
    2. 面向对象
    3. 常用类
    4. 集合、线程、网络
  2. Java web学习
    1. Mysql
      1. 配置
      2. 安装
      3. SQL
      4. 数据库设计
    2. 数据库编程
      1. JDBC
      2. 工厂模式
      3. DAO/DTO
      4. 数据库连接池
  3. 网页设计
    1. HTML
    2. CSS
    3. JAVASCRIPT
    4. 框架
    5. layui/bootstrap/jQuery
  4. 交互
    1. servlet
      1. 创建与配置。。。
      2. 待补充
    2. JSP(Javaserverpage)--基于服务器运行
      1. 服务器与HTTP协议
  5. 项目实战 还需要补充以及更新

第一部分 JAVA学习

名词解释

  • JVM
    虚拟机,模逆运行环境
  • JRE 不做开发,只运行,JRE足够 运行环境,包含JVM和解释器,完整的运行环境
  • JDK 需要进行开发活动 包含JRE+类库+开发工具包(编译器+调试工具)

安装

安装JDK,官网下载的JDK20(老实的用JDK8u202版本)。然后配置环境变量。

配置环境变量

  1. 编辑系统环境变量,
  2. 新建系统环境变量
  3. 新建%JAVA_HOME%变量,路径为java根目录
  4. 编辑系统变量中的path,新建路径%JAVA_HOME%\bin
  5. 打开cmd,使用java -version和javac命令检测是否成功。

开始java

java的文件的演变过程:.java -->.class-->解释器运行

helloworld

class helloworld {
	public static void main(String[] args){
		System.out.print("HEllo world!");
	}
}
  • helloworld类,作为容器
  • main为方法,即C语言中的main函数入口。

CMD中调用javac指令编译java文件,生成**class(字节码)**文件。

两个文件

如果想要直接运行First.java,直接在cmd中调用java指令,然后调用名,不是文件名!

关于报错以及代码规范

在最开始的时候,我的文件名为First.java,类用public声明为helloworld,编译器报错

  • 规范: 在声明类的时候,如果用public声明类,那么文件名必须跟public声明的类名一样。同理,在一个类文件中声明了一个public类后,其余的类不允许声明为public。

  • 规范: java对大小写敏感,注意大小写以及中英文符号。

  • 规范: 类名首字母最好大写。

注释

与C语言一样,使用//,/* /,以及用于整个文档的文档注释/* */。使用中文进行注释的时候必须采用编码,否则为乱码。

-encoding utf-8

使用javadoc能够直接生成文档的注释(仅限于文档注释),加入-d 指令可以指定文件夹生成注释文件(.html)。

变量

用法同c语言一样。

命名规则:

  • 可以由数字、字母、_、货币符号组成,数字不能作为开头(同C)
  • 不能与关键字与保留字重名
  • 不能与字面常量重名,比如true、false、null。
  • 驼峰命名
    • 类用首字母大写
    • 变量名用驼峰,首个单词开头小写,第二个开头大写,以此类推。

数据类型

java中有两种数据类型,即基本数据类型引用数据类型

基本数据类型

  1. 整数
    大小 整数类型
    1字节 byte
    2字节 short
    4字节 int
    8字节 long
  2. 浮点数(java默认浮点数为double类型,若要用float,在小数后面加f)
    大小 浮点数类型 精度
    4字节 float 单精度
    8字节 double 双精度
  3. boolean 4字节 真与假

与C不同的是,java的bool类型不能参与算术运算

  1. 字符

    主要关注0、A和a即可。 char类型,占用2字节,采用Unicode编码。赋值同C,如

    char = 'A'; char = 48; char ='\u0041';
    

引用数据类型

  1. 字符串String

    String str1="hello";
    
  2. 数组

    待补充

CMD交互

像是C语言的scanf一样,java中需要导入相关的类,即java.util.Scanner

该类中包含以下方法:

  • .nextInt();//获得整数
  • .nextDouble();//获得小数
  • .next();//获得字符串
  • .next().charAt(0);//获得单个字符,即字符串中的第0个位置的字符
import java.util.Scanner;
public class variable{
	public static void main(String[] args){
		Scanner input=new Scanner(System.in);
		System.out.println("Input ID:");
		String name= input.next();
		System.out.println("Your Id is "+name);
	}
}

注意与C不同的地方,在引入Scanner类后,必须先要声明一个新的Scanner,input为这个新Scanner的名字,我们调用的方法是Scanner里的.next()方法,即input这个容器的.next()方法。输入的类型一定要于调用的方法匹配。

这就是上述代码的结果。

PS:本节开始体现出与C的区别,调用类必须要给ta个容器,如input这里。

为了测试中文的输入输出,我编写了以下代码:

import java.util.Scanner;
public class trans2Unicode{
	public static void main(String[] args){
		System.out.println("Input a character输入单字符: ");
		Scanner input=new Scanner(System.in);
		char t1 = '中';
		String t2 = input.next();
		System.out.println((int)t1);	
		System.out.println(t2);
	}	
}

运行后,发现t1为正常的,而当我是用.next().charAt(0)获取t1的值时,输出变为?。之后是用字符串再次对中文的输出作了测试,发觉还是无法正常输出,经过搜索,发现要给System.in加一个构造参数,"GBK"。这样输出就是正常的了,我还测试了"UTF-8",输出依然异常。

import java.util.Scanner;
public class trans2Unicode{
	public static void main(String[] args){
		System.out.println("Input a character输入单字符: ");
		Scanner input=new Scanner(System.in,"GBK");
		char t1 = '中';
		String t2 = input.next();
		System.out.println((int)t1);	
		System.out.println(t2);
	}	
}

这是GBK编码。 这是utf-8编码。

怀疑是java版本问题,在更换为jdk 8u202之后,即使没有给System.in加入编码作为构造参数,也能够正常输出了,下列代码为读取输入的第一个单字符并输出该字符的unicode码。

import java.util.Scanner;
public class test2unicode{
		public static void main(String[] args){
		System.out.println("输入:");
		Scanner input= new Scanner(System.in);
		char _id=input.next().charAt(0);
		System.out.println(_id+"的编码为:"+(int)_id);
		}
}

效果如图所示:成功输出编码以及原本字符。

判断

同C语言语法一样。else if情况下,必须有一个最终的else语句,否则会出问题。

循环

同C语言语法一样。但是在switch循环中,多了一个defaul选项,意味着输入均不存在case中时的默认操作。

经典循环测试用例“打印三角形”。

public class Main {
    public static void main(String[] args) {
        for(int i=1;i<6;i++){
            for(int j=1;j<=i;j++)
                System.out.print("*");
            System.out.println("");
        }
        }
}

输出如图所示。

PS:已经不再使用CMD进行源文件编译,使用IDEA编译,相关配置与该教程相近

学到这里有一些问题记录一下:

1. 对于java来说,好像每个类都可以有一个main函数,这与C语言有着很大的不同,经过查询,貌似只会调用主类的main作为入口。 2. 包的概念貌似只是用来方便管理。 3. 包的名称必须用域名的倒序来书写。 4. 类的概念较为抽象,一系列的运算可以以类为单位

测试小项目:输入1900年之后的某一年某一月,输出该月的月历,要对应星期的某一天:

public class calendarTest {
    public static void main(String[] args){
        //输入日期
        Scanner inputDate = new Scanner(System.in);
        System.out.println("请输入年份(1900年之后):");
        int year=inputDate.nextInt();
        System.out.println("请输入月份: ");
        int month=inputDate.nextInt();
        //判断年月的合法性-
        //计算年份月份的对应
        int daysum=0;
        //平年闰年
        for(int i=1900;i<year;i++){
            if (i%4==0 && i%100!=0 || i%400 ==0)
                daysum+=366;
            else daysum+=365;
        }
        for(int i = 1 ;i<month;i++){
            if(i==1 || i==3 || i==5 || i==7 || i==8 || i==10 || i==12)
                daysum+=31;
            else if(i==4||i==6||i==9||i==11)
                daysum+=30;
            else if(i==2){
                if(year%4==0 && year%100!=0 || year%400 ==0)
                    daysum+=29;
                    else daysum+=28;
                }
            else daysum+=28;
        }
        int weekDate = 1+daysum%7;
        if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12)
            daysum=31;
        else if(month==4||month==6||month==9||month==11)
            daysum=30;
        else if(month==2){
            if(year%4==0 && year%100!=0 || year%400 ==0)
                daysum=29;
            else daysum=28;
        }
        month=1;
        while (daysum>0){
            System.out.print(month+"号为");
            switch (weekDate)
            {
                case 1:System.out.print("星期一\n");break;
                case 2:System.out.print("星期二\n");break;
                case 3:System.out.print("星期三\n");break;
                case 4:System.out.print("星期四\n");break;
                case 5:System.out.print("星期五\n");break;
                case 6:System.out.print("星期六\n");break;
                case 0:System.out.print("星期天\n");break;
            }
            weekDate=(weekDate+1)%7;
            month++;
            daysum--;
        }
    }
}

输入2023年8月份,最终结果如图所示:

为了方便观看,采用常用的日历形式输出,上文代码中输出部分改写为以下形式:

month=1;
System.out.print("一\t二\t三\t四\t五\t六\t日\n");
for(int i=weekDate-1;i>0;i--)
   System.out.print("\t");
while (daysum>0){
   System.out.print(month+"\t");
   weekDate=weekDate%7;
   if (weekDate==0)
      System.out.print("\n");
   weekDate++;
   month++;
   daysum--;
}

输出样式如图:

方法

方法就是C中的函数,面向过程叫函数,面向对象叫方法。

public static void name(){
}

void返回值类型,同C一样。方法的定义要在类中,与main方法平级,这里与C不同。实参与形参同C。

数组

声明方法与C不同,java中数组声明方法同下:

int[] a=new int[5];
//或者
int[] a;
a=new int[5];

还是将作为一个容器看待,a是一个int[]类型的新容器。

//带有初始值的,只能在声明的时候初始化,上述第二种声明方法无法初始化。
int[] a ={1,2,3,4,5};
//or
int[] a=new int[]{1,2,3,4,5};

java中的数组有个.length属性,可以直接输出长度。

数组扩容

扩容思路1:

  1. 创建一个大于原数组长度的数组
  2. 旧数组复制到新数组

扩容思路2:(不止数字类型,任何的类的数组都可)

  1. System.arraycopy(原数组,原数组起始,新数组,新数组起始);
  2. java.util.Arrays.copyOf(原数组,新长度);//返回带有原值的新数组 注意上述的返回值描述,必须要用个地址接收,如下:
     int[] a={1,2,3,4,5};
         a=Arrays.copyOf(a,a.length*2);
         System.out.println(Arrays.toString(a));
    
    如果没有接收,如下:
     int[] a={1,2,3,4,5};
        Arrays.copyOf(a,a.length*2);
        System.out.println(Arrays.toString(a));
    
    

两者的输出对比: 数组打印方法:Arrays.toString(数组)。数组名字为地址。传参数时,不修改地址的情况下,对数组的修改会影响原本的数组,如果涉及到修改地址,那么就不会影响原本数组。

可变长参数

可接收多个同类型实参,个数不限,但是要在最后的位置且只能有一个可变参数。 比如:

add ( int ... nums){
...
}

此时nums被认为是个数组,长度灵活具有length等属性。

排序

冒泡、选择...逻辑一样。冒泡例子:

  public static int[] bubbleSort(int[] nums){
        int temp;
        for (int i=0;i<nums.length-1;i++) {
            for (int j = 0; j < nums.length - i - 1; j++) {
                if (nums[j] > nums[j + 1]) {
                    temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                }
            }
        }
        return nums;
    }

java中自带排序方法,Arrays.sort()(升序)。 计数排序很有意思。

查找

  • 顺序查找
  • 二分查找,Arrays.binarySearch(nums,num)自带的二分查找

二维数组

同C,但是注意在声明的时候,高维长度必须声明,低维长度可以省略,表示java中二维数组不一定是一个方形。 杨辉三角作为例子:(待补充)

面向对象

三大特征——封装、继承和多态。 一切都为对象,对象有特征行为静态属性(特征)动态行为(方法)。 也就是说可以将一系列的有相同属性方法的对象封装成一个类来操作,这跟C有很大不同,对于C来说只有函数概念,所以面向的是过程,我们无法对一个函数赋予它属性来调用。

类的概念

“类”就是模板,蓝图,比如一辆汽车,它有着一个设计图,设计图就是类。 而“对象”就是根据这张蓝图new出来的一辆具体的汽车,也就是前文中的“容器”。

public class Hero {
    //属性
    String name;
    String position;
     int attack;
    int prevent;
    //行为
    public void eat(){
        System.out.println(name+"在吃药");
    }
    public void move(){
        System.out.println(name+"在赶路");
    }
}

上述代码为声明变量,使用方法同int之类的类。注意,在调用时如果不赋值,那么int类型默认为0,string默认为NULL。同时,如果在类中的方法里给出了默认值,那么局部变量优先。如果在声明了局部变量值之后想要在调用时赋值,那么可以给变量加入this关键字来声明。

成员

方法重载

一个方法可以执行多个方法,比如吃东西,吃不同的东西不用重复去写不同的方法。方法重载的方法就是通过传不同的参数来实现。 参数的要求:类型不同,个数不同,顺序不同。与返回值类型、访问修饰符无关!!

public void eat (int a){
}
public void eat (string a){
}

构造方法

  • 用于创建对象的方法。
  • 没有返回值类型
  • 名称与类名相同
  • 只能通过new调用,不能用.**来调用

如果没有写构造方法,那么就会使用无参构造方法。 this关键字是类中的默认引用。 除去这类使用方法,this还可以用来调用本类中的其他构造方法。注意,在构造方法中的this跟方法中的this指向不同。

public eat(int a,int b){
}
public eat(int a){
this(a,b)//必须作为第一行语句执行
}

封装

尽可能隐藏内部实现细节,控制对象的修改和访问权限。 使用访问修饰符:private 使用方法:在类之内使用内部方法来赋值,写set、get方法。

setAge(int age){
this.age=age;
}
getAge(){
return this.age;
}

封装的好处还有方便修改,比如修改了类中的变量名,没有封装的话要全部修改,有封装的话只需要修改类中的变量名就可以。

继承

类与类之间特征和行为的赠予与获得。 父类的抽取: 进行共性抽取,定义父类。 比如:

    1. 属性:名
    2. 属性:健康值
    3. 属性:品种
    4. 方法:跑
    5. 方法:吃
  1. 企鹅
    1. 属性:名
    2. 属性:健康值
    3. 属性:性别
    4. 方法:游
    5. 方法:吃
    1. 属性:名
    2. 属性:健康值
    3. 属性:亲密度
    4. 属性:品种
    5. 方法:飞
    6. 方法:吃

根据这几个动物的特性,抽取出的父类:

  1. 属性:名
  2. 属性:健康值
  3. 方法:吃

想象一个场景,有几千种动物需要建立类,挨个建立效率很低,采用继承方法能够提取父类,子类中的细节各不相同,减少了工作量。

使用方法

提取父类,在子类中删除共有特征和方法。

public class *** extends *** {
...
}//在类声明加入 extends + 父类名称

在经过测试以及网络搜索后,发现构造函数不可以继承,只能在子类通过super()调用。

继承的特点

继承可以多级继承,只能有一个直接父类,但是间接可以继承父类继承的父类。C++允许多个父类继承。 也就是说在java中,一个程序员和一个音乐家都是人,如果程序员想要同时作为音乐家不能够直接继承,只能够通过其他方法实现。

属性的继承

  1. 父类所有属性都可以被继承
  2. 父类的私有属性能被继承但不能访问
  3. 如果子类定义了与父类相同的变量名,优先从最底层(子类)开始找。同名属性可以通过super关键字进行区分 (在实际测试中我发现父类的同名变量会优先使用,跟课程中不一样)
  4. 当子类的成员变量与父类同名时,若对该成员变量进行操作的方法继承于父类,则改变和获取的是父类的成员变量,父类属性不可被重写,只会被调用,父类方法可以被重写,也可以被调用
class 父类(){
int b;
}
class 子类 extends(){
int b;
super.b=3;
this.b=2;
}

访问修饰符

private-本类中访问 public-项目中的任何地方都可以访问 default-作用于本类中或者同包中 protected-本类、本包、其他包中的子类

方法的重写/覆盖

  • 重写/覆盖
    • 当父类的方法不能满足子类需求时,需要方法重写
    • 访问权限可以放大,比如父类方法为protected,子类重写方法为public
    • 返回值类型可以跟父类一致,也可以是父类返回类型的子类
    • 方法名一定要一样
    • 传参要一样,否则为方法重载了

##继承中的对象创建 在具有继承关系的对象的创建中,构建子类对象会优先调用父类构造。 但是并没有构造父类的空间,而是在子类对象的属性空间里。

多态

就是在前文中讲到的一个问题,一个程序员同时是音乐家,同时是个艺术家的解决方法。 概念:父类引用指向子类对象,从而产生多种形态。

Pet pet=new Dog();

向上转型

Pet与Dog必须具有直接或间接的继承关系。但是只能调用父类中的公用方法,执行子类的方法——重写作为基础。

多态的应用

  1. 父类类型作为形参
  2. 父类类型作为返回值实现多态

向下转型

调用子类独有方法。 一个对象在调用方法时,能调到哪些方法取决于对象的类型。 执行方法的时候,执行哪个方法取决于实际类型。 解决方法:强制类型转换

Pet pet = new Dog();
Dog dog = (Dog)pet;

报错判断辅助语句:A instanceof B

判断A是否为B类。

抽象类abstract

不应该被创建的抽象的类,关键词abstract,无法被实例化。 比如动物和狗,动物是抽象的笼统的,狗是具体的,我们创建一只狗作为对象而不是一只动物。 抽象类作为父类提供固有的属性与方法,作为参数实现多态。抽象类也是有构造方法的,但是自己不能用,给子类调用。

抽象方法

与抽象类类似,有些方法也不应该被实现,比如“吃”,不同的动物吃不同的东西。

//抽象方法只需要声明,不需要实现
public abstract void eat();

抽象方法能放到抽象类和接口中,抽象方法所在的类一定是抽象类。

静态static

静态的东西只有一份,类的所有对象共享一块空间。 概念: 可以修饰属性和方法(类属性、类方法)。不会因为创建多个对象导致多个静态属性。 静态方法之间可以互相调用。静态方法优先加载属于类中的一部分,所以无法调用非静态的方法。同时static不能用this、super,原因如上。 部分静态方法:

  • Arrays.copyOf()
  • Arrays.sort()
  • Math.random()
  • Math.sqrt
  • 使用类名调用 静态方法可以继承,不能重写,没有多态。也就是说只用类有关,与对象是谁无关。 静态方法类似重写的操作成为隐藏。 静态属性只能定义在类中,不能在方法中!

静态代码块

先加载静态属性,之后静态代码块。

static{
代码...
}

final类

final关键字:

  • 修饰类,类不能继承
  • 修饰方法,方法不能重写
  • 修饰变量,就是常量

如果修饰变量,那么在类中的构造方法,在构造方法中必须要给该常量赋值(或者在代码块中),否则会报错。如果修饰静态的常量,必须在静态代码块赋值,并且应该率先赋值。 如果用来修饰常量对象,那么该对象的地址不能再变换了。

接口

接口相当于特殊的抽象类,定义方式、组成部分与抽象类类似。

//类
class dog(){
}
//接口
interface dog(){
public final static int m;
public final static void m1();
}

JDK8以前接口中只能写公开静态常量,公开静态方法。

//调用接口
public class subtest impements dog{
//重写方法
}

接口也可以实现多态。

什么是接口?

微观:接口是一种能力和约定。 Java为单继承,父类的方法种类无法满足子类需求时,可实现接口扩展子类。 比如一个学生会音乐会编码:

public class student implements imusic,icode{
...
}

实现接口中的抽象方法时,访问修饰符必须为public。 类与类的关系:单继承 类与接口的关系:多实现 接口与接口的关系:多继承

接口标准

较为抽象,比如以下代码:

public interface Iusb {
    void service_();
}//定义接口
public class Usbdisk implements Iusb{
    public void service_(){
        System.out.println("使用U盘");
    }
}//接口的实现1
public class Usbfan implements Iusb{
    public void  service_(){
        System.out.println("使用USB风扇");
    }
}//接口的实现2
public class Computer {
    Iusb usb;
}//包含接口标准的类
public class Test {
    public static void main(String[] args){
        Computer com1 = new Computer();
        Usbdisk ud=new Usbdisk();
        Usbfan uf=new Usbfan();
        com1.usb=ud;
        com1.usb.service_();
    }

最终输出: 可见,接口在这里接入了不同的类实现。通过接口这个标准被主类调用。 接口也可以作为标记。

接口回调

先写接口,接口的具体实现交给使用者。 JDK8中不仅可以写抽象的方法,还可以写default修饰的方法。

public interface ICall(){
void call();
default doCall(){
call();
}
}

(难以理解) #Object类的方法 任何类都直接或者间接继承了object类的方法。

内部类

在类内部定义的类。内部类可以直接访问外部类的私有成员而不破坏封装。其次内部类可以为外部类提供必要的内部功能组件。

//Outer外部类 In内部类
Outer out=new Outer();
In in= out.new In();
in.inmethod();

如果不关心外部类,内部类实现可以这样写:


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

如果想在内部类里调用外部类的同名变量,有两种方法,一种是在调用方法中将外部类作为参数传进来:

class Out {
    int num=1;
    class in{
        int num=2;
        void inMethod(Out out){
            out.num++;
}
}
}

另一种直接使用this关键字:

class Out {
    int num=1;
    class in{
        int num=2;
        void inMethod(){
            out.this.num++;
}
}
}

对内部类使用private关键字意味着更强的封装。

局部内部类

写到方法里的类。局部内部类中使用的局部变量会变成常量——编译器自动,因为变量生命周期不一致。

匿名内部类

没有类名的局部内部类,必须继承一个父类或者接口。适用于仅用一次的类,省略很多代码,没有写出类的定义,但是编译时会生成一个类文件(自动命名)

public interface IUsb {
    void service();
}
public abstract class Sup {
    public abstract void test();
}

public class Noname {
    public static void main(String[] args){
        IUsb usbdisk=new IUsb() {
           public void service(){
               System.out.println("使用U盘");
           }
        };
        usbdisk.service();

        Sup sub=new Sup(){
            public void test(){
                System.out.println("抽象父类");
        }
    };
        sub.test();
    }
    }
}

在示例中,并没有声明一个子类,而是通过局部内部匿名类新建了一个usbdisk对象,该对象继承了接口IUsb。sub的父类为抽象类Sup,原理同样。 接口能实现的,抽象类都能,抽象类能实现的,普通类都能。哪怕父类为普通类,也可以进行内部类操作。

包装类

基本类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

###装箱、拆箱

String类

常用方法:contains()是否包含,charAt(),concat()拼接,split()以什么符号分割字符串

正则表达式

用符号作字符串匹配:

  • ^表示正则表达式开头,$表示结尾
  • \d表示数字,\d{6}表示匹配6位数字,\d{6,10}6到10位
  • 两种检测方法:

#Random类

Random r=new Random(种子);
r.nextInt(规定最高为多少)

确定了种子的话,调用几次可能就会出现同样的值——伪随机

课程不全,已经学完,目前缺少对应用方面的了解,以及对于大部分常用类的知识缺乏。


本文章使用limfx的vscode插件快速发布