admin 管理员组文章数量: 1184232
java基础补充:
配置Path环境变量
目的:我们想要在任意的目录下都可以打开指定的软件。就可以把软件的路径配置到环境变量中
Java的三大分类:javaSE,javaME,javaEE
java的可跨平台性:jvm虚拟机来实现
编译运行方式:
编程,编写源码
编译,将源码转换为机器语言(二进制代码)
运行,机器识别二进制代码并运行
编译型语言和解释型语言的区别:一个是整体翻译,一个是一行一行的翻译
JRE:由JVM,核心类库,运行工具所组成(java的运行环境)
JDK:由JVM,核心类库,开发工具所组成的
开发工具:javac编译工具,java运行工具,jdb调试工具,jhat内存分析工具等
JDK>JRE>JVM
字面量:
字面量是指数据在程序中的书写格式
字面量的分类:整数,小数,布尔,空,字符,字符串
\t制表符
在打印的时候,把前面字符串的长度补齐到8,或者是8的整数倍。最少补一个空格,最多补8个空格
public class HelloWorld {
public static void main(String[] args){
System.out.println("Hello World");//此为输出语句/** */
System.out.println("hello"+'\t'+"av");
System.out.println("hell"+'\t'+"avm");
}
}
输出为:
变量:
变量是指在程序运行时可能发生变化的值
变量的定义格式:
数据类型 变量名 = 数据值;
变量的使用方式:
输出打印
参与计算
修改记录的值
注意:在变量中只能存一个值,变量名不允许重复定义,一条语句可以定义多个变量,变量在使用之前一定要进行赋值,变量的作用域范围
计算机的存储规则
在计算机中所有的数据都是以二进制形式来存储的(二进制就是0和1)
数字,字母,汉字,
常见的数据在计算机中分为多种不同进制的表现形式
二进制:由1和0组成
十进制:0~9组成
八进制0~7组成并且以0开头
十六进制0~9和a~f以0x开头
java的基本数据类型:
如果要定义long类型的变量我们需要在数据值的后面加上一个L作为后缀
float 类型的数据也需要在末尾加上一个F作为后缀;
注意:
java数据类型分为基本数据类型和引用数据类型
引用数据类型是指在内存中存储的是对数据的引用(即内存地址),而不是数据本身。引用数据类型主要包括类、接口、数组等。
标识符:
标识符是给类,方法,变量起的名字
标识符命名规则:
由数字,字母,下划线和$组成且数字不能开头
不能是关键字
区分大小写
软性规则:
小驼峰命名法
标识符是一个 单词的时候全部小写
标识符由多个单词组成的时候,第一个单词首字母小写,其他单词首字母大写
大驼峰命名法
标识符是一个单词的时候,首字母大写
标识符由多个单词组成的时候,每个单词的首字母大写
java的输出
System.out.println();//括号内填写要输出的数据
注意null不能够直接被输出如果要输出需要用“”括起来
java的输入
基本格式:
import java.util.Scannern;//导包且放在主框架之外
Scanner 变量名 = new Scanner(System.in);//创建一个对象
int 变量名=scan.nextInt();//不同的数据类型有不同的接收数据的格式和写法具体详见Java官网
char 变量名 = scan.next().char.At(0);//Java中字符的输入
//输入的导包
import java.util.Scanner;
public class scanf {
public static void main(String[] args){//主函数
Scanner sc= new Scanner(System.in);//创建一个对象
System.out.println("输入一个整形数据");
int a= sc.nextInt();//输入一个整形数据
System.out.println("输入一个char型数据");
char b=sc.next().charAt(0);//输入一个char型数据
System.out.println(a);//打印数据
System.out.println(b);//打印字符
}
}
分支
基础语法:
-
if 语句:
- 单分支:
if (条件) { 代码块 } - 双分支:
if (条件) { 代码块1 } else { 代码块2 } - 多分支:
if (条件1) { 代码块1 } else if (条件2) { 代码块2 } ... else { 代码块n }
- 单分支:
-
switch 语句:
switch (表达式) { case 值1: 代码块1; break; case 值2: 代码块2; break; ... default: 代码块n; }- Java 12 引入了对 switch 表达式的增强,允许直接返回值。
-
三元运算符(三目运算符):
条件 ? 表达式1 : 表达式2相当于if....else...- 这是一个简化的 if-else 结构,常用于简单的条件赋值。
-
逻辑运算符:写在“条件表达式”中的也就是圆括号内
&&(逻辑与)||(逻辑或)!(逻辑非)- 这些运算符可以组合多个条件,用于 if 或 while 等循环结构中。
-
条件运算符:
?:(三元运算符)- 这是一个更简洁的条件选择方式,用于选择两个值中的一个。
- 算数运算符:
“+”操作的三种情况:
数字,字符串,字符相加
隐式转换:
把一个取值范围 小的数据转换成取值范围大的数据
byte<short<int<long<float<double(取值范围由小到大)
隐式转换的两种提升法则
取值范围小的,和取值范围大的进行运算,小的会先提升为大的,再进行运算
byte short char 三种类型的数据在运算的时候,都会直接先提升为int,然后在进行运算
强制类型转换:
目的:把一个取值范围大的数值,赋值给取值范围小的变量。是不允许直接赋值的。如果一定要这么做就需要加入强制转换
格式:目标的数据类型 变量名=(目标数据类型)被强转的数据;
循环
基础语法:
-
for 循环:
- 基本形式:
for (初始化表达式;条件表达式;更新表达式) { 代码块 }/*其中括号中的表达式都可以省略*/ - 增强的 for 循环(用于遍历数组或集合):
for (元素类型 变量名 : 数组或集合) { 代码块 } -
public class next { public static void main(String[] args) { // 基本 for 循环 for (int i = 0; i < 10; i++) { System.out.print(i+" "); }System.out.println();//用于换行 // 增强的 for 循环 int[] numbers = {1, 2, 3, 4, 5}; for (int number : numbers) { System.out.print(number+" "); } } }
- 基本形式:
-
while 循环:
while (条件表达式) { 代码块 }- 先判断条件,如果条件为真,则执行循环体。
-
//while循环 public class next{ public static void main(String[] args){ int i = 0; while (i < 10) { System.out.println(i);//输出 i++;//更新表达式可以防止陷入死循环 } } }
-
do-while 循环:
do { 代码块 } while (条件表达式);- 先执行循环体,然后判断条件,如果条件为真,则继续执行循环体。
-
//do while();切记不要忘记“()”后面的“;” public class next{ public static void main(String[] args){ int i = 0; do { System.out.print(i+" "); i++; } while (i < 10); } }
数组
定义:在Java中,数组是一种容器对象,用于存储固定数量的单一类型的元素。数组的定义涉及到声明数组、创建数组和初始化数组(相当于一次性定义了许多个内存相连的不同变量)
数组的声明 :
int[] 变量名;
变量名= new int[]{};如果方括号内有参数那么右侧的花括号应该删去否则会出错
2 int[] 变量名= new int[]{};
3 int 变量名[]= new int[]{};
4 int 变量名= {}
静态初始化:数组变量的初始化与数组元素的赋值操作同时进行
动态初始化:数组变量的初始化与数组元素的赋值操作分开进行
求数组的长度
length
System.out.println(数组名.length)
二维数组的长度为System.out.println(数组名.length)这是二维数组的长度
System.out.println(数组名[0].length)这是第一行中元素的值
在数组阶段常用的函数:
求数组的长度:length
数组名.length;//可以将数组的长度赋值到一个变量中也可以让他作为循环的停止条件
数组的复制:System.arraycopy
System.arraycopy(被复制的数组名,被复制数组中的起始位置(也就是数组下标),目标数组名,目标数组要复制到的位置(也是下标),需要复制元素的个数);
数组的打印:for(i:each);
for(i:each);//其中i代表下标,each代表数组名
数组的排序:Arrays.sort:
Arrays.sort(数组名);//对数组进行升序排序(从小到大)
数组中数据的查找Arrays.binarySearch():
Arrays.binarySearch(数组名,key);//在查找时必须是有序的数组
数组的填充Arrays.fill:
Arrays.fill(数组名,需要被赋的值);//将数组所有的元素设置为指定的值
数组的比较Arrays.equals
Arrays.equals(数组1,数组2);//比较完成后会返回一个值来用于判定是否相等
数组转换为字符串Arrays.toString
String sc=Arrays.toString(数组名);
面向对象 (高内聚低耦合)
基础知识:
面向对象的特征:
封装 继承 多态 抽象
其他关键字的使用 this super package import
this关键字的使用案例:
public class mxdx {
int sd;//类的属性 感觉和c语言的全局变量有些相似
String color="红色的";
int many;
//类的构造方法 感觉是个函数包
public void car(){
System.out.println("车能跑");
System.out.println(this.color);//this的作用是调用类的属性也就是全局变量的调用
System.out.println(this.many);
}
//主类和主方法
public static void main(String[] args){//如果无法运行那多半是主函数写的时候出现问题
mxdx fly=new mxdx();//创建一个实例
fly.car();//调用对象的方法 函数的调用
mxdx Newcar = new mxdx();
Newcar.color="绿色的";
Newcar.many=100000;
Newcar.car();
//System.out.println(this.color); this关键字无法使用在主函数中也就是静态函数中
mxdx Mx=new mxdx();
System.out.println(Mx.sd);
}
}
其中调用对象的意思为 ××的。。
面向对象的创建过程:
-
类加载:加载类的字节码文件,验证并准备类。
-
内存分配:为对象分配内存空间。
-
对象初始化:
-
调用父类构造方法。
-
初始化实例变量。
-
执行构造方法。
-
-
对象使用:通过对象引用访问对象的属性和方法。
-
对象销毁:垃圾回收器回收不再使用的对象。
静态成员变量:
静态成员变量属于类本身,而不是某个具体的对象实例。所有对象实例共享同一个静态变量。
特点
-
静态变量在类加载时初始化,且只初始化一次。
-
可以通过类名直接访问,也可以通过对象实例访问(不推荐)。
public class Counter {
public static int count = 0; // 静态变量
public Counter() {
count++; // 每次创建对象时,静态变量 count 增加
}
public static void main(String[] args) {
System.out.println(Counter.count); // 0
new Counter();
new Counter();
System.out.println(Counter.count); // 2
}
}
静态成员方法:
代码块:
-
构造方法
-
定义:
-
在 Java 中,构造方法(Constructor)是一种特殊的方法。它的名字与类名相同,并且没有返回类型(包括
void也不能写)。构造方法主要用于在创建对象时初始化对象的状态,也就是给对象的成员变量赋初始值且在创建对象时自动调用的方法。 -
作用
-
对象初始化:
-
当使用
new关键字创建一个对象时,构造方法会被自动调用。它负责初始化对象的属性,确保对象在使用之前处于一个合理的初始状态。也就是变量赋初值。
默认的构造方法
注意:
-
名称:构造方法的名称必须与类名完全相同。
-
返回类型:构造方法没有返回类型,甚至连void都没有。
-
自动调用:当使用
new关键字创建类的新对象时,构造方法会被自动调用。 -
初始化对象:构造方法的主要作用是初始化新创建的对象。
如果一个类中没有显式地定义构造方法,Java 编译器会自动为这个类提供一个默认的构造方法。这个默认构造方法没有参数,并且方法体为空
例子
public class Mxdx {
//类的属性
String color;
int speed;
int seat=5;
//构造方法
public Mxdx(String color,int speed){//构造方法的函数名和类名相同,其中不能前缀void,否则会无法运行
// 2此为自动创建的构造方法如果改变形参,那么实参也必须发生改变
//当自己定义了函数的形参是java就无法自动创建了
System.out.println("这是构造方法");
System.out.println(color);
System.out.println("车子的行驶速度为:"+speed);
}
public void run(){
System.out.println(this.color+"颜色的车在跑");
}
public static void main(String[] args){
Mxdx mx=new Mxdx("红色的",120);//默认调用的构造方法
// mx.color="red";
mx.run();
}
}
我在主函数中添加了实参,并在构造方法中添加了形参;由结果显示实参传递了过去
但是由结果显示类的属性中的color并没有发生变化显示结果为null,如果需要其改变类的属性值
需要借助"this"函数的力量
解决方法:
在构造方法中添加了this.类的属性名=需要赋予的值;即可
修改如下
public class Mxdx {
//类的属性
String color;
int speed;
int seat=5;
//构造方法
public Mxdx(String color,int speed){//构造方法的函数名和类名相同,其中不能前缀void,否则会无法运行
// 2此为自动创建的构造方法如果改变形参,那么实参也必须发生改变
//当自己定义了函数的形参是java就无法自动创建了
System.out.println("这是构造方法");
System.out.println(color);
System.out.println("车子的行驶速度为:"+speed);
this.color="绿色";
}
public void run(){
System.out.println(this.color+"颜色的车在跑");
}
public static void main(String[] args){
Mxdx mx=new Mxdx("红色的",120);//默认调用的构造方法
// mx.color="red";
mx.run();
}
}
此时color的颜色改变将作用于全局相当于C语言中全局变量的效果
构建方法的重载
-
和普通方法一样,构造方法也可以重载。重载的构造方法是指在一个类中有多个构造方法,它们的名字相同,但参数列表不同(参数的个数、类型或顺序不同)。
import java.util.Scanner;
public class Dxcz {
String name;
String waihao;
int age;
//构造两个构造方法
public Dxcz(String name,String waihao,int age){//括号内的为形参且形参与实参相对应才能够调用此构造方法
this.age=age;
this.name=name;
this.waihao=waihao;
System.out.println(waihao);//切记如果这样写“System.out.println(this.waihao);”将会无法接收到数据但是不会报错
System.out.println(name);
System.out.println(age);
System.out.println("这是第一个构造方法");
}
public Dxcz(String name,String waihao){
// this.name = name;
System.out.println("这是第二个构造方法");
}
public static void main(String[] arge){
Scanner sc=new Scanner(System.in);
System.out.println("请输入年龄");
int age=sc.nextInt();
System.out.println("请输入名字");
String name=sc.next();
System.out.println("请输入外号");
String waihao=sc.next();
Dxcz c1=new Dxcz(name,waihao,age);//括号内的为实参
}
}
上述例子中我添加了两个构造方法,其中形参的数量并不相同所以区分为第一和第二构造方法
在下方的实参调用中我传入了三个参数并且与第一个构造方法相对应所以第一个构造方法被调用了
注意:在构造方法中一定要用this接收其中的传过来的数据否则输出时会出现“null”的现象
结果如下:
构造方法的嵌套:
借助this函数实现在构造方法中调用构造方法
//同时掉用两个构造方法需要使用“this"
import java.util.Scanner;
public class Dxcz {
String name;
String waihao;
int age;
//构造两个构造方法
public Dxcz(String name,String waihao){//注意当被嵌套的构造方法中的型参参数大于调用他的
//构造方法时会报错
this.age=age;
this.name=name;
this.waihao=waihao;
System.out.println(waihao);//切记如果这样写“System.out.println(this.waihao);”将会
//无法接收到数据但是不会报错
System.out.println(name);
System.out.println(age);
System.out.println("这是第一个构造方法");
}
public Dxcz(String name,String waihao,int age){//括号内的为形参且形参与实参相对应才能够
//调用此构造方法
this( name, waihao);
System.out.println("这是第二个构造方法");//因为这是在第二个构造方法中调用第一个构造
//方法所以第一个构造方法的型参数要<第二个构造方法的
//型参参数
}
public static void main(String[] arge){
Scanner sc=new Scanner(System.in);
System.out.println("请输入年龄");
int age=sc.nextInt();
System.out.println("请输入名字");
String name=sc.next();
System.out.println("请输入外号");
String waihao=sc.next();
//在此调用上面的构造方法
Dxcz c1=new Dxcz(name,waihao,age);//括号内的为实参
}
}
结果:
由结果显示在第二个构造方法中调用了第一个构造方法
总结:
如何区分类,对象,构造方法,无参构造器,对象变量,和this关键字的使用
// 定义一个类
public class Student {
// 实例变量(对象变量)
String name;
int age;
// 无参构造器
public Student() {
this.name = "Unknown"; // 使用this访问实例变量
this.age = 0; // 使用this访问实例变量
}
// 带参数的构造器
public Student(String name, int age) {
this.name = name; // 使用this区分局部变量和实例变量
this.age = age; // 使用this区分局部变量和实例变量
}
// 方法
public void displayInfo() {
System.out.println("Name: " + this.name); // 使用this访问实例变量
System.out.println("Age: " + this.age); // 使用this访问实例变量
}
}
// 主类
public class Main {
public static void main(String[] args) {
// 使用无参构造器创建对象
Student student1 = new Student();
student1.displayInfo(); // 输出:Name: Unknown, Age: 0
// 使用带参数的构造器创建对象
Student student2 = new Student("Alice", 20);
student2.displayInfo(); // 输出:Name: Alice, Age: 20
}
}
继承性:
定义:在Java面向对象编程中,继承性(Inheritance)是一种允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法的机制。继承性是面向对象编程的三大基本特征之一,另外两个是封装性和多态性
简单的来说就是如果b集合是a集合的一个子集那b就可以继承a的所有特征(相当于传统意义中的父子继承性)
假设一个案例,自行车是载具类的一个分支那载具就是(父类)自行车就是(子类)
案例
注意 前提三个类必须在同一个软件包中
extends 关键字的作用是让子类继承父类的 属性,及在此关键字之后的为 父类
私有的内容是不能被继承的
1父类
//父类
package com.xy;
public class car {
// private void programe(){//注意private关键字是私有的所以子类无法继承
//System.out.println("跑的太快容易出安全问题");
// }
public void xingneng(){
System.out.println("车能跑");
System.out.println("车能停");
}
}
子类
子类
package com.xy;
public class bick extends car{//bicK 继承car的所有属性
}
测试入口
package com.xy;
public class next {
public static void main(String[] args) {
bick b = new bick();//构造bick对象
b. xingneng();//在子类中调用父类中的性能模块
//b.programe();//会报错因为子类无法继承父类中的私有方法
}
}
测试结果
注意:
继承是通过extends关键字来实现的
一个父类只能继承于一个子类,如果想要实现一个父类继承多个子类可以使用接口
多态
定义:
多态(Polymorphism)是指同一个操作作用于不同的对象时,可以有不同的解释和不同的执行结果。换句话说,多态允许不同的类对象对同一消息做出响应,但响应的方式可能不同。
多态性是面向对象编程的另一个重要特征,它主要有两种形式:
-
编译时多态(静态多态):通常通过方法重载(Overloading)实现。方法重载是指在同一个类中,可以有多个同名的方法,但它们的参数列表(参数的数量、类型或顺序)必须不同。编译器根据方法调用时传递的参数类型和数量来决定调用哪个方法。
-
运行时多态(动态多态):通常通过方法重写(Overriding)和接口实现来实现。方法重写是指在子类中重写父类的方法,使得子类对象在调用该方法时,执行的是子类中的实现。运行时多态的关键在于使用父类类型的引用来引用子类的对象,然后根据对象的实际类型来决定调用哪个类的方法。
同一个对象有多种形态
注意 :把子类的对象赋值给父类的引用(变量)向上转型
案例
package com.dt;
public class dog {
public void eat(){
System.out.println("骨头");
}
}
package com.dt;
public class cat {
public void eat(){
System.out.println("猫粮");
}
}
package com.dt;
public class person {
public void feeddog(dog d) {
d.eat();
}
public void feedcat(cat c) {
c.eat();
}
}
测试
package com.dt;
public class text {
public static void main(String[] args) {
dog d=new dog();
cat c=new cat();
person p=new person();
p.feedcat(c);
p.feeddog(d);
}
}
运行结果
封装:(高内聚低耦合)
定义:在Java面向对象编程中,封装(Encapsulation)是一种将数据(属性)和操作这些数据的方法(行为)捆绑在一起的机制。封装的目的是隐藏类的内部实现细节,只暴露公共的接口给外部使用。这样可以减少系统的复杂性,增加代码的安全性和易维护性。
定义源于网络如有雷同纯属巧合。
高内聚:类的内部数据操作细节自己完成,不允许外界干涉
低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用。
封装的主要特点包括:
-
数据隐藏:通过将类的属性设置为私有(private),防止外部直接访问这些属性,从而保护数据不被外部代码错误地修改。
-
接口暴露:通过提供公共(public)的方法来访问和修改私有属性,这些方法被称为getter和setter方法。这样可以控制对属性的访问,并在方法内部添加逻辑来验证数据的合法性。
-
代码维护:封装使得代码的修改更加容易,因为内部实现的改变不会影响到使用类的外部代码。
-
减少耦合:封装降低了类之间的耦合度,因为类的内部实现是隐藏的,外部代码只需要关注公共接口
封装的设计思想:把该暴露的暴露出来,把该隐藏的隐藏起来
权限修饰符:private(私有的)、缺省,protected(受保护的),public(公共的)
权限修饰符的作用是使用4种权限修饰类及类的内部成员。当这些成员被调用时,体现可见性的大小。
封装实例:
import java.util.Scanner;
public class Fengzhuangtext {
public static void main(String[] args){
int i;
anmile anmile1=new anmile();
anmile1.name="麋鹿";
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个数据");
i=sc.nextInt();
anmile1.setlegs(i);
if(anmile1.legs>0)//判断腿的个数是否合法
anmile1.eat();
}
static class anmile{
//属性
String name;//名字
int legs;//腿的个数
public void setlegs(int a){//判断腿的个数是否合法的主代码(方法)
if(a%2==0&&a<=4){
legs=a;}
else System.out.println("您输入的数据非法");
}
public void eat(){
System.out.println("动物觅食");
}
}
}
//封装的概念和作用是利用关键字和权限修饰符来完成对程序的暴漏和隐藏
public class Student {
// 私有属性,外部无法直接访问
private String name; // 学生姓名
private int age; // 学生年龄
private String gender; // 学生性别
}
//上述代码中就属于将其私自的隐藏起来了外部无法看到和调用
Java的面向对象编程(OOP)和C语言的函数式编程在概念和实现上有很大的区别。以下是它们之间的一些主要区别:
-
编程范式:
- Java:Java是一种面向对象的编程语言,它支持封装、继承和多态性等面向对象的概念。
- C语言:C是一种过程式编程语言,它主要关注于函数和过程的执行。
-
数据和函数的关系:
- Java:在Java中,数据(属性)和函数(方法)被封装在对象(类)中。每个对象都有自己的数据和行为。
- C语言:在C中,数据和函数是分开的。数据结构通常用结构体(struct)定义,而函数则是独立的,不直接与数据关联。
-
封装:
- Java:Java通过类实现封装,将数据和操作这些数据的方法组合在一起,隐藏内部实现细节。
- C语言:C语言没有内置的封装机制,虽然可以通过结构体和函数指针模拟面向对象的行为,但它不是原生支持的。
-
继承:
- Java:Java支持单继承,一个类可以继承另一个类的属性和方法。
- C语言:C语言不支持继承。虽然可以通过结构体和函数指针模拟继承,但这需要更多的手动工作,并且不如Java中的继承机制直接和强大。
-
多态性:
- Java:Java支持多态性,允许一个引用类型指向多种实际类型的对象,并在运行时解析方法调用。
- C语言:C语言不支持多态性。虽然可以通过函数指针实现某种形式的多态性,但这通常不如Java中的多态性灵活和强大。
-
内存管理:
- Java:Java有垃圾回收机制,自动管理内存的分配和回收。
- C语言:C语言需要程序员手动管理内存,包括分配和释放。
-
类型系统:
- Java:Java有严格的类型检查,并且支持自动装箱和拆箱。
- C语言:C语言的类型系统较为简单,需要程序员更仔细地管理类型转换。
-
库和API:
- Java:Java有丰富的标准库和API,支持网络编程、文件I/O、多线程等。
- C语言:C语言的标准库也提供了基本的功能,但不如Java丰富,且很多现代功能需要依赖于第三方库。
-
跨平台性:
- Java:Java代码“一次编写,到处运行”,因为它运行在Java虚拟机(JVM)上。
- C语言:C语言代码通常需要为不同的平台编译,虽然有跨平台的库和工具链,但不如Java的跨平台性直接。
总的来说,Java的面向对象特性提供了更好的代码组织、复用和维护性,而C语言则提供了更接近硬件的控制和性能优化能力。选择哪种语言取决于项目需求和特定场景(以上内容来自于网络,如有雷同纯属巧合)
异常处理:
异常概念:
异常指的是在程序的执行过程中,出现的非正常现象,如果不处理最终 会导致JVM的停运。但异常并不是代码中的逻辑错误和语法错误
异常的抛出机制:
java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这种异常对象,并处理;如果没有捕获(catch)这个异常对象,并处理;如果没有捕获的话这个对象会停止。
java的 异常体系:
Throwable:
public void printStackTrace():打印异常的详细信息。包含异常的类型,异常的原因,异常出现的位置。在开发和调试阶段都得使用printStackTrace
public String getMessage():获取发生异常的原因
Error和Exception:
Error:
java.lang.Error
java虚拟机无法解决的严重问题。如:JVM系统内部错误。资源耗尽等情况。一般不编写针对性的代码进行处理
例如:StackOverflowError(栈内存溢出)和OutOfMemoryError(堆内存溢出,简称oom)
Exception:
java.lang.Exception
编译异常和运行时异常:
编译时异常:在执行javac.exe命令时,出现的异常
运行时异常:在执行java.exe命令时,出现的异常
常见的运行时的异常:
异常的实际例子:
package Error;
import java.util.Scanner;
public class ErrorText {
public static void main(String[] args) {
//1栈溢出StackOverflowError(
// main(args);
//举例2OutOfMemoryError
// byte[] arr=new byte[1024*1024*100];//假如说运行内存没有达到此数组大小时会发生堆溢出现象
}
public void text1 (){
int [] arr=new int[10];
System.out.println(arr[10]);//数组越界
}
public void text2 (){
String str="hello";
str=null;
System.out.println(str.toString());//空指针
//int[][] arr1=new int[10][];
//System.out.print(arr1[0][0]);//这也是个空指针
}
public void text3 (){
Object obj=new String();
// String str=(String) obj;//正常情况不会出现强转异常
// Date date=(Date) obj;//会出现强转异常并且无法运行
}
public void text4 (){
String str="123";
int i=Integer.parseInt(str);//将字符串参数 str 解析为一个十进制整数。
System.out.println(i);
}
public void text5 (){
Scanner sc=new Scanner(System.in);//当输入的数据不是数字时会弹出错误提示,输入的类型和我们需要的不匹配
int num=sc.nextInt();
System.out.println(num);
}
public void text6 (){
int num=10;
System.out.println(num/0);//此时运行后会提示运算错误
}
}
以上是实际运行中可能出现的异常的实例,每个测试与上述图片中的错误相对应;
编译时的错误:
实例
package Error;
import java.io.FileInputStream;
import java.io.FileReader;
import java.util.Scanner;
public class compile {
public static void main(String[] args) {}
// public void text7() {
// class clz=class.forName(className:"java.long.String");//编译时异常
// }//无法运行只能注释掉
public void text8(){//资源管理
File file=new File(pathname:"hello.txt");//如果输入一个正确的位置有可能不出现异常
FileInputStream fis=new FileInputStream(file);//文件的阅读,阅读完成后会返回-1
int data = fis.read();//可能报IDException
while(data !=-1){
System.out.print((char)data);
data=fis.read();//可能报IDException
}
fis.close();//可能报IDException
}
}
接口:
接口是一种规则是对行为的抽象
接口的定义和使用:
接口用关键字interface来定义
public interface接口名{}
接口不能实例化(不能New一个对象出来)
接口和类之间是实现关系,通过implements关键字表示
多线程:
程序:为完成 某种任务所需要的代码指令集
进程:程序的一次执行过程 或者说正在内存中运行的应用程序
线程:进程可以细分成线程,一个线程就是一个指令集,一个进程至少需要有一个线程
并行:指两个或两个事件同时发生,同一时刻有多条指令在cpu上同时执行
并发:指两个或多个事件在同一时间段内发生,及在同一时间段内有多条指令在cpu上快速轮换交替.看起来像是同时执行。
线程的多种方式:
获取线程的名字代码
Thread.currentThread().getName()
创建方式1:继承 Thread类;
实例1
创建一个分线程用于遍历100以内的偶数?
//创建分线程的步骤
package thread;
public class EvenNumbeTest {
//创建一个继承与Thread类的子类
static class PrintThread extends Thread {
//重写Thread类的run()--->将此线程要执行的操作,声明在此方法中
public void run(){
int i;
for(i=0;i<=100;i=i+2){
System.out.println(i);
}
}
}
public static void main(String[] args){
//创建当前THread的子类的对象
PrintThread t1 = new PrintThread(); //此处必须和继承的父类的类名相同。
//通过对象调用start()
t1.start();
}
}
如果在其后面在添加一个main函数并且执行run()函数中的操作可能会发生交互 也就是两个线程分段执行
注意:
不能够使用t1.run()更换t1.start();如果更换结果相同但是idea中会认为是主函数做的所以这就变成单线程的使用了就没有多线程之分了
不能让已经start的线程再次执行start线程如果想要再多一个线程只需要将PrintThread t1 = new PrintThread(); 中的t1改成t2;
判断是否是多线程的时候可以从main函数开始看看能否连成一条线如果可以那就是单线程,另外则相反。
例题:
//创建一个多线程并且一个输出100以内的偶数一个输出100以内的奇数
//创建一个多线程并且一个输出100以内的偶数一个输出100以内的奇数
//创建分线程的步骤
package thread;
public class EvenNumbeTest {
//创建一个继承与Thread类的子类
static class PrintThread extends Thread {
//重写Thread类的run()--->将此线程要执行的操作,声明在此方法中
public void run(){
int i;
for(i=0;i<=100;i=i+2){
System.out.println(i);
}
}
}
static class EvenNumberThread extends Thread {//这是第二个对象用于输出奇数(前面必须加上static否则无法运行)
public void run(){
int i;
for(i=0;i<=100;i++){
if(i%2!=0)
System.out.println(i);
}
}
}
public static void main(String[] args){
//创建当前THread的子类的对象
PrintThread t1 = new PrintThread(); //此处必须和继承的父类的类名相同。
//通过对象调用start()
EvenNumberThread t2=new EvenNumberThread();
t1.start();
t2.start();
}
}
结果如下
结果看起来很乱,其中带有*号的是t1而没有的是t2此结果是因为他们产生了交互
创建分线程的方法2实现Runnable接口:
2.1步骤:
1
实例如下:
package Runnable;
//创建一个实现Runnable接口的类
class EvenNumberPrint implements Runnable{//实现接口
//实现接口中的run()-->将此线程所执行的操作填入到此方法中
public void run(){
int i;
for(i=1;i<=100;i=i+2){
System.out.println(i+"***");
}
}
}
public class EvenNumberTest {
public static void main(String[] args) {
//创建类的对象
EvenNumberPrint p = new EvenNumberPrint();
//将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
Thread t1= new Thread(p);
//创建Thread类的实例调用start():1启动线程2调用当前线程中的run()
t1.start();
for(int j=1;j<=100;j++){
System.out.println(j+"$$$");
}
}
}
结果如下
D:\java\bin\java.exe "-javaagent:D:\java\idea\idea1\IntelliJ IDEA 2024.2.4\lib\idea_rt.jar=65254:D:\java\idea\idea1\IntelliJ IDEA 2024.2.4\bin" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\Users\55\IdeaProjects\untitled\out\production\untitled thread.EvenNumbeTest
135791113151719212325272931333537394143454749515355575961636567697173757779818385878991939597990***2***4***6***8***10***12***14***16***18***20***22***24***26***28***30***32***34***36***38***40***42***44***46***48***50***52***54***56***58***60***62***64***66***68***70***72***74***76***78***80***82***84***86***88***90***92***94***96***98***100***
进程已结束,退出代码为 0
比较两种方式:
共同点:启动线程,使用的都是Thread类中定义的start()
创建的线程对象,都是Thread类或者其子类的实例。
不同点:一个是类的继承,一个是接口的实现。
建议:建议使用实现Runnable接口的方式。
Runnable方式的好处:1 实现的方式,避免的类的单机成型的局限性
2 跟适合处理共享数据的问题
3实现了代码和数据的分离
联系:public class Thread implements Runnable(代理模式)
线程中的常用结构:
1.线程中的构造器
public Thread():分配一个新的线程对象
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字
2.线程中的常用方法
start():1启动线程2调用线程的run()
run():将线程要执行的操作,声明在run()中
currentThread():获取当前执行代码对应的线程
getName():获取线程名
setName():设置线程名
sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
yield():一旦执行此方法,就释cpu的执行权
join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
isAlive():判断当前线程是否还存活(返回一个布尔类型的值)
过时的方法(不建议使用):
stop():强行结束一个线程的执行,直接进入死亡状态。不建议使用
void suspend()/void resume():可能造成死锁,所以不建议使用
3线程的优先级:
getPriority():获取线程的优先级
Thread类内部声明的三个常量:
MAX_PRIORITY(10):最高优先级
MIN _PRIORITY(1):最低优先级
NORM_PRTORITY():普通优先级,默认情况下main线程具有普通优先级。
注意:若是两个线程设置成一个最高优先级一个最低优先级但是运行后的结果时会产生交互的而优先级高的被执行到的概率会大一点
二、线程的生命周期
四种状态:创建,就绪,运行,死亡
同步代码块解决两种线程创建方式的线程安全问题
例题1 开启三个窗口售票不能出现漏票,错票,一共100张
package notsafe;
class SaleTicket implements Runnable{
int ticket=100;
public void run(){
while(true){
if(ticket>0){System.out.println(ticket);ticket--;}
else break;
}
}
}
public class WindowTest {
public static void main(String[] args){
SaleTicket s = new SaleTicket();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.start();
t2.start();
t3.start();
}
}
在结果中我们可以看到三个窗口产生了交互造成了一个票好几个窗口在卖。
解决方法:
方式1(同步代码块):synchronized(同步监视器){
//需要被同步的代码
}
说明:
需要被同步的代码,即为操作共享数据的代码
共享数据:即多个线程多需要操作的数据。比如:ticket
需要被同步的代码,在被synchronized包裹后,就使得一个线程在操作这些代码的过程中,其它线程 必须等待。
同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码
同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
package notsafe;
class SaleTicket implements Runnable{
int ticket=100;
// Dog dog1=new Dog();
public void run(){
while(true){
//try{
// Thread.sleep(5);
//}
synchronized(this){ if(ticket>0){System.out.println(ticket);ticket--;}
else break;}//其中参数框中的this可以声明任意一个类或对象填入即可但是必须要确保他是唯一的
}
}
}
public class WindowTest {
public static void main(String[] args){
SaleTicket s = new SaleTicket();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.start();
t2.start();
t3.start();
}
}
//static class Dog( ){}
结果
可以看到问题解决了!
注意:
在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this。
方式2(同步方法):如果同步代码块正好在一个方法里那么我们可以直接同步方法
//Runnable接口的代码同步实现
package notsafe;
public class WindowTest1 {
int ticket =100;
boolean flag = true;
class SaleTicket implements Runnable{
// Dog dog1=new Dog();
public void run(){
while(true){
show();
}
}
}
public synchronized void show(){
if(ticket>0){System.out.println(Thread.currentThread().getName()+"售票,票数为"+ticket);ticket--;}
else flag=false;}
// public class WindowTest {
public static void main(String[] args){
WindowTest1 windowTest1 = new WindowTest1(); // 创建外部类的实例
SaleTicket s = windowTest1.new SaleTicket(); // 通过外部类的实例创建内部类的实例
// notsafe.SaleTicket s = new notsafe.SaleTicket();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.start();
t2.start();
t3.start();
}
}
问题已经解决
线程的死锁问题:
产生死锁的四大条件:
1,互斥使用
2,占有且等待
3,不可抢占
4,循环等待
//整个程序的作用是模拟死锁
public class Host {
public static void main(String[] args) {
Object a = new Object(); // 创建锁对象 a
Object b = new Object(); // 创建锁对象 b
// 线程1:先获取 a 的锁,再获取 b 的锁
new Thread(() -> {
synchronized (a) { // 获取 a 的锁
System.out.println(Thread.currentThread().getName() + " i get a");
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " i want get b");
synchronized (b) { // 获取 b的锁
System.out.println("i get b");
}
}
}).start();
// 线程2:也按照先获取 a 的锁,再获取 b 的锁的顺序
new Thread(() -> {
synchronized (b) { // 获取 b 的锁
System.out.println(Thread.currentThread().getName() + " i get b");
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " i want get b");
synchronized (a) { // 获取 a 的锁
System.out.println("i get a");
}
}
}).start();
}
}
结果如下 :
Thread-1i get b
Thread-0i get a
Thread-1i want get b
Thread-0i want get b
线程的通信:
为什么要处理线程间的通信:
当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那末多线程之间需要一些通信机制,可以协调他们的工作,以此实现多线程共同操作一份数据。
常用的类和基础API(基于面向对象):
String类的理解(JDK8为例)
1.1类的声明
public final class String
implements java.io.Serializable,Comparable<String>,CharSequence
final:String是不可被继承的
Serializable:可序列化的接口。凡是实现此接口的类的对象就可以通过网络或本地流行数据的传输。
Comparable:凡是实现此接口的类,其对象都可以比较大小。
1.2内部声明的属性:
private final char value[];//存储字符串数据的容器
final: 指明此value数组一旦初始化,其地址就不可变
jdk9开始:
private final byte[] value;//存储字符串数据的容器。(将char改为byte的目的是节省内存空间)
字符串的常量池:
jdk7之前存放在方法区中
jdk7之后存放在堆空间。
String的不可变性:
当对现有的字符串进行拼接操作时,需要重新开辟空间保存拼接以后的字符串,不能在原有的位置修改
当字符串变量重新赋值时,需要重新指定一个字符串常量的位置进行赋值,不能在原有的位置修改
当调用字符串的replace()替换现有的某个字符时,需要重新开辟空间保存修改以后的字符串,不能在原有的位置修改。
public void text1(){
String s1="hello";
String s2="hello";
s2="hi";
System.out.println(s1);//此时的结果时hello
}
关键字replace的使用:
replace(‘字符1’,‘字符2’)的作用是将字符串中所有的与字符1相等的字符改变为字符2
4,String实例化的两种方式
package leiAndAPI;
public class Sringtext1 {
public static void main(String[] args) {
String t1 = "HELLO";
String t2 = "hello";
String t3 = new String("hello1");
System.out.println(t1+" "+t2+" "+t3);
}
}
结果
HELLO hello hello1
//设想一下此程序的输出结果是什么
public class Sringtext1 {
public static void main(String[] args) {
String t1 = "hello";//方法1
String t2 = "hello";
String t3 = new String("hello");
String t4=new String("hello");//方法2
System.out.println(t1==t2);
System.out.println(t1==t3);
System.out.println(t1==t4);
System.out.println(t3==t4);
}
}
结果如下
那两种方法创建有何区别?
从上图中可以看到s1和s2中的value地址和资源池中的地址相同但是由于s1和s2是两个不同方法创建的字符串所以其内存中的地址是不同的因此我们结果的输出是false
但是如果我们写成
System.out.println(s1.equals(s2));//此结果为true其中无论是怎样比较都是如此
//因为equals()函数的主要作用是将其两个字符串中的数据进行单个比较
package leiAndAPI;
public class stringtext2 {
static class person {
String name;
}
public static void main(String[] args){
person p1 = new person();
person p2 = new person();
p1.name = "Tom";
p2.name = "Tom";
p1.name = "joke";
System.out.println(p2.name);
}
}
结果是tom但是其运行原理和上面的“hello”完全不同
示例图:
其中可以看到p1和p2的指针并不是直接指向“tom”的地址而是通过指向堆中的内存块而内存块中存储了“tom”和"jerry"的地址,因此我们对p1进行修改时可以看到p2并没有发生变化,这是因为在修改后指针的指向发生了变化。
例题
String s2 = new String(hello);中一共有几个对象?
答:两个!
因为堆空间中有new的对象,但是在常量池中还有一个字面量。
String的连接操作:
在上述的一些代码中我们可以看到在通过连接符并输出后结果都是完全一样的,但是给出的却是false这是因为它们的地址各不相同。idea自动给new 了个对象
concat(xxx):不管是常量调用此方法还是变量调用此方法,无论参数是多少其结果都是返回一个性的new的对象
String和char[]的转换需要使用String的toCharArray()
//本质是利用for循环可以将其字符串改为字符数组
String与byte[]之间的转换(难度)
public void test4(){
String str = new String("hello");
//String到byte[]:调用String的getBytes()
byte[] arr=str.getBytes();
}
//结果是每个字符所对应的ASCII码
注意:在转换中字符使用的是默认的字符集对应ASCII码占1个字节
而汉字占三个字节(Ufc-8中)
在gbf中字符占用1个字节,汉字2个字节
String的构造器和常用的方法
构造器:
public String():初始化新创建的String对象,以使其便是空字符序列。
String(String original):初始化一个新创建的‘String’对象,使表示一个与参数相同的字符序列;换句话说,新
public String(char[] value):通过当前参数中的字符数组来构造新的String。
public String(char[] value,int offset,int count):通过字符数组的一部分来构造新的String.
public String(byte[] bytes):通过使用平台的**默认字符集**解码当前参数中的字节数组来构造新的String.
public String(byte[] bytes,String charsetName):通过使用指定的字符集解码当前参数中的字节数组来构造新的String。
常用API 2
package leiAndAPI;
public class StringMethodTest {
public static void main(String[] args){
test1();
}
public static void test1(){
String s1="";
String s2=new String();
String s3=new String("");
String s4=null;//和上面完全不同报空指针异常
String s5="hello";
String s6="HEllo";
System.out.println(s1.isEmpty());//判断字符串是否为空返回true或false三个都是如此
System.out.println(s5.length());//输出字符串的长度为5;
System.out.println(s6.equals(s5));//true(区分大小写)
System.out.println(s6.equalsIgnoreCase(s5));//false(不区分大小写)
System.out.println(s5pareTo(s6));//1或-1或随机但不是0(0是相同而其余表示不相同)
System.out.println(s6pareToIgnoreCase(s5));//0(不分大小写)
String s7=s6.toLowerCase();
System.out.println(s6);//将大写字母转换成小写字母(汉字,数字原样输出)
String s8=" he llo ";
System.out.println("******"+s8.trim() +"******");
}
}
结果如下:
public void text2(){
String s1="hello world";
System.out.println(s1.contains("lo"));//true(判断字符串是否包含括号内的字符)
System.out.println(s1.indexOf("lo"));//查看字符串中是否包含括号内的数如果没有包含返回-1
System.out.println(s1.indexOf("lo",1));//从指定的下标处开始找
System.out.println(s1.lastIndexOf("lo"));//从后往前找
}
public void text3(){
String s1="Java的学习之路";
System.out.println(s1.substring(2));//从第二个字符开始
System.out.println(s1.substring(2,5));//从第二个字符开始到第五个字符结束
}
以上这些是String中常用和常见的方法用来完成各种常见的操作。
正则表达式:
| 正则表达式 | 说明 | 示例 | 匹配结果 | ||
|---|---|---|---|---|---|
. | 匹配任意单个字符(除换行符) | "a.c" | 匹配 "abc"、"adc",但不匹配 "ac" | ||
[abc] | 匹配字符集合中的任意一个字符 | "a[bc]d" | 匹配 "abd"、"acd" | ||
[^abc] | 匹配不在字符集合中的任意一个字符 | "a[^bc]d" | 匹配 "aed",但不匹配 "abd" | ||
[a-z] | 匹配任意一个小写字母 | "a[a-z]b" | 匹配 "aab"、"acb" | ||
[A-Z] | 匹配任意一个大写字母 | "A[A-Z]B" | 匹配 "AAB"、"ACB" | ||
[0-9] | 匹配任意一个数字 | "a[0-9]b" | 匹配 "a1b"、"a9b" | ||
\d | 匹配任意一个数字(等价于 [0-9]) | "\d\d\d" | 匹配 "123" | ||
\D | 匹配任意一个非数字字符(等价于 [^0-9]) | "\D\D" | 匹配 "ab",但不匹配 "12" | ||
\w | 匹配任意一个字母、数字或下划线(等价于 [a-zA-Z0-9_]) | "\w\w" | 匹配 "ab"、"12"、"_a" | ||
\W | 匹配任意一个非字母数字下划线字符(等价于 [^a-zA-Z0-9_]) | "\W\W" | 匹配 "!@",但不匹配 "ab" | ||
\s | 匹配任意一个空白字符(空格、制表符、换行符等) | "a\sb" | 匹配 "a b"、"a\tb" | ||
\S | 匹配任意一个非空白字符 | "a\Sb" | 匹配 "a1b",但不匹配 "a b" | ||
* | 匹配前面的字符或子表达式零次或多次 | "a*b" | 匹配 "b"、"ab"、"aaaab" | ||
+ | 匹配前面的字符或子表达式一次或多次 | "a+b" | 匹配 "ab"、"aaaab",但不匹配 "b" | ||
? | 匹配前面的字符或子表达式零次或一次 | "a?b" | 匹配 "b"、"ab" | ||
{n} | 匹配前面的字符或子表达式恰好 n 次 | "a{3}b" | 匹配 "aaab" | ||
{n,} | 匹配前面的字符或子表达式至少 n 次 | "a{3,}b" | 匹配 "aaab"、"aaaaab" | ||
{n,m} | 匹配前面的字符或子表达式至少 n 次,最多 m 次 | "a{2,4}b" | 匹配 "aab"、"aaab"、"aaaab",但不匹配 "ab" | ||
^ | 匹配字符串的开头 | "^abc" | 匹配以 "abc" 开头的字符串 | ||
$ | 匹配字符串的结尾 | "abc$" | 匹配以 "abc" 结尾的字符串 | ||
| ` | ` | 匹配左右两边任意一个表达式 | `"a | b"` | 匹配 "a" 或 "b" |
(abc) | 捕获分组,匹配括号内的表达式 | "(abc)def" | 匹配 "abcdef",并捕获 "abc" | ||
\1 | 匹配第一个捕获分组的内容 | "([a-z])\1" | 匹配 "aa"、"bb",但不匹配 "ab" |
以上是常用的正则表达式(来源于网络)。
正则表达式的主要作用就是相当于转义字符,可用来设置用户名,密码,邮箱,等需要特殊格式的地方。
三个类的对比:String.StringBuffer.StringBuilder
String:不可变的字符序列;
StringBuffer:可变的字符序列;JDK1声明,线程安全的,效率低;底层使用char[](jdk8之前),底层使用byte
StringBuilder:可变的字符序列;JDK5声明,线程不安全,效率高;底层使用char[](jdk8及之前),底层使用byte
package leiAndAPI;
public class Stringbuiliber {
//对于StringBuffer
String s1 = new String();//char[] value = new char[0]
String s2 = new String("abc");//char[] value = new char[]{'a','b','c'}
//对于StringBuilder的属性有char[] value;//存储字符序列 int count;//实际存储的字符个数
String s3 = new String();//char[] value = new char[16]
String s4 = new String("abc");//char[] value = new char[]{16+“abc”.length()}
}
以上是两个不同的关键字所包含的内容的理解
1.如果说要频繁的对字符串进行改删则需要以上的两个方法中的一个都可以很方便的完成操作,不建议使用String”直接更改“因为这是无法更改的而是new了一个新的方法或对象来进行更改
2。如果开发中不涉及到线程安全问题,建议使用StringBuilder替掉StringBuffer
3.如果开发中大体确定要操作的字符的个数,建议使用带int capacity参数的构造器。因为可以避免底层多次扩容,性能更高
StringBuffer和StringBuilder中的常用方法
增:
append(xx)
删:
delete(int start,int end)
deleteCharAt(int index)
改:
replace(int start,int end,String str)
setCharAt(int index,char c)
查:
charAt(int index)
插:
insert(int index,xx)
长度:
length()
反转(只在StringBuffer中才可以用)
reverse
package leiAndAPI;
public class Stringbuiliber {
public static void main(String[] args){
test2();
}
public static void test2(){
StringBuilder sBuilder=new StringBuilder("hello");
sBuilder.insert(2,1);//在字符串的第二个字符后面插入“,”后面的字符(可以是字符串也可以是数字字符)
sBuilder.insert(2,"abc");//将“abc”插入到字符串第二个字符后
System.out.println(sBuilder);
StringBuilder sBuilder1= sBuilder.reverse();//将原始的字符串翻转后将其赋值给新创建的变量
System.out.println(sBuilder1);
}
结果
如果在最后加一句System.out.println(sBuilder1.length()) ;//将会返回最后组合完成后的实际字符数据也就是9
public static void test3(){
StringBuilder sBuilder=new StringBuilder("hello");
sBuilder.setLength(2);//注意setLength中的“L”需要大写
sBuilder.append("c");
System.out.println(sBuilder);
}
上述示例中setlength(2)的作用是只读取其中的前两个字符放弃后面的字符串
所以我们在运行或操作时“llo”其实被抹掉了
因此我们运行完成后在进行append("c")的操作后会将c添加到he的后面
结果:
5.对比三者的执行效率:
效率从高到低排列:
StringBuilder>StringBuffer>String
jdk8之前的API(日期):
1.system类的currentTimeMillis();
获取当前时间对应的毫秒数,long类型,时间戳
当前时间与1970年1月1日0时0分0秒的毫秒数
常用来计算时间差
package leiAndAPI;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
public class date {
public static void main(String[] args){
test1();
}
public static void test1() {
public static void test1()throws ParseException{
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化:日期--》字符串
Date date=new Date();
String strDate=sdf.format(date);
System.out.println(strDate);
}
}
上述代码逻辑上没有问题但是会在主函数中提示问题,问题为:java: 未报告的异常错误java.text.ParseException; 必须对其进行捕获或声明以便抛出
出现这种问题时我们应当使用try....catch...来解决
改正:
package leiAndAPI;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
public class date {
public static void test1() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String strDate = sdf.format(date);
System.out.println(strDate);
} catch (Exception e) {
e.printStackTrace();
}
}
// public static void test1()throws ParseException{
// SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// //格式化:日期--》字符串
// Date date=new Date();
// String strDate=sdf.format(date);
// System.out.println(strDate);
// }
public static void main(String[] args){
test1();
}
}
try.....catch....finally(捕获代码)的意思是假设当我们遇到问题时,应该如何去做
try(抛出问题):即为那个假设的问题(如果一旦出现执行catch中的语句)
catch(捕获并处理问题):这是遇到问题时处理的语句
finally:如果异常非常的多则最后需要它来进行收低一般可以不写。
完整版捕获语句
try{
//需要运行并且可能抛出异常的代码
}
catch(Exception e)
{
e.printStackTrace();
//基本上可以解决所有的栈堆问题
}
栈和堆的区别:
| 特性 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 内存分配方式 | 自动分配和释放(由编译器管理) | 动态分配(由程序员通过 new 分配,由垃圾回收器释放) |
| 存储内容 | 局部变量、方法调用上下文 | 对象实例、数组 |
| 线程隔离 | 每个线程独立 | 线程共享 |
| 内存大小 | 相对较小(通常由操作系统或虚拟机配置) | 相对较大 |
| 性能 | 访问速度快(局部变量直接存储在栈中) | 访问速度稍慢(需要通过引用访问) |
| 典型问题 | 栈溢出(Stack Overflow) | 内存泄漏(Memory Leak)、内存不足(OutOfMemoryError) |
(以上内容来自于网络)
java的比较器:
comparable中的抽象方法 :compareTo(Object o)
在此方法中,指明如何判断当前类的对象的大小。比如:按照价格的高低进行大小的比较。(或从低到高排序)
如果返回值是正数:当前对象大。
如果返回值是负数:当前对象小。
如果返回值是0,一样大
2.方式一:实现Comparable接口的方式
-
Comparable是Java中的一个接口,用于定义对象的自然排序规则。 -
当一个类实现了
Comparable接口时,它必须实现接口中的compareTo()方法,该方法用于比较两个对象的大小。
实现步骤:
1,具体的类A实现Comparable接口
2,重写Comparable接口中的compareTo(object obj)方法,在此方法中指明比较类A的对象的大小的标准
*3,创建类A的多个实例,进行大小的比较或排序。
package leiAndAPI;
import java.util.Arrays;
public class comper0 {
public static void main(String[] args) {
// test(); // 测试字符串数组排序
test2(); // 测试 Product 对象的比较
}
// 自然排序
public static void test() {
String[] arr = new String[]{"test", "tom", "tony", "daming", "jack"};
Arrays.sort(arr); // 默认从小到大
// 排序后遍历
for (String s : arr) {
System.out.println(s);
}
}
// 自然排序 Comparable 接口排序
public static void test2() {
Product p1 = new Product("huawei", 5999);
Product p2 = new Product("vivo", 6999);
int compare = p1pareTo(p2);
if (compare > 0) {
System.out.println("p1大");
} else if (compare < 0) {
System.out.println("p2大");
} else {
System.out.println("p1和p2一样大");
}
}
// 定义 Product 类并实现 Comparable 接口
static class Product implements Comparable<Product> {
private String name; // 商品名称
private int price; // 商品价格
// 构造方法用来接收上方传下来的参数
public Product(String name, int price) {
this.name = name;
this.price = price;
}
// 实现 compareTo 方法,按照价格进行比较
@Override//这里对compareTo方法进行了重写指明了要比较的元素为两个手机的价格否则上方会
//报错无法运行
public int compareTo(Product other) {
return Integerpare(this.price, other.price);
}
// 重写 toString 方法,方便打印
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price + "}";
}
}
}
运行过程:
/*运行流程:首先为了实现Product类中的compare功能需要声明Product类来实现comparable用来实现他并在其中定义了两个变量即为name 和price 然后需要一个构造方法来接收上方传来的数值和字符串, 这时我们要将数据进行比较但是如果没有声明我们将会无法使用比较器,所以我们重新书写了一个方法名为compareTo来只进行价格的比较, 最后重写了一个方法用来打印(可以没有) */
结果是
p2大
网络爬虫:
目的:爬取固定网站中的数据
此案例是为了爬取姓氏,男生名和女生名,组成一个全新的姓名并打印输出
package com.text;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.URL;
import java.URLConnection;
public class webCrawler {
public static void main(String[] args) {
// 定义变量记录网址
String xingshi = "https://baijiaxing.256cha/";//此网址为姓氏
String nanxingshi = "https://www.meimeiming/zhouyi/5062.html";//此网址为男生名字
String nvxingshi = "https://www.meimeiming/zhouyi/4139.html";//此网址为女生名字
try {
// 爬取数据将网址上所有的数据拼接成字符串
String xingshistr = webcrawler(xingshi);
String nanxistr = webcrawler(nanxingshi);
String nvxistr = webcrawler(nvxingshi);
System.out.println(xingshistr);
System.out.println(nanxistr);
System.out.println(nvxistr);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String webcrawler(String net) throws Exception {
// 定义StringBuilder拼接爬取到的数据
StringBuilder builder = new StringBuilder();
// 创建一个URL对象
URL url = new URL(net);
// 链接上这个网址
URLConnection connection = url.openConnection();
// 读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
builder.append(line).append("\n");
}
// 释放资源
br.close();
// 返回数据
return builder.toString();
}
}
结果为:
结果是错误的,爬虫并没有返回数据
原因:当爬虫尝试去爬取网页中的数据时服务器拒绝了其访问导致结果出现问题,可以尝试更改网址,由于能力有限不做过多更改
集合框架
集合框架(Collection Framework)是 Java 中用于存储和管理一组对象的工具类库。它提供了一系列的接口和实现类,用于高效地组织、存储、检索和操作数据集合。集合框架是 Java 标准库的重要组成部分,广泛应用于各种开发场景。
内存方面需要针对多个数据进行存储的容器:
数组,集合类
数组的特点:
数组一旦初始化其长度就会固定了
数组中的多个元素是有序的,可重复的,连续的并且依次紧密排列的
类型 的一致性(数据安全是优点)不是此类型的数据就不能添加到此数组中
int [] arr=new int[10];
arr[0] = 1;
arr[1]="AA";//编译错误
Object[] arr1=new Object[10];
arr1[0]=new String();
arr1[1]=new Date();
//这是优点,由此可见我们如果需要在同一个数组中存放不同类型的数据通过Object也可以实现对单独元素类型的设置
数组的弊端:
数组一旦初始化,其长度就确定了
数组中存储的数据具有单一性。对于无序,不可重复的数据就无能为力了
数组中可用的方法或属性都极少,具体的需求需要我们自己组织代码逻辑
针对数组的插入,排序筛选等操作需要很麻烦的操作或者说复杂度比较高
java集合框架体系(java.util包下我们接下来的操作都是对其包下面的子接口进行操作)
java.util.Collection(包下的接口):存储一个一个的数据
|--------子接口:List: 存储有序的可重复的数据
ArrayList(l主要实现类)、LinkedList、特点:有序,可重复,有索引
Vector
|--------子接口:Set:存储无序的不可重复的数据(互异性,确定性,无序性)
HashSet,无序,不重复,无索引。
LinkedHashSet,有序,不重复,无索引
TreeSet,按照大小默认升序排序,不重复,无索引
java.util.Map(包下的接口):存储一对一对的数据(key-value键值对,(x1,y1)、(x2,y2)----->y=f(x))
|--------- HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
4.学习的程度把握:
层次1:针对具体特点的多个数据,知到选择相应的适合的接口的主要实现类,会实例化,会调用常用的方法。
层次2:区分接口中不同的实现类的区别。
层次3:针对常用的实现类,需要熟悉底层的源码.2熟悉常见的数据结构
对比Collection包中的List和Set子接口的不同
包中常用的关键字
package jihe;
import java.util.ArrayList;
import java.util.Collection;
public class collection1 {
public static void main(String[] args) {
//多态写法 注意(在使用两个接口时,需要在函数外导入他们所在的包否则无法运行)
Collection<String> list =new ArrayList<>();//其中<>不能为空,这里指的是集合中所指的泛型如果不知道可以写“?”但是并不建议因为会影响集合的操作性
list.add("hello");
list.add("world");
System.out.println(list);
System.out.println(list.isEmpty());//判断是否为空这里返回的是false
list.clear();//清空集合内的元素
//获取集合的大小
System.out.println(list.size());//数据的个数和数组中list.leng()作用相同
}
}
Collection遍历:
方式1(迭代器):lterator是迭代器的代表
注意:使用迭代器取元素的同时不要越界否则会发生异常
基于上面的方法我们可以看当需要用一次取一次很麻烦那么我们可以用while()循环
语法
while(iterator.hasNext()){//查看所给出的位置是否为空如果是True进入
String 变量名= iterator.next();//定义一个新的变量用来存放集合中的数据
System.out.println(变量名);
}
此时可以看出便捷了许多
方式2 增强for():
1,其实就是上面迭代器循环的简化版
2,增强for循环可以用来遍历集合也可以用来遍历数组
格式:
for(元素的数据类型 变量名(随便取):数组或者集合)
{
System.out.printf(变量名(同上));
}
在idea中可以直接 集合名.for《回车》会自动为你补齐
{
public static void main(String[] args){
Collection<String> list=new ArrayList<>();
list.add("小明");
list.add("汤姆");
list.add("托尼");
list.add("琳达");
System.out.println(list);
//从集合对象中获取迭代器对象
Iterator<String> iterator=list.iterator();
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// while(iterator.hasNext()){//询问位置内是否有数据如果有是true如果有将会移动到下一个位置
// String n=iterator.next();
// System.out.println(n);
// }
for(String s:list){
System.out.println(s);
}
}
}
最后那一段也可以简化
c.forEach(new Consumer<String>()){
public void accept(String s){
System.out.println(s);
}
}
c.forEach(new Consumer<String>()){
public void accept(String s){
System.out.println(s);
}
}
简化
c.forEach((String s)->{
System.out.println(s);
})
最后简化为
c.forEach(s ->System.out.println(s));
c.forEach(System.out.::println);
List集合:
特点,特有方法:
有序,可重复,有索引
但是ArrayList和LinkedList他们之间底层架构不同适合的场景不同!
用法:
package jihe;
import java.util.ArrayList;
import java.util.List;
public class List1 {
public static void main(String[] args) {
//创建一个ArrayList集合对象有序可重复,有索引
List<String> list = new ArrayList<>();//因为List是一个接口接口是不能用来new的所以要用ArrayList(这是一行经典代码)这是典型的多态
list.add("a");
list.add("b");
list.add("b");
list.add("d");
System.out.println(list);//[a, b, b, d]
//public void add(int index,E element):在某个索引位置插入元素
list.add(2,"c");
System.out.println(list);
//public E remove(int index):根据索引删除元素,返回被删除元素
list.remove(2);
System.out.println(list);
//public E get(int index):返回集合中指定位置的元素
System.out.println(list.get(3));
//public E set(int index,E element):修改索引位置处的元素,修改成功后,会返回原来的数据
list.set(2,"hello");
System.out.println(list);
}
}
结果为
[a, b, b, d]
[a, b, c, b, d]
[a, b, b, d]
d
[a, b, hello, d]
注意上方给的数据是指的所在数据的角标也就是说如果返回第二个数据那么就是(2-1)(角标和数组一样都是从0开始)
遍历方式:
1,for()循环因为List有索引
2,迭代器
3,增强for循环
4,Lambda表达式
package jihe;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Iterator;
public class List1 {
public static void main(String[] args) {
//创建一个ArrayList集合对象有序可重复,有索引
List<String> list = new ArrayList<>();//因为List是一个接口接口是不能用来new的所以要用ArrayList(这是一行经典代码)这是典型的多态
list.add("a");
list.add("b");
list.add("b");
list.add("d");
System.out.println(list);//[a, b, b, d]
// //public void add(int index,E element):在某个索引位置插入元素
// list.add(2,"c");
// System.out.println(list);
// //public E remove(int index):根据索引删除元素,返回被删除元素
// list.remove(2);
// System.out.println(list);
// //public E get(int index):返回集合中指定位置的元素
// System.out.println(list.get(3));
// //public E set(int index,E element):修改索引位置处的元素,修改成功后,会返回原来的数据
// list.set(2,"hello");
// System.out.println(list);
//
//for循环
for(int i=0;i<list.size();i++){
System.out.print(list.get(i));
}
System.out.println( );
//迭代器
Iterator<String> it=list.iterator();
while(it.hasNext()){//判断集合内是否还有下一个元素有的话返回True
System.out.print(it.next());//it.next是获取集合内的下一个的元素
}
System.out.println( );
//增强for循环(foreach)
for (String s : list) {//可以直接写成list.for然后回车
System.out.print(s);
}
System.out.println( );
//Lambda
list.forEach(s->{
System.out.println();
});
}
}
结果为
可以看到都能够实现对集合元素的输出
ArrayList集合的 底层原理:
特点:
-
动态扩容,支持重复元素,保持插入顺序。
-
支持索引访问,随机访问高效。
-
线程不安全,适合频繁读取,不适合频繁修改。
-
提供多种遍历方式,包括
for循环、for-each循环和Iterator。 -
注意它基于动态数组来实现所以集合中的元素如果在出现删除后的空缺后会自动向前补齐
基于数组来实现的
查询速度快:会从起始地点通过增加移动单位直接到指定的位置
删除速度慢:如果要删除某一个数据那么其后方的数据将会依次向前
增加数据的速度慢:如果要从已经排列好的集合某位置中插入一个数据那么需要将此位置后的数据集体向后移动一个单位或者增加扩容
1,利用无参数构造器创建的集合,会在底层创建一个默认长度为0的数组
2,添加新数据时,底层会创建一个数组长度为10的数组
3,如果超出10位数组会自动扩充1.5倍
4,如果数据超出1.5倍以数组的实际长度为准
总结:
查询快增删慢。
LinkedList集合的底层原理:
基于双链表来进行存储的
链表:
链表中的结点是独立的对象,在内存中是不连续的,每个节点包含数据值和下一个结点的地址
特点:
查询比较慢,无论是那个数据都只能从头开始找
增删非常的快
总结:查询慢,增删快
两种链表的区别
增加的方法
LinkedList的基本使用和压栈和出栈:
package jihe;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.LinkedList;
public class List2 {
public static void main(String[] args){
//test1();
test2();
}
public static void test1(){
LinkedList<String> list = new LinkedList<>();
//创建一个队列
list.addLast("1号选手");
list.addLast("2号选手");
list.addLast("3号选手");
list.addLast("4号选手");
System.out.println(list);
//出队
System.out.printf( list.removeFirst());
System.out.printf("", list.removeFirst());
System.out.printf("", list.removeFirst());
System.out.println(list);
}
public static void test2(){
//创建一个栈
LinkedList<String> stack=new LinkedList<>();
//压栈(push)
stack.addLast("第一颗子弹");
stack.addLast("第二颗子弹");
stack.addLast("第三颗子弹");
stack.push("第四颗子弹");//相当于stack.addFirst
System.out.println(stack);
//出栈
System.out.println( stack.removeFirst());
System.out.println( stack.removeFirst());
System.out.println( stack.pop());//相当于stack.removeFirst
System.out.println(stack);
}
}
结果如下
方式3 lambda表达式:
由此可以看出两个子接口的差别是非常大的
Set系列集合的特点:
HashSet集合的特点
哈希值:
就是一个int 类型的数值,Java中的每个对象都有一个哈希值。
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值
public int hashCode():返回对象的哈希码值。
对象哈希码的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
基于哈希表来实现
哈希表是一种增删改查数据,性能都较好的数据结构
哈希表
jdk8之前,哈希表=数组+链表
jdk8开始,哈希表=数组+链表+红黑树
特点:
1,创建一个默认长度为16的数组,默认加载因子为0.75,数组名table
2,使用元素的哈希值对数组的长度求余计算出应存入的位置
3,判断当前位置是否为null,如果是null直接存入
4,如果不为null,表示有元素,则调用equals方法比较
相等,则不存,不相等,则存入数组
JDK8之前,新元素存入数组,占老元素的位置,老元素挂下面
JDK8开始之后,新元素直接挂在老元素下面。
假如数组占满了,会继续扩容但是会导致链表过长,查询性能过低
JDK8开始后,哈希表中引入了红黑树后,进一步提高了操作数据的性能。
条件:
当链表长度超过8,且数组长度>=64时,自动将链表转换成红黑树
了解一下数据结构(红黑树)
存在的问题:
这种现象被称为瘸子现象
改变方法:
package jihe;
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package jihe;
import java.util.HashSet;
import java.util.Set;
public class Hashcunchu {
public static class Main {
public static void main(String[] args) {
// 创建一个HashSet集合,用于存储Person对象
Set<Person> people = new HashSet<>();
// 向HashSet中添加一个Person对象,姓名为"Alice",年龄为30
people.add(new Person("Alice", 30));
// 再次尝试添加一个姓名为"Alice"、年龄为30的Person对象
// 由于Person类正确重写了equals和hashCode方法,HashSet会识别它们为同一个对象,不会重复添加
people.add(new Person("Alice", 30));
// 添加一个不同的Person对象,姓名为"Bob",年龄为25
people.add(new Person("Bob", 25));
// 打印HashSet中的所有元素
// 输出结果将显示HashSet成功去除了重复的Person对象
System.out.println("HashSet elements: " + people);
}
}
}
结果
深入了解HashSet集合去重复的机制
注意:HashSet是不会去重复的
如果想要去掉重复数据需要我们进行将两个相同数据的Hash值和其中存入的内容也相同才可以
判断方法是使用equals方法(只要两个对象的内容一样就会返回True)
HashCode:只要两个对象 内容一样,返回的hash值就是一样的
结论:如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
LinkedHashSet集合的底层原理:
有序,不重复,无索引
1 依然是基于哈希表(数组、链表、红黑树实现的)
2 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置
TreeSet集合:
可排序,不重复,无索引
注意:
对于数值类型:integer,Double,默认按照数值本身的大小进行升序排序
对于字符串类型的数据,默认以首字符的编号升序排序
对于自定义数据类型是无法进行默认排序的如(Student())
因为在自定义对象中所传入的数据类型不同就像是不同的形参向内传输不同的数据因此它无法运行 或者说直接抛出异常
自定义排序规则:
方式2的排序方法
三种集合方式对数据的影响和不同:
package jihe;
import java.util.*;
public class set2 {
public static void main(String[] args){
Set<Integer> set = new HashSet<>();//无序,不重复,无索引
Set<Integer> set1 = new LinkedHashSet<>();//有序,不重复,无索引
Set<Integer> set2 = new TreeSet<>();//可排序,不重复,无索引
set.add(6);
set.add(2);
set.add(20);
set.add(3);
set.add(1);
set.add(1);
set.add(7);
set.add(8);
set.add(9);
set1.addAll(set);//将集合中的数据全部添加到另外两个集合中去
set2.addAll(set);
System.out.println(set);
System.out.println(set1);
System.out.println(set2);
}
}
最后总结:
集合元素并发性的问题
假如我们操作的一个集合内有许多名字,但是我们要把名字中带有“李”字的去掉那么我们应该直接使用Collection集合中提供的remove()方法,这样可以防止在进行名字的删除时出现漏删的现象
Map集合:
特点:
1 Map集合被称之为双列集合也就是说在集合中一次必须存入两个元素而Collections集合是单列集合
2 Map集合的每个元素Key=Value称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做键值对集合。
3 Map集合的键是唯一的不可重复的而值是可以重复的,但是键和值都是一 一对应的,每个键只能找到属于自己的那个值
Map集合体系的特点:
HashMap:无序,不重复,无索引
LinkedHashMap:有序,不重复,无索引
TreeMap:可排序,不重复,无索引
首先第一个
package jihe.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class map1 {
public static void main(String[] args){
//Map<键的类型,值的类型> 集合变量名(可随意)= new HashMap<>()
Map<String,Integer> set = new HashMap<>();//一行经典代码,按照键,无序,不重复,无索引
set.put("手表",100);//用于往集合内输入数据
set.put("手表",2000);//如果是重复数据后面的值在运行时会覆盖掉前面的数据(对于键来说)
set.put("手机",3);
set.put("电脑",4);
System.out.println(set);
}
}
结果为
可以看到此集合的特点是无序,不重复,无索引的
LinkedHashMap:
package jihe.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.LinkedHashMap;
public class map1 {
public static void main(String[] args){
//Map<键的类型,值的类型> 集合变量名(可随意)= new HashMap<>()
// Map<String,Integer> set = new HashMap<>();//一行经典代码,按照键,无序,不重复,无索引
Map<String,Integer> set = new LinkedHashMap<>();
set.put("手表",100);//用于往集合内输入数据
set.put("手表",2000);//如果说有重复数据的话,后面的值会覆盖掉前面的值,(这是对于键来说的)
set.put("手机",3);
set.put("电脑",4);
set.put(null,null);
System.out.println(set);
}
}
结果为:
有序,无重复,无索引
TreeMap
package jihe.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.TreeMap;
public class map1 {
public static void main(String[] args){
//Map<键的类型,值的类型> 集合变量名(可随意)= new HashMap<>()
// Map<String,Integer> set = new HashMap<>();//一行经典代码,按照键,无序,不重复,无索引
Map<String,Integer> set = new LinkedHashMap<>();
set.put("手表",100);//用于往集合内输入数据
set.put("手表",2000);//如果说有重复数据的话,后面的值会覆盖掉前面的值,(这是对于键来说的)
set.put("手机",3);
set.put("电脑",4);
set.put(null,null);
System.out.println(set);
Map<Integer,String> set1=new TreeMap<>();
set1.put(20,"王五");
set1.put(20,"赵六");
set1.put(21,"李四");
set1.put(23,"张三");
System.out.println(set1);
}
}
这是可以进行自己排序的TreeMap集合
结果为
集合的并发修改异常问题:
package jihe.Map;
import java.util.*;
public class bingfa {
public static void main(String[] args) {
List list=new ArrayList();
list.add("李老大");
list.add("二老李");
list.add("小李");
list.add("王铁锤");
System.out.println(list);
//要求从集合中找出带有李字的人的名字
Iterator itr=list.iterator();
while(itr.hasNext()){
String name=itr.next().toString();
if(name.contains("李"))//目的是从集合中找出带有李字的名字来
{
System.out.println(name);
//List.remove(name);//会发生并发性的报错
}
}
}
}
貌似没什么问题但是如果我将注释的那行代码运行时他会抛出一个异常错误即集合的并发性错误
原因:
因为我们所调用的这个迭代器方法有bug,但是迭代器自己知道会发生这个bug所以他会抛出异常给你
但是如果我们换成for循环呢
package jihe.Map;
import java.util.*;
public class bingfa {
public static void main(String[] args) {
List list=new ArrayList();
list.add("李老大");
list.add("二老李");
list.add("小李");
list.add("王铁锤");
System.out.println(list);
//要求从集合中找出带有李字的人的名字
Iterator itr=list.iterator();
// while(itr.hasNext()){
// String name=itr.next().toString();
// if(name.contains("李"))//目的是从集合中找出带有李字的名字来
// {
// System.out.println(name);
// //List.remove(name);//会发生并发性的报错
// }
//
// }
for(int i=0;i<list.size();i++){//list.size()求集合的长度
String name=list.get(i).toString();//将集合中的数据转化并赋值到name中
if(name.contains("李")){//判断是否含有李字
list.remove(name);//删除带有李字的名字
}
}
System.out.println(list);
}
}
结果
从上方代码中我们可以看到我们的代码直接漏下了一个这是因为在运行时指针的指向如果判断成功后会自动向下跳转因此我们的第二个数据就被漏删了
解决方法
在上方代码的if语句中增加一个i--;即可这样他在判断完成后就会将其指针返回到当前位置以抵消自动增加的一个单位
那么迭代器中如何进行修改呢?
解决方法:
package jihe.Map;
import java.util.*;
public class bingfa {
public static void main(String[] args) {
List list=new ArrayList();
list.add("李老大");
list.add("二老李");
list.add("小李");
list.add("王铁锤");
System.out.println(list);
//要求从集合中找出带有李字的人的名字
Iterator itr=list.iterator();
while(itr.hasNext()){
String name=itr.next().toString();
if(name.contains("李"))//目的是从集合中找出带有李字的名字来
{
// System.out.println(name);
//List.remove(name);//会发生并发性的报错
//解决方法
itr.remove();
}
}
//for(int i=0;i<list.size();i++){//list.size()求集合的长度
// String name=list.get(i).toString();//将集合中的数据转化并赋值到name中
// if(name.contains("李")){//判断是否含有李字
// list.remove(name);//删除带有李字的名字
// }
}
System.out.println(list);
}
}
使用迭代器自带的删除工具就可以避免被误删,其原理和for循环修改后一样
collections基础知识增强:
可变参数:
就是一种特殊形参,定义在方法里,构造器的形参列表里,格式是:数据类型 ...参数名称;
可变参数的特点和好处
特点:可以不传数据给他;可以传一个或者同时传多个数据给他;也可以传一个数据给他
好处:常常用来灵活的接收数据
可变参数的定义
例:int...nums 这就是一个可变参数只不过是在形参的位置的定义中我们在变量和类型之间增加了"..."
package jihe;
import java.util.Arrays ;
public class ParamTest {
public static void main(String[] args){
//特点
test1();
test1(10);
test1(10,20,30);
test1(new int []{1,2,3,4});
}
public static void test1(int ...nums){//此时定义了一个可变参数
//可变参数在方法内部,本质就是一个数组
System.out.println(nums.length);//可变参数中的数组长度
System.out.println(Arrays.toString(nums));//可变参数数组中的数据
System.out.println("----------------------------");
}
}
结果为
0
[]
----------------------------
1
[10]
----------------------------
3
[10, 20, 30]
----------------------------
4
[1, 2, 3, 4]
----------------------------
进程已结束,退出代码为 0
从上面的结果中可以看出代码在进行逐级传参时时能够传输不同规模的数据甚至可以是数组,这便是可变参数的优点
但是注意
在一个构造方法中只能有一个可变参数
例如:public static void main(int ...nums)[} √
public static void main(int...nums,double...nums1) ×
掌握collections集合工具类的使用
package jihe;
import jihe.Set.Set1;
import java.util.Collections;
import java.util.ArrayList ;
import java.util.List;
public class ParamTest {
public static void main(String[] args){
//一次性添加多个数据
List<String> names = new ArrayList<String>();
Collections.addAll(names,"张三", "李四","王五","张麻子");
System.out.println(names);
//将集合内数据的顺序打乱(可以在斗地主中使用)
Collections.sort(names);
System.out.println(names);
//对集合进行升序排序(注意要注意类型否则会抛出异常因为它无法进行排序)
List<Integer> nums=new ArrayList<>();
nums.add(1);
nums.add(2);
nums.add(-1);
Collections.sort(nums);
System.out.println(nums);
List<Set1.Student> students = new ArrayList<>();
students.add(new Set1.Student("张三", "22","176"));
students.add(new Set1.Student("李四", "25","175"));
students.add(new Set1.Student("王五", "23","186"));
students.add(new Set1.Student("张麻子", "21","176.5"));
System.out.println(students);
//比较
// //特点
// test1();
// test1(10);
// test1(10,20,30);
// test1(new int []{1,2,3,4});
}
// public static void test1(int ...nums){//此时定义了一个可变参数
// //可变参数在方法内部,本质就是一个数组
// System.out.println(nums.length);//可变参数中的数组长度
// System.out.println(Arrays.toString(nums));//可变参数数组中的数据
// System.out.println("----------------------------");
// }
}
结果为
实例:
泛型:
定义:
泛型是一种允许在定义类、接口和方法时使用类型参数的机制。通过泛型,你可以编写能够处理任意类型数据的代码,同时在编译时提供类型安全检查。
如果没有泛型的话我们可以向其中添加任意的数据,但是如果我们在获取数据的时候,无法使用他的特有行为
Java中的泛型是伪泛型,在Java文件中是泛型但是在class文件中会消失,避免了强制类型转换可能出现的异常
好处:编译时检查,减少了数据类型转换
泛型类、接口:
定义语法:
class <泛型标识>{
private 泛型标识 变量名;
}
常用的泛型标识:T,E,K,V
泛型方法:
注意:
1.泛型类在创建对象的时候,没有指定类型,将按照object来操作
2.泛型类不支持基本数据类型,只能是类类型
3,泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
//源码:集合实现过程的源码
package com.fanxing;
import java.util.Arrays;
public class MyArraysList<E> {
Object[] obj=new Object[10];
int size;
public boolean add(E e) {
obj[size] = e;
size++;
return true;
}
public E get(int index) {
return (E) obj[index];
}
public String toString(){
return Arrays.toString(obj);
}
}
类型通配符:
类型擦除:
泛型和数组:
泛型和反射:
复习例题:
面向对象:
题目:创建一个student类的学生对象用来存储学生的姓名,年龄,并通过用户键入
import java.util.Scanner;
// 定义一个Student类
class Student {
// 成员变量
private String name;
private int age;
// 无参构造方法
public Student() {
}
// 带参构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写toString方法,方便打印学生信息
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main {
public static void main(String[] args) {
// 创建Scanner对象,用于读取用户输入
Scanner scanner = new Scanner(System.in);
// 提示用户输入学生的名字
System.out.print("请输入学生的名字:");
String name = scanner.nextLine();
// 提示用户输入学生的年龄
System.out.print("请输入学生的年龄:");
int age = scanner.nextInt();
// 创建Student对象,并设置属性值
Student student = new Student();
student.setName(name);
student.setAge(age);
// 打印学生的信息
System.out.println("学生信息如下:");
System.out.println(student);
// 关闭scanner
scanner.close();
}
}
上述的Main类可以重新创建一个Java类
题目:定义一个集合添加一些学生对象,并进行遍历,学生类的属性为:姓名年龄
对象的构建:
package com.text;
//创建一个学生类
public class Student {
private String name;
private int age;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
注意:下方代码与集合结合:
package com.text;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
public class Studenttest {
public static void main(String[] args) {
List<Student> list=new ArrayList<>();
//创建学生对象
Student s=new Student("zhangsan",19);
Student s1=new Student("李四",20);
//向集合中添加数据
list.add(s);
list.add(s1);
for (int i = 0; i < list.size(); i++) {
//获取数据
Student result= list.get(i);
//如果按照下面的方法书写的话打印的是地址值
// System.out.println(result);
System.out.println(result.getName()+" "+result.getAge());
}
}
}
题目:判断手机的价格如果价格大于3000直接输出
package com.text;
//判断手机的价格如果大于3000直接输出
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
public class iphone {
private String pinpai;
private int prace;
public iphone() {
}
public iphone(String pinpai, int prace) {
this.pinpai = pinpai;
this.prace = prace;
}
public String getPinpai() {
return pinpai;
}
public void setPinpai(String pinpai) {
this.pinpai = pinpai;
}
public int getPrace() {
return prace;
}
public void setPrace(int prace) {
this.prace = prace;
}
public static void main(String[] args){
//创建集合对象
List<iphone> sr= new ArrayList<>();
//初始化数据
iphone s1=new iphone("小米",1000);
iphone s2=new iphone("苹果",8000);
iphone s3=new iphone("锤子",2999);
//向集合中添加数据
sr.add(s1);
sr.add(s2);
sr.add(s3);
//判断数据并输出
for (int i = 0; i < sr.size(); i++) {
iphone i1=sr.get(i);
int i2=i1.getPrace();
if(i2>3000){System.out.println(i1.getPinpai()+" "+i1.getPrace());}
}
}
}
考试总结和错题纠正:
第一题:
某双色球系统,红球是1-35之间的数据,篮球是1-15之间的数据,一注双色球号码是由6个不重复的号码和1个篮球号码组成的。
具体功能点的要求如下:
- 请随机一组双色球号码,6个红球号码要求不重复,且升序排序输出,篮球号码放在最后面输出。
- 假设上图展示的是中奖号码,请用程序判断出第一个功能随机出的双色球号码中了几个红球和几个篮球。
考试中产生的问题是不会使随机数的产生不重复
代码实现如下:
import java.util.*;
public class DoubleColorBall {
public static void main(String[] args) {
// 随机生成双色球号码
List<Integer> randomRedBalls = generateRandomRedBalls();
int randomBlueBall = generateRandomBlueBall();
System.out.println("随机生成的双色球号码为:");
System.out.print("红球号码:");
for (int redBall : randomRedBalls) {
System.out.print(redBall + " ");
}
System.out.println("蓝球号码:" + randomBlueBall);
// 假设的中奖号码
List<Integer> winningRedBalls = Arrays.asList(3, 12, 18, 25, 30, 34); // 示例中奖红球号码
int winningBlueBall = 8; // 示例中奖蓝球号码
// 判断中奖情况
int[] matchResult = checkWinning(randomRedBalls, randomBlueBall, winningRedBalls, winningBlueBall);
System.out.println("随机生成的号码中,中了 " + matchResult[0] + " 个红球和 " + matchResult[1] + " 个蓝球。");
}
// 随机生成6个不重复的红球号码,并升序排序
public static List<Integer> generateRandomRedBalls() {
List<Integer> redBalls = new ArrayList<>();
Random random = new Random();
while (redBalls.size() < 6) {
int randomRedBall = random.nextInt(35) + 1; // 生成1-35之间的随机数
if (!redBalls.contains(randomRedBall)) { // 确保不重复
redBalls.add(randomRedBall);
}
}
Collections.sort(redBalls); // 升序排序
return redBalls;
}
// 随机生成1个蓝球号码
public static int generateRandomBlueBall() {
Random random = new Random();
return random.nextInt(15) + 1; // 生成1-15之间的随机数
}
// 判断中奖情况
public static int[] checkWinning(List<Integer> randomRedBalls, int randomBlueBall,
List<Integer> winningRedBalls, int winningBlueBall) {
int redBallMatchCount = 0;
int blueBallMatchCount = 0;
// 判断红球中奖情况
for (int randomRedBall : randomRedBalls) {
if (winningRedBalls.contains(randomRedBall)) {
redBallMatchCount++;
}
}
// 判断蓝球中奖情况
if (randomBlueBall == winningBlueBall) {
blueBallMatchCount = 1;
}
return new int[]{redBallMatchCount, blueBallMatchCount};
}
}
上述代码为AI生成目的是理解其中的解题思路
创新点:
升序排序:Collections.sort(集合名称);
自己学习并整理后的代码
package com.kaoshi;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Random;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class qiu {
public static void main(String[] args) {
Set<Integer> arr=new HashSet<>();
int brr=0;
Random rand=new Random();
for(int i=0;i<6;i++) {
int a= rand.nextInt(1, 35);//将随机的数据传递到数组中并且根据hashset集合的特性其中的值不会重复,且自动升序排序
arr.add(a);
}
brr=rand.nextInt(1,15);
ArrayList<Integer> sore=new ArrayList<>(arr);//将HashSet中的元素全部 转移到ArrayList集合中去
Collections.sort(sore);//对于List集合中的数据升序排序
for(Integer a:sore) {
System.out.print(a+" ");
}
//System.out.print(sore+" ");
System.out.println(" "+brr);
//System.out.print(arr);
// System.out.println(brr);
}
}
第二题(15分)
需求
目前有100名囚犯,每个囚犯的编号是1-200之间的随机数。现在要求依次随机生成100名囚犯的编号(要求这些囚犯的编号是不能重复的),然后让他们依次站成一排。(注:位置是从1开始计数的),接下来,国王命令手下先干掉全部奇数位置处的人。剩下的人,又从新按位置1开始,再次干掉全部奇数位置处的人,依此类推,直到最后剩下一个人为止,剩下的这个人为幸存者。
具体功能点的要求如下:
请输出幸存者的编号,以及他第一次所占的位置值是多少。
评分细则
- 能做出第一步:生产100个随机编号,且占位成功的,给3分。
- 能成功删除奇数位置处的数据的,给5分。
- 能正确获取结果的给2分。
AI代码:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class PrisonerSurvival {
public static void main(String[] args) {
// 第一步:生成100个不重复的随机编号
Set<Integer> prisonerNumbers = new HashSet<>();
Random rand = new Random();
while (prisonerNumbers.size() < 100) {
int randomNum = rand.nextInt(1, 201); // 生成1到200之间的随机数
prisonerNumbers.add(randomNum);
}
// 将集合转换为列表,方便后续操作
ArrayList<Integer> prisonerList = new ArrayList<>(prisonerNumbers);
// 第二步:模拟逐步淘汰过程
int initialPosition = 1; // 初始位置从1开始
while (prisonerList.size() > 1) {
// 模拟干掉奇数位置的人
for (int i = prisonerList.size() - 1; i >= 0; i -= 2) {
prisonerList.remove(i);
}
// 每轮淘汰后,位置重新计数
initialPosition *= 2;
}
// 第三步:输出幸存者的编号和第一次所占的位置值
int survivorNumber = prisonerList.get(0);
System.out.println("幸存者的编号是:" + survivorNumber);
System.out.println("幸存者第一次所占的位置值是:" + initialPosition);
}
}
核心代码理解
以初始坐标为点没结束一轮,坐标更新,完成接下来的操作。
因为ArrayList集合基于动态数组的特性它在删除元素后的空位后面的元素会自动向前补齐
集合长度刷新在次进入while循环直到剩最后一位 下标刷新
版权声明:本文标题:java自学笔记和自我总结(随时更新中) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1766534777a3467594.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论