JAVA
2022补充知识
栈
1.定义栈
Stack<栈中的数据类型> name=new Stack<>();
import java.util.Stack; //存在于Java.util包中,需要引入
Stack<int> sta=new Stack<>();
2.栈中常用方法
sta.push(3); //向栈中打入数据3
sta.pop(); //弹出一个数据
sta.isEmpty(); //返回一个bool值 表示栈是否为空
HashMap
本质:存储Key --value键值对的对象
注意 HashMap和HashSet都是无序的,不会记录插入顺序
比起数组,链表需要按序查找的局限,哈希表具有索引直取的优良特性
import java.util.HashMap; //存在于Java.util包中,需要引入
//HashMap 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。
HashMap<Integer,String>Sites=new HashMap<Integer,String>();
//put添加键值对
Sites.put(1,"feel");
Sites.put(2,"hello");
System.out.println(Sites);//{1=feel,2=hello}
//键与值也可以取其他类型:
HashMap<String,String> Sites=new HashMap<String,String>();
Sites.put("yy","hello");
Sites.put("xx","helllll");
//通过key操作数据
//get查询元素
System.out.println(Sites.get("yy")) ;//hello
//remove 删除元素
Sites.remove("yy");
//计算哈希表大小
xxxxx(Sites.size());//2
//获取哈希表中所有value(返回一个数组)
Sites.values()
//获取哈希表中所有的key(返回一个数组)
Sites.keySet()
// 检查 hashMap 中是否存在指定的 key 对应的映射关系 return bool
Sites.containsKey(xx)
//检查 hashMap 中是否存在指定的 value 对应的映射关系。
Sites.containsValue(xx)
HashSet(哈希集)
本质:一个没有重复元素的集合(并不是数组)
import java.util.HashSet;
HashSet<String> sites=new HashSet<String>();
sites.add("a");
sites.add("b");
sites.add("a")//重复的元素将不再被添加
System.out.println(sites); //[a,b]
//判段相关元素是否存在
xxx(sites.contains("a")); //true
//删除元素
sites.move("a");
//计算元素个数
sites.size();
//也可以使用增强for循环迭代
ArrayList类
本质:数组队列,没有固定的大小限制,可以添加或删除元素
快速上手
import java.util.ArrayList; //引入
ArrayList<E> xxx=new ArrayList<>();
ArrayList中的元素都是对象,所以E只能为引用数据类型,当然目前我对这些名词也不是非常了解,总之需要用包装类
import java.util.Comparator;
ArrayList<String>sites=new ArrayList<String>();
//注意索引从0开始
//添加元素
sites.add("cirs");
sites.add("william");
//访问元素
xxx(sites.get(1)) ; //william
//修改元素
sites.set(1,"jackson"); //将William更改为jackson
//删除元素
sites.remove(1);
//计算大小
sites.size();
//排序 sort()方法
sites.sort(xxx); //参数为顺序方式
// 在此,Java Comparator 接口的 naturalOrder() 方法指定元素以自然顺序(升序)排序Comparator 接口还提供了对元素进行降序排列的方法:使用前记得引入
sites.sort(Comparator.naturalOrder());
sites.sort(Comparator.reverseOrder());
//其他方式实现升序
sites.sort((a,b)->a-b);
数组方法
增强for循环
int[] nums={1,2,3};
//遍历数组值 该方法通过定义一个变量(如num)装载当前遍历到的数组值
for(int num:nums){
System.out.println("当前遍历到的数字是"+num);
}
//keySet() 方法可以与 for-each 循环一起使用,用来遍历迭代 HashMap 中的所有键。4
for(int key:sites.keySet()){
System.out.println("key:"+key);
}
2021前置知识
数组
1.初始化方式
//静态:立即赋值
int[] arr;//声明
arr=new int[]{1,2,3}; //先声明再初始化(赋值)
int[] arr=new int[]{1,2,3};//声明时赋值(支持简写)
/*简写*/ int[] arr={1,2,3}
//动态:系统分配初始值
int[] arr=new int[3]; //数组长度为3 默认值为0
关于 int[] 我的理解:
类似js 中,可以把int[]看成数组字面量,和Array类似,是一种数组标识,故可以理解为何静态初始化时 [] 中不能加数字。
上述的Array初始化时都需要指定固定的大小,使用ArrayList类则可以突破这一限制,可以理解为一个数组队列,可以任意添加,删除,寻找,甚至排序元素(见上方)
2.内存原理
数组变量(数组名)存储数组对象的首地址,被储存在栈中,初始化后,数组对象被存储在堆中连续的存储空间。一旦初始化完成,内存分配即结束,后续再无法改变数组的长度。
3.二维数组
//静态初始化
int[][] arr=new int[][]{
{1},
{1,2,3},
{4,5}
};//类似的也可以简写、也可以先声明再写
//动态
int[][] arr=new int[3][4]; //动态初始化3*4列的数组
//ps:对于一个二维数组,二维数组的长度就是其行的数目,即3
4.特殊的二维数组—锯齿形(三角状)
//动态初始化锯齿行数组
int[][] arr=new int[5][];//只指定第一个下标
arr[0]=new int[5];
arr[1]=new int[4];
arr[2]=new int[3];
arr[3]=new int[2];
arr[4]=new int[1];
5.补充:打印数组的方式
import java.util.Arrays;
int[] arr= {1,2,3};
System.out.println(Arrays.toString(arr)); //打印数组 [1,2,3]
//ps:Arrays.toString()方法内核是创建了一个StringBuilder,依次添加‘[’ '1' '2' '3' ],最后用toString转换为String类型,返回一个字符串
方法
补充:注意:java中,函数的形参必须是完整的参数类型,加参数名,即 int num1, 不能只写参数类型:int
1.定义
理解角度:就是函数,部分代码封装起来,以供程序反复调用。
//定义的语法格式
/*例子*/ public static int hanshuming(int num1,int num2){
XXXXX
}//修饰符 返回值类型 方法名称 (形参列表)
2.方法调用的内存原理
每调用一个方法,jvm创建一个新的栈帧,用于保存传入形参和局部变量的值,该方法操作的都是该栈帧中的值。待调用结束,释放对应栈帧。
可见:实参和形参的存储单元不同
3.方法重载
重载概念:方法名称相同,形参列表不同的方法(即参数类型,顺序,数目,任意一个不同即可),对返回值不做要求。在调用方法时,编译器会寻找最准确的方法进行调用。
4.数组的引用传递
向方法传递数组时,传递的是数组名,相当于把地址值赋给了栈中的另一个变量pa, pa指向堆中的数组对象,可以对其进行操纵,对栈中的局部变量newarr进行初始化,newarr的数组对象被存在堆区,方法执行完毕,局部变量pa、newarr都将被释放,但newarr值被传递出去,给了copy,copy指向那片堆区
我认为ps:方法调用完毕,释放的是栈中的变量,即栈中保存的地址,而其指向的数组对象在堆区。故不会消失,copy得到地址后,还可以指向
int[] array={1,2,3};
int[] copy=rev(array);
public static void rev(int[] pa){
int[] newarr=new int[pa.length];
/*复制值的过程 xxxxxxx*/
return newarr;
}
面向对象
1.类与对象
类是封装对象行为和属性的载体,对象是类抽象出来的实例。
类的特性:封装(隐藏类的细节,增加安全性),继承,多态
//类的定义
class Person{ //类名大写!!
String name; //声明属性
int age;
public void say(){ //定义方法
System.out.println("haha");
}
}
//对象的创建
Person wang=new Person();//对象名是一个引用变量,类似数组变量。利用new实例化一个对象后,将返回对象的引用 ,并赋给wang,wang存在于栈中,指向堆中的对象。当某对象不再被任何变量引用时(即没有变量存其地址),该对象变为垃圾,等待回收。
//new创建对象时,会为每个对象开辟独立的堆空间,保存成员变量的值,且会*****自动为成员变量赋默认值。!!!****
2.访问控制符
控制使用权限的关键字,在类、成员变量、方法前均可以添加。
有:public 、 private 、 protected 、 默认(即不加)
- public :均可以访问,对于其他包类的访问,需要导入相关Public类所在的包
- protected:同一包中、其他包的子类均可访问,其他包中的非子类不能访问
- 默认:包访问性,同一包才能访问,与是否是子类无关
- private:最高级别保护,只有在当前类中才能访问
3.构造方法
//new 关键字创建对象时被调用,用于为类中属性初始化
class Person{
public Person(){ //方法名与类名相同 且 没有返回值类型!!!
System.out.println("构造方法自动被调用");
}
}
//如果在类中未定义任何构造方法,系统自动默认提供一个。若已经有,则不再提供
//常见错误:你自己定义了一个有参构造,下面又 Person p=new Person();会因找不到相应的无参构造而报错。
//构造方法是可以重载的,根据需要设置参数。为避免上述错误,在定义类的构造方法时,应预先定义一个无参的构造方法。
4.this 关键字
this就是当前调用对象本身。一般在类中使用,通过this可以调用类中的属性、方法。
特别的,在构造函数中 通过 this(形参列表) 可以调用相应形参列表的其他构造函数!但只能掉一次,且位于首行
5.垃圾回收
使用new 创建数组、对象等引用类型,都将在堆中为其分配一块内存保存对象,当某对象不再被任何变量引用时(即没有变量存其地址),该对象变为垃圾,等待回收。JVM会自动清理被占用的 内存,无需显示释放(与C不同)
6.static关键字
static表示静态的,用于修饰成员变量,成员方法,以及代码块。不能修饰成员方法中的局部变量!!!
//静态变量
//static修饰的成员变量称为静态变量或类变量,被类的所有对象共享,为类所有,故可以用类名直接访问,无需创建对象
class Person{
String name;
static int age;
}
public class TestStatic{
public static void main(String[] args){
System.out.println(Person.age);//该内存中只有一份age,不管创建多少个对象,age都是同一个
Person p=new Person();
System.out.println(p.name);//未使用static 修饰的成员变量,称为实例变量,未具体对象所有,必须通过引用变量调用
}
}
//静态方法
//为类所有,故可以用类名直接访问,无需创建对象 ,但静态方法只能使用静态成员不能使用实例成员
//由于main方法固定声明为静态方法,所以想要在main方法中使用的方法必须声明为静态
class Person{
String name;
static int age;
public static void say(){
System.out.println(age); //正确
System.out.println(name); //错误 实例变量必须在对象开辟内存后才能使用
}
}
7.内部类
内部类与外部类,经编译后生成的两个类是独立的;
内部类是外部类的成员,内部类可以直接访问外部类的所有成员,而外部类想访问内部类,则需要创建内部类对象。
内部类可以为静态,可以用protected、private修饰,而外部类,只能用public 和默认显示
//成员内部类
//外部类外创建内部类对象语法
class Other{
private String name="waibu";
private int age;
class Inner{ //定义内部类成员
private String name="neibu";
public void say(){
System.out.println(Other.this.name); //外部类名.this 表示外部类对象 waibu
System.out.println(this.name);//neibu
System.out.println(age); //不重名的情况下可以直接访问
}
}
}
public class TestInner{
public static void main(String[] args){
//在外部创建一个内部类对象
Other.Inner obj=new Other().new Inner();
obj.say();//waibu neibu 0
}
}
//静态内部类
class Other{
private static String name="waibu";
private int age;
static class Inner{ //定义静态内部类成员
private static String name="neibu";
public void say(){
System.out.println(Other.name); // 表示外部类静态对象 waibu
System.out.println(name);//neibu
System.out.println(age); //不重名的情况下可以直接访问
}
}
}
public class TestInner{
public static void main(String[] args){
String str=Other.Inner.name; //访问静态内部类的静态成员
//在外部创建一个静态内部类对象
Other.Inner obj=new Other.Inner(); //因为静态成员可以直接用类名调用,无需创建对象
obj.say();//waibu neibu 0
}
}
//静态成员的访问无需创建对象,直接用类名调用,但实例对象就要创建,对象调用
//方法内部类
//即在成员方法中定义的类,与局部变量类似,作用域仅在当前方法中,只能在方法中对其进行实例化。
面向对象(下)
1.继承
继承:即在已有类型上定义新的类,新的类继承原有类的属性与方法,并扩展新的功能。已有类称为父类/基类,新的类称为子类/派生类。
//继承的语法格式
class parent{
String name;
double property;
protected void say(){
System.out.println("BABA");
}
}
class son extends parent{
int age; //增加属性,扩展父类功能
public void say(){
System.out.println("haizi"); //重写父类方法
super.say(); //"BABA"
}
}
2.重写父类方法的理解
-
重写父类方法:即重新定义父类的成员方法,被子类重写的方法不能比原父类方法的权限更严格。
-
重写父类方法只能改变修饰符,类名,返回值,参数,都不能变。
注意:参数不能变仅指参数类型/个数不能变,但形参的参数名可以任意指定
-
方法重写与方法重载的区别:方法重载是在一个类中,而重写是在不同类(父子类)
3.super关键字的使用
使用1:当父类方法被重写,子类对象将不能访问原方法,如果需要访问被重写的原方法,可以用super实现。
使用二:在实例化子类对象时,首先会调用父类的构造方法,在调用子类本身。
//1.利用super显式调用
class Parent{
String name;
public Parent(){
System.out.println("隐式调用默认调用无参的构造函数");
}
public Parent(String name){
System.out.println(name);
}
}
class Son extends Parent{
int age; //增加属性,扩展父类功能
public Son(){
super("haizi gei baba de "); //调用父类的有参构造
}
}
//2.隐式调用
class Parent{
String name;
public Parent(){
System.out.println("实例化对象时,先默认调用无参的构造函数");
}
public Parent(String name){
System.out.println(name);
}
}
class Son extends Parent{
int age; //增加属性,扩展父类功能
public Son(){
System.out.println("然后再输出自己的构造函数内容");
}
}
4.final关键字的使用
- final关键字修饰类
final关键字修饰的类为最终类,不能再被其他类继承
- final关键字修饰方法
final关键字修饰的方法,不能再被其子类重写,故称之为最终方法。
-
final关键字修饰变量
final关键字修饰的变量,称为常量,只能初始化(赋值)一次
//例1:final修饰局部变量 final int age=18; //age不能再被修改 //2.final修饰成员变量 //java 虚拟机不会为final修饰的变量默认初始化,所以这里age未被初始化,不能使用! //故被final修饰的成员变量,必须在声明时初始化:1.构造函数中赋值2.直接赋值 class Parent{ final int age; public void say(){ system.out.println(age); //18 } //利用构造函数赋值 public Parent(int age){ this.age=age; } } public class TestInner{ public static void main(String[] args){ Parent p=new Parent(18); }}
5.抽象类与接口
1.抽象类(abstract关键字)
-
抽象类、抽象方法:用abstract关键字修饰的类、方法。
-
抽象方法:只有方法的声明,没有方法体的方法称为抽象方法。该方法的方法体由子类根据实际需求实现。
只要含一个抽象方法的类一定是抽象类。:抽象类也可以不含任何抽象方法。此时当作正常类用就行,只是不能被实例化。
-
抽象类不能被实例化(new),只能通过子类继承才实现,因为可能包含抽象方法,而抽象方法不能被调用。
由此得:抽象方法不能被final、static、private关键字修饰,(对于static:因为其修饰的方法能直接通过类名调用,而抽象方法不能被调用)
//定义一个抽象类 abstract class Parent{ //声明两个抽象方法 public abstract void say(String s); public abstract void work(); } class Son extends Parent{ //实现抽象方法 public void say(String s){ System.out.println(s); //实现抽象方法时,参数列表要相同 } //抽象类的子类必须实现父类所有的抽象方法,否则子类仍继承有抽象方法,故子类也必须声明为抽象类 public void work(){ System.out.println("work is happy"); } }
2.接口(interface关键字)
//静态变量和公共抽象方法的集合,形式上类似于一个类
//接口中定义的变量、方法都隐含默认的修饰符
interface Parent{
String name; //等价于 public static final String name;
void say(); //等价于 public abstract void say();
}
//其功能与抽象类类似,都是用于为对象定义共同的行为,概述一个类的外围能力
怎么来实现接口中的抽象方法呢?
我们需要用类来实现接口,用到关键字(implements)
//代码接上
interface Person{
void work();
}
//一个类可以用于实现多个接口
class Child implements Person,Parent{
//ps:在实现时一定要加public ,因为原方法的权限是public ,不能降低方法的可视性
public void say(){
System.out.println("实现say()函数");
}
public void work(){
System.out.println("实现work()方法");
}
}
接口之间也是可以继承的,我认为没有很大意义,不过是先提前把接口都装到一个子接口中,然后实现时只用写一个罢了
interface All extends Person,Parent{
void all();
}
GradedaoImplement gd=new GradedaoImplement();
SqlSession session=GetSqlSession.createSqlSession();
String courseName=req.getParameter("courseName");
List<Grade> gradeList=gd.getGrade(courseName,session);
req.setAttribute("gl", gradeList);
req.getRequestDispatcher("showGrade.jsp");
//实现接口时,必须实现该接口继承的所有接口。
class Child implements All{
public void all(){
System.out.println("实现all()函数");
}
public void say(){
System.out.println("实现say()函数");
}
public void work(){
System.out.println("实现work()方法");
}
}
sum:由此我们看出,interface相比抽象类更加强大,因为其不论是 接口之间的继承、还是 类来实现接口 都可以有多个。
而抽象类/类之间的继承都只能是单继承。
6.多态
package pers.hxy.begin;
class A{
public String say() {
return "I'm your grandpa : A";
}
}
class B extends A{
public String say() {
return "I'm your mother : B";
}
}
class C extends B{
public String say() {
return "I'm your sonC";
}
}
class D extends B{
public String say() {
return "I'm your sonD";
}
}
public class Test2 {
public static void main(String[] args) {
A person1=null;
person1=new B(); //使用A类型变量引用B类型 A->B向下指,可以直接指
System.out.println(person1.say());
person1=new C();//使用A类型变量引用C类型
System.out.println(person1.say());
person1=new D();//使用A类型变量引用D类型
System.out.println(person1.say());
System.out.println("--------------------------------");
B person2=(B)person1;//向下转型,可以看作将person2指向person1,即向上指的时候,因为是向上指,所以要将被指对象降级再转型
System.out.println(person2.say());//调用方法还是由实际类型决定
C person3=(C)person2;
System.out.println(person3.say());
}
}
错误原因:编译时,检测的是变量的声明类型,即B类型向下转化为C类型,但再运行时,转换的是变量的实际类型,即D类型到C类型的转换,而C&D类型之间并没有关联,故不能通过强转实现转换。
包(package)
1.关于包
-
包的定义:形式上:是一组相关类和接口的集合 功能上:是区别类的名字空间的机制(不同包中类的名字可以相同,提高了复用性)
-
包的规范:(三个一)
一般都小写;必须是程序代码中的第一行可执行代码;package语句最多只能一句;
命名规则:公司域名反转作为包名(略)
2.带包编译命令
//引言:之前手动编译时,我们先将包对应的目录文件夹一个个手动创建好,在再其中写一个java源文件,然后编译运行
//实际上这一切都可以用命令提示符完成,只需提前将package写入java文件
package pers.hxy.helloworld
public class Person{
public void say(){
System.out.println("hahaha");
}
}
//1.使用 javac -d.Person.java 编译源文件
//-d表示生成包对应的目录,.表示编译好的字节码文件放入当前文件夹中
//使用java pers.hxy.helloworld.Person运行命令(采用包名.类名的方法:可以理解为先切换到源文件所在的目录)
//java应用程序打包
//更方便的给别人使用
//在要打包的顶层目录的上一级目录打开命令行窗口
jar -cvf xxx.jar pers(顶层目录名)
//然后添加环境变量,就over了
set classpath=.;D:\新建文件夹\helloword\src\xxx.jar 相关路径
//补充:解压包命令(解压到当前目录)
jar -xvf xxx.jar
2.import语句
public所修饰的类允许跨包使用,但必须引入该类所在的包
//引入位置:package语句之下,类定义之上
//两种引入形式
//1.引入类: 1个或多个 不能只导入包名
import 包名.类名
import 包名.*
//2.加static引入某个类的静态成员/方法 一个/多个 不能只导入类名
import static 包名.类名.成员名
import static 包名.类名.*
//注意:直接用import static引入静态变量之后,直接使用变量即可,无需加类名调用(你又没有引入该类:))
3.java常用包(内置的)
java.lang:核心包(如String,Math,System等),系统自动导入,无需手动
java.util:工具包
4.Lambda 表达式
1.关于Lambda 表达式
实际上就是js中的箭头函数,只是=> 变成了->
//关于箭头函数的讲解(我的理解:匿名函数的变形)
(a,b)->{a-b} //括号中是参数列表,参数类型可省略,若只有一个参数,则可以不加括号
//{}中是函数体,若只有一条语句,{}、return 都可以省略
//其他省就省了,为啥return关键字也可以被省略呢?原因是,编译器会认为:既然只有一个语句,那么这个语句执行的结果就 应该是返回值,所以return也就不需要了。
2.Lambda 表达式的意义
简化某些接口的实现(函数式接口),实际上是简化某些接口中抽象函数的实现
在以前,我们实现一个接口的抽象方法,需要用一个类去实现,非常麻烦
而对于:函数式接口 :仅包含一个抽象方法(ps:但是可以包含多个非抽象方法:静态方法,默认方法),则可以直接用Lambda 表达式实现
public interface ServiceOne{
//唯一一个抽象方法
void printmessage(String message);
//默认方法不是抽象方法
default void pringMessage2(String message){
//方法体
}
}
public class Testlam{
public static void main(String[] args){
//实现ServiceOne接口
ServiceOne service1=s->System.out.println(s); //实现直接打印功能
ServiceOne service2=s->System.out.println(s+"!");//实现打印且加感叹号功能
ServiceOne service3=s->System.out.println(s+"haha");//实现打印且加haha功能
service1.printmessage("I'm your father ");
service2.printmessage("I'm your father ");
service3.printmessage("I'm your father ");
}
}
//输出:
//I'm your father
//I'm your father !
//I'm your father haha
我们发现通过lambda表达式,可以直接实例化一个ServiceOne接口对象,且该接口中的抽象方法已经被填充!!!
通过Lambda表达式,可以实例化一个接口对象,且为其实现抽象方法,不同接口对象,可以实现不同方法。
不同对象调用同一方法名,实现不同功能(怎么有股多态那味儿)
异常
1.基础概念
- 异常是一个在程序执行期间发生的事件,它中断了正在执行程序的正常指令流。
2.异常类别
java将异常封装到一个类中,出现错误时,会抛出异常。
Java类库中定义了异常类,他们都是Throwable的子类。即:Exception(异常)&Error(错误) 各自都包含大量子类
Error:程序无法处理的错误。描述了运行系统的内部错误,以及资源耗尽的问题。由JVM抛出。大多与代码编者本身无关。
Exception:程序本身可以处理的异常。
RuntimeException(运行时异常):运行期间,JVM抛出的异常,如那些:可以通过语法检测,但是最终在控制台报错。
(此类错误常常能准确定位到发生错误的代码段,通过错误调试解决)
CheckedException(可检查异常):编译期间出现异常。必须进行处理。也就是写代码出现红线的错误
3.异常的处理!!!
-
try-catch处理异常
public class TestThrow2 { public static int div(int a,int b){ //if(b==0) { //throw new ArithmeticException("不能输入零!!!"); //} return a/b; } public static void main(String[] args) { // TODO 自动生成的方法存根 try { //若发现异常,则将产生的异常对象抛出 int val=div(10, 0); //若该步发生了异常,则将跳过后续代码,直接进入catch, System.out.println(val); //若try中抛出的异常对象类型符合,catch中定义的,则传入相应的catch中 }catch(ArithmeticException e){ System.out.println(e); System.out.println(e.getMessage());//getMessage():Throwable常用方法:异常对象信息 System.out.println(e.toString());//toString() :Throwable常用方法:返回异常类全名及异常对象信息 }finally{ /*xxx代码*/ } // int val=div2(10, 0); // System.out.println(val); System.out.println("hah"); } } //输出结果: //java.lang.ArithmeticException: 不能输入零!!! //不能输入零!!! //java.lang.ArithmeticException: 不能输入零!!! //hah -->接使用异常处理的意义 //讲解: try{ 可能会发生的异常的程序代码 } catch(异常类型1 e1 ){ 针对异常进行处理的代码 }catch(异常类型2 e2){ 针对异常进行处理的代码 }... finally{ 释放资源代码; }
关于finally
//取部分代码: int a=1; try { int val=div(10, 0); System.out.println(val); }catch(ArithmeticException e){ System.out.println(e); System.out.println(e.toString()); //System.exit(0); jvm退出指令 return a //只是为了测试,随便乱返回一个 }finally{ a++; system.out.println("ha"); //此时finally代码仍会执行,就算不发生异常,finally也会执行,所以:finally里面的代码最终一定会执行(除了JVM退出) } //try中处理return的机制:先保存return值,在执行finally,再取出保存值(也就是说:a会变,但原来的值已经被保存了) //最终返回的return值仍为1
-
关于异常的嵌套
//由上面代码可知:只要try中发现异常对象,则立刻跳过try中后续代码,进入到相应的catch中,
//故若一个try中包含多个异常,则只能捕获出一种(其他可能出错的程序,都被跳过了),想要捕获到几种,则要采用嵌套
public class work1 {
public static int chu(int a,int b){
return a/b;
}
public static char say(char[] s) {
return s[5];
}
public static void main(String[] args) {
//异常嵌套:使可捕获到多种错误
try{
try {
try {
//先测试下标越界
char[] strs= {'c','h','a'};
System.out.println(say(strs));
}catch(IndexOutOfBoundsException e2) {
System.out.println(e2);
}
//再测试算数异常
int c=chu(10, 0); //上层错误代码一定要放到下层try的下面,不然,下层try的代码将直接被跳过
System.out.println(c);
}catch (ArithmeticException e1){
System.out.println(e1);
}
//测试空指针异常
//字符串未初始化时会抛出空指针异常
String work=null;
System.out.println(work.length());
}catch(NullPointerException e3) {
System.out.println(e3);
}finally {
System.out.println("异常处理完毕,程序结束");
}
}
}
4.Throws与Throw
所有系统定义的编译和运行异常(即非自定义的)都可以由系统自动抛出,如上例:main调用div方法,div方法产生异常,但方法内部并没有处理异常的代码,故自动将异常对象抛出调用者——>main,main中有处理异常的方法,故程序正常运行。
如果是多级调用,则将从异常产生处一层层抛出,直到可处理异常的方法中,若到了主函数还没有,则由虚拟机进行处理,程序中断。
-
Throws关键字用于抛出异常对象
Throws的意义就是向我们声明:如果该方法产生异常,该方法不处理异常,由他的调用者帮忙处理
-
Throw关键字用于抛出异常的实例对象
//语法格式: throw new 异常对象(); throw new 异常对象("xxx"); 通过throw方式还可以自定义异常对象信息
作用:1.指明此处一定有异常对象(这点实际上没多大用,因为系统定义的异常都会被自动抛出)
2.有些错误jvm看来不是错误,我们可以自己手动引发错误!!来提醒其他开发者
5.自定义异常
package pers.hxy.begin;
class DivException extends Exception{
public DivException() {
super(); //可以不用写,默认会调用父类的无参构造
}
public DivException(String message) {
super(message); //实例化有参子类时,调用父类的有参构造
}
}
public class TestThrow {
public static int div(int a,int b) throws DivException{//手动抛出该实例(自定义异常对象不能被自动抛出)
if(b==0) {
throw new DivException("错误,除数不能为0"); //产生异常对象实例,并定义了异常对象信息
}
return a/b;
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
try {
int val=div(10, 0);
System.out.println(val);
}catch(Exception e){ //因为Exception是所有异常类的父类,所以用其捕获该异常对象
System.out.println(e);
System.out.println(e.toString());
}
}
}
//输出:
pers.hxy.begin.DivException: 错误,除数不能为0
pers.hxy.begin.DivException: 错误,除数不能为0
对于自定义的异常对象,需要手动产生,手动抛(当然不想抛,就在产生实例的那个方法中处理异常呗)
6.总结:使用异常处理的意义
由第一个代码例中我们发现,尽管产生了异常,但由于我们使用try-catch进行了捕获操作,程序仍然完整执行了(hah被输出,如果没有异常处理,则程序在异常的那一步则直接终止)。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
基础类库(java自带的类)
Scanner类
基于正则表达式的文本扫描器,功能:获取动态传入的参数值
有多个构造方法,用于接收不同的数据来源
import java.util.Scanner;//Scanner类位于java.util包
public static void main(String[] args){
//System.in代表标准输入,即键盘输入
Scanner sc=new Scanner(System.in);
//hasNext() 如果当前输入流还有值,则返回true,没有则暂停,等待键入
//用hasNext()作判段条件,输入流有值则执行,无值将等待输入,有了又执行,然后相当于程序永远不会终止,直到输入exit
while(sc.hasNext()){
//next()如果当前输入流有值,则直接取走一个,没有则程序暂停等待键入
String s=sc.next();
if(s.equals("exit")){
break;
}
System.out.println("输入的内容为:"+s);
}
sc.close(); //释放资源
}
//ps:hasNextxxx() xxx可以为Int Long 等代表基本类型的字符串,如果值判断是否包含下一个字符串,则用hasNext()
因为system.in属于IO流,一旦打开,它一直在占用资源,因此使用完毕后切记要关闭。
System类与Runtime类
java可以通过 **System类与Runtime类中的属性方法 **与程序的系统平台进行信息的交互
但程序不能创建System类与Runtime类的对象
-
System类
它的属性和方法都是静态的,所以就算不能实例化System对象也不影响调用
//常用方法 long start=System.currentTimeMillis() //返回当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。 //返回值为long类型 String name=System.getProperty(String key)//注意传入的是字符串 //通过键获取当前系统属性中键KEY对应的值 //如: System.getProperty("user.name"); //当前系统用户名为 Lenovo System.getProperty("user.dir"); //当前用户工作目录 System.getProperty("os.name"); //当前系统名称 System.exit()//终止当前正在运行的java虚拟机 无返回值 void
-
Runtime类
Runtime类代表Java程序的运行时环境 ( JRE=JVM+Api ),每个java程序都有一个与之对应的Runtime实例
Runtime类的属性和方法不都是静态的,所以需要通过静态函数getRuntime()方法创建对象
//通过静态函数getRuntime()方法获取当前java程序的Runtime对象 Runtime r=Runtime.getRuntime(); long start=r.freeMemory();//返回Java虚拟机中的空闲内存量 =r.totalMemory();//返回Java虚拟机中的总内存量 =r.maxMemory();//返回Java虚拟机中的可用最大内存量 r.exec("xxx") //单独的进程中执行指定的字符串命令
Math类
Math类包含了许多数学运算的方法。且基本都是静态的。
Math类的构造方法是私有的,所以不能被实例化;
Math类被final修饰,因此不能有子类。
import java.lang.Math; //Math位于lang包中
//列几个常见的
//Math类提供了两个静态常量E(自然对数)、 PI(圆周率)
Math.E //2.718281828459045
Math.PI//3.141592653589793
//以下三个方法只能用于Int型
Math.abs(-10) //10绝对值函数
Math.max(10,6) //10 最大值函数
Math.min(10,6) //6 最小值函数
Math.random(); //产生[0,1)的double型随机数
Random类
用于生成一个伪随机数,范围更加广
//有两个构造函数
Random r=new Random(); //无参构造,以当前时间作为种子,相当于每次的种子都不一样,故每次生成随机数不一样
Random r=new Random(10); //传一个指定参数作为种子,每次生成随机数一样
r.nextDouble() //返回一个0.0~1.0之间的double型随机数,
r.nextDouble()*100//返回一个0~100之间的double型随机数,
r.nextFloat() //返回一个0.0~1.0之间的Float型随机数,
r.nextInt() //返回一个int
r.nextInt(n) //返回一个[0,n)之间的Int
Date类
//创建日期的方式
import java.util.Date
Date date1=new Date(); //调用空构造方法,创建当前日期
Date date2=new Date(xxxxxxxxxx); //返回距离1970 1/1 00:00 xxxxxxxxxx毫秒的日期
Calendar类
Calendar类是抽象类,不能被实例化 ,可以通过静态方法getInstance()获取实例
//Calendar类提供了关于当前日期的静态常量!!
//YEAR MONTH DAY_OF_MONTH HOUR_OF_DAY MINUTE SECOND MILLISEECOND
// 年 月 日 时 分 秒 毫秒
上述常量使用时不能直接使用,必须通过get()方法获取!但get()方法需要实例对象调用
import java.util.Calendar;
Calendar calendar=Calendar.getInstance(); //通过getInstance()静态方法获取Calendar实例
System.out.println(calendar.get(Calendar.YEAR)); //通过get()方法得到常量值
System.out.println(calendar.get(Calendar.MONTH)); System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
DateFormat类
DateFormat类是抽象类,不能被实例化,可以4种通过静态方法获取,不同风格的日期/时间格式器
//获取日期格式器
DateFormat dateFormat=DateFormat.getDateInstance();
//利用格式器将时间日期格式化
dateFormat.format(new Date()); //2021年10月15日
//获取时间格式器
DateFormat dateFormat2=DateFormat.getTimeInstance();
dateFormat2.format(new Date()) //下午4:45:39
SimpleDateFormat类
DateFormat类的子类,可以直接使用父类格式方法 format()
作用:创建实例时,指定格式器的格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");//创建年月日格式为 xxxx-xx-xx 的格式器 如:2021-10-05
SimpleDateFormat sdf2=new SimpleDateFormat("Gyyyy-MM-dd hh:mm:ss:SSS");//G表示公元 公元2021-10-05 05:44:18:538 SSS表毫秒
System.out.println(sdf.format(new Date()); //2021-10-15
String类
关于拼接问题:
字符是char 类型,字符串是String 类型
1、数字拼接char,得到的还是数字,相当于和它的ASCII编码相加(如果定义成String 会编译错误)
2、数字拼接String,得到的是String
3、数字同时拼接char 和 String,就看和谁先拼接,和谁后拼接
4、String 拼接任何类型,得到的都是String
1.String类的初始化
关于String类:String类表示不可变的字符串,一旦其被创建,对象中的字符序列就不能再被改变。
//1.直接赋值初始化
String s1="abc";
//JVM首先会去常量池中查找是否存在"abc"这个对象,如果不存在,则在常量池中创建"abc"这个对象,然后将池中"abc"这个对象的引用地址返回给"abc"对象的引用s1,这样s1会指向字符串常量池中"abc"这个字符串对象;如果存在,则不创建任何对象,直接将池中"abc"这个对象的地址返回,赋给引用s1
//2.构造方法初始化
String s2=new String(); //初始化一个空字符对象
String s3=new String(['x','y','z']); //新建一个String对象,字符序列为字符数组当前包含的字符
String s4=new String("xyz") //新建一个String对象
//采用new关键字新建一个字符串对象时,JVM首先在常量池中查找有没有"xyz"这个字符串对象,如果有,则不在池中再去创建"xyz"这个对象了,直接在堆中创建一个"xyz"字符串对象,然后将堆中的这个"xyz"对象的地址返回赋给引用s3,这样,s3就指向了堆中创建的这个"xyz"字符串对象;如果没有,则首先在字符串池中创建一个"xyz"字符串对象,然后再在堆中创建一个"xyz"字符串对象,然后将堆中这个"xyz"字符串对象的地址返回赋给s3引用。s4则指向了堆中创建的另一个"xyz"字符串对象。s3 、s4是两个指向不同对象的引用,故s3!=s4。
2.String类常用方法
由于String类表示不可变的字符串,故大多都是 用于判段、获取信息、得到一个新的对象
String str="hello.com";
//利用索引获取该位置的值
System.out.println(str.charAt(1));//e
//将字符串转化为一个字符数组
char[] c=str.toCharArray();
//按指定符号分隔,拆成字符串数组
String[] sp=str.split("\\.") //先将.转义为 \. 再将\转义\\ 注意:split中的参数一定是string型
System.out.println(sp[0]); //hello
System.out.println(sp[1]); //com
//字符串截取
System.out.println(str.substring(2));//llo 从索引2截取到末尾
System.out.println(str.substring(2,4)); //ll 截取2,3号位
//大小写转换
String str1=str.toUpperCase();
System.out.println(str1);//HELLO.COM
String str2=str.toLowerCase();
System.out.println(str2);//hello.com
//完成字符串中的字符替换,替换全部的
String newstr=str.replace('.', '#'); //hello#com 注意replace是用于字符替换,而不是字符串替换
System.out.println(newstr);
//字符数组转换为字符串
String.valueOf(c); //char[] c;
//关于字符串的比较问题:equals(xxx) VS ==
//equals() 比较的是字符串的内容是否相同,这正是我们需要的
//== 比较的是字符串的地址是否相同
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
}
因为他们的地址都是常量池中"hello"对象的引用地址,所以s1==s2
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1 == s2); //false
System.out.println(s1.equals(s2)); //true
}
StringBuffer类
StringBuffer用于描述可变序列。不用生成新的对象,可在原来序列上修改
StringBuffer类常用方法(包含String类,这里不重复写)
StringBuffer sb=new StringBuffer("hh");
//1.末尾追加内容
sb.append('a');
sb.append("\t");
sb.append("just");
System.out.println(sb); //hha just
//2.指定位置加入内容
sb.insert(1,"a");
System.out.println(sb); //hhaa just
//3.字符串反转
sb.reverse(); //tsuj aahh
//4.删除指定范围字符串
sb.delete(4,5); //tsujaahh
StringBuilder类
StringBuffer是线程安全的,而StringBuilder是线程不安全的,故其效率更高
多线程
基本概念
多线程:一个进程在执行过程中产生多个更小的程序单元,称为线程
每个运行的程序就是进程,进程可以同时执行多个任务,每个任务就是线程。
进程在系统中独立存在,多个进程可以在单个处理器上并发执行且互不影响。
IO流
输入:从外部存储–>程序 输出:程序–>外部存储
抽象类OutputStream 是一切字节输出流的基类,InputStream是一切字节输入流的基类
字节输出流
package pers.hxy.begin;
import java.io.*;
public class Testio {
public static void main(String[] args) throws IOException {
// TODO 自动生成的方法存根
//创建字节输出流对象(使用outputStream的子类)
FileOutputStream io1=new FileOutputStream("hello.txt");
//若想实现追加写入,即每一次运行都会接着上面的写,而不是从头开始重新写,
// FileOutputStream io1=new FileOutputStream("hello.txt",true); //将从末尾开始追加
//三种写出方式
//1.一次性只写一个int 自动根据ascII码转化为字符
io1.write(97); //a
byte[] by= {97,98,99,100};
//2.一次性写入一个字节数组
io1.write(by); //abcd
//小tips可以自动将想输入内容转换成字节数组,再传入
byte[] by2="whoYouWANT".getBytes();
io1.write(by2);
//2.1 写入一部分数据
io1.write(by2,0,2); //wh 从0开始写2个
//写数据的时候想换行怎么办?
io1.write("\r\n".getBytes()); //想在windows下识别换行:\r\n
//释放资源,关闭输出流
io1.close();
}
}
字节输入流
//字节输入流
package pers.hxy.begin;
import java.io.*;
public class Testio {
public static void main(String[] args) throws IOException {
// TODO 自动生成的方法存根
//创建字节输入流对象(使用InputStream的子类)
FileInputStream fis=new FileInputStream("hello.txt");
//读取数据
//1.读取一个数据 注意:不管什么流,用.read()返回的都是int类类型
int b=fis.read();
System.out.println((char)b); //a
System.out.println(b); //97
//1.1.常用:读取全部数据
int by;
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
//2.一次性读取定长的数据(用指定长度的字节数组去装读取的数据,只能读取这么多)
byte[] bys=new byte[5];
int len;
len=fis.read(bys); //len:此时read()返回实际读取的字节数,读取到的数据存在了bys中
System.out.println(new String(bys,0,len)); //将字节数组转换为对应的字符串
System.out.print(bys);
fis.close();
}
}
- 实例:复制图片
数据源---->----读----->-----程序------>-------写-------->-------目的地
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("D:\\2021前端---jQuery\\image\\monster4.png");
//将文件内容复制到D盘下的end文件中
FileOutputStream fos=new FileOutputStream("mn.png");
//方1:基本字节流一次性读写一个字节
// int by;
// while((by=fis.read())!=-1) { //这种方法最慢!效率最低
// fos.write(by);
// }
//方2:基本字节流一次性读写一个字节数组(1024个字节)
byte[] by=new byte[1024];
int len;
while((len=fis.read(by))!=-1) {
fos.write(by,0,len);
}
fis.close();
fos.close();
}
//方3:字节缓冲流一次性读写一个字节数组(这种方法最快)
略,就是对输入流和输出流做一个封装,改一下最上面两行代码即可
字节缓冲流
实际上就是对输出流 和 输入流的一个 封装。
public class Testio2 {
public static void main(String[] args) throws IOException {
BufferedInputStream bin=new BufferedInputStream(new FileInputStream("hello.txt"));
int len;
byte[] bys=new byte[1024];
while((len=bin.read(bys))!=-1) {
System.out.println(new String(bys,0,len));
}
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("hello.txt"));
bos.write("I LOVE YOU!".getBytes());
bin.close();
bos.close();
}
}
字符流
字符流=字节流+编码方式
字符流有两个抽象基类:Writer Reader
OutputStreamWriter:字符输出流 读取字符,采用指定字符集将其编码为字节
InutputStreamReader: 字符输入流 读取字节,采用指定字符集将其解码为字符(若未指定,则都采用默认字符集UTF-8)
用字节流操作汉字不是很方便,所以Java提供字符流
一个汉字: 3个字节(UTF-8编码) 2个字节(BGK编码)
无论采用哪种存储方式,第一个字节都是负数
常见编码表:ASCii字符集 GBK字符集(简体中文码表) Unicode(标准万国码)(其中UTF-8是最常用的编码方案)
- 字符输出流
public class Testio2 {
public static void main(String[] args) throws IOException {
//创建一个使用默认字符编码的字符输出流对象
//构造函数参数必须是OuputStream类
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("hello.txt"));
//直接写一个字符(可以写对应的int)
osw.write(97);
//直接传入一个字符串
osw.write("hahas\n");
//传入字符串的一部分
osw.write("我爱你",0,2);
osw.flush();//字符流写数据的时候会先累积在缓冲区里面,若想立即写入,则需要手动刷新
osw.close(); //关闭Io流前,会自动进行刷新
}
}
-
字符输入流
public class Testio2 { public static void main(String[] args) throws IOException { //构造函数参数必须是InputStream类 InputStreamReader isr=new InputStreamReader(new FileInputStream("hello.txt")); //一次读取一个字符 // int ch; // while((ch=isr.read())!=-1) { // System.out.print((char)ch); // } //一次读取一个字符数组 int len; char[] c=new char[1024]; while((len=isr.read(c))!=-1) { System.out.print(String.valueOf(c)); } isr.close(); } }
-
字符缓冲流
//字符缓冲输入流
BufferedReader br=new BufferedReader(new FileReader("hello.txt"));
//也可以这样:PS:FileReader内置一构造函数生成FileInputStream对象,然后调用父类构造生成Reader类
//BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("hello.txt")));
//字符缓冲输出流
BufferedWriter bw=new BufferedWriter(new FileWriter("hello.txt"));
//缓冲流的特有功能
bw.newLine(); //换行
br.readLine();//一次读一行字符
String line;
while((line=br.readLine())!=null) {
System.out.println(line);
}
关于:JAVA GUI
Graphical User Interface (GUI)图形化的用户界面
1.2Java提供了三个主要包做GUI开发:
java.awt 包 – 主要提供字体/布局管理器
javax.swing 包[商业开发常用] – 主要提供各种组件(窗口/按钮/文本框)
java.awt.event 包 – 事件处理,后台功能的实现。
Swing组件
(1)顶层容器::常用有JFrame,JDialog 绝大多数Swing图形界面程序使用JFrame作为顶层容器
(2)中间容器:JPanel相当于div,JOptionPane,JScrollPane,JLayeredPane 等,主要以panel结尾。
(3)基本组件:JLabel,JButton,JTextField,JPasswordField,JRadioButton 等。 标签、按钮、文本框、密码框、单选按钮